Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

marchaen/another-keyboard-layer

Repository files navigation

Another Keyboard Layer (AKL)

Programm das einen weiteren Tastaturlayer emuliert.

Auf diesen virtuellen Layer können schwer zu erreichende Tasten oder komplizierte Tastenkombinationen gelegt werden. Dann kann auf den Layer mit einer konfigurierbaren Taste wie z. B. CapsLock gewechselt werden, während diese gehalten wird. Siehe [_features] für mehr Details.

Wichtig
Dieses Projekt ist archiviert, da ich eine für mich einfachere Lösung gefunden habe CapsLock mit Esc zu tauschen (PowerToys Keyboard Manager) und ich somit keinen Use Case mehr für dieses Projekt habe.
Anmerkung
Leider funktioniert die Anzeige von AsciiDoc-Dokumenten in GitHub nicht wirklich gut, deshalb sollte die Dokumentation besser lokal angesehen werden. Siehe die Build Anweisungen fĂĽr Generelle Dokumentation.
Wichtig
Diese Funktionalität gibt es sehr ähnlich bereits in Linux. Siehe Caps + hjkl as arrow keys. Deshalb werden durchgestrichene Features nicht mehr implementiert beziehungsweise weiter entwickelt werden.

Features

Erklärung aller Features und generelle Fortschrittsübersicht.

Konfigurierbare Layer-Umschalttaste

Die Taste welche gehalten wird, um auf den virtuellen Layer zu schalten ist frei wählbar.

Standarttastaturkombination beim leeren Umschalten

Wenn die Layer-Umschalttaste losgelassen wird ohne eine Tastenkombination auszulösen kann optional eine Standarttastenkombination gesendet werden.

NĂĽtzlich um z. B. ein weiteres Escape auf CapsLock zu legen und somit die eigentliche Funktionsweise von CapsLock komplett zu Ersetzen.

Sprach- und Layout unabhängige Implementation

Alle Tasten werden daran erkannt welchen Text sie produzieren, wenn sie einzeln gedrückt werden. Das bedeutet egal welche Kombination von Ausgabe verändernden Tasten mit gedrückt werden, es wird nur der Basistext erkannt.

Beispiel: Die 2-Taste (deutsches Layout)

2 = 2 ⇒ 2 erkannt
Shift+2 = " ⇒ 2 erkannt
Strg+Alt+2 = ² ⇒ 2 erkannt

Deadkeys also Tasten die nur in Kombination mit einer anderen Taste auch Text produzieren und Tasten welche Unicode Symbole produzieren werden auch erkannt.

Beispiel: Deadkeys und Unicode

´ = [Keine Ausgabe] ⇒ ´ erkannt
^ = [Keine Ausgabe] ⇒ ^ erkannt
ä = ä ⇒ ä erkannt
ü = ü ⇒ ü erkannt

Textbasierte als auch graphische Konfigurationsmöglichkeiten

Das Projekt stellt einen visuellen Editor fĂĽr die Konfiguration des virtuellen Layers bereit. NatĂĽrlich kann auch die TOML-Datei direkt bearbeitet werden und dann fĂĽr das Starten des virtuellen Layers mit der Cli verwendet werden.

Starten mit dem System (nur auf Windows)

Startet AKL automatisch mit dem System. Es macht wenig Sinn diese Funktionalität für Linux bereit zu stellen, da es dort zu viele verschiedene Möglichkeiten gibt und die Entscheidung darüber den Benutzer überlassen werden sollte.

Livereload der Konfiguration

Es gibt die Möglichkeit den virtuellen Layer automatisch neu zu starten, sobald manuelle Änderungen an der Konfigurationsdatei vorgenommen worden sind. Siehe Cli Optionen: [_verfügbare_optionen_englisch]

Fortschritt

Allgemeiner Fortschritt des Projekts ohne die genauen Features direkt zu nennen, stattdessen wird die Fertigstellung der Komponenten betrachtet.

  • âś“ AKL.Common Abstraktion

  • âś“ Windows Prototyp

  • âś“ akl-core-system-lib Windows Implementation

  • âś“ AKL.Cli Implementation

  • ❏ Linux Prototyp (Ohne Root Zugriff und ohne plugdev-Gruppe)

  • ❏ akl-core-system-lib Linux Implementation

  • âś“ Starten mit dem System unter Windows

  • ❏ AKL.Gui

