User Tools

Site Tools


guides:bluetooth

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

  • Follow the entire Pairing process of this guide from the Arch Linux wiki.
  • There have been issues before with the Intel Bluetooth controllers that come in some of the Beelink computers. If the troubleshooting in the Arch Linux wiki link above doesn't work, try these two commands:
sudo rmmod btusb
sudo modprobe btusb

Connecting to keyboards that have been paired before

  • If you've already paired with a keyboard before you only need to run this in terminal (lines with # are what you type in):
# 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
guides/bluetooth.txt · Last modified: by cwervo

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki