Table of Contents

Connect keyboards to Folk via Bluetooth

Folk program to connect a Bluetooth keyboard

# ---------------------------------------------------------
# Bluetooth Keyboard Auto-Pairing Tool (Simplified)
# Last updated: Wednesday, September 9, 2026
# Tested on: folk-hex, folk-convivial, folk-sva
# ---------------------------------------------------------

# Defensive Cleanup: Clear stale state from previous runs
Retract! $this has phase /any/
Retract! $this target is /any/ named /any/

# Initialize the state machine
When /nobody/ claims $this has phase /any/ {
    puts "[clock milliseconds] auto_bt: No phase detected, initializing to 'scanning'."
    Hold! -key [list $this phase] Claim $this has phase scanning
}

# Phase: scanning
When $this has phase scanning {
    puts "[clock milliseconds] auto_bt: Phase -> scanning"
    set scanner [list scanner $this]
    
    Wish $this is outlined blue

    # Start the Bluetooth scanning pipeline
    Wish $scanner runs Unix command "/bin/sh" with arguments \
        [list "-c" "/usr/bin/bluetoothctl power on; /usr/bin/bluetoothctl pairable on; /usr/bin/stdbuf -oL /usr/bin/bluetoothctl scan on"]

    When $scanner has Unix output lines /lines/ {
        foreach line $lines {
            # Look for lines matching: "[NEW] Device F4:73:35:8E:B1:7F K380 for Mac"
            if {[regexp {Device ([0-9A-F:]+) (.*)} $line match mac name]} {
                set name [string trim $name]; puts "[clock milliseconds] auto_bt: Seen device: $name ($mac)"
                if {[regexp -nocase {keyboard|k380|mac} $name]} {
                    puts "[clock milliseconds] auto_bt: Found potential keyboard '$name' ($mac). Switching to 'connecting' phase."
                    Hold! -key [list $this target] Claim $this target is $mac named $name
                    Hold! -key [list $this phase] Claim $this has phase connecting
                }
            }
        }
    }
}

# Display scanning dots
When $this has phase scanning &\
     the clock time is /t/ {
    set dot_states [list "o.." ".o." "..o"]
    set dots [lindex $dot_states [expr {int($t * 4) % 3}]]
    Wish $this is footnoted "Scanning for Keyboards $dots\n(Put your keyboard in pairing mode!)"
}

# Phase: connecting
When $this has phase connecting &\
     $this target is /mac/ named /name/ {
    puts "[clock milliseconds] auto_bt: Phase -> connecting to $name ($mac)"
    set connector [list connector $this $mac]
    
    Wish $this is outlined yellow
    Wish $this is footnoted "Connecting to $name...\n($mac)"
    
    Wish $connector runs Unix command "/usr/bin/bluetoothctl" with arguments [list "connect" $mac]
    
    When $connector has Unix output lines /connectLines/ {
        if {[llength $connectLines] > 0} {
            set start_idx [expr {[llength $connectLines] - 10}]
            if {$start_idx < 0} { set start_idx 0 }
            set recent_lines [join [lrange $connectLines $start_idx end] "\n"]
            set full_output [join $connectLines "\n"]
            Wish $this is labelled $recent_lines
            
            if {[regexp -nocase {successful} $full_output]} {
                puts "[clock milliseconds] auto_bt: Connection successful for $name."
                Wish $this is footnoted "Connecting to $name...\nStatus: Successful"
                Hold! -key [list $this phase] Claim $this has phase connected
            } elseif {[regexp -nocase {failed|error|not available} $full_output]} {
                puts "[clock milliseconds] auto_bt: Connection failed for $name."
                Wish $this is footnoted "Connecting to $name...\nStatus: Failed"
                Hold! -key [list $this phase] Claim $this has phase scanning
            } else {
                Wish $this is footnoted "Connecting to $name...\nWaiting for status..."
            }
        }
    }
}

# Phase: connected
When $this has phase connected &\
     $this target is /mac/ named /name/ {
    puts "[clock milliseconds] auto_bt: Phase -> connected to $name ($mac)"
    Wish $this is outlined green
    Wish $this is footnoted "Connected: $name\nMAC: $mac"

    # Set up the visual countdown start time
    When /nobody/ claims $this has countdown-start /any/ {
        Hold! -key [list $this countdown-start] Claim $this has countdown-start [expr {[clock milliseconds] / 1000.0}]
        
        # Trigger the robust 3-second state transition timer
        Hold! -keep 3000ms -key [list $this reset-timer] Claim $this is waiting for reset
        Hold! -key [list $this reset-timer] {}
    }
}

# Process state transition when the timer expires
When $this has phase connected &\
     /nobody/ claims $this is waiting for reset {
    puts "[clock milliseconds] auto_bt: Reset timer fired. Clearing target and returning to 'scanning' phase."

    # Re-scan for new keyboards in /dev/input/by-path/ to inform the system of hot-plugs
    set allDevices [glob -nocomplain "/dev/input/by-path/*"]
    foreach device $allDevices {
        set properties [exec udevadm info --query=property --name=$device]
        if {[string match *ID_INPUT_KEYBOARD=1* $properties]} {
            if {[file readable $device] == 0} {
                puts stderr "auto_bt: Device $device is not readable. Attempting to change permissions."
                catch {exec sudo chmod +r $device}
            }
            # Assert a global claim for the new keyboard so builtin-programs/keyboard.folk picks it up
            Hold! -key [list auto-bt-keyboard $device] Claim $device is a keyboard device
        }
    }

    Hold! -key [list $this target] {}
    Hold! -key [list $this countdown-start] {}
    Hold! -key [list $this phase] Claim $this has phase scanning
}

# Visual countdown display
When $this has phase connected &\
     $this has countdown-start /start_t/ &\
     the clock time is /t/ {
    set elapsed [expr {$t - $start_t}]
    set remaining [expr {3 - int($elapsed)}]
    if {$remaining > 0} {
        Wish $this is titled "Resuming scanning in $remaining seconds ..."
    }
}

When $this target is /mac/ named /name/ {
    Wish $this is left-margined "Connected Keyboards:\n- $name ($mac)"
}

When /nobody/ claims $this target is /any/ named /any/ {
    Wish $this is left-margined "Connected Keyboards:\n(0 connected keyboards)"
}

Connecting a new Bluetooth keyboard manually

sudo rmmod btusb
sudo modprobe btusb

Connecting to keyboards that have been paired before

# bluetoothctl
[bluetooth]#

[bluetooth]# power on
Changing power on succeeded
[CHG] Controller 06:05:04:03:02:01 Powered: yes

[bluetooth]# pairable on

[bluetooth]# scan on
Changing pairable on succeeded

[K380 for Mac]# devices
Device F4:73:35:8E:B1:7F K380 for Mac
Device F4:73:35:93:7F:9D Keyboard K380
Device 71:E3:81:9B:FC:20 71-E3-81-9B-FC-20
Device 54:11:33:FA:24:3F 54-11-33-FA-24-3F
Device BE:16:AE:00:20:F7 MELK-OA10   F7
Device 6B:8C:DD:FC:B1:77 6B-8C-DD-FC-B1-77
Device 5A:55:D8:31:3E:3C 5A-55-D8-31-3E-3C
Device B0:D2:78:B6:47:6D Sbb88cc3eaa738db2C
Device 08:65:F0:3D:89:CA LEDnetWF0200273D89CA

[bluetooth]# connect F4:73:35:8E:B1:7F
Attempting to connect to F4:73:35:8E:B1:7F
Connection successful