TrueAxis Source

Full source code

This page shows the complete TrueAxis source for transparency. The Competitive edition is based on this code, while the Advanced edition includes additional features and optimizations. Use the Copy button to copy it all at once.
Back to site

#!/usr/bin/env python3
import sys
import time
import threading
import pygame
import json
import os
import vgamepad as vg
from PySide6.QtCore import QObject, Signal, Slot, Property, QTimer, Qt, QEvent, QThread, QSettings
from PySide6.QtGui import QGuiApplication, QIcon, QPixmap, QPainter, QColor, QFont, QAction
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QStyle

# Try to import pynput for keyboard support
try:
    from pynput.keyboard import Controller as KeyboardController, Key
    KEYBOARD_AVAILABLE = True
except ImportError:
    print("Warning: pynput not installed. Keyboard bindings will not be available.")
    print("Install with: pip install pynput")
    KEYBOARD_AVAILABLE = False
    KeyboardController = None
    Key = None

# --- Platform-specific imports for auto-start ---
if sys.platform == 'win32':
    import winreg
    import winshell
    from win32com.client import Dispatch
elif sys.platform == 'darwin':  # macOS
    import subprocess
    import plistlib
    from pathlib import Path

# --- CONFIG & PROFILES ---
CONFIG_FILE = "trueaxis_config.json"
BUTTON_MAPPING_FILE = "trueaxis_buttons.json"
SETTINGS_FILE = "trueaxis_settings.json"

PROFILES = {
    "Logitech G29 (Standard)": {"axes": [0, 2, 3], "desc": "Recommended first choice for G29"},
    "Logitech G29 (Alt Mode)": {"axes": [0, 1, 2], "desc": "Use if Gas/Brake inputs are swapped"},
    "Logitech G920 / G923": {"axes": [0, 1, 2], "desc": "Standard Xbox-layout wheels"},
    "Driving Force GT": {"axes": [0, 1, 2], "desc": "Legacy Logitech wheels"},
    "Logitech G27": {"axes": [0, 2, 3], "desc": "Classic G27 axis mapping"},
    "PlayStation 4/5 (Standard)": {"axes": [0, 5, 4], "desc": "Steer: L-Stick, Gas: R2, Brake: L2"},
    "PlayStation 4/5 (Alt Mode)": {"axes": [0, 4, 3], "desc": "Use if triggers are reversed"},
    "PlayStation (DirectInput)": {"axes": [0, 2, 5], "desc": "Raw DirectInput mode"},
    "Fanatec (Standard)": {"axes": [0, 1, 2], "desc": "Yellow compatibility mode"},
    "Fanatec (Alt 1)": {"axes": [0, 2, 3], "desc": "Alternative axis configuration"},
    "Fanatec (Alt 2)": {"axes": [0, 4, 5], "desc": "Common on CSL DD bases"},
    "Fanatec (Alt 3)": {"axes": [0, 5, 6], "desc": "Higher axis pedal mapping"},
    "Thrustmaster T300/T150": {"axes": [0, 1, 2], "desc": "Standard Thrustmaster mapping"},
    "Thrustmaster T-GT / T248": {"axes": [0, 2, 1], "desc": "Newer generation wheels"},
    "Thrustmaster (Combined)": {"axes": [0, 1, 1], "desc": "Combined pedal axis mode"},
    "Generic Gamepad (Xbox/XInput)": {"axes": [0, 5, 4], "desc": "Standard Xbox controller layout"},
    "Generic Wheel (DirectInput)": {"axes": [0, 1, 2], "desc": "Universal DirectInput wheels"},
    "Combined Pedals Mode": {"axes": [0, 1, 1], "desc": "Single axis for Gas/Brake"},
}

XBOX_BUTTON_NAMES = [
    "DISABLED",
    "--- XBOX BUTTONS ---",
    "A (Cross)",
    "B (Circle)",
    "X (Square)",
    "Y (Triangle)",
    "LB (L1)",
    "RB (R1)",
    "Back (Share)",
    "Start (Options)",
    "LThumb (L3)",
    "RThumb (R3)",
    "Guide (PS)",
    "DPad Up",
    "DPad Down",
    "DPad Left",
    "DPad Right",
    "--- KEYBOARD KEYS ---",
    "Key: Space",
    "Key: Enter",
    "Key: Shift",
    "Key: Ctrl",
    "Key: Alt",
    "Key: Tab",
    "Key: Esc",
    "Key: 0",
    "Key: 1",
    "Key: 2",
    "Key: 3",
    "Key: 4",
    "Key: 5",
    "Key: 6",
    "Key: 7",
    "Key: 8",
    "Key: 9",
    "Key: A",
    "Key: B",
    "Key: C",
    "Key: D",
    "Key: E",
    "Key: F",
    "Key: G",
    "Key: H",
    "Key: I",
    "Key: J",
    "Key: K",
    "Key: L",
    "Key: M",
    "Key: N",
    "Key: O",
    "Key: P",
    "Key: Q",
    "Key: R",
    "Key: S",
    "Key: T",
    "Key: U",
    "Key: V",
    "Key: W",
    "Key: X",
    "Key: Y",
    "Key: Z",
    "Key: F1",
    "Key: F2",
    "Key: F3",
    "Key: F4",
    "Key: F5",
    "Key: F6",
    "Key: F7",
    "Key: F8",
    "Key: F9",
    "Key: F10",
    "Key: F11",
    "Key: F12",
    "Key: Up Arrow",
    "Key: Down Arrow",
    "Key: Left Arrow",
    "Key: Right Arrow",
]

XBOX_BUTTON_MAP = {
    "DISABLED": None,
    "--- XBOX BUTTONS ---": None,
    "A (Cross)": vg.XUSB_BUTTON.XUSB_GAMEPAD_A,
    "B (Circle)": vg.XUSB_BUTTON.XUSB_GAMEPAD_B,
    "X (Square)": vg.XUSB_BUTTON.XUSB_GAMEPAD_X,
    "Y (Triangle)": vg.XUSB_BUTTON.XUSB_GAMEPAD_Y,
    "LB (L1)": vg.XUSB_BUTTON.XUSB_GAMEPAD_LEFT_SHOULDER,
    "RB (R1)": vg.XUSB_BUTTON.XUSB_GAMEPAD_RIGHT_SHOULDER,
    "Back (Share)": vg.XUSB_BUTTON.XUSB_GAMEPAD_BACK,
    "Start (Options)": vg.XUSB_BUTTON.XUSB_GAMEPAD_START,
    "LThumb (L3)": vg.XUSB_BUTTON.XUSB_GAMEPAD_LEFT_THUMB,
    "RThumb (R3)": vg.XUSB_BUTTON.XUSB_GAMEPAD_RIGHT_THUMB,
    "Guide (PS)": vg.XUSB_BUTTON.XUSB_GAMEPAD_GUIDE,
    "DPad Up": vg.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_UP,
    "DPad Down": vg.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_DOWN,
    "DPad Left": vg.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_LEFT,
    "DPad Right": vg.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_RIGHT,
    "--- KEYBOARD KEYS ---": None,
}

# Keyboard key mapping (only if pynput is available)
if KEYBOARD_AVAILABLE:
    KEYBOARD_MAP = {
        "Key: Space": ' ',
        "Key: Enter": Key.enter,
        "Key: Shift": Key.shift,
        "Key: Ctrl": Key.ctrl,
        "Key: Alt": Key.alt,
        "Key: Tab": Key.tab,
        "Key: Esc": Key.esc,
        "Key: 0": '0', "Key: 1": '1', "Key: 2": '2', "Key: 3": '3', "Key: 4": '4',
        "Key: 5": '5', "Key: 6": '6', "Key: 7": '7', "Key: 8": '8', "Key: 9": '9',
        "Key: A": 'a', "Key: B": 'b', "Key: C": 'c', "Key: D": 'd', "Key: E": 'e',
        "Key: F": 'f', "Key: G": 'g', "Key: H": 'h', "Key: I": 'i', "Key: J": 'j',
        "Key: K": 'k', "Key: L": 'l', "Key: M": 'm', "Key: N": 'n', "Key: O": 'o',
        "Key: P": 'p', "Key: Q": 'q', "Key: R": 'r', "Key: S": 's', "Key: T": 't',
        "Key: U": 'u', "Key: V": 'v', "Key: W": 'w', "Key: X": 'x', "Key: Y": 'y',
        "Key: Z": 'z',
        "Key: F1": Key.f1, "Key: F2": Key.f2, "Key: F3": Key.f3, "Key: F4": Key.f4,
        "Key: F5": Key.f5, "Key: F6": Key.f6, "Key: F7": Key.f7, "Key: F8": Key.f8,
        "Key: F9": Key.f9, "Key: F10": Key.f10, "Key: F11": Key.f11, "Key: F12": Key.f12,
        "Key: Up Arrow": Key.up,
        "Key: Down Arrow": Key.down,
        "Key: Left Arrow": Key.left,
        "Key: Right Arrow": Key.right,
    }
else:
    KEYBOARD_MAP = {}

QML_CODE = """
import QtQuick
import QtQuick.Controls.Basic
import QtQuick.Layouts
import QtQuick.Window

ApplicationWindow {
    id: mainWindow
    width: 520
    height: 1120
    visible: true
    title: "TrueAxis v3.5"
    color: "#09090b"
    
    // Auto-start minimized flag
    property bool autoStartMinimized: false
    
    // Premium Silver/Gray Color Palette - Clean and professional
    readonly property color colorPrimary: "#94a3b8"
    readonly property color colorPrimaryHover: "#64748b"
    readonly property color colorAccent: "#cbd5e1"
    readonly property color colorBg: "#0a0a0f"
    readonly property color colorSurface: "#0f0f16"
    readonly property color colorSurfaceHover: "#17171f"
    readonly property color colorCard: "#14141c"
    readonly property color colorTextPrimary: "#f5f5f7"
    readonly property color colorTextSecondary: "#cbd5e1"
    readonly property color colorTextMuted: "#737380"
    readonly property color colorBorder: "#26262f"
    readonly property color colorBorderLight: "#3a3a45"
    readonly property color colorSuccess: "#22c55e"
    readonly property color colorWarning: "#f59e0b"
    readonly property color colorError: "#ef4444"
    readonly property color colorInactive: "#3a3a45"
    
    // Silver gradient colors for axis bars
    readonly property color hudSilver1: "#1e293b"
    readonly property color hudSilver2: "#94a3b8"
    readonly property color hudSilver3: "#475569"
    
    property var silverGradient: [
        {position: 0.0, color: hudSilver1},
        {position: 0.5, color: hudSilver2},
        {position: 1.0, color: hudSilver3}
    ]
    
    // Handle close event - close application
    onClosing: function(close) {
        console.log("Closing application...")
        // Stop emulation if running
        if (backend.running) {
            backend.stop_mapping()
        }
        // Save settings before quitting
        backend.force_save_settings()
        trayManager.save_settings()
        close.accepted = true
        Qt.quit()
    }
    
    // Handle window state changes
    onVisibilityChanged: {
        if (visibility === Window.Minimized && trayManager.hideToTrayEnabled) {
            hide()
            trayManager.show_tray()
        }
    }
    
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            orientation: Gradient.Radial
            GradientStop { position: 0.0; color: "#2d3748" }
            GradientStop { position: 0.4; color: "#1a202c" }
            GradientStop { position: 1.0; color: "#0a0a0f" }
        }
    }
    
    Column {
        id: mainColumn
        anchors.fill: parent
        spacing: 0
            
        // HEADER - matching web demo layout
        Item {
            width: parent.width
            height: 110
            
            Column {
                anchors.left: parent.left
                anchors.leftMargin: 24
                anchors.top: parent.top
                anchors.topMargin: 28
                spacing: 4
                
                Row {
                    spacing: 12
                    
                    Text {
                        text: "TrueAxis"
                        font.pixelSize: 32
                        font.bold: true
                        color: colorTextPrimary
                        font.family: "Inter"
                    }
                    
                    // Version badge with silver gradient border
                    Rectangle {
                        width: 52
                        height: 28
                        radius: 8
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.verticalCenterOffset: 4
                        
                        gradient: Gradient {
                            GradientStop { position: 0.0; color: "#475569" }
                            GradientStop { position: 0.5; color: "#94a3b8" }
                            GradientStop { position: 1.0; color: "#475569" }
                        }
                        
                        Text {
                            anchors.centerIn: parent
                            text: "v3.5"
                            font.pixelSize: 11
                            font.bold: true
                            color: "#e2e8f0"
                            font.family: "Inter"
                        }
                    }
                    
                    // Spacer
                    Item { width: 1; Layout.fillWidth: true }
                }
                
                Text {
                    text: "Competitive"
                    font.pixelSize: 13
                    font.bold: true
                    color: "#94a3b8"
                    font.family: "Inter"
                }
            }
        }
        
        Rectangle {
            width: parent.width - 48
            height: 0
            color: colorBorder
            anchors.horizontalCenter: parent.horizontalCenter
        }
        
        // AUTOSTART & TRAY SETTINGS - Clean integrated design
        SectionCard {
            title: ""
            cardHeight: autoStartCheckbox.checked ? 95 : 60
            
            Column {
                spacing: 10
                width: parent.width
                
                // Main settings row - clean and minimal
                Row {
                    spacing: 12
                    width: parent.width
                    
                    // Launch at startup
                    Rectangle {
                        width: 145
                        height: 34
                        radius: 8
                        color: autoStartCheckbox.checked ? colorSurface : "transparent"
                        border.width: 1
                        border.color: autoStartCheckbox.checked ? colorPrimary : colorBorder
                        
                        Row {
                            anchors.centerIn: parent
                            spacing: 8
                            
                            Rectangle {
                                width: 16
                                height: 16
                                radius: 8
                                color: autoStartCheckbox.checked ? colorPrimary : colorSurface
                                border.width: 1
                                border.color: colorBorder
                                anchors.verticalCenter: parent.verticalCenter
                                
                                Rectangle {
                                    width: 8
                                    height: 8
                                    radius: 4
                                    color: colorTextPrimary
                                    anchors.centerIn: parent
                                    visible: autoStartCheckbox.checked
                                }
                            }
                            
                            Text {
                                text: "Launch at startup"
                                font.pixelSize: 10
                                color: autoStartCheckbox.checked ? colorTextPrimary : colorTextMuted
                                anchors.verticalCenter: parent.verticalCenter
                                font.family: "Inter"
                            }
                        }
                        
                        MouseArea {
                            id: autoStartCheckbox
                            anchors.fill: parent
                            cursorShape: Qt.PointingHandCursor
                            property bool checked: false
                            onClicked: {
                                checked = !checked
                                backend.set_auto_start(checked)
                            }
                        }
                        
                        Connections {
                            target: backend
                            function onAutoStartChanged(enabled) {
                                autoStartCheckbox.checked = enabled
                            }
                        }
                    }
                    
                    // Start minimized
                    Rectangle {
                        width: 125
                        height: 34
                        radius: 8
                        color: startMinimizedCheckbox.checked ? colorSurface : "transparent"
                        border.width: 1
                        border.color: startMinimizedCheckbox.checked ? colorPrimary : colorBorder
                        opacity: autoStartCheckbox.checked ? 1.0 : 0.4
                        
                        Row {
                            anchors.centerIn: parent
                            spacing: 8
                            
                            Rectangle {
                                width: 16
                                height: 16
                                radius: 8
                                color: startMinimizedCheckbox.checked ? colorPrimary : colorSurface
                                border.width: 1
                                border.color: colorBorder
                                anchors.verticalCenter: parent.verticalCenter
                                
                                Rectangle {
                                    width: 8
                                    height: 8
                                    radius: 4
                                    color: colorTextPrimary
                                    anchors.centerIn: parent
                                    visible: startMinimizedCheckbox.checked
                                }
                            }
                            
                            Text {
                                text: "Start minimized"
                                font.pixelSize: 10
                                color: startMinimizedCheckbox.checked ? colorTextPrimary : colorTextMuted
                                anchors.verticalCenter: parent.verticalCenter
                                font.family: "Inter"
                            }
                        }
                        
                        MouseArea {
                            id: startMinimizedCheckbox
                            anchors.fill: parent
                            cursorShape: Qt.PointingHandCursor
                            property bool checked: false
                            enabled: autoStartCheckbox.checked
                            onClicked: {
                                if (enabled) {
                                    checked = !checked
                                    backend.set_start_minimized(checked)
                                }
                            }
                        }
                        
                        Connections {
                            target: backend
                            function onStartMinimizedChanged(enabled) {
                                startMinimizedCheckbox.checked = enabled
                            }
                        }
                    }
                    
                    // Minimize to tray
                    Rectangle {
                        width: 125
                        height: 34
                        radius: 8
                        color: hideToTrayCheckbox.checked ? colorSurface : "transparent"
                        border.width: 1
                        border.color: hideToTrayCheckbox.checked ? colorPrimary : colorBorder
                        opacity: trayManager.trayAvailable ? 1.0 : 0.4
                        
                        Row {
                            anchors.centerIn: parent
                            spacing: 8
                            
                            Rectangle {
                                width: 16
                                height: 16
                                radius: 8
                                color: hideToTrayCheckbox.checked ? colorPrimary : colorSurface
                                border.width: 1
                                border.color: colorBorder
                                anchors.verticalCenter: parent.verticalCenter
                                
                                Rectangle {
                                    width: 8
                                    height: 8
                                    radius: 4
                                    color: colorTextPrimary
                                    anchors.centerIn: parent
                                    visible: hideToTrayCheckbox.checked
                                }
                            }
                            
                            Text {
                                text: "Minimize to tray"
                                font.pixelSize: 10
                                color: hideToTrayCheckbox.checked ? colorTextPrimary : colorTextMuted
                                anchors.verticalCenter: parent.verticalCenter
                                font.family: "Inter"
                            }
                        }
                        
                        MouseArea {
                            id: hideToTrayCheckbox
                            anchors.fill: parent
                            cursorShape: Qt.PointingHandCursor
                            property bool checked: false
                            enabled: trayManager.trayAvailable
                            onClicked: {
                                if (enabled) {
                                    checked = !checked
                                    trayManager.set_hide_to_tray(checked)
                                }
                            }
                        }
                        
                        Connections {
                            target: trayManager
                            function onHideToTrayChanged(enabled) {
                                hideToTrayCheckbox.checked = enabled
                            }
                        }
                    }
                }
                
                // Status indicator - shows when Launch at startup is enabled
                Rectangle {
                    width: parent.width
                    height: 28
                    radius: 6
                    color: colorSurface
                    border.width: 1
                    border.color: colorBorder
                    visible: autoStartCheckbox.checked
                    
                    Row {
                        anchors.centerIn: parent
                        spacing: 6
                        
                        Rectangle {
                            width: 6
                            height: 6
                            radius: 3
                            color: colorSuccess
                            anchors.verticalCenter: parent.verticalCenter
                        }
                        
                        Text {
                            text: startMinimizedCheckbox.checked ? 
                                  "Auto-start & minimize enabled" : 
                                  "Auto-start enabled"
                            font.pixelSize: 10
                            color: colorTextSecondary
                            font.family: "Inter"
                            anchors.verticalCenter: parent.verticalCenter
                        }
                    }
                }
            }
        }
        
        Item { width: 1; height: 20 }
        
        // DEVICE SELECTION
        SectionCard {
            title: "Input Device"
            cardHeight: 120
            
            Column {
                spacing: 10
                width: parent.width
                
                Row {
                    spacing: 10
                    width: parent.width
                    
                    StyledComboBox {
                        id: deviceCombo
                        width: parent.width - 110
                        model: backend.devices
                        
                        property bool updatingFromBackend: true  // Start as true to prevent initial change
                        
                        Component.onCompleted: {
                            // Set flag to false after a delay to allow proper initialization
                            updatingFromBackend = false
                        }
                        
                        // Listen for device changes from backend
                        Connections {
                            target: backend
                            function onCurrentDeviceIndexChanged(index) {
                                deviceCombo.updatingFromBackend = true
                                deviceCombo.currentIndex = index
                                deviceCombo.updatingFromBackend = false
                            }
                        }
                        
                        onCurrentIndexChanged: {
                            if (!updatingFromBackend && currentIndex >= 0) {
                                backend.select_device(currentIndex)
                            }
                        }
                    }
                    
                    StyledButton {
                        text: "↻  Refresh"
                        width: 100
                        secondary: true
                        hoverEffect: true
                        onClicked: backend.refresh_devices()
                    }
                }
                
                StyledButton {
                    text: "Open Input Inspector"
                    width: parent.width
                    height: 32
                    secondary: true
                    fontSize: 11
                    hoverEffect: true
                    onClicked: inspectorDialog.open()
                }
            }
        }
        
        // PROFILE SELECTION
        SectionCard {
            title: "Profile"
            cardHeight: 90
            
            Column {
                spacing: 8
                width: parent.width
                
                StyledComboBox {
                    id: profileCombo
                    width: parent.width
                    model: backend.profileNames
                    
                    property bool updatingFromBackend: true  // Start as true to prevent initial change
                    
                    Component.onCompleted: {
                        // Set flag to false after a delay to allow proper initialization
                        updatingFromBackend = false
                    }
                    
                    // Listen for profile changes from backend
                    Connections {
                        target: backend
                        function onCurrentProfileChanged(profileName) {
                            profileCombo.updatingFromBackend = true
                            var profileNames = backend.profileNames
                            for (var i = 0; i < profileNames.length; i++) {
                                if (profileNames[i] === profileName) {
                                    profileCombo.currentIndex = i
                                    break
                                }
                            }
                            profileCombo.updatingFromBackend = false
                        }
                    }
                    
                    onCurrentTextChanged: {
                        if (!updatingFromBackend && currentText !== "") {
                            backend.select_profile(currentText)
                        }
                    }
                }
                
                Text {
                    id: profileDesc
                    text: "Select a profile to see description"
                    font.pixelSize: 11
                    color: colorTextMuted
                    wrapMode: Text.WordWrap
                    width: parent.width
                    font.family: "Inter"
                }
            }
        }
        
        // LIVE INPUT PREVIEW
        SectionCard {
            title: ""
            cardHeight: 155
            
            Column {
                spacing: 0
                width: parent.width
                
                AxisBar {
                    id: steerBar
                    label: "Steering"
                    value: 0
                    direction: 0
                    gradientColors: silverGradient
                }
                
                Item { width: 1; height: 4 }
                
                AxisBar {
                    id: gasBar
                    label: "Gas"
                    value: 0
                    gradientColors: silverGradient
                }
                
                Item { width: 1; height: 4 }
                
                AxisBar {
                    id: brakeBar
                    label: "Brake"
                    value: 0
                    gradientColors: silverGradient
                }
                
                Item { width: 1; height: 10 }
                
                Rectangle {
                    width: parent.width
                    height: 32
                    radius: 8
                    color: colorSurface
                    
                    Row {
                        anchors.centerIn: parent
                        spacing: 20
                        
                        StyledCheckBox {
                            id: invertGasCheckbox
                            text: "Invert Gas"
                            checked: false
                            
                            Component.onCompleted: {
                                checked = backend.invert_gas
                            }
                            
                            onToggled: backend.set_invert_gas(checked)
                        }
                        
                        StyledCheckBox {
                            id: invertBrakeCheckbox
                            text: "Invert Brake"
                            checked: false
                            
                            Component.onCompleted: {
                                checked = backend.invert_brake
                            }
                            
                            onToggled: backend.set_invert_brake(checked)
                        }
                    }
                }
            }
        }
        
        // STEERING TUNING
        SectionCard {
            title: ""
            cardHeight: 130
            
            Column {
                spacing: 10
                width: parent.width
                
                Row {
                    spacing: 10
                    width: parent.width
                    
                    Text {
                        text: "Square Steering"
                        font.pixelSize: 11
                        font.bold: true
                        color: colorTextSecondary
                        width: 120
                        anchors.verticalCenter: parent.verticalCenter
                        font.family: "Inter"
                    }
                    
                    StyledCheckBox {
                        id: squareCheckbox
                        text: ""
                        checked: false
                        anchors.verticalCenter: parent.verticalCenter
                        
                        Component.onCompleted: {
                            checked = backend.square_input
                            squareSteeringRow.visible = checked
                        }
                        
                        onToggled: {
                            backend.set_square_input(checked)
                            squareSteeringRow.visible = checked
                        }
                    }
                    
                    Item { width: 1; Layout.fillWidth: true }
                }
                
                Row {
                    id: squareSteeringRow
                    spacing: 10
                    width: parent.width
                    visible: false
                    
                    Text {
                        text: "Square Area:"
                        font.pixelSize: 11
                        color: colorTextMuted
                        width: 120
                        anchors.verticalCenter: parent.verticalCenter
                        font.family: "Inter"
                    }
                    
                    Rectangle {
                        id: slider
                        width: parent.width - 120 - 50
                        height: 24
                        radius: 12
                        color: colorSurface
                        
                        property real from: 0.0
                        property real to: 0.99
                        property real value: 0.5
                        property real stepSize: 0.01
                        
                        Component.onCompleted: {
                            value = backend.square_area
                        }
                        
                        Rectangle {
                            width: parent.width * ((parent.value - parent.from) / (parent.to - parent.from))
                            height: parent.height
                            radius: 12
                            color: colorPrimary
                        }
                        
                        Rectangle {
                            x: (parent.width * ((parent.value - parent.from) / (parent.to - parent.from))) - 8
                            y: (parent.height - 16) / 2
                            width: 16
                            height: 16
                            radius: 8
                            color: colorTextPrimary
                            border.color: colorPrimary
                            border.width: 2
                        }
                        
                        MouseArea {
                            anchors.fill: parent
                            hoverEnabled: true
                            cursorShape: Qt.PointingHandCursor
                            
                            function updateValue(x) {
                                var newValue = slider.from + (x / width) * (slider.to - slider.from)
                                newValue = Math.max(slider.from, Math.min(slider.to, newValue))
                                if (slider.stepSize > 0) {
                                    newValue = Math.round(newValue / slider.stepSize) * slider.stepSize
                                }
                                if (newValue !== slider.value) {
                                    slider.value = newValue
                                    backend.set_square_area(newValue)
                                }
                            }
                            
                            onPressed: updateValue(mouse.x)
                            onPositionChanged: if (pressed) updateValue(mouse.x)
                        }
                    }
                    
                    Text {
                        text: Math.round(slider.value * 100) + "%"
                        font.pixelSize: 11
                        color: colorTextMuted
                        width: 40
                        anchors.verticalCenter: parent.verticalCenter
                        font.family: "Inter"
                    }
                }
                
                Text {
                    id: squareDesc
                    text: "Standard circular steering response"
                    font.pixelSize: 10
                    color: colorTextMuted
                    width: parent.width
                    wrapMode: Text.WordWrap
                    font.family: "Inter"
                    
                    Connections {
                        target: squareCheckbox
                        function onToggled() {
                            if (squareCheckbox.checked) {
                                squareDesc.text = "Steering will reach 100% at " + Math.round(slider.value * 100) + "% input"
                            } else {
                                squareDesc.text = "Standard circular steering response"
                            }
                        }
                    }
                    
                    Connections {
                        target: slider
                        function onValueChanged() {
                            if (squareCheckbox.checked) {
                                squareDesc.text = "Steering will reach 100% at " + Math.round(slider.value * 100) + "% input"
                            }
                        }
                    }
                }
            }
        }
        
        // BUTTON MAPPING
        SectionCard {
            title: ""
            cardHeight: 100
            
            Column {
                spacing: 10
                width: parent.width
                
                StyledButton {
                    text: "Configure Buttons"
                    width: parent.width
                    secondary: true
                    hoverEffect: true
                    onClicked: buttonDialog.open()
                }
                
                Text {
                    id: buttonStatus
                    text: "No buttons mapped"
                    font.pixelSize: 11
                    color: colorTextMuted
                    font.family: "Inter"
                }
            }
        }
        
        // ACTIVATE BUTTON
        Item {
            width: parent.width
            height: 95
            
            Column {
                anchors.left: parent.left
                anchors.leftMargin: 24
                anchors.right: parent.right
                anchors.rightMargin: 24
                spacing: 12
                
                Row {
                    spacing: 6
                    
                    Text {
                        id: statusDot
                        text: "●"
                        font.pixelSize: 12
                        color: colorInactive
                    }
                    
                    Text {
                        id: statusText
                        text: "Ready to activate"
                        font.pixelSize: 11
                        color: colorTextMuted
                        font.family: "Inter"
                    }
                }
                
                StyledButton {
                    id: activateBtn
                    text: backend.running ? "DEACTIVATE" : "ACTIVATE"
                    width: parent.width
                    height: 48
                    primary: !backend.running
                    danger: backend.running
                    fontSize: 14
                    hoverEffect: true
                    onClicked: backend.toggle_mapping()
                }
            }
        }
        
        Item { width: 1; height: 20 }
    }
    
    // INPUT INSPECTOR DIALOG
    Dialog {
        id: inspectorDialog
        width: 420
        height: 550
        anchors.centerIn: parent
        modal: true
        title: "Input Inspector"
        
        background: Rectangle {
        color: colorBg
        radius: 12
        border.color: colorBorder
        border.width: 1
        }
        
        header: Item {
        height: 70
        
        Column {
            anchors.left: parent.left
            anchors.leftMargin: 20
            anchors.top: parent.top
            anchors.topMargin: 18
            spacing: 6
            
            Text {
                text: "Input Inspector"
                font.pixelSize: 18
                font.weight: Font.DemiBold
                color: colorTextPrimary
                font.family: "Inter"
            }
            
            Text {
                text: "Identify axis IDs by moving controls"
                font.pixelSize: 12
                color: colorTextMuted
                font.family: "Inter"
            }
        }
        }
        
        contentItem: ScrollView {
        clip: true
        
        Column {
            spacing: 6
            width: parent.width
            
            Repeater {
                model: backend.numAxes
                
                Rectangle {
                    width: 380
                    height: 50
                    radius: 8
                    color: colorCard
                    border.color: colorBorder
                    border.width: 1
                    
                    property int axisIndex: index
                    property real axisValue: 0.5
                    
                    Component.onCompleted: {
                        axisValue = backend.getAxisValue(index)
                    }
                    
                    Connections {
                        target: backend
                        function onAxisValuesChanged() {
                            axisValue = backend.getAxisValue(axisIndex)
                        }
                    }
                    
                    Row {
                        anchors.fill: parent
                        anchors.margins: 12
                        spacing: 0
                        
                        Text {
                            text: "Axis " + parent.parent.axisIndex
                            font.pixelSize: 11
                            font.bold: true
                            color: colorTextSecondary
                            width: 50
                            anchors.verticalCenter: parent.verticalCenter
                            font.family: "Inter"
                        }
                        
                        Item {
                            width: parent.width - 50 - 55
                            height: parent.height
                            anchors.verticalCenter: parent.verticalCenter
                            
                            Rectangle {
                                id: axisBarBg
                                anchors.fill: parent
                                anchors.leftMargin: 5
                                anchors.rightMargin: 5
                                height: 8
                                radius: 4
                                color: colorSurface
                                anchors.verticalCenter: parent.verticalCenter
                                clip: true
                                
                                Rectangle {
                                    id: axisBarFill
                                    width: axisBarBg.width * Math.max(0, Math.min(1, axisValue))
                                    height: parent.height
                                    radius: 4
                                    
                                    gradient: Gradient {
                                        orientation: Gradient.Horizontal
                                        GradientStop { position: 0.0; color: hudSilver1 }
                                        GradientStop { position: 0.5; color: hudSilver2 }
                                        GradientStop { position: 1.0; color: hudSilver3 }
                                    }
                                    
                                    Behavior on width {
                                        NumberAnimation { duration: 50; easing.type: Easing.OutQuad }
                                    }
                                }
                            }
                        }
                        
                        Text {
                            id: axisValueText
                            text: Math.round(Math.max(0, Math.min(1, axisValue)) * 100) + "%"
                            font.pixelSize: 10
                            color: colorTextMuted
                            width: 45
                            anchors.verticalCenter: parent.verticalCenter
                            horizontalAlignment: Text.AlignRight
                            font.family: "Inter"
                        }
                    }
                }
            }
        }
        }
        
        footer: DialogButtonBox {
        padding: 16
        background: Rectangle {
            color: colorBg
            border.color: colorBorder
            border.width: 1
        }
        
        StyledButton {
            text: "Close"
            secondary: true
            hoverEffect: true
            onClicked: inspectorDialog.close()
        }
        }
    }
    
    // BUTTON MAPPING DIALOG
    Dialog {
        id: buttonDialog
        width: 480
        height: 550
        anchors.centerIn: parent
        modal: true
        title: "Button Mapping"
        
        background: Rectangle {
        color: colorBg
        radius: 12
        border.color: colorBorder
        border.width: 1
        }
        
        header: Item {
        height: 70
        
        Column {
            anchors.left: parent.left
            anchors.leftMargin: 20
            anchors.top: parent.top
            anchors.topMargin: 18
            spacing: 6
            
            Text {
                text: "Button Mapping"
                font.pixelSize: 18
                font.weight: Font.DemiBold
                color: colorTextPrimary
                font.family: "Inter"
            }
            
            Text {
                text: backend.numButtons + " buttons available"
                font.pixelSize: 12
                color: colorTextMuted
                font.family: "Inter"
            }
        }
        }
        
        contentItem: Column {
        spacing: 8
        
        ScrollView {
            width: parent.width
            height: 390
            clip: true
            
            Column {
                spacing: 2
                width: parent.width
                
                Repeater {
                    model: backend.numButtons
                    
                    Rectangle {
                        width: 440
                        height: 44
                        radius: 6
                        color: colorCard
                        border.color: colorBorder
                        border.width: 1
                        
                        property int buttonIndex: index
                        property bool buttonPressed: false
                        
                        Component.onCompleted: {
                            buttonPressed = backend.getButtonState(index)
                        }
                        
                        Connections {
                            target: backend
                            function onButtonStatesChanged() {
                                buttonPressed = backend.getButtonState(buttonIndex)
                            }
                        }
                        
                        Row {
                            anchors.centerIn: parent
                            spacing: 10
                            width: parent.width - 20
                            
                            Rectangle {
                                width: 18
                                height: 18
                                radius: 9
                                color: parent.parent.buttonPressed ? colorAccent : colorInactive
                                border.color: parent.parent.buttonPressed ? "#1d4ed8" : colorBorder
                                border.width: 2
                                anchors.verticalCenter: parent.verticalCenter
                                
                                Rectangle {
                                    anchors.centerIn: parent
                                    width: 6
                                    height: 6
                                    radius: 3
                                    color: parent.parent.parent.parent.buttonPressed ? "#ffffff" : "transparent"
                                    visible: parent.parent.parent.parent.buttonPressed
                                }
                                
                                Behavior on color {
                                    ColorAnimation { duration: 100 }
                                }
                                
                                Behavior on border.color {
                                    ColorAnimation { duration: 100 }
                                }
                            }
                            
                            Text {
                                text: "BTN " + parent.parent.buttonIndex
                                font.pixelSize: 12
                                font.weight: Font.Medium
                                color: colorTextSecondary
                                width: 55
                                anchors.verticalCenter: parent.verticalCenter
                                font.family: "Inter"
                            }
                            
                            Item { width: 1; Layout.fillWidth: true }
                            
                            StyledComboBox {
                                width: 180
                                height: 30
                                model: backend.xboxButtonNames
                                currentIndex: {
                                    var mapping = backend.get_button_mapping(buttonIndex)
                                    return model.indexOf(mapping)
                                }
                                onCurrentTextChanged: {
                                    backend.set_button_mapping(buttonIndex, currentText)
                                }
                            }
                        }
                    }
                }
            }
        }
        
        Row {
            spacing: 8
            width: parent.width
            
            StyledButton {
                text: "Save & Close"
                width: parent.width * 0.65
                height: 40
                primary: true
                hoverEffect: true
                onClicked: {
                    backend.save_button_mapping()
                    buttonDialog.close()
                }
            }
            
            StyledButton {
                text: "Cancel"
                width: parent.width * 0.35 - 8
                height: 40
                secondary: true
                hoverEffect: true
                onClicked: buttonDialog.close()
            }
        }
        }
    }
    
    // CONNECTIONS
    Connections {
        target: backend
        
        function onSteeringChanged(value) {
            steerBar.value = value
            // Convert 0-1 value to -1 to 1 for direction
            steerBar.direction = (value * 2) - 1
        }
        
        function onGasChanged(value) {
            gasBar.value = value
        }
        
        function onBrakeChanged(value) {
            brakeBar.value = value
        }
        
        function onStatusChanged(text, color) {
            statusText.text = text
            statusText.color = color
            statusDot.color = color
        }
        
        function onProfileDescChanged(desc) {
            profileDesc.text = desc
        }
        
        function onButtonStatusChanged(status) {
            buttonStatus.text = status
        }
        
        function onAutoStartChanged(enabled) {
            autoStartCheckbox.checked = enabled
        }
        
        function onStartMinimizedChanged(enabled) {
            startMinimizedCheckbox.checked = enabled
        }
    }
    
    Connections {
        target: trayManager
        
        function onHideToTrayEnabledChanged(enabled) {
            hideToTrayCheckbox.checked = enabled
        }
        
        function onTrayAvailableChanged(available) {
            hideToTrayCheckbox.enabled = available
        }
    }
    
    // CUSTOM COMPONENTS
    component SectionCard: Item {
        property string title: ""
        property string subtitle: ""
        property int cardHeight: 100
        default property alias content: contentArea.children
        
        width: parent.width
        height: headerRow.height + card.height + 20
        
        Row {
            id: headerRow
            anchors.left: parent.left
            anchors.leftMargin: 24
            anchors.top: parent.top
            spacing: 10
            
            Text {
                text: title
                font.pixelSize: 14
                font.bold: true
                color: colorTextPrimary
                font.family: "Inter"
            }
            
            Text {
                text: " " + subtitle
                font.pixelSize: 11
                color: colorTextMuted
                font.family: "Inter"
            }
        }
        
        Rectangle {
            id: card
            anchors.top: headerRow.bottom
            anchors.topMargin: 8
            anchors.left: parent.left
            anchors.leftMargin: 24
            anchors.right: parent.right
            anchors.rightMargin: 24
            height: cardHeight
            radius: 12
            color: colorCard
            border.color: colorBorder
            border.width: 1
            
            Item {
                id: contentArea
                anchors.fill: parent
                anchors.margins: 16
            }
        }
    }
    
    component AxisBar: Item {
        property string label: ""
        property color barColor: colorPrimary
        property real value: 0.0
        property real direction: 0.0 // -1 to 1 for steering direction
        property var gradientColors: mainWindow.tealGradient
        
        width: parent.width
        height: 24
        
        Row {
            anchors.fill: parent
            spacing: 0
            
            Text {
                text: label
                font.pixelSize: 11
                font.bold: true
                color: colorTextSecondary
                width: 70
                anchors.verticalCenter: parent.verticalCenter
                font.family: "Inter"
            }
            
            Item {
                width: parent.width - 70 - 40
                height: parent.height
                anchors.verticalCenter: parent.verticalCenter
                
                Rectangle {
                    id: axisBarBg
                    anchors.fill: parent
                    anchors.leftMargin: 5
                    anchors.rightMargin: 5
                    height: 8
                    radius: 4
                    color: colorSurface
                    anchors.verticalCenter: parent.verticalCenter
                    clip: true
                    
                    Rectangle {
                        width: parent.width * Math.max(0, Math.min(1, value))
                        height: parent.height
                        radius: 4
                        
                        gradient: Gradient {
                            orientation: Gradient.Horizontal
                            GradientStop { position: gradientColors[0].position; color: gradientColors[0].color }
                            GradientStop { position: gradientColors[1].position; color: gradientColors[1].color }
                            GradientStop { position: gradientColors[2].position; color: gradientColors[2].color }
                        }
                        
                        Behavior on width {
                            NumberAnimation { duration: 50; easing.type: Easing.OutQuad }
                        }
                    }
                }
            }
            
            Text {
                text: Math.round(Math.max(0, Math.min(1, value)) * 100) + "%"
                font.pixelSize: 10
                color: colorTextMuted
                width: 30
                anchors.verticalCenter: parent.verticalCenter
                horizontalAlignment: Text.AlignRight
                font.family: "Inter"
            }
        }
    }
    
    component StyledButton: Rectangle {
        id: btn
        property string text: ""
        property bool primary: false
        property bool danger: false
        property bool secondary: false
        property bool hoverEffect: false
        property int fontSize: 12
        signal clicked()
        
        width: 100
        height: 38
        radius: secondary ? 8 : 10
        color: {
            if (danger) return btnMouse.containsMouse && hoverEffect ? "#dc2626" : colorError
            if (primary) return btnMouse.containsMouse && hoverEffect ? colorPrimaryHover : colorPrimary
            return btnMouse.containsMouse && hoverEffect ? colorSurfaceHover : colorSurface
        }
        border.color: secondary ? colorBorder : "transparent"
        border.width: secondary ? 1 : 0
        
        Text {
            anchors.centerIn: parent
            text: btn.text
            font.pixelSize: fontSize
            font.bold: true
            color: primary || danger ? colorBg : (secondary ? colorTextSecondary : colorTextPrimary)
            font.family: "Inter"
        }
        
        MouseArea {
            id: btnMouse
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            onClicked: btn.clicked()
            
            onPressed: {
                if (hoverEffect) {
                    btn.scale = 0.98
                }
            }
            
            onReleased: {
                if (hoverEffect) {
                    btn.scale = 1.0
                }
            }
            
            onEntered: {
                if (hoverEffect) {
                    btn.z = 1
                }
            }
            
            onExited: {
                if (hoverEffect) {
                    btn.z = 0
                    btn.scale = 1.0
                }
            }
        }
        
        Behavior on color {
            ColorAnimation { duration: 150 }
        }
        
        Behavior on scale {
            NumberAnimation { duration: 100; easing.type: Easing.OutQuad }
        }
    }
    
    component StyledComboBox: Rectangle {
        id: combo
        property var model: []
        property int currentIndex: 0
        property string currentText: model[currentIndex] || ""
        
        width: 200
        height: 38
        radius: 8
        color: comboMouse.containsMouse ? colorSurfaceHover : colorSurface
        border.color: colorBorder
        border.width: 1
        
        Text {
            anchors.left: parent.left
            anchors.leftMargin: 12
            anchors.verticalCenter: parent.verticalCenter
            text: combo.currentText
            font.pixelSize: 12
            color: colorTextPrimary
            font.family: "Inter"
            elide: Text.ElideRight
            width: parent.width - 40
        }
        
        Text {
            anchors.right: parent.right
            anchors.rightMargin: 12
            anchors.verticalCenter: parent.verticalCenter
            text: "▼"
            font.pixelSize: 8
            color: colorTextMuted
        }
        
        MouseArea {
            id: comboMouse
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            onClicked: popup.open()
        }
        
        Popup {
            id: popup
            y: parent.height + 4
            width: parent.width
            height: Math.min(listView.contentHeight + 8, 300)
            padding: 4
            
            background: Rectangle {
                color: colorSurface
                radius: 8
                border.color: colorBorder
                border.width: 1
            }
            
            contentItem: ListView {
                id: listView
                clip: true
                model: combo.model
                currentIndex: combo.currentIndex
                
                delegate: Rectangle {
                    width: ListView.view.width
                    height: 32
                    color: delegateMouse.containsMouse ? colorSurfaceHover : "transparent"
                    radius: 6
                    
                    Text {
                        anchors.left: parent.left
                        anchors.leftMargin: 12
                        anchors.verticalCenter: parent.verticalCenter
                        text: modelData
                        font.pixelSize: 12
                        color: colorTextPrimary
                        font.family: "Inter"
                    }
                    
                    MouseArea {
                        id: delegateMouse
                        anchors.fill: parent
                        hoverEnabled: true
                        cursorShape: Qt.PointingHandCursor
                        onClicked: {
                            combo.currentIndex = index
                            popup.close()
                        }
                    }
                }
            }
        }
        
        Behavior on color {
            ColorAnimation { duration: 150 }
        }
    }
    
    component StyledCheckBox: Item {
        property string text: ""
        property bool checked: false
        property bool enabled: true
        signal toggled()
        
        width: checkRow.width
        height: 20
        
        Row {
            id: checkRow
            spacing: 8
            
            Rectangle {
                width: 16
                height: 16
                radius: 4
                color: checked && enabled ? colorPrimary : "transparent"
                border.color: enabled ? (checked ? colorPrimary : colorBorderLight) : colorInactive
                border.width: 1
                anchors.verticalCenter: parent.verticalCenter
                
                Text {
                    anchors.centerIn: parent
                    text: "✓"
                    font.pixelSize: 10
                    font.bold: true
                    color: colorBg
                    visible: checked && enabled
                }
                
                Behavior on color {
                    ColorAnimation { duration: 150 }
                }
                
                Behavior on border.color {
                    ColorAnimation { duration: 150 }
                }
            }
            
            Text {
                text: parent.parent.text
                font.pixelSize: 11
                color: enabled ? colorTextSecondary : colorInactive
                anchors.verticalCenter: parent.verticalCenter
                font.family: "Inter"
            }
        }
        
        MouseArea {
            anchors.fill: parent
            cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
            onClicked: {
                if (enabled) {
                    checked = !checked
                    toggled()
                }
            }
        }
    }
}
"""

class SystemTrayManager(QObject):
    """Manages system tray functionality for TrueAxis"""
    hideToTrayEnabledChanged = Signal(bool)
    trayAvailableChanged = Signal(bool)
    
    def __init__(self, main_window=None, backend=None):
        super().__init__()
        self.main_window = main_window
        self.backend = backend
        self.tray_icon = None
        self._hide_to_tray_enabled = False
        self._tray_available = False
        self.setup_tray()
    
    @Property(bool, notify=hideToTrayEnabledChanged)
    def hideToTrayEnabled(self):
        return self._hide_to_tray_enabled
    
    @Property(bool, notify=trayAvailableChanged)
    def trayAvailable(self):
        return self._tray_available
    
    @Slot(bool)
    def set_hide_to_tray(self, enabled):
        self._hide_to_tray_enabled = enabled
        self.hideToTrayEnabledChanged.emit(enabled)
        self.save_settings()
    
    def setup_tray(self):
        """Setup system tray icon and menu"""
        if QSystemTrayIcon.isSystemTrayAvailable():
            self._tray_available = True
            self.trayAvailableChanged.emit(True)
            
            # Create tray icon
            self.tray_icon = QSystemTrayIcon()
            
            # Create a simple icon for TrueAxis
            icon = self.create_trueaxis_icon()
            self.tray_icon.setIcon(icon)
            self.tray_icon.setToolTip("TrueAxis - Running in background")
            
            # Create tray menu
            tray_menu = QMenu()
            
            # Show/Hide action
            self.show_action = QAction("Show TrueAxis", tray_menu)
            self.show_action.triggered.connect(self.show_main_window)
            tray_menu.addAction(self.show_action)
            
            tray_menu.addSeparator()
            
            # Exit action
            exit_action = QAction("Exit", tray_menu)
            exit_action.triggered.connect(self.quit_application)
            tray_menu.addAction(exit_action)
            
            self.tray_icon.setContextMenu(tray_menu)
            
            # Connect tray icon click
            self.tray_icon.activated.connect(self.on_tray_activated)
        else:
            self._tray_available = False
            self.trayAvailableChanged.emit(False)
            print("System tray is not available on this system")
    
    def create_trueaxis_icon(self):
        """Create a TrueAxis tray icon"""
        pixmap = QPixmap(32, 32)
        pixmap.fill(Qt.GlobalColor.transparent)
        
        painter = QPainter(pixmap)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # Draw a T for TrueAxis in circle
        painter.setBrush(QColor(16, 185, 129))  # Primary color
        painter.setPen(Qt.PenStyle.NoPen)
        painter.drawEllipse(4, 4, 24, 24)
        
        # Draw T
        painter.setPen(QColor(255, 255, 255))
        painter.setFont(QFont("Inter", 16, QFont.Bold))
        painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "T")
        
        painter.end()
        
        return QIcon(pixmap)
    
    @Slot()
    def show_main_window(self):
        """Show the main window from tray"""
        if self.main_window:
            self.main_window.show()
            self.main_window.raise_()
            self.main_window.requestActivate()
            self.hide_tray()
            if self.show_action:
                self.show_action.setText("Show TrueAxis")
    
    @Slot()
    def hide_main_window(self):
        """Hide the main window to tray"""
        if self.main_window:
            self.main_window.hide()
    
    @Slot()
    def show_tray(self):
        """Show the tray icon"""
        if self.tray_icon and self._tray_available:
            self.tray_icon.show()
    
    @Slot()
    def hide_tray(self):
        """Hide the tray icon"""
        if self.tray_icon:
            self.tray_icon.hide()
    
    def on_tray_activated(self, reason):
        """Handle tray icon activation"""
        if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
            self.show_main_window()
        elif reason == QSystemTrayIcon.ActivationReason.Trigger:
            pass  # Single click - do nothing
    
    def quit_application(self):
        """Quit the application"""
        app = QApplication.instance()
        if app:
            app.quit()
    
    @Slot()
    def save_settings(self):
        """Save tray settings"""
        # Don't save during backend initialization
        if self.backend and hasattr(self.backend, '_initializing') and self.backend._initializing:
            return
            
        try:
            settings = {}
            if os.path.exists(SETTINGS_FILE):
                with open(SETTINGS_FILE, 'r') as f:
                    settings = json.load(f)
            
            settings['tray_settings'] = {
                'hide_to_tray': self._hide_to_tray_enabled
            }
            
            with open(SETTINGS_FILE, 'w') as f:
                json.dump(settings, f, indent=2)
        except Exception as e:
            print(f"Error saving tray settings: {e}")
    
    def load_settings(self):
        """Load tray settings"""
        try:
            if os.path.exists(SETTINGS_FILE):
                with open(SETTINGS_FILE, 'r') as f:
                    settings = json.load(f)
                    if 'tray_settings' in settings:
                        tray_settings = settings['tray_settings']
                        self._hide_to_tray_enabled = tray_settings.get('hide_to_tray', False)
                        self.hideToTrayEnabledChanged.emit(self._hide_to_tray_enabled)
        except:
            pass


class AutoStartManager(QObject):
    """Manages auto-start with Windows functionality"""
    
    def __init__(self):
        super().__init__()
        self.app_name = "TrueAxis"
        self.app_path = os.path.abspath(sys.argv[0])
        
        # Check if running as PyInstaller executable
        if getattr(sys, 'frozen', False):
            self.app_path = sys.executable
    
    def enable_auto_start(self):
        """Enable auto-start with Windows"""
        try:
            if sys.platform == 'win32':
                return self._enable_windows_auto_start()
            elif sys.platform == 'darwin':  # macOS
                return self._enable_macos_auto_start()
            elif sys.platform == 'linux':
                return self._enable_linux_auto_start()
            else:
                print(f"Auto-start not supported on platform: {sys.platform}")
                return False
        except Exception as e:
            print(f"Error enabling auto-start: {e}")
            return False
    
    def disable_auto_start(self):
        """Disable auto-start with Windows"""
        try:
            if sys.platform == 'win32':
                return self._disable_windows_auto_start()
            elif sys.platform == 'darwin':  # macOS
                return self._disable_macos_auto_start()
            elif sys.platform == 'linux':
                return self._disable_linux_auto_start()
            else:
                print(f"Auto-start not supported on platform: {sys.platform}")
                return False
        except Exception as e:
            print(f"Error disabling auto-start: {e}")
            return False
    
    def is_auto_start_enabled(self):
        """Check if auto-start is enabled"""
        try:
            if sys.platform == 'win32':
                return self._is_windows_auto_start_enabled()
            elif sys.platform == 'darwin':  # macOS
                return self._is_macos_auto_start_enabled()
            elif sys.platform == 'linux':
                return self._is_linux_auto_start_enabled()
            else:
                return False
        except Exception as e:
            print(f"Error checking auto-start: {e}")
            return False
    
    def _enable_windows_auto_start(self):
        """Enable auto-start on Windows using registry"""
        try:
            import winreg
            key = winreg.HKEY_CURRENT_USER
            sub_key = r"Software\Microsoft\Windows\CurrentVersion\Run"
            
            with winreg.OpenKey(key, sub_key, 0, winreg.KEY_WRITE) as reg_key:
                winreg.SetValueEx(reg_key, self.app_name, 0, winreg.REG_SZ, f'"{self.app_path}" --minimized')
            return True
        except Exception as e:
            print(f"Windows registry error: {e}")
            return False
    
    def _disable_windows_auto_start(self):
        """Disable auto-start on Windows"""
        try:
            import winreg
            key = winreg.HKEY_CURRENT_USER
            sub_key = r"Software\Microsoft\Windows\CurrentVersion\Run"
            
            with winreg.OpenKey(key, sub_key, 0, winreg.KEY_WRITE) as reg_key:
                winreg.DeleteValue(reg_key, self.app_name)
            return True
        except Exception as e:
            print(f"Windows registry error: {e}")
            return False
    
    def _is_windows_auto_start_enabled(self):
        """Check if auto-start is enabled on Windows"""
        try:
            import winreg
            key = winreg.HKEY_CURRENT_USER
            sub_key = r"Software\Microsoft\Windows\CurrentVersion\Run"
            
            with winreg.OpenKey(key, sub_key, 0, winreg.KEY_READ) as reg_key:
                value, _ = winreg.QueryValueEx(reg_key, self.app_name)
                return value is not None
        except WindowsError:
            return False
        except Exception as e:
            print(f"Windows registry error: {e}")
            return False
    
    def _enable_macos_auto_start(self):
        """Enable auto-start on macOS using launchd"""
        try:
            launchd_dir = Path.home() / "Library" / "LaunchAgents"
            launchd_dir.mkdir(parents=True, exist_ok=True)
            
            plist_content = {
                'Label': f'com.trueaxis.{self.app_name.lower()}',
                'Program': self.app_path,
                'RunAtLoad': True,
                'KeepAlive': False,
                'WorkingDirectory': os.path.dirname(self.app_path),
            }
            
            plist_path = launchd_dir / f'com.trueaxis.{self.app_name.lower()}.plist'
            with open(plist_path, 'wb') as f:
                plistlib.dump(plist_content, f)
            
            # Load the launch agent
            subprocess.run(['launchctl', 'load', str(plist_path)])
            return True
        except Exception as e:
            print(f"macOS launchd error: {e}")
            return False
    
    def _disable_macos_auto_start(self):
        """Disable auto-start on macOS"""
        try:
            plist_path = Path.home() / "Library" / "LaunchAgents" / f'com.trueaxis.{self.app_name.lower()}.plist'
            
            if plist_path.exists():
                # Unload the launch agent
                subprocess.run(['launchctl', 'unload', str(plist_path)])
                plist_path.unlink()
            return True
        except Exception as e:
            print(f"macOS launchd error: {e}")
            return False
    
    def _is_macos_auto_start_enabled(self):
        """Check if auto-start is enabled on macOS"""
        try:
            plist_path = Path.home() / "Library" / "LaunchAgents" / f'com.trueaxis.{self.app_name.lower()}.plist'
            return plist_path.exists()
        except:
            return False
    
    def _enable_linux_auto_start(self):
        """Enable auto-start on Linux using .desktop file"""
        try:
            autostart_dir = Path.home() / ".config" / "autostart"
            autostart_dir.mkdir(parents=True, exist_ok=True)
            
            desktop_content = f"""[Desktop Entry]
Type=Application
Name={self.app_name}
Exec={self.app_path} --minimized
Comment=TrueAxis Application
Categories=Utility;
StartupNotify=false
Terminal=false
Hidden=false
"""
            
            desktop_path = autostart_dir / f"{self.app_name.lower()}.desktop"
            with open(desktop_path, 'w') as f:
                f.write(desktop_content)
            
            # Make it executable
            os.chmod(desktop_path, 0o755)
            return True
        except Exception as e:
            print(f"Linux autostart error: {e}")
            return False
    
    def _disable_linux_auto_start(self):
        """Disable auto-start on Linux"""
        try:
            desktop_path = Path.home() / ".config" / "autostart" / f"{self.app_name.lower()}.desktop"
            if desktop_path.exists():
                desktop_path.unlink()
            return True
        except Exception as e:
            print(f"Linux autostart error: {e}")
            return False
    
    def _is_linux_auto_start_enabled(self):
        """Check if auto-start is enabled on Linux"""
        try:
            desktop_path = Path.home() / ".config" / "autostart" / f"{self.app_name.lower()}.desktop"
            return desktop_path.exists()
        except:
            return False


class InputReader(QThread):
    """Separate thread for reading input to prevent UI lag"""
    axisValuesChanged = Signal(list)
    buttonStatesChanged = Signal(list)
    
    def __init__(self):
        super().__init__()
        self.joystick = None
        self._running = True
        
    def set_joystick(self, joystick):
        self.joystick = joystick
        
    def run(self):
        while self._running:
            try:
                if self.joystick:
                    pygame.event.pump()
                    
                    # Read axis values
                    axis_values = []
                    num_axes = self.joystick.get_numaxes()
                    for i in range(num_axes):
                        try:
                            raw = self.joystick.get_axis(i)
                            axis_values.append((raw + 1) / 2)
                        except:
                            axis_values.append(0.5)  # Default center position
                    
                    # Read button states
                    button_states = []
                    num_buttons = self.joystick.get_numbuttons()
                    for i in range(num_buttons):
                        try:
                            button_states.append(bool(self.joystick.get_button(i)))
                        except:
                            button_states.append(False)
                    
                    # Emit signals
                    self.axisValuesChanged.emit(axis_values)
                    self.buttonStatesChanged.emit(button_states)
                
                # Small delay to prevent CPU overload
                time.sleep(0.01)
            except Exception as e:
                print(f"Error in input reader: {e}")
                time.sleep(1)
    
    def stop(self):
        self._running = False
        self.wait()


class TrueAxisBackend(QObject):
    devicesChanged = Signal(list)
    profileDescChanged = Signal(str)
    currentProfileChanged = Signal(str)
    currentDeviceIndexChanged = Signal(int)
    invertGasChanged = Signal(bool)
    invertBrakeChanged = Signal(bool)
    squareInputChanged = Signal(bool)
    squareAreaChanged = Signal(float)
    steeringChanged = Signal(float)
    gasChanged = Signal(float)
    brakeChanged = Signal(float)
    statusChanged = Signal(str, str)
    isRunningChanged = Signal(bool)
    buttonStatusChanged = Signal(str)
    axisValuesChanged = Signal()
    buttonStatesChanged = Signal()
    numAxesChanged = Signal()
    numButtonsChanged = Signal()
    autoStartChanged = Signal(bool)
    startMinimizedChanged = Signal(bool)
    
    def __init__(self):
        super().__init__()
        
        self._running = False
        self._devices = []
        self._current_device_index = 0
        self._current_profile = "Logitech G29 (Standard)"
        self._invert_gas = False
        self._invert_brake = False
        self._square_input = False
        self._square_area = 0.5  # Default: 50% square area
        self._auto_start = False
        self._start_minimized = False
        self._initializing = True  # Flag to prevent saving during initialization
        
        self.mapper_thread = None
        self.input_reader = None
        self.vg_gamepad = None
        self.joystick = None
        self.button_mapping = {}
        self._axis_values = []
        self._button_states = []
        
        # Initialize keyboard controller if available
        if KEYBOARD_AVAILABLE:
            self.keyboard = KeyboardController()
            self._pressed_keys = set()  # Track currently pressed keyboard keys
        else:
            self.keyboard = None
            self._pressed_keys = set()
        
        # Initialize auto-start manager
        self.auto_start_manager = AutoStartManager()
        
        # Initialize pygame for joystick support
        pygame.init()
        pygame.joystick.init()
        
        # Load configuration
        self.load_button_mapping()
        self.load_settings()
        
        # Create input reader thread
        self.input_reader = InputReader()
        self.input_reader.axisValuesChanged.connect(self.update_axis_values)
        self.input_reader.buttonStatesChanged.connect(self.update_button_states)
        self.input_reader.start()
        
        # Initialize tray manager
        self.tray_manager = SystemTrayManager(backend=self)
        self.tray_manager.load_settings()
        
        # Start update timer for processing inputs
        self.update_timer = QTimer()
        self.update_timer.timeout.connect(self.process_inputs)
        self.update_timer.start(16)  # ~60 FPS for smooth updates
        
        # Initial device refresh
        self.refresh_devices()
        
        # Restore saved device selection after a short delay (to let devices enumerate)
        if self._current_device_index >= 0:
            QTimer.singleShot(800, self.restore_device_selection)
        
        # Mark initialization as complete after a delay (to let QML finish loading)
        QTimer.singleShot(1000, lambda: setattr(self, '_initializing', False))
    
    @Property(bool, notify=isRunningChanged)
    def running(self):
        return self._running
    
    @Property(list, notify=devicesChanged)
    def devices(self):
        return self._devices
    
    @Property(list, constant=True)
    def profileNames(self):
        return list(PROFILES.keys())
    
    @Property(str, notify=currentProfileChanged)
    def current_profile(self):
        return self._current_profile
    
    @Property(int, notify=currentDeviceIndexChanged)
    def current_device_index(self):
        return self._current_device_index
    
    @Property(bool, notify=invertGasChanged)
    def invert_gas(self):
        return self._invert_gas
    
    @Property(bool, notify=invertBrakeChanged)
    def invert_brake(self):
        return self._invert_brake
    
    @Property(bool, notify=squareInputChanged)
    def square_input(self):
        return self._square_input
    
    @Property(float, notify=squareAreaChanged)
    def square_area(self):
        return self._square_area
    
    @Property(list, constant=True)
    def xboxButtonNames(self):
        return XBOX_BUTTON_NAMES
    
    @Property(bool, constant=True)
    def keyboardAvailable(self):
        return KEYBOARD_AVAILABLE
    
    @Property(int, notify=numAxesChanged)
    def numAxes(self):
        if self.joystick:
            try:
                return self.joystick.get_numaxes()
            except:
                pass
        return 0
    
    @Property(int, notify=numButtonsChanged)
    def numButtons(self):
        if self.joystick:
            try:
                return self.joystick.get_numbuttons()
            except:
                pass
        return 0
    
    @Slot(int, result=float)
    def getAxisValue(self, index):
        try:
            if index < len(self._axis_values):
                value = float(self._axis_values[index])
                # Ensure value is a valid number
                if not (value >= 0 and value <= 1):
                    value = max(0.0, min(1.0, value))
                return value
        except (ValueError, TypeError):
            pass
        return 0.5
    
    @Slot(int, result=bool)
    def getButtonState(self, index):
        if index < len(self._button_states):
            return bool(self._button_states[index])
        return False
    
    @Slot(int, result=str)
    def get_button_mapping(self, button_index):
        return self.button_mapping.get(str(button_index), "DISABLED")
    
    @Slot(int, str)
    def set_button_mapping(self, button_index, xbox_button):
        # Ignore section headers
        if xbox_button in ["--- XBOX BUTTONS ---", "--- KEYBOARD KEYS ---"]:
            return
            
        if xbox_button == "DISABLED":
            if str(button_index) in self.button_mapping:
                del self.button_mapping[str(button_index)]
        else:
            self.button_mapping[str(button_index)] = xbox_button
        # Auto-save button mapping changes
        self.save_button_mapping()
    
    @Slot()
    def save_button_mapping(self):
        # Don't save during initialization to prevent overwriting loaded settings
        if hasattr(self, '_initializing') and self._initializing:
            return
            
        try:
            with open(BUTTON_MAPPING_FILE, 'w') as f:
                json.dump(self.button_mapping, f, indent=2)
            self.update_button_status()
        except:
            pass
    
    def load_button_mapping(self):
        if os.path.exists(BUTTON_MAPPING_FILE):
            try:
                with open(BUTTON_MAPPING_FILE, 'r') as f:
                    self.button_mapping = json.load(f)
                self.update_button_status()
            except:
                pass
    
    def load_settings(self):
        """Load application settings"""
        try:
            if os.path.exists(SETTINGS_FILE):
                with open(SETTINGS_FILE, 'r') as f:
                    settings = json.load(f)
                    
                    # Load auto-start setting
                    if 'auto_start' in settings:
                        self._auto_start = settings['auto_start']
                        self.autoStartChanged.emit(self._auto_start)
                        
                        # Apply auto-start setting if needed
                        if self._auto_start:
                            self.apply_auto_start_setting()
                    
                    # Load start minimized setting
                    if 'start_minimized' in settings:
                        self._start_minimized = settings['start_minimized']
                        self.startMinimizedChanged.emit(self._start_minimized)
                    
                    # Load other settings
                    if 'invert_gas' in settings:
                        self._invert_gas = settings['invert_gas']
                        self.invertGasChanged.emit(self._invert_gas)
                    if 'invert_brake' in settings:
                        self._invert_brake = settings['invert_brake']
                        self.invertBrakeChanged.emit(self._invert_brake)
                    if 'square_input' in settings:
                        self._square_input = settings['square_input']
                        self.squareInputChanged.emit(self._square_input)
                    if 'square_area' in settings:
                        self._square_area = settings['square_area']
                        self.squareAreaChanged.emit(self._square_area)
                    if 'current_profile' in settings:
                        self._current_profile = settings['current_profile']
                        self.currentProfileChanged.emit(self._current_profile)
                        desc = PROFILES.get(self._current_profile, {}).get("desc", "Select a profile to see description")
                        self.profileDescChanged.emit(desc)
                    if 'current_device_index' in settings:
                        self._current_device_index = settings['current_device_index']
                        self.currentDeviceIndexChanged.emit(self._current_device_index)
                        # Auto-start emulation if auto-start is enabled and device exists
                        QTimer.singleShot(1000, self.try_auto_start_emulation)
        except Exception as e:
            print(f"Error loading settings: {e}")
    
    @Slot()
    def refresh_ui_from_settings(self):
        """Re-emit all signals to update QML UI with current backend state"""
        self.currentProfileChanged.emit(self._current_profile)
        desc = PROFILES.get(self._current_profile, {}).get("desc", "Select a profile to see description")
        self.profileDescChanged.emit(desc)
        self.currentDeviceIndexChanged.emit(self._current_device_index)
        self.invertGasChanged.emit(self._invert_gas)
        self.invertBrakeChanged.emit(self._invert_brake)
        self.squareInputChanged.emit(self._square_input)
        self.squareAreaChanged.emit(self._square_area)
        self.autoStartChanged.emit(self._auto_start)
        self.startMinimizedChanged.emit(self._start_minimized)
        self.update_button_status()  # Refresh button mapping status
        
        # Also refresh tray manager settings to update UI
        if self.tray_manager:
            self.tray_manager.hideToTrayEnabledChanged.emit(self.tray_manager._hide_to_tray_enabled)
    
    def try_auto_start_emulation(self):
        """Try to auto-start emulation with saved device"""
        if not self._auto_start or not self._start_minimized:
            return
            
        try:
            # Check if the saved device index is valid
            if self._current_device_index < len(self._devices) and self._devices[self._current_device_index] != "No devices found":
                # Select the device
                self.select_device(self._current_device_index)
                # Small delay to ensure device is ready
                QTimer.singleShot(500, self.start_mapping)
                print(f"Auto-starting emulation with device index {self._current_device_index}")
        except Exception as e:
            print(f"Failed to auto-start emulation: {e}")
    
    def restore_device_selection(self):
        """Restore the saved device selection after startup"""
        try:
            # Check if the saved device index is valid
            if 0 <= self._current_device_index < len(self._devices):
                if self._devices[self._current_device_index] != "No devices found":
                    # Select the device without saving (to avoid triggering save during load)
                    self.select_device(self._current_device_index)
                    print(f"Restored device selection: {self._devices[self._current_device_index]}")
        except Exception as e:
            print(f"Failed to restore device selection: {e}")
    
    def save_settings(self, force=False):
        """Save application settings"""
        # Don't save during initialization to prevent overwriting loaded settings
        if not force and hasattr(self, '_initializing') and self._initializing:
            return
            
        try:
            settings = {}
            if os.path.exists(SETTINGS_FILE):
                with open(SETTINGS_FILE, 'r') as f:
                    settings = json.load(f)
            
            settings.update({
                'auto_start': self._auto_start,
                'start_minimized': self._start_minimized,
                'invert_gas': self._invert_gas,
                'invert_brake': self._invert_brake,
                'square_input': self._square_input,
                'square_area': self._square_area,
                'current_profile': self._current_profile,
                'current_device_index': self._current_device_index,
            })
            
            with open(SETTINGS_FILE, 'w') as f:
                json.dump(settings, f, indent=2)
        except Exception as e:
            print(f"Error saving settings: {e}")
    
    @Slot()
    def force_save_settings(self):
        """Force save settings, callable from QML"""
        self.save_settings(force=True)
    
    @Slot(bool)
    def set_auto_start(self, enabled):
        """Set auto-start with Windows"""
        self._auto_start = enabled
        self.autoStartChanged.emit(enabled)
        
        if enabled:
            success = self.auto_start_manager.enable_auto_start()
            if success:
                self.statusChanged.emit("Auto-start enabled", "#22c55e")
            else:
                self.statusChanged.emit("Failed to enable auto-start", "#ef4444")
                self._auto_start = False
                self.autoStartChanged.emit(False)
        else:
            success = self.auto_start_manager.disable_auto_start()
            if success:
                self.statusChanged.emit("Auto-start disabled", "#71717a")
            else:
                self.statusChanged.emit("Failed to disable auto-start", "#ef4444")
                self._auto_start = True
                self.autoStartChanged.emit(True)
        
        self.save_settings()
    
    @Slot(bool)
    def set_start_minimized(self, enabled):
        """Set start minimized to tray"""
        self._start_minimized = enabled
        self.startMinimizedChanged.emit(enabled)
        self.save_settings()
    
    def apply_auto_start_setting(self):
        """Apply the current auto-start setting"""
        current_state = self.auto_start_manager.is_auto_start_enabled()
        if current_state != self._auto_start:
            if self._auto_start:
                self.auto_start_manager.enable_auto_start()
            else:
                self.auto_start_manager.disable_auto_start()
    
    def update_button_status(self):
        count = len(self.button_mapping)
        if count == 0:
            status = "No buttons mapped"
        elif count == 1:
            status = "1 button mapped"
        else:
            status = f"{count} buttons mapped"
        self.buttonStatusChanged.emit(status)
    
    @Slot()
    def refresh_devices(self):
        # Stop emulation if running
        if self._running:
            self.stop_mapping()
        
        # Clear current joystick from input reader
        if self.input_reader:
            self.input_reader.set_joystick(None)
        
        try:
            # Reinitialize pygame joystick system
            pygame.joystick.quit()
            pygame.joystick.init()
            
            # Get available devices
            count = pygame.joystick.get_count()
            self._devices = []
            
            for i in range(count):
                try:
                    joystick = pygame.joystick.Joystick(i)
                    joystick.init()  # Initialize to get name
                    device_name = joystick.get_name()
                    self._devices.append(f"{device_name} (ID: {i})")
                    joystick.quit()  # Quit to avoid conflicts
                except Exception as e:
                    print(f"Error initializing device {i}: {e}")
                    self._devices.append(f"Device {i} (Error)")
            
            if not self._devices:
                self._devices = ["No devices found"]
            
            self.devicesChanged.emit(self._devices)
            
            # Reset current joystick
            self.joystick = None
            self._axis_values = []
            self._button_states = []
            self.numAxesChanged.emit()
            self.numButtonsChanged.emit()
            self.axisValuesChanged.emit()
            self.buttonStatesChanged.emit()
            
            # Reset live preview values
            self.steeringChanged.emit(0.5)
            self.gasChanged.emit(0.0)
            self.brakeChanged.emit(0.0)
            
            if count > 0:
                # Don't auto-select during initialization if we have a saved device selection
                # The restore_device_selection() will handle selecting the saved device
                if not (hasattr(self, '_initializing') and self._initializing):
                    # Only auto-select during normal refresh (not during startup)
                    self.select_device(0)
                self.statusChanged.emit("Ready to activate", "#71717a")
            else:
                self.statusChanged.emit("No input device detected", "#f59e0b")
        except Exception as e:
            print(f"Error refreshing devices: {e}")
            self._devices = ["Error scanning devices"]
            self.devicesChanged.emit(self._devices)
            self.statusChanged.emit("Error scanning devices", "#ef4444")
    
    @Slot(int)
    def select_device(self, index):
        if self._running:
            # Don't allow device change while emulating
            self.statusChanged.emit("Stop emulation before changing device", "#f59e0b")
            return
        
        if 0 <= index < len(self._devices):
            self._current_device_index = index
            self.currentDeviceIndexChanged.emit(index)
            
            try:
                # Clean up old joystick if exists
                if self.joystick:
                    try:
                        self.joystick.quit()
                    except:
                        pass
                
                # Extract device ID from the string (format: "Device Name (ID: X)")
                try:
                    device_id = int(self._devices[index].split("(ID: ")[1].rstrip(")"))
                except:
                    device_id = index
                
                # Initialize new joystick
                self.joystick = pygame.joystick.Joystick(device_id)
                self.joystick.init()
                
                # Update input reader with new joystick
                if self.input_reader:
                    self.input_reader.set_joystick(self.joystick)
                
                # Initialize arrays with default values
                num_axes = self.joystick.get_numaxes()
                num_buttons = self.joystick.get_numbuttons()
                self._axis_values = [0.5] * num_axes
                self._button_states = [False] * num_buttons
                
                # Emit signals for UI updates
                self.numAxesChanged.emit()
                self.numButtonsChanged.emit()
                self.axisValuesChanged.emit()
                self.buttonStatesChanged.emit()
                
                print(f"Selected device: {self.joystick.get_name()} with {num_axes} axes and {num_buttons} buttons")
                self.statusChanged.emit("Ready to activate", "#71717a")
                
                # Save device selection (but not during initialization)
                if not (hasattr(self, '_initializing') and self._initializing):
                    self.save_settings()
                
            except Exception as e:
                print(f"Error selecting device: {e}")
                self.joystick = None
                self._axis_values = []
                self._button_states = []
                self.numAxesChanged.emit()
                self.numButtonsChanged.emit()
                self.statusChanged.emit(f"Error selecting device: {str(e)}", "#ef4444")
    
    @Slot(str)
    def select_profile(self, profile_name):
        if profile_name in PROFILES:
            self._current_profile = profile_name
            self.currentProfileChanged.emit(profile_name)
            desc = PROFILES[profile_name]["desc"]
            self.profileDescChanged.emit(desc)
            self.save_settings()
    
    @Slot(bool)
    def set_invert_gas(self, value):
        self._invert_gas = value
        self.invertGasChanged.emit(value)
        self.save_settings()
    
    @Slot(bool)
    def set_invert_brake(self, value):
        self._invert_brake = value
        self.invertBrakeChanged.emit(value)
        self.save_settings()
    
    @Slot(bool)
    def set_square_input(self, value):
        self._square_input = value
        self.squareInputChanged.emit(value)
        self.save_settings()
    
    @Slot(float)
    def set_square_area(self, value):
        self._square_area = value
        self.squareAreaChanged.emit(value)
        self.save_settings()
    
    @Slot()
    def toggle_mapping(self):
        if self._running:
            self.stop_mapping()
        else:
            self.start_mapping()
    
    def update_axis_values(self, values):
        """Update axis values from input reader thread"""
        if values != self._axis_values:
            self._axis_values = values
            self.axisValuesChanged.emit()
    
    def update_button_states(self, states):
        """Update button states from input reader thread"""
        if states != self._button_states:
            self._button_states = states
            self.buttonStatesChanged.emit()
    
    def process_inputs(self):
        """Process input values for display and emulation"""
        if not self.joystick or not self._axis_values:
            return
        
        try:
            # Always update live input preview, regardless of emulation state
            if self._current_profile in PROFILES:
                mapping = PROFILES[self._current_profile]["axes"]
                
                def get_axis_value(idx):
                    if idx < len(self._axis_values):
                        return self._axis_values[idx]
                    return 0.5
                
                # Steering with square input
                if mapping[0] < len(self._axis_values):
                    raw_value = (self._axis_values[mapping[0]] * 2) - 1  # Convert to -1 to 1
                    
                    if self._square_input:
                        # Apply square steering transformation
                        normalized = abs(raw_value)
                        square_threshold = self._square_area
                        
                        if normalized <= square_threshold:
                            # Inside square area - linear response
                            scaled = normalized / square_threshold
                        else:
                            # Outside square area - jump to 100%
                            scaled = 1.0
                        
                        # Apply direction
                        final_s = scaled if raw_value >= 0 else -scaled
                        steer = (final_s + 1) / 2
                    else:
                        # Normal circular steering
                        steer = self._axis_values[mapping[0]]
                    
                    self.steeringChanged.emit(max(0.0, min(1.0, steer)))
                
                # Gas with invert
                if mapping[1] < len(self._axis_values):
                    gas = self._axis_values[mapping[1]]
                    if self._invert_gas:
                        gas = 1.0 - gas
                    self.gasChanged.emit(max(0.0, min(1.0, gas)))
                
                # Brake with invert
                if mapping[2] < len(self._axis_values):
                    brake = self._axis_values[mapping[2]]
                    if self._invert_brake:
                        brake = 1.0 - brake
                    self.brakeChanged.emit(max(0.0, min(1.0, brake)))
        except Exception as e:
            print(f"Error processing inputs: {e}")
    
    @Slot()
    def start_mapping(self):
        if not self.joystick:
            self.statusChanged.emit("No input device selected", "#ef4444")
            return
        
        self._running = True
        self.isRunningChanged.emit(True)
        self.statusChanged.emit("Active: Emulating controller", "#22c55e")
        
        # Start mapping thread
        self.mapper_thread = threading.Thread(target=self.mapping_loop, daemon=True)
        self.mapper_thread.start()
    
    @Slot()
    def stop_mapping(self):
        self._running = False
        self.isRunningChanged.emit(False)
        self.statusChanged.emit("Ready to activate", "#71717a")
    
    def apply_square_steering(self, raw_input):
        """Apply square steering transformation based on configurable area"""
        if not self._square_input:
            return raw_input
        
        normalized = abs(raw_input)
        square_threshold = self._square_area
        
        if normalized <= square_threshold:
            # Inside square area - linear response
            scaled = normalized / square_threshold
        else:
            # Outside square area - jump to 100%
            scaled = 1.0
        
        # Apply direction
        return scaled if raw_input >= 0 else -scaled
    
    def mapping_loop(self):
        """Main emulation loop - runs in separate thread"""
        if not self.vg_gamepad:
            self.vg_gamepad = vg.VX360Gamepad()
        
        mapping = PROFILES[self._current_profile]["axes"]
        
        while self._running:
            try:
                # Make local copies to avoid threading issues
                button_states = self._button_states.copy() if self._button_states else []
                axis_values = self._axis_values.copy() if self._axis_values else []
                
                # STEERING with configurable square area
                if mapping[0] < len(axis_values):
                    raw_s = (axis_values[mapping[0]] * 2) - 1  # Convert to -1 to 1
                    
                    # Apply square steering transformation
                    final_s = self.apply_square_steering(raw_s)
                    
                    # Clamp to valid range
                    final_s = max(-1.0, min(1.0, final_s))
                    
                    self.vg_gamepad.left_joystick(x_value=int(final_s * 32767), y_value=0)
                
                # GAS with invert
                if mapping[1] < len(axis_values):
                    norm_g = axis_values[mapping[1]]
                    if self._invert_gas:
                        norm_g = 1.0 - norm_g
                    self.vg_gamepad.right_trigger(value=int(max(0.0, min(1.0, norm_g)) * 255))
                
                # BRAKE with invert
                if mapping[2] < len(axis_values):
                    norm_b = axis_values[mapping[2]]
                    if self._invert_brake:
                        norm_b = 1.0 - norm_b
                    self.vg_gamepad.left_trigger(value=int(max(0.0, min(1.0, norm_b)) * 255))
                
                # BUTTONS - Handle both Xbox buttons and keyboard keys
                for btn_idx_str, mapping_name in self.button_mapping.items():
                    btn_idx = int(btn_idx_str)
                    if btn_idx < len(button_states):
                        button_pressed = button_states[btn_idx]
                        
                        # Check if it's an Xbox button
                        xbox_obj = XBOX_BUTTON_MAP.get(mapping_name)
                        if xbox_obj:
                            # Handle Xbox button
                            if button_pressed:
                                self.vg_gamepad.press_button(xbox_obj)
                            else:
                                self.vg_gamepad.release_button(xbox_obj)
                        
                        # Check if it's a keyboard key
                        elif KEYBOARD_AVAILABLE and mapping_name in KEYBOARD_MAP:
                            key = KEYBOARD_MAP[mapping_name]
                            key_id = f"{btn_idx}_{mapping_name}"
                            
                            if button_pressed and key_id not in self._pressed_keys:
                                # Button just pressed - press key
                                try:
                                    self.keyboard.press(key)
                                    self._pressed_keys.add(key_id)
                                except:
                                    pass
                            elif not button_pressed and key_id in self._pressed_keys:
                                # Button just released - release key
                                try:
                                    self.keyboard.release(key)
                                    self._pressed_keys.remove(key_id)
                                except:
                                    pass
                
                # Update virtual gamepad
                self.vg_gamepad.update()
                time.sleep(0.001)  # 1ms delay to prevent CPU overload
                
            except Exception as e:
                print(f"Error in mapping loop: {e}")
                time.sleep(1)
        
        # Clean up when stopping
        if self.vg_gamepad:
            # Release all buttons when stopping
            for btn_name, btn_obj in XBOX_BUTTON_MAP.items():
                if btn_obj:
                    self.vg_gamepad.release_button(btn_obj)
            self.vg_gamepad.update()
        
        # Release all keyboard keys when stopping
        if KEYBOARD_AVAILABLE and self.keyboard:
            for key_id in list(self._pressed_keys):
                mapping_name = key_id.split('_', 1)[1] if '_' in key_id else ""
                if mapping_name in KEYBOARD_MAP:
                    try:
                        self.keyboard.release(KEYBOARD_MAP[mapping_name])
                    except:
                        pass
            self._pressed_keys.clear()


class MainWindowHandler(QObject):
    """Handles main window events to intercept minimize button"""
    def __init__(self, main_window, tray_manager, start_minimized=False):
        super().__init__()
        self.main_window = main_window
        self.tray_manager = tray_manager
        self.start_minimized = start_minimized
        
    def eventFilter(self, obj, event):
        """Intercept window state change events to handle minimize button"""
        if obj is self.main_window and event.type() == QEvent.Type.WindowStateChange:
            # Check if window was minimized
            if self.main_window.windowState() == Qt.WindowState.WindowMinimized:
                # Check if tray feature is enabled
                if self.tray_manager.hideToTrayEnabled and self.tray_manager.trayAvailable:
                    # Hide window to tray
                    self.main_window.hide()
                    self.tray_manager.show_tray()
                    return True  # Event handled
        return False  # Event not handled


if __name__ == "__main__":
    # Check for command line arguments
    start_minimized = "--minimized" in sys.argv
    
    # Use QApplication instead of QGuiApplication to support QSystemTrayIcon
    app = QApplication(sys.argv)
    
    # Set application style and fonts
    app.setStyle("Fusion")
    
    # Don't quit when last window is closed (we'll manage this manually)
    app.setQuitOnLastWindowClosed(False)
    
    # Create and initialize backend
    backend = TrueAxisBackend()
    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("backend", backend)
    engine.rootContext().setContextProperty("trayManager", backend.tray_manager)
    engine.loadData(QML_CODE.encode())
    
    if not engine.rootObjects():
        print("ERROR: Failed to load QML!")
        sys.exit(-1)
    
    # Get the main window and set it in the tray manager
    main_window = engine.rootObjects()[0]
    backend.tray_manager.main_window = main_window
    
    # Refresh UI to sync with loaded settings (do this after QML is ready)
    QTimer.singleShot(500, backend.refresh_ui_from_settings)
    
    # Install event filter to intercept minimize button
    window_handler = MainWindowHandler(main_window, backend.tray_manager, backend._start_minimized)
    main_window.installEventFilter(window_handler)
    
    # Set window icon
    icon_pixmap = QPixmap(32, 32)
    icon_pixmap.fill(Qt.GlobalColor.transparent)
    painter = QPainter(icon_pixmap)
    painter.setRenderHint(QPainter.Antialiasing)
    painter.setBrush(QColor(16, 185, 129))
    painter.setPen(Qt.PenStyle.NoPen)
    painter.drawEllipse(4, 4, 24, 24)
    painter.setPen(QColor(255, 255, 255))
    painter.setFont(QFont("Inter", 16, QFont.Bold))
    painter.drawText(icon_pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "T")
    painter.end()
    main_window.setIcon(QIcon(icon_pixmap))
    
    # Handle start minimized
    if backend._start_minimized or start_minimized:
        # Check if tray is available
        if backend.tray_manager.trayAvailable:
            main_window.hide()
            backend.tray_manager.show_tray()
            backend.statusChanged.emit("Running in system tray", "#22c55e")
        else:
            # If tray not available, just minimize to taskbar
            main_window.showMinimized()
    
    # Save settings before app quits
    app.aboutToQuit.connect(lambda: backend.save_settings(force=True))
    app.aboutToQuit.connect(lambda: backend.tray_manager.save_settings())
    
    sys.exit(app.exec())