Task 340: .KLC File Format

Task 340: .KLC File Format

File Format Specifications for .KLC

The .KLC file format is a plain text format used by the Microsoft Keyboard Layout Creator (MSKLC) to define custom keyboard layouts for Windows. It is Unicode-encoded (typically UTF-8 or UTF-16) and consists of key-value pairs and tabular data sections describing the keyboard layout, locale, key mappings, and optional features like dead keys and ligatures. The format is not officially documented by Microsoft, but its structure can be inferred from examples and third-party parsers. Files start with a header section, followed by shift state definitions, key mappings, and key name lists. Optional sections include dead key definitions and ligature tables.

  1. List of all the properties of this file format intrinsic to its file system:
  • KBD: The keyboard identifier code and description (e.g., KBD code "description").
  • COPYRIGHT: The copyright notice.
  • COMPANY: The company or creator name.
  • LOCALENAME: The locale name (e.g., "bg-BG").
  • LOCALEID: The locale ID (e.g., "00000402").
  • VERSION: The version number (e.g., 1.0).
  • SHIFTSTATE: Defines the shift states (columns for normal, shift, ctrl, etc.).
  • LAYOUT: The main table of key mappings, including scancode (SC), virtual key (VK), caplock behavior (Cap), and characters for each shift state.
  • KEYNAME: List of standard key codes and their names.
  • KEYNAME_EXT: List of extended key codes and their names.
  • KEYNAME_DEAD: List of dead key codes and their names (optional).
  • DEADKEY: Definitions for dead keys, specifying the base key and combinations (optional).
  • LIGATURE: Definitions for ligature characters (optional).
  1. Two direct download links for files of format .KLC:
  1. Ghost blog embedded html javascript that allows a user to drag n drop a file of format .KLC and it will dump to screen all these properties.
KLC File Parser
Drag and drop .KLC file here
  1. Python class that can open any file of format .KLC and decode read and write and print to console all the properties from the above list.
import json

class KLCParser:
    def __init__(self):
        self.properties = {}

    def read(self, filepath):
        with open(filepath, 'r', encoding='utf-16' if filepath.endswith('.klc') else 'utf-8') as f:
            content = f.read()
        lines = content.split('\n')
        current_section = None
        for line in lines:
            line = line.strip()
            if not line:
                continue
            if line.isupper():
                current_section = line
                self.properties[current_section] = []
            elif current_section:
                self.properties[current_section].append(line)
            else:
                parts = line.split(maxsplit=1)
                if len(parts) == 2:
                    key, value = parts
                    self.properties[key] = value
        # Post-processing for specific sections
        if 'SHIFTSTATE' in self.properties:
            self.properties['SHIFTSTATE'] = [l.split('//')[0].strip() for l in self.properties['SHIFTSTATE'] if l[0].isdigit()]
        if 'LAYOUT' in self.properties:
            self.properties['LAYOUT'] = [l.split('//')[0].strip() for l in self.properties['LAYOUT'] if l[0].isalnum()]
        # Handle DEADKEY, LIGATURE if present

    def print_properties(self):
        print(json.dumps(self.properties, indent=4))

    def write(self, filepath):
        with open(filepath, 'w', encoding='utf-16') as f:
            for key, value in self.properties.items():
                if isinstance(value, str):
                    f.write(f"{key} {value}\n")
                elif isinstance(value, list):
                    f.write(f"{key}\n")
                    for item in value:
                        f.write(f"{item}\n")
            f.write("ENDKBD\n")  # Optional end marker if needed

# Example usage
if __name__ == "__main__":
    parser = KLCParser()
    parser.read('example.klc')
    parser.print_properties()
    parser.write('output.klc')
  1. Java class that can open any file of format .KLC and decode read and write and print to console all the properties from the above list.
import java.io.*;
import java.util.*;

public class KLCParser {
    private Map<String, Object> properties = new HashMap<>();

    public void read(String filepath) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filepath), "UTF-16"))) {
            String line;
            String currentSection = null;
            while (line = reader.readLine() != null) {
                line = line.trim();
                if (line.isEmpty()) continue;
                if (line.toUpperCase().equals(line)) {
                    currentSection = line;
                    properties.put(currentSection, new ArrayList<String>());
                } else if (currentSection != null) {
                    ((List<String>) properties.get(currentSection)).add(line);
                } else {
                    String[] parts = line.split("\\s+", 2);
                    if (parts.length == 2) properties.put(parts[0], parts[1]);
                }
            }
        }
        // Post-processing
        if (properties.containsKey("SHIFTSTATE")) {
            List<String> shift = (List<String>) properties.get("SHIFTSTATE");
            shift.removeIf(l -> !Character.isDigit(l.charAt(0)));
            shift.replaceAll(l -> l.split("//")[0].trim());
        }
        if (properties.containsKey("LAYOUT")) {
            List<String> layout = (List<String>) properties.get("LAYOUT");
            layout.removeIf(l -> !Character.isAlphabetic(l.charAt(0)) && !Character.isDigit(l.charAt(0)));
            layout.replaceAll(l -> l.split("//")[0].trim());
        }
    }

    public void printProperties() {
        System.out.println(properties);
    }

    public void write(String filepath) throws IOException {
        try (PrintWriter writer = new PrintWriter(new FileWriter(filepath, java.nio.charset.StandardCharsets.UTF_16))) {
            for (Map.Entry<String, Object> entry : properties.entrySet()) {
                if (entry.getValue() instanceof String) {
                    writer.println(entry.getKey() + " " + entry.getValue());
                } else if (entry.getValue() instanceof List) {
                    writer.println(entry.getKey());
                    for (String item : (List<String>) entry.getValue()) {
                        writer.println(item);
                    }
                }
            }
            writer.println("ENDKBD");
        }
    }

    public static void main(String[] args) throws IOException {
        KLCParser parser = new KLCParser();
        parser.read("example.klc");
        parser.printProperties();
        parser.write("output.klc");
    }
}
  1. Javascript class that can open any file of format .KLC and decode read and write and print to console all the properties from the above list.
