Fix HID timeout/EINTR errors and relative icon paths
- Treat "timeout" and "interrupted system call" from ReadWithTimeout as non-fatal — hidraw on Linux returns errors instead of (0, nil) for both, causing false device-dead detection and reconnect loops - Resolve icons_dir relative to the config file when the path is not absolute, so the service finds icons regardless of working directory - Installer now copies bundled icons to the config dir on first install
This commit is contained in:
53
install.sh
53
install.sh
@@ -46,12 +46,43 @@ prompt_yn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prompt_input() {
|
prompt_input() {
|
||||||
|
# echo-ne goes to /dev/tty so it isn't captured when called inside $()
|
||||||
local msg="$1" default="$2"
|
local msg="$1" default="$2"
|
||||||
echo -ne " ${ARROW} ${msg} ${DIM}[${default}]${NC}: "
|
echo -ne " ${ARROW} ${msg} ${DIM}[${default}]${NC}: " >/dev/tty
|
||||||
read -r _input </dev/tty
|
read -r _input </dev/tty
|
||||||
echo "${_input:-$default}"
|
echo "${_input:-$default}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pick_directory() {
|
||||||
|
local default="$1"
|
||||||
|
local result=""
|
||||||
|
|
||||||
|
if command -v fzf &>/dev/null; then
|
||||||
|
echo -e "\n ${DIM}arrow keys to navigate · enter to select · ctrl-c to type path manually${NC}\n" >/dev/tty
|
||||||
|
result=$(
|
||||||
|
find "$HOME" -maxdepth 4 -type d 2>/dev/null | sort |
|
||||||
|
fzf --height=50% \
|
||||||
|
--reverse \
|
||||||
|
--border=rounded \
|
||||||
|
--prompt=" ❯ " \
|
||||||
|
--header=" Select dotfiles directory" \
|
||||||
|
--preview="ls -1 {} 2>/dev/null | head -30" \
|
||||||
|
--preview-window="right:35%:border-left" \
|
||||||
|
--query="dotfiles" \
|
||||||
|
--bind="ctrl-c:abort"
|
||||||
|
) || result=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# fzf not available or ctrl-c pressed — fall back to plain text input
|
||||||
|
if [[ -z "$result" ]]; then
|
||||||
|
echo -ne " ${ARROW} Path to dotfiles repo ${DIM}[${default}]${NC}: " >/dev/tty
|
||||||
|
read -r result </dev/tty
|
||||||
|
result="${result:-$default}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$result"
|
||||||
|
}
|
||||||
|
|
||||||
abspath() {
|
abspath() {
|
||||||
# Expand ~, resolve to absolute path without requiring it to exist yet.
|
# Expand ~, resolve to absolute path without requiring it to exist yet.
|
||||||
local p="${1/#\~/$HOME}"
|
local p="${1/#\~/$HOME}"
|
||||||
@@ -193,7 +224,7 @@ CONFIG_DIR=""
|
|||||||
|
|
||||||
if prompt_yn "Use a dotfiles directory?" "y"; then
|
if prompt_yn "Use a dotfiles directory?" "y"; then
|
||||||
nl
|
nl
|
||||||
DOTFILES_RAW="$(prompt_input "Path to dotfiles repo" "${DEFAULT_DOTFILES}")"
|
DOTFILES_RAW="$(pick_directory "${DEFAULT_DOTFILES}")"
|
||||||
DOTFILES="$(abspath "${DOTFILES_RAW}")"
|
DOTFILES="$(abspath "${DOTFILES_RAW}")"
|
||||||
|
|
||||||
if [[ ! -d "${DOTFILES}" ]]; then
|
if [[ ! -d "${DOTFILES}" ]]; then
|
||||||
@@ -243,6 +274,24 @@ else
|
|||||||
ok "config.yaml already exists — not overwritten"
|
ok "config.yaml already exists — not overwritten"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy bundled icons (never overwrite existing ones the user may have customised).
|
||||||
|
if [[ -d "icons" ]]; then
|
||||||
|
copied=0
|
||||||
|
for f in icons/*; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
dest="${CONFIG_DIR}/icons/$(basename "$f")"
|
||||||
|
if [[ ! -f "$dest" ]]; then
|
||||||
|
cp "$f" "$dest"
|
||||||
|
(( copied++ )) || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if (( copied > 0 )); then
|
||||||
|
ok "Copied ${copied} bundled icon(s) to ${CONFIG_DIR}/icons/"
|
||||||
|
else
|
||||||
|
ok "Bundled icons already present — not overwritten"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 7. Symlink (dotfiles mode only) ───────────────────────────────────────────
|
# ── 7. Symlink (dotfiles mode only) ───────────────────────────────────────────
|
||||||
if [[ "${USE_DOTFILES}" == "true" ]]; then
|
if [[ "${USE_DOTFILES}" == "true" ]]; then
|
||||||
nl
|
nl
|
||||||
|
|||||||
@@ -49,5 +49,11 @@ func Load(path string) (*Config, error) {
|
|||||||
return nil, fmt.Errorf("parse config: %w", err)
|
return nil, fmt.Errorf("parse config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve relative icons_dir against the config file's directory so the
|
||||||
|
// binary works regardless of the working directory (e.g. as a systemd service).
|
||||||
|
if !filepath.IsAbs(cfg.IconsDir) {
|
||||||
|
cfg.IconsDir = filepath.Join(filepath.Dir(path), cfg.IconsDir)
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sstallion/go-hid"
|
"github.com/sstallion/go-hid"
|
||||||
@@ -152,6 +153,12 @@ func (sd *StreamDeck) ReadButtons() ([]bool, error) {
|
|||||||
data := make([]byte, readReportSize)
|
data := make([]byte, readReportSize)
|
||||||
n, err := sd.dev.ReadWithTimeout(data, 250)
|
n, err := sd.dev.ReadWithTimeout(data, 250)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// hidraw on Linux returns errors rather than (0, nil) for non-fatal
|
||||||
|
// conditions: timeout waiting for data, or EINTR (signal interrupted).
|
||||||
|
msg := strings.ToLower(err.Error())
|
||||||
|
if strings.Contains(msg, "timeout") || strings.Contains(msg, "interrupted") {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user