Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Beta bug report
description: Report an InputFlow Linux beta problem with PowerToys Mouse Without Borders.
title: "[Beta Bug]: "
labels: ["bug", "beta"]
body:
- type: markdown
attributes:
value: |
Attach the diagnostics bundle from `scripts/inputflow-diagnostics-bundle.sh` when possible. Review it before posting publicly; the bundle redacts secrets by best effort.
- type: textarea
id: summary
attributes:
label: Problem summary
description: What failed, and what did you expect instead?
validations:
required: true
- type: textarea
id: diagnostics
attributes:
label: Diagnostics bundle
description: Attach the generated archive or paste the path/name if sharing privately.
placeholder: inputflow-diagnostics-YYYYMMDD-HHMMSS-PID.tar.gz
validations:
required: true
- type: input
id: linux-distro
attributes:
label: Linux distro and version
placeholder: Fedora 40, Ubuntu 24.04, Arch, etc.
validations:
required: true
- type: dropdown
id: session-type
attributes:
label: Linux session type
options:
- X11
- Wayland
- XWayland/mixed
- Unknown
validations:
required: true
- type: input
id: desktop
attributes:
label: Desktop/compositor
placeholder: GNOME, KDE Plasma, Sway, Hyprland, etc.
- type: input
id: windows-version
attributes:
label: Windows version
placeholder: Windows 11 24H2
validations:
required: true
- type: input
id: powertoys-version
attributes:
label: PowerToys version
placeholder: v0.xx.x
validations:
required: true
- type: textarea
id: monitor-layout
attributes:
label: Monitor and machine layout
description: Include machine order and display layout, for example AAB, BAA, ABA, stacked, mixed DPI/resolution, wrap on/off.
validations:
required: true
- type: dropdown
id: auth-source
attributes:
label: Linux key source
options:
- key_file
- key_secret_id
- inline key
- not configured
- unknown
- type: dropdown
id: clipboard
attributes:
label: Clipboard mode
options:
- enabled send/receive
- enabled receive-only
- disabled
- unknown
- type: textarea
id: steps
attributes:
label: Reproduction steps
placeholder: |
1. Start PowerToys Mouse Without Borders on Windows.
2. Start InputFlow on Linux.
3. Move cursor from ...
validations:
required: true
13 changes: 7 additions & 6 deletions docs/beta-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,20 @@ The desktop controller shows the same health check together with the user servic
./mwb-desktop-ui.sh status
```

Review warnings for missing `/dev/uinput` access, missing `inputflow` group membership, missing packaged files, unavailable clipboard helpers, or invalid authentication configuration.
Review warnings for missing `/dev/uinput` access, Wayland input-gating, missing `inputflow` group membership, missing packaged files, unavailable clipboard helpers, unavailable Secret Service/session bus, or invalid authentication configuration.

## Diagnostics Bundle

Until a one-click diagnostics bundle command is available, collect this minimal bundle for beta reports:
Create a redacted support bundle before filing beta reports:

```bash
./build/mwb_client doctor --config ~/.config/mwb-client/config.ini > inputflow-doctor.txt
systemctl --user status --no-pager mwb-client.service > inputflow-service-status.txt
journalctl --user -u mwb-client.service --since "30 minutes ago" --no-pager > inputflow-service-log.txt
./scripts/inputflow-diagnostics-bundle.sh \
--config ~/.config/mwb-client/config.ini \
--state ~/.local/state/mwb-client/state.ini \
--output .
```

Also include `~/.config/mwb-client/config.ini` with `key`, `key_file`, `key_secret_id`, Windows IPs, and hostnames redacted as needed. Do not attach exported Windows helper scripts or unredacted Secret Service identifiers to public issues.
The archive includes `summary.json` for machine-readable triage, redacted config/state summaries, session and `/dev/uinput` checks, package/build details, service status, recent user-service journal lines, and `mwb_client doctor` output. Review the archive before posting publicly. Do not attach exported Windows helper scripts or unredacted Secret Service identifiers to public issues.

## Connection Quality

Expand Down
94 changes: 93 additions & 1 deletion scripts/inputflow-diagnostics-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,95 @@ redacted_copy_or_note() {
} >"$output_file"
}

json_escape() {
local value="${1:-}"
value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
value="${value//$'\n'/\\n}"
value="${value//$'\r'/\\r}"
value="${value//$'\t'/\\t}"
printf '%s' "$value"
}

json_string() {
printf '"%s"' "$(json_escape "${1:-}")"
}

json_bool() {
if [[ "${1:-}" == "yes" || "${1:-}" == "true" || "${1:-}" == "1" ]]; then
printf 'true'
else
printf 'false'
fi
}

config_value() {
local lookup="$1"
local line trimmed key value
[[ -r "$CONFIG_PATH" ]] || return 0
while IFS= read -r line || [[ -n "$line" ]]; do
trimmed="${line#"${line%%[![:space:]]*}"}"
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
[[ -z "$trimmed" || "$trimmed" == \#* || "$trimmed" == \;* || "$trimmed" != *"="* ]] && continue
key="${trimmed%%=*}"
value="${trimmed#*=}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
if [[ "$key" == "$lookup" ]]; then
printf '%s\n' "$value"
return 0
fi
done <"$CONFIG_PATH"
}

write_json_summary() {
local output_file="$1"
local host machine_name port key_source clipboard_enabled clipboard_send_enabled screen_width screen_height
local config_present=no config_readable=no state_present=no state_readable=no uinput_present=no uinput_writable=no uinput_module=no
local peer_lines=0

[[ -e "$CONFIG_PATH" ]] && config_present=yes
[[ -r "$CONFIG_PATH" ]] && config_readable=yes
[[ -e "$STATE_PATH" ]] && state_present=yes
[[ -r "$STATE_PATH" ]] && state_readable=yes
[[ -e /dev/uinput ]] && uinput_present=yes
[[ -w /dev/uinput ]] && uinput_writable=yes
[[ -d /sys/module/uinput ]] && uinput_module=yes
if [[ -r "$STATE_PATH" ]]; then
peer_lines="$(grep -c '^peer=' "$STATE_PATH" 2>/dev/null || true)"
peer_lines="${peer_lines:-0}"
fi

host="$(config_value host)"
machine_name="$(config_value machine_name)"
port="$(config_value port)"
clipboard_enabled="$(config_value clipboard_enabled)"
clipboard_send_enabled="$(config_value clipboard_send_enabled)"
screen_width="$(config_value screen_width)"
screen_height="$(config_value screen_height)"
if [[ -n "$(config_value key_secret_id)" ]]; then
key_source="secret_service"
elif [[ -n "$(config_value key_file)" ]]; then
key_source="key_file"
elif [[ -n "$(config_value key)" ]]; then
key_source="inline"
else
key_source="missing"
fi

{
printf '{\n'
printf ' "schema_version": 1,\n'
printf ' "created_at": '; json_string "$(date -Is 2>/dev/null || date)"; printf ',\n'
printf ' "config": {"path": '; json_string "$CONFIG_PATH_DISPLAY"; printf ', "present": '; json_bool "$config_present"; printf ', "readable": '; json_bool "$config_readable"; printf ', "host_configured": '; [[ -n "$host" ]] && printf true || printf false; printf ', "machine_name_configured": '; [[ -n "$machine_name" ]] && printf true || printf false; printf ', "port": '; json_string "$port"; printf ', "key_source": '; json_string "$key_source"; printf ', "clipboard_enabled": '; json_string "$clipboard_enabled"; printf ', "clipboard_send_enabled": '; json_string "$clipboard_send_enabled"; printf ', "screen_override": '; json_string "${screen_width}x${screen_height}"; printf '},\n'
printf ' "state": {"path": '; json_string "$STATE_PATH_DISPLAY"; printf ', "present": '; json_bool "$state_present"; printf ', "readable": '; json_bool "$state_readable"; printf ', "peer_lines": '; printf '%s' "$peer_lines"; printf '},\n'
printf ' "session": {"xdg_session_type": '; json_string "${XDG_SESSION_TYPE:-}"; printf ', "xdg_current_desktop": '; json_string "${XDG_CURRENT_DESKTOP:-}"; printf ', "desktop_session": '; json_string "${DESKTOP_SESSION:-}"; printf ', "wayland_display_set": '; [[ -n "${WAYLAND_DISPLAY:-}" ]] && printf true || printf false; printf ', "display_set": '; [[ -n "${DISPLAY:-}" ]] && printf true || printf false; printf ', "dbus_session_bus_set": '; [[ -n "${DBUS_SESSION_BUS_ADDRESS:-}" ]] && printf true || printf false; printf '},\n'
printf ' "input": {"uinput_present": '; json_bool "$uinput_present"; printf ', "uinput_writable": '; json_bool "$uinput_writable"; printf ', "uinput_module_loaded": '; json_bool "$uinput_module"; printf '},\n'
printf ' "tools": {"wl_copy": '; have wl-copy && printf true || printf false; printf ', "wl_paste": '; have wl-paste && printf true || printf false; printf ', "xclip": '; have xclip && printf true || printf false; printf ', "xsel": '; have xsel && printf true || printf false; printf ', "secret_tool": '; have secret-tool && printf true || printf false; printf ', "systemctl": '; have systemctl && printf true || printf false; printf ', "journalctl": '; have journalctl && printf true || printf false; printf ', "ip": '; have ip && printf true || printf false; printf ', "ss": '; have ss && printf true || printf false; printf '}\n'
printf '}\n'
} >"$output_file"
}

write_config_summary() {
local output_file="$1"
{
Expand Down Expand Up @@ -206,7 +295,9 @@ modified=%y' "$STATE_PATH" 2>/dev/null || true
return
fi
printf 'readable=yes\n'
printf 'peer_lines=%s\n' "$(grep -c '^peer=' "$STATE_PATH" 2>/dev/null || printf '0')"
local peer_lines
peer_lines="$(grep -c '^peer=' "$STATE_PATH" 2>/dev/null || true)"
printf 'peer_lines=%s\n' "${peer_lines:-0}"
printf '\n[redacted state]\n'
redact_stream <"$STATE_PATH"
} >"$output_file"
Expand All @@ -227,6 +318,7 @@ EOF
}

write_manifest
write_json_summary "$BUNDLE_DIR/summary.json"
write_config_summary "$BUNDLE_DIR/config-summary.txt"
write_state_summary "$BUNDLE_DIR/app-state.txt"

Expand Down
20 changes: 18 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1648,17 +1648,19 @@ int HandleDoctorCommand(const std::vector<std::string>& args) {
PrintDoctorLine("INFO", "drm", DrmSummary());

const std::filesystem::path uinputPath("/dev/uinput");
const bool uinputExists = std::filesystem::exists(uinputPath);
const bool uinputWritable = uinputExists && access(uinputPath.c_str(), W_OK) == 0;
PrintDoctorLine(std::filesystem::exists("/sys/module/uinput") ? "OK" : "WARN", "uinput module",
std::filesystem::exists("/sys/module/uinput") ? "loaded" : "not loaded");
if (!std::filesystem::exists(uinputPath)) {
if (!uinputExists) {
PrintDoctorLine("WARN", "uinput", "/dev/uinput missing; load the uinput kernel module");
} else {
struct stat uinputStat {};
const bool haveStat = stat(uinputPath.c_str(), &uinputStat) == 0;
const std::string detail = haveStat
? "/dev/uinput group=" + GroupName(uinputStat.st_gid) + " mode=" + FormatMode(uinputStat.st_mode)
: "/dev/uinput exists";
PrintDoctorLine(access(uinputPath.c_str(), W_OK) == 0 ? "OK" : "WARN", "uinput", detail);
PrintDoctorLine(uinputWritable ? "OK" : "WARN", "uinput", detail);
}

if (getgrnam("inputflow") == nullptr) {
Expand Down Expand Up @@ -1688,6 +1690,12 @@ int HandleDoctorCommand(const std::vector<std::string>& args) {
} else {
PrintDoctorLine("WARN", "session", "no Wayland or X11 session variables detected");
}
const bool waylandSession = sessionType != nullptr && std::string(sessionType) == "wayland";
if (waylandSession && !uinputWritable) {
PrintDoctorLine("WARN", "wayland input", "Wayland session detected but /dev/uinput is not writable; compositor-mediated injection may still require user approval");
} else if (waylandSession) {
PrintDoctorLine("INFO", "wayland input", "/dev/uinput is writable; compositor policy may still gate input injection");
}

if (const char* desktop = std::getenv("XDG_CURRENT_DESKTOP"); desktop != nullptr && *desktop != '\0') {
PrintDoctorLine("INFO", "desktop", desktop);
Expand All @@ -1697,6 +1705,14 @@ int HandleDoctorCommand(const std::vector<std::string>& args) {
" DISPLAY=" + EnvValueOrUnset("DISPLAY"));
PrintDoctorLine("INFO", "runtime env", "XDG_RUNTIME_DIR=" + EnvValueOrUnset("XDG_RUNTIME_DIR") +
" DBUS_SESSION_BUS_ADDRESS=" + EnvValueOrUnset("DBUS_SESSION_BUS_ADDRESS"));
if (FindExecutableInPath("secret-tool")) {
PrintDoctorLine("OK", "secret service tool", "secret-tool available");
} else {
PrintDoctorLine("INFO", "secret service tool", "secret-tool unavailable; key_secret_id depends on libsecret support and an unlocked session");
}
if (!config.keySecretId.empty() && EnvValueOrUnset("DBUS_SESSION_BUS_ADDRESS") == "<unset>") {
PrintDoctorLine("WARN", "secret service", "key_secret_id is configured but the D-Bus session bus is not advertised");
}

if (FindExecutableInPath("wl-copy") && FindExecutableInPath("wl-paste")) {
PrintDoctorLine("OK", "clipboard helpers", "wl-clipboard");
Expand Down
Loading