class KLCParser {
    constructor() {
        this.properties = {};
    }

    async read(filepath) {
        // For browser, use Fetch or FileReader; here assuming Node.js for console
        const fs = require('fs');
        const content = fs.readFileSync(filepath, 'utf16le');
        const lines = content.split('\n').map(line => line.trim());
        let currentSection = null;
        lines.forEach(line => {
            if (!line) return;
            if (line.toUpperCase() === line) {
                currentSection = line;
                this.properties[currentSection] = [];
            } else if (currentSection) {
                this.properties[currentSection].push(line);
            } else {
                const [key, ...value] = line.split(/\s+/);
                this.properties[key] = value.join(' ');
            }
        });
        // Post-processing
        if (this.properties.SHIFTSTATE) {
            this.properties.SHIFTSTATE = this.properties.SHIFTSTATE.filter(l => /\d/.test(l[0])).map(l => l.split('//')[0].trim());
        }
        if (this.properties.LAYOUT) {
            this.properties.LAYOUT = this.properties.LAYOUT.filter(l => /[0-9a-fA-F]/.test(l[0])).map(l => l.split('//')[0].trim());
        }
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 2));
    }

    write(filepath) {
        const fs = require('fs');
        let output = '';
        for (const [key, value] of Object.entries(this.properties)) {
            if (typeof value === 'string') {
                output += `${key} ${value}\n`;
            } else if (Array.isArray(value)) {
                output += `${key}\n`;
                value.forEach(item => output += `${item}\n`);
            }
        }
        output += 'ENDKBD\n';
        fs.writeFileSync(filepath, output, 'utf16le');
    }
}

// Example usage (Node.js)
(async () => {
    const parser = new KLCParser();
    await parser.read('example.klc');
    parser.printProperties();
    parser.write('output.klc');
})();
  1. C class that can open any file of format .KLC and decode read and write and print to console all the properties from the above list.

Since C does not have built-in classes, I'll use C++ for this implementation.

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <sstream>

class KLCParser {
private:
    std::map<std::string, std::vector<std::string>> properties;

public:
    void read(const std::string& filepath) {
        std::ifstream file(filepath, std::ios::binary);
        if (!file) {
            std::cerr << "Cannot open file" << std::endl;
            return;
        }
        std::string line;
        std::string currentSection;
        while (std::getline(file, line)) {
            // Trim line (simple trim)
            line.erase(0, line.find_first_not_of(" \t"));
            line.erase(line.find_last_not_of(" \t") + 1);
            if (line.empty()) continue;
            bool isSection = true;
            for (char c : line) {
                if (!isupper(c)) {
                    isSection = false;
                    break;
                }
            }
            if (isSection) {
                currentSection = line;
                properties[currentSection] = {};
            } else if (!currentSection.empty()) {
                properties[currentSection].push_back(line);
            } else {
                std::istringstream iss(line);
                std::string key, value;
                iss >> key;
                std::getline(iss, value);
                properties[key] = {value};
            }
        }
        // Post-processing
        if (properties.count("SHIFTSTATE")) {
            auto& shift = properties["SHIFTSTATE"];
            std::vector<std::string> newShift;
            for (auto& l : shift) {
                if (isdigit(l[0])) {
                    size_t pos = l.find("//");
                    if (pos != std::string::npos) l = l.substr(0, pos);
                    l.erase(l.find_last_not_of(" \t") + 1);
                    newShift.push_back(l);
                }
            }
            shift = newShift;
        }
        if (properties.count("LAYOUT")) {
            auto& layout = properties["LAYOUT"];
            std::vector<std::string> newLayout;
            for (auto& l : layout) {
                if (isalnum(l[0])) {
                    size_t pos = l.find("//");
                    if (pos != std::string::npos) l = l.substr(0, pos);
                    l.erase(l.find_last_not_of(" \t") + 1);
                    newLayout.push_back(l);
                }
            }
            layout = newLayout;
        }
    }

    void printProperties() {
        for (const auto& pair : properties) {
            std::cout << pair.first << ":" << std::endl;
            for (const auto& val : pair.second) {
                std::cout << "  " << val << std::endl;
            }
        }
    }

    void write(const std::string& filepath) {
        std::ofstream file(filepath, std::ios::binary);
        if (!file) {
            std::cerr << "Cannot open file for writing" << std::endl;
            return;
        }
        for (const auto& pair : properties) {
            if (pair.second.size() == 1 && !pair.second[0].empty()) {
                file << pair.first << " " << pair.second[0] << "\n";
            } else {
                file << pair.first << "\n";
                for (const auto& val : pair.second) {
                    file << val << "\n";
                }
            }
        }
        file << "ENDKBD\n";
    }
};

int main() {
    KLCParser parser;
    parser.read("example.klc");
    parser.printProperties();
    parser.write("output.klc");
    return 0;
}