Anmerkung
Die Implementationen wurden mit der Hilfe von Prototypen entwickelt, damit die benötigten Funktionen (Tastaturevents blockieren und Tastenanschläge simulieren) einzeln herausgefunden werden können.

Komponenten

Das Projekt Another Keyboard Layer ist in GUI, Cli, Common und Native / Core Komponenten auf geteilt, wobei nur die Native / Core Library in Rust geschrieben wird, da die Kommunikation mit Low Level Apis in C# zwar möglich aber sehr mühsam ist.

Außerdem soll während der Verwirklichung des Projekts GUI-Entwicklung und Interfacing mit Native Bibliotheken in C# besser gelernt werden.

Anmerkung
Das Interfacing mit Rust ist sehr viel einfacher, da die Bindings fĂĽr C# automatisch mit csbindgen generiert werden.
Ăśberblick
@startuml
!theme reddress-darkblue

node "Operating System" {
    interface "Raw keyboard events" as keyboard

    note bottom of keyboard
        Implementation
        is os dependent.
    end note
}

component "Core Library (""akl-core-system-library"")" as core

interface "Toml Configuration" as config
component "Shared Functionality (""AKL.Common"")" as common

component "Main User Interface (""AKL.Gui"")" as gui
component "Basic Cli Wrapper (""AKL.Cli"")" as cli

gui --> common : uses
cli --> common : uses

common --> core : configures, starts and stops
config <- common : writes and reads

note left of core
    Implements the
    configurable
    virtual layer.
end note

core --> keyboard : interacts with

@enduml

Kern (akl-core-system-lib)

Der Kern wird in Rust geschrieben und wird direkt mit dem jeweiligen Betriebssystem interagieren, um global Tastaturevents abzuhören und wie konfiguriert zu verändern. Außerdem wird der Kern ein C Interface bereit stellen und im Build-Vorgang automatisch CSharp Bindings für dieses generieren.

Abstraktion der Tastaturanschläge in Events

Darstellung der Verarbeitung der Tastaturanschläge unabhängig vom Betriebssystem.

Strukturierung
@startuml
!theme reddress-darkblue

enum Key {
    Text(char)
    Virtual(VirtualKey)
}

enum Action {
    Press
    Release
}

class Event {
    key: Key
    action: Action
}

enum ResponseAction {
    Nothing
    Block
    ReplaceWith(KeyCombination)
}

class EventProcessor {
    switch_key: Key,
    default_combination: Option<KeyCombination>,
    mappings: HashMap<KeyCombination, KeyCombination>,
    currently_pressed: Vec<Key>,
    block_events: bool,
    key_combination_executed: bool,
    +process(event: Event) -> ResponseAction
}

class NativeInputHook {
    {static} processor: EventProcessor

    ## native_input_handling ##

}

note right of NativeInputHook
Zuständig für das Parsen von
Rohevents und AusfĂĽhrung von
aus dem Processing entstehenden
ResponseActions.
end note

Event *- Key
Action -* Event
EventProcessor - ResponseAction
EventProcessor -- Event
NativeInputHook -- EventProcessor : uses to process events

@enduml
Verarbeitung von Events
@startuml
!theme reddress-darkblue

start

if (Action is **press**?) then (yes)
    if (Key is switch key?) then (yes)
        :Set block events = true;
        :Clear currently pressed keys;
        :Return block event>
        detach
    else (no)
    endif

    if (Don't block events?) then (yes)
        :Return don't change event>
        detach
    else (no)
    endif

    :Add key to currently pressed keys;
    :Create key combination from pressed keys;

    if (Key combination is target for replacement?) then (yes)
        :Set key combination executed = true;
        :Remove current key from currently pressed;
        :Return replace with replacement combination>
        detach
    else (no)
    endif

    :Return block event>
elseif (Action is **release**?) then (yes)
    if (Key is switch key?) then (yes)
        :Set block events = false;

        if (Not key combination executed and default combination is set?) then (yes)
            :Return replace with default combination>
            detach
        else (no)
        endif

        :Reset key combination executed flag;
        :Return block event>
        detach
    else (no)
        if (Key in currently pressed?) then (yes)
            :Remove key from currently pressed keys;

            if (Block events is true?) then (yes)
                :Return block event>
                detach
            else (no)
            endif
        else (no)
        endif
    endif

    :Return don't change event>
else (invalid action data)
    :Skip event;
    detach
endif

stop

@enduml

Graphische Benutzerschnittstelle (AKL.Gui)

Die graphische Benutzerschnittstelle bietet eine Möglichkeit den virtuellen Layer komplett zu konfigurieren und diesen zu aktivieren oder zu deaktivieren. Zu konfigurierende Werte sind unter anderem der Umschalter, die Taste welche bei schnellem Drücken des Umschalters ohne eine weitere Taste simuliert werden soll und die eigentlichen Mappings. Des Weiteren dient dieses Modul dem Erlernen der GUI Programmierung mit dem Avalonia Framework, welches von der Struktur sehr stark WPF ähnelt.

Mockup
@startsalt
!theme reddress-darkblue
scale 2

{
    [X] Start with System
    Switch Key          | "CapsLock       "
    Default Combination | "Escape         "
    .
    {SI
        {#
            **Target**        | **Replacement**
            "h"               | "ArrowLeft"
            "j"               | "ArrowDown"
            "k"               | "ArrowUp"
            "l"               | "ArrowRight"
            "               " | "               "
        }
    }
    [ <&plus> Add mapping ]
    .
    [ <&reload> Restart Virtual Layer ] | [ <&hard-drive> Save configuration ]
}

@endsalt

Textbasierte Benutzerschnittstelle (AKL.Cli)

Zusätzlich zur graphischen Schnittstelle soll die textbasierte Benutzerschnittstelle ein kleiner Wrapper um den Kern sein, der diesen nur mit Hilfe der gespeicherten Konfiguration vom GUI initialisiert, außerdem sollte für die Implementierung, Microsofts eigens für das Entwickeln von CLIs erstellte Bibliothek System.CommandLine verwendet werden.

VerfĂĽgbare Optionen (Englisch)

Programmflow Visualisierung

@startuml
!theme reddress-darkblue

start

:Parse cli arguments;

if (Config path overriden?) then (yes)
    :Use custom config path;
else (no)
    :Use default config path;
endif

if (Config file exists?) then (no)
    if (Custom path set?) then (yes)
        :Print error message>
        :Exit program early;
        detach
    else (no)
        :Save default configuration;
    endif
else (yes)
endif

:Load configuration file;

if (Parsing config sucessfull?) then (no)
    :Print error message with cause>
    :Exit program early;
    detach
else (yes)
    if (Live reload activated?) then (yes)
        :Start file watcher in background thread;
        :Create callback for updating the virtual layer;
    else (no)
    endif

    :Start virtual layer;
    :Block until receiving Ctrl + C;
endif

stop

@enduml

Gemeinsame Funktionalität von Gui und Cli (AKL.Common)

Dieses Modul sollte das Parsen und Schreiben der Konfigurationsdatei als auch die Interaktion mit akl-core-system-lib übernehmen, damit keine Logik unnötigerweise dupliziert wird.

Nachdem Verarbeiten der Konfiguration sollten die Einstellungen falls nötig automatisch angewendet werden z. b. Autostart An / Aus.

Anmerkung
Die Konfiguration soll im TOML format gespeichert werden und in C# mit Tomlyn verarbeitet werden.
Definition der Api fĂĽr Cli und Gui
@startuml
!theme reddress-darkblue

enum VirtualKeyCode {
    Space,
    Tab,
    Return
    ...
}

note left of VirtualKeyCode::...
Steht fĂĽr die restlichen Special / Named Keys, allerdings
macht es keinen Sinn diese hier aufzulisten.
end note

enum KeyKind {
    Text,
    Virtual
}

class Key {
    {field} -VirtualKeyCode? virtualKey
    {field} -Char? textKey
    {field} -KeyKind kind
    {static} {method} +Key TryParse(string raw)
}

class KeyCombination {
    {field} -Key[] keys;
    {static} {method} +KeyCombination TryParse(string raw)
}

note top of KeyCombination
Jede Tastenkombination kann maximal
aus 4 Tasten bestehen. Leider gibt
es keinen ergonomischen Weg dies in
C# Klassen darzustellen.
end note

class AklConfiguration {
    {field} -TomlAklConfiguration origin;
    {field} +bool Autostart
    {field} +Key SwitchKey
    {field} +KeyCombination? DefaultCombination
    {field} +Dictionary<KeyCombination, KeyCombination> Mappings
}

note left of AklConfiguration::TomlAklConfiguration
Internes Model fĂĽr die De-/Serialization
in Toml mit Tomlyn.
end note

class VirtualLayer {
    {field} +AklConfiguration Configuration
    {method} +Update()
}

class AklConfigurationProvider {
    {field} -FileInfo file
    {field} -AklConfiguration configuration
    {static} {method} +AklConfigurationProvider LoadFromFile(FileInfo file)
    {method} +AklConfiguration GetConfiguration()
    {method} +void SaveToFile()
}

Key *-- VirtualKeyCode
Key *-- KeyKind

KeyCombination o-- Key

AklConfiguration o- KeyCombination
AklConfiguration o- Key

AklConfigurationProvider --> AklConfiguration : loads and saves
VirtualLayer *-- AklConfiguration : needs

@enduml

Build Anweisungen

Binaries

Das Gui und die Cli können mit dotnet publish -c Release für die eigene Platform gebaut werden. Wenn man die Option --os <OS> am Ende hinzufügt, kann man für die jeweils andere Platform bauen (gültige Werte sind win / linux), allerdings wird empfohlen das <OS> immer anzugeben.

Windows
$ dotnet publish -c Release --os win
Linux
$ dotnet publish -c Release --os linux
Wichtig
Support für Linux wurde eingestellt, weil es nicht möglich ist ohne Root-Berechtigung Keyboard-Events beliebig zu verändern / zu schicken und dass das wichtigste Feature für die Linux-Version gewesen wäre.

Anforderungen

Generelle Dokumentation

Je nach Präferenz kann Dokumentation lokal oder auch mit Docker generiert werden. Die folgenden Befehle generieren dieses Dokument im HTML und PDF format, außerdem wird die Manpage für das Command-line Interface erstellt.

Lokal
$ ./build-documentation.py
Mit Docker
$ ./build-documentation.py with-docker

Anforderungen

akl-core-system-lib Dokumentation

Die Dokumentation fĂĽr die akl-core-system-lib kann mit folgenden Befehl generiert werden.

$ cargo doc --document-private-items --no-deps --target x86_64-pc-windows-gnu

Anforderungen

Konfigurationsdatei

Die folgende Konfiguration sollte dem Benutzer über das Gui möglich sein, allerdings könnte er die Datei auch direkt verändern und das Programm mit der Cli starten.

Standartkonfigurationsdatei mit Kommentaren
link:AKL.Common/default-config.toml[role=include]

Lizenz

Diese Software ist lizenziert unter der Apache License, Version 2.0.

Sofern nicht ausdrücklich anderes angeben, wird jeder Beitrag der absichtlich für die Aufnahme in dieses Werk eingereicht wird, wie in der Apache-2.0-Lizenz definiert, ohne zusätzliche Bedingungen oder Konditionen wie oben lizenziert.

Drittpartei Bibliotheken

Diese Software verwendet die folgenden Bibliotheken unter Einhaltung der dazugehörigen Lizenzvereinbarungen.

AKL.Common
Bibliothek Lizenz

Tomlyn

Simplified BSD License

AKL.Cli
Bibliothek Lizenz

System.CommandLine

MIT

akl-core-system-lib
Bibliothek Lizenz

thiserror

Apache-2.0 oder MIT

num_enum

BSD-3, Apache-2.0 oder MIT

log

Apache-2.0 oder MIT

simplelog

Apache-2.0 oder MIT

windows

Apache-2.0 oder MIT

csbindgen

Apache-2.0 oder MIT