Let xrdp repair stuck keyboard and mouse state on reconnect

An xrdp reconnect can leave a virtual mouse button or key down. A stuck mouse button creates an X11 pointer grab, so every window appears unable to receive clicks even though XFCE and the taskbar are still alive. The old workaround was to click the stuck button once; a reconnect hook can repair both devices automatically without destroying the desktop session.

Keep the session alive in /etc/xrdp/sesman.ini:

KillDisconnected=false
DisconnectedTimeLimit=0
ReconnectScript=reconnectwm.sh

ReconnectScript is the important part. It runs when xrdp-sesman reconnects a client to an existing session. Put the repair command in /etc/xrdp/reconnectwm.sh:

#!/bin/sh

/usr/local/bin/rdp-input-reset >/dev/null 2>&1 || true

The helper only resets a device when X reports a key or button still down:

#!/bin/sh
set -eu

for name in xrdpMouse xrdpKeyboard; do
    id=$(xinput list --id-only "$name" 2>/dev/null || true)
    [ -n "$id" ] || continue

    if xinput query-state "$id" | grep -Eq '(key|button)\[[0-9]+\]=down'; then
        xinput disable "$id"
        xinput enable "$id"
    fi
done

Run the helper manually when the connection has not dropped:

DISPLAY=:10.0 XAUTHORITY="$HOME/.Xauthority" rdp-input-reset

This is safer than restarting xfwm4, the panel, or the whole desktop: it clears the stale XRDP input devices while preserving windows, terminals, and the session. It is not magic—the hook only runs for a reconnect to an existing session. If the connection remains open while input gets stuck, run the helper manually. KillDisconnected=true is a stronger isolation workaround, but it eventually kills the disconnected desktop, so it is the wrong trade-off for a persistent RDP session.

GitHub Pages 404.html Can Read the Deep Link

GitHub Pages can serve a custom 404.html for a path such as /note/123 without changing the address bar. The fallback page can read the requested path and pass the note ID to the single-page app:

const match = window.location.pathname.match(/^\/note\/([^/]+)\/?$/);
const noteId = match?.[1] ?? null;

This lets a static app display the requested note, but the original HTTP response is still 404. A host or edge rewrite is needed for the same clean URL to return HTTP 200.

Your API returns 403 to Python but 200 to curl? It's Cloudflare's Browser Integrity Check

An AI agent posting to my API through Python's urllib kept getting 403 Forbidden. The same request sent with curl succeeded. The request never reached the API at all: Cloudflare rejected it at the edge.

The difference is the User-Agent header. Compare the two:

curl -s -o /dev/null -w '%{http_code} %header{server}\n' https://api.example.com/health
# 200 cloudflare
curl -s -A 'Python-urllib/3.12' https://api.example.com/health
# error code: 1010

error code: 1010 together with server: cloudflare means Cloudflare refused the client's signature. On a zone with no Bot Fight Mode and no WAF rules, the usual cause is Browser Integrity Check (BIC). It is on by default, and it blocks requests whose headers look like those of abusive bots. Against my zone I tried six User-Agents. Python-urllib/3.12 got 403. python-requests, curl, Go-http-client, node, and no UA at all all got 200. So one Python script fails while its neighbour using requests works, and the failure looks random.

You can confirm BIC is the cause with the zone settings API:

curl -s -H "X-Auth-Key: $CF_API_KEY" -H "X-Auth-Email: $CF_API_EMAIL" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/settings/browser_check"
# "value": "on"

The quick fix is to make every client send a browser-ish UA. That fixes your own scripts, but every other agent and SDK still hits the wall, and an API that only answers browsers is an odd thing to publish. A better fix is to turn BIC off only for the API hosts, and keep it on for the rest of the zone. A Configuration Rule does exactly that:

curl -s -X PUT -H "X-Auth-Key: $CF_API_KEY" -H "X-Auth-Email: $CF_API_EMAIL" \
  -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/rulesets/phases/http_config_settings/entrypoint" \
  -d '{"rules":[{
        "description": "API hosts: no Browser Integrity Check",
        "expression": "(http.host in {\"api.example.com\" \"upload.example.com\"})",
        "action": "set_config",
        "action_parameters": {"bic": false}
      }]}'

A PUT on the phase entrypoint replaces the whole rule list. If the zone already has Configuration Rules, fetch the entrypoint first and send the existing rules back along with the new one. On a zone with none, the GET returns error 10003 ("could not find entrypoint ruleset") and the PUT creates the entrypoint. The change took effect within seconds: the Python-urllib request went from 403 to the origin's own response.

Don't forget sibling hosts. The API worked after the change, but the image upload host behind it still returned 403 to the same agent until I added it to the rule.

gmail-cli Terms of Service

gmail-cli is a personal command-line tool run by David Wei (shukebeta) for Google accounts he owns or operates. It is not offered to the public.

Use. Only the account owner, or an assistant acting for him, may use gmail-cli, and only on accounts that authorized it through Google OAuth. Use must follow Google's terms and the law: no spam, no unsolicited bulk mail, no access to accounts you do not own.

No warranty. gmail-cli is provided as is, without warranty of any kind. The operator is not liable for lost mail, missed messages or any damage arising from its use.

Access. Authorization can be revoked at any time at myaccount.google.com/permissions. The operator may stop or change the tool at any time.

Privacy. See the gmail-cli Privacy Policy on this blog.

Contact. [email protected]

Last updated: 2026-11-04.

gmail-cli Privacy Policy

gmail-cli is a personal command-line tool run by David Wei (shukebeta). It is not a public service: only Google accounts owned or operated by David Wei authorize it, including mailboxes used by his own AI assistant.

What it accesses. With your consent through Google OAuth, gmail-cli reads, labels, sends and filters mail and manages Gmail settings (filters, send-as addresses) in the authorized account. It requests only the scopes shown on the consent screen.

Where data goes. Mail content and OAuth tokens stay on the operator's own machines. Nothing is sold, shared with third parties, or used for advertising. Data is used only to carry out the actions the account owner asks for.

Limited Use. gmail-cli's use and transfer of information received from Google APIs adheres to the Google API Services User Data Policy, including the Limited Use requirements.

Retention and deletion. Mail is kept only as long as a task needs it. Revoke access at any time at myaccount.google.com/permissions; the operator then deletes the stored token.

Contact. [email protected]

Last updated: 2026-11-04.