mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
evdev: add evdev monitor for caps lock state
This commit is contained in:
@@ -40,3 +40,9 @@ packages:
|
|||||||
outpkg: mocks_cups
|
outpkg: mocks_cups
|
||||||
interfaces:
|
interfaces:
|
||||||
CUPSClientInterface:
|
CUPSClientInterface:
|
||||||
|
github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev:
|
||||||
|
config:
|
||||||
|
dir: "internal/mocks/evdev"
|
||||||
|
outpkg: mocks_evdev
|
||||||
|
interfaces:
|
||||||
|
EvdevDevice:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
|||||||
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
||||||
- Backlight control - Internal display brightness via `login1` or sysfs
|
- Backlight control - Internal display brightness via `login1` or sysfs
|
||||||
- LED control - Keyboard/device LED management
|
- LED control - Keyboard/device LED management
|
||||||
|
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
|
||||||
|
|
||||||
**Plugin System**
|
**Plugin System**
|
||||||
- Plugin registry integration
|
- Plugin registry integration
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/charmbracelet/log v0.4.2
|
github.com/charmbracelet/log v0.4.2
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34
|
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34
|
||||||
@@ -61,7 +62,6 @@ require (
|
|||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
|
||||||
golang.org/x/sys v0.38.0
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
48
core/go.sum
48
core/go.sum
@@ -14,12 +14,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
|
|||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||||
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
|
||||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
|
||||||
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||||
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||||
@@ -28,16 +24,10 @@ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoF
|
|||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||||
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
|
||||||
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
|
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
|
||||||
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
|
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
|
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
|
||||||
@@ -49,8 +39,6 @@ github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV
|
|||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||||
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -66,25 +54,23 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 h1:4KqVJTL5eanN8Sgg3BV6f2/QzfZEFbCd+rTak1fGRRA=
|
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30/go.mod h1:snwvGrbywVFy2d6KJdQ132zapq4aLyzLMgpo79XdEfM=
|
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 h1:EC9n6hr6yKDoVJ6g7Ko523LbbceJfR0ohbOp809Fyf4=
|
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 h1:EC9n6hr6yKDoVJ6g7Ko523LbbceJfR0ohbOp809Fyf4=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0/go.mod h1:E3VhlS+AKkrq6ZNn1axE2/nDRJ87l1FJk9r5HT2vPX0=
|
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0/go.mod h1:E3VhlS+AKkrq6ZNn1axE2/nDRJ87l1FJk9r5HT2vPX0=
|
||||||
github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w=
|
github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w=
|
||||||
github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
|
github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd h1:30HEd5KKVM7GgMJ1GSNuYxuZXEg8Pdlngp6T51faxoc=
|
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd/go.mod h1:lz8PQr/p79XpFq5ODVBwRJu5LnOF8Et7j95ehqmCMJU=
|
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9 h1:SOFrnF9LCssC6q6Rb0084Bzg2aBYbe8QXv9xKGXmt/w=
|
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9 h1:SOFrnF9LCssC6q6Rb0084Bzg2aBYbe8QXv9xKGXmt/w=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9/go.mod h1:0wtvm/JfPC9RFVEAP3ks0ec5h64/qmZkTTUE3pjz7Hc=
|
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9/go.mod h1:0wtvm/JfPC9RFVEAP3ks0ec5h64/qmZkTTUE3pjz7Hc=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
|
||||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||||
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83 h1:B+A58zGFuDrvEZpPN+yS6swJA0nzqgZvDzgl/OPyefU=
|
||||||
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||||
@@ -103,8 +89,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
@@ -117,7 +101,6 @@ github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
|||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
@@ -127,18 +110,12 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
|||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
|
||||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@@ -148,33 +125,18 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
|||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34 h1:iTAt1me6SBYsuzrl/CmrxtATPlOG/pVviosM3DhUdKE=
|
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34 h1:iTAt1me6SBYsuzrl/CmrxtATPlOG/pVviosM3DhUdKE=
|
||||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34/go.mod h1:jzmUN5lUAv2O8e63OvcauV4S30rIZ1BvF/PNYE37vDo=
|
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34/go.mod h1:jzmUN5lUAv2O8e63OvcauV4S30rIZ1BvF/PNYE37vDo=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
|
||||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
|
||||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
295
core/internal/mocks/evdev/mock_EvdevDevice.go
Normal file
295
core/internal/mocks/evdev/mock_EvdevDevice.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks_evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
go_evdev "github.com/holoplot/go-evdev"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockEvdevDevice is an autogenerated mock type for the EvdevDevice type
|
||||||
|
type MockEvdevDevice struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockEvdevDevice_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockEvdevDevice) EXPECT() *MockEvdevDevice_Expecter {
|
||||||
|
return &MockEvdevDevice_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) Close() error {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Close")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
||||||
|
type MockEvdevDevice_Close_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) Close() *MockEvdevDevice_Close_Call {
|
||||||
|
return &MockEvdevDevice_Close_Call{Call: _e.mock.On("Close")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Close_Call) Run(run func()) *MockEvdevDevice_Close_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Close_Call) Return(_a0 error) *MockEvdevDevice_Close_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Close_Call) RunAndReturn(run func() error) *MockEvdevDevice_Close_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) Name() (string, error) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func() string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||||
|
type MockEvdevDevice_Name_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) Name() *MockEvdevDevice_Name_Call {
|
||||||
|
return &MockEvdevDevice_Name_Call{Call: _e.mock.On("Name")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Name_Call) Run(run func()) *MockEvdevDevice_Name_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Name_Call) Return(_a0 string, _a1 error) *MockEvdevDevice_Name_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Name_Call) RunAndReturn(run func() (string, error)) *MockEvdevDevice_Name_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) Path() string {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Path")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func() string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_Path_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Path'
|
||||||
|
type MockEvdevDevice_Path_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) Path() *MockEvdevDevice_Path_Call {
|
||||||
|
return &MockEvdevDevice_Path_Call{Call: _e.mock.On("Path")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Path_Call) Run(run func()) *MockEvdevDevice_Path_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Path_Call) Return(_a0 string) *MockEvdevDevice_Path_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Path_Call) RunAndReturn(run func() string) *MockEvdevDevice_Path_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOne provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) ReadOne() (*go_evdev.InputEvent, error) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ReadOne")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 *go_evdev.InputEvent
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (*go_evdev.InputEvent, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func() *go_evdev.InputEvent); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*go_evdev.InputEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_ReadOne_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadOne'
|
||||||
|
type MockEvdevDevice_ReadOne_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOne is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) ReadOne() *MockEvdevDevice_ReadOne_Call {
|
||||||
|
return &MockEvdevDevice_ReadOne_Call{Call: _e.mock.On("ReadOne")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_ReadOne_Call) Run(run func()) *MockEvdevDevice_ReadOne_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_ReadOne_Call) Return(_a0 *go_evdev.InputEvent, _a1 error) *MockEvdevDevice_ReadOne_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_ReadOne_Call) RunAndReturn(run func() (*go_evdev.InputEvent, error)) *MockEvdevDevice_ReadOne_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// State provides a mock function with given fields: t
|
||||||
|
func (_m *MockEvdevDevice) State(t go_evdev.EvType) (go_evdev.StateMap, error) {
|
||||||
|
ret := _m.Called(t)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for State")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 go_evdev.StateMap
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(go_evdev.EvType) (go_evdev.StateMap, error)); ok {
|
||||||
|
return rf(t)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(go_evdev.EvType) go_evdev.StateMap); ok {
|
||||||
|
r0 = rf(t)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(go_evdev.StateMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(go_evdev.EvType) error); ok {
|
||||||
|
r1 = rf(t)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_State_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'State'
|
||||||
|
type MockEvdevDevice_State_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// State is a helper method to define mock.On call
|
||||||
|
// - t go_evdev.EvType
|
||||||
|
func (_e *MockEvdevDevice_Expecter) State(t interface{}) *MockEvdevDevice_State_Call {
|
||||||
|
return &MockEvdevDevice_State_Call{Call: _e.mock.On("State", t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_State_Call) Run(run func(t go_evdev.EvType)) *MockEvdevDevice_State_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(go_evdev.EvType))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_State_Call) Return(_a0 go_evdev.StateMap, _a1 error) *MockEvdevDevice_State_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_State_Call) RunAndReturn(run func(go_evdev.EvType) (go_evdev.StateMap, error)) *MockEvdevDevice_State_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockEvdevDevice creates a new instance of MockEvdevDevice. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewMockEvdevDevice(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *MockEvdevDevice {
|
||||||
|
mock := &MockEvdevDevice{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
27
core/internal/server/evdev/handlers.go
Normal file
27
core/internal/server/evdev/handlers.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params map[string]interface{} `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleRequest(conn net.Conn, req Request, m *Manager) {
|
||||||
|
switch req.Method {
|
||||||
|
case "evdev.getState":
|
||||||
|
handleGetState(conn, req, m)
|
||||||
|
default:
|
||||||
|
models.RespondError(conn, req.ID.(int), "unknown method: "+req.Method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetState(conn net.Conn, req Request, m *Manager) {
|
||||||
|
state := m.GetState()
|
||||||
|
models.Respond(conn, req.ID.(int), state)
|
||||||
|
}
|
||||||
133
core/internal/server/evdev/handlers_test.go
Normal file
133
core/internal/server/evdev/handlers_test.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
mocks "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/evdev"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockNetConn struct {
|
||||||
|
net.Conn
|
||||||
|
readBuf *bytes.Buffer
|
||||||
|
writeBuf *bytes.Buffer
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockNetConn() *mockNetConn {
|
||||||
|
return &mockNetConn{
|
||||||
|
readBuf: &bytes.Buffer{},
|
||||||
|
writeBuf: &bytes.Buffer{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNetConn) Read(b []byte) (n int, err error) {
|
||||||
|
return m.readBuf.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNetConn) Write(b []byte) (n int, err error) {
|
||||||
|
return m.writeBuf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNetConn) Close() error {
|
||||||
|
m.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleRequest(t *testing.T) {
|
||||||
|
t.Run("getState request", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: true},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newMockNetConn()
|
||||||
|
req := Request{
|
||||||
|
ID: 123,
|
||||||
|
Method: "evdev.getState",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleRequest(conn, req, m)
|
||||||
|
|
||||||
|
var resp models.Response[State]
|
||||||
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 123, resp.ID)
|
||||||
|
assert.NotNil(t, resp.Result)
|
||||||
|
assert.True(t, resp.Result.Available)
|
||||||
|
assert.True(t, resp.Result.CapsLock)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown method", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newMockNetConn()
|
||||||
|
req := Request{
|
||||||
|
ID: 456,
|
||||||
|
Method: "evdev.unknownMethod",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleRequest(conn, req, m)
|
||||||
|
|
||||||
|
var resp models.Response[any]
|
||||||
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 456, resp.ID)
|
||||||
|
assert.NotEmpty(t, resp.Error)
|
||||||
|
assert.Contains(t, resp.Error, "unknown method")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleGetState(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newMockNetConn()
|
||||||
|
req := Request{
|
||||||
|
ID: 789,
|
||||||
|
Method: "evdev.getState",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGetState(conn, req, m)
|
||||||
|
|
||||||
|
var resp models.Response[State]
|
||||||
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 789, resp.ID)
|
||||||
|
assert.NotNil(t, resp.Result)
|
||||||
|
assert.True(t, resp.Result.Available)
|
||||||
|
assert.False(t, resp.Result.CapsLock)
|
||||||
|
}
|
||||||
256
core/internal/server/evdev/manager.go
Normal file
256
core/internal/server/evdev/manager.go
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
evdev "github.com/holoplot/go-evdev"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
evKeyType = 0x01
|
||||||
|
evLedType = 0x11
|
||||||
|
keyCapslockKey = 58
|
||||||
|
ledCapslockKey = 1
|
||||||
|
keyStateOn = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type EvdevDevice interface {
|
||||||
|
Name() (string, error)
|
||||||
|
Path() string
|
||||||
|
Close() error
|
||||||
|
ReadOne() (*evdev.InputEvent, error)
|
||||||
|
State(t evdev.EvType) (evdev.StateMap, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
device EvdevDevice
|
||||||
|
state State
|
||||||
|
stateMutex sync.RWMutex
|
||||||
|
subscribers map[string]chan State
|
||||||
|
subMutex sync.RWMutex
|
||||||
|
closeChan chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() (*Manager, error) {
|
||||||
|
device, err := findKeyboard()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find keyboard: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialCapsLock := readInitialCapsLockState(device)
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: device,
|
||||||
|
state: State{Available: true, CapsLock: initialCapsLock},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go m.monitorCapsLock()
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInitialCapsLockState(device EvdevDevice) bool {
|
||||||
|
ledStates, err := device.State(evLedType)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Could not read LED state: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ledStates[ledCapslockKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
func findKeyboard() (EvdevDevice, error) {
|
||||||
|
pattern := "/dev/input/event*"
|
||||||
|
matches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to glob input devices: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil, fmt.Errorf("no input devices found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range matches {
|
||||||
|
device, err := evdev.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isKeyboard(device) {
|
||||||
|
deviceName, _ := device.Name()
|
||||||
|
log.Debugf("Found keyboard: %s at %s", deviceName, path)
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
device.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no keyboard device found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isKeyboard(device EvdevDevice) bool {
|
||||||
|
deviceName, err := device.Name()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.ToLower(deviceName)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(name, "keyboard"):
|
||||||
|
return true
|
||||||
|
case strings.Contains(name, "kbd"):
|
||||||
|
return true
|
||||||
|
case strings.Contains(name, "input") && strings.Contains(name, "key"):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) monitorCapsLock() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("Panic in evdev monitor: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.closeChan:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
event, err := m.device.ReadOne()
|
||||||
|
if err != nil {
|
||||||
|
if !isClosedError(err) {
|
||||||
|
log.Warnf("Failed to read evdev event: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if event == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Type == evKeyType && event.Code == keyCapslockKey && event.Value == keyStateOn {
|
||||||
|
m.toggleCapsLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isClosedError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr := err.Error()
|
||||||
|
switch {
|
||||||
|
case strings.Contains(errStr, "closed"):
|
||||||
|
return true
|
||||||
|
case strings.Contains(errStr, "bad file descriptor"):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) toggleCapsLock() {
|
||||||
|
m.stateMutex.Lock()
|
||||||
|
m.state.CapsLock = !m.state.CapsLock
|
||||||
|
newState := m.state
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("Caps lock toggled: %v", newState.CapsLock)
|
||||||
|
m.notifySubscribers(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetState() State {
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
defer m.stateMutex.RUnlock()
|
||||||
|
return m.state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
|
m.subMutex.Lock()
|
||||||
|
defer m.subMutex.Unlock()
|
||||||
|
|
||||||
|
ch := make(chan State, 16)
|
||||||
|
m.subscribers[id] = ch
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
|
m.subMutex.Lock()
|
||||||
|
defer m.subMutex.Unlock()
|
||||||
|
|
||||||
|
if ch, ok := m.subscribers[id]; ok {
|
||||||
|
close(ch)
|
||||||
|
delete(m.subscribers, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) notifySubscribers(state State) {
|
||||||
|
m.subMutex.RLock()
|
||||||
|
defer m.subMutex.RUnlock()
|
||||||
|
|
||||||
|
for _, ch := range m.subscribers {
|
||||||
|
select {
|
||||||
|
case ch <- state:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() {
|
||||||
|
m.closeOnce.Do(func() {
|
||||||
|
close(m.closeChan)
|
||||||
|
|
||||||
|
if m.device != nil {
|
||||||
|
if err := m.device.Close(); err != nil && !isClosedError(err) {
|
||||||
|
log.Warnf("Error closing evdev device: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.subMutex.Lock()
|
||||||
|
for id, ch := range m.subscribers {
|
||||||
|
close(ch)
|
||||||
|
delete(m.subscribers, id)
|
||||||
|
}
|
||||||
|
m.subMutex.Unlock()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializeManager() (*Manager, error) {
|
||||||
|
if os.Getuid() != 0 && !hasInputGroupAccess() {
|
||||||
|
return nil, fmt.Errorf("insufficient permissions to access input devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasInputGroupAccess() bool {
|
||||||
|
pattern := "/dev/input/event*"
|
||||||
|
matches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil || len(matches) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
testFile, err := os.Open(matches[0])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
testFile.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
314
core/internal/server/evdev/manager_test.go
Normal file
314
core/internal/server/evdev/manager_test.go
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
evdev "github.com/holoplot/go-evdev"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
mocks "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/evdev"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManager_Creation(t *testing.T) {
|
||||||
|
t.Run("manager created successfully with caps lock off", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(t, m)
|
||||||
|
assert.True(t, m.state.Available)
|
||||||
|
assert.False(t, m.state.CapsLock)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("manager created successfully with caps lock on", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: true},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(t, m)
|
||||||
|
assert.True(t, m.state.Available)
|
||||||
|
assert.True(t, m.state.CapsLock)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_GetState(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
state := m.GetState()
|
||||||
|
assert.True(t, state.Available)
|
||||||
|
assert.False(t, state.CapsLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Subscribe(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test-client")
|
||||||
|
assert.NotNil(t, ch)
|
||||||
|
assert.Len(t, m.subscribers, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Unsubscribe(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test-client")
|
||||||
|
assert.Len(t, m.subscribers, 1)
|
||||||
|
|
||||||
|
m.Unsubscribe("test-client")
|
||||||
|
assert.Len(t, m.subscribers, 0)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch:
|
||||||
|
assert.False(t, ok, "channel should be closed")
|
||||||
|
default:
|
||||||
|
t.Error("channel should be closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_ToggleCapsLock(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test-client")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
m.toggleCapsLock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
newState := <-ch
|
||||||
|
assert.True(t, newState.CapsLock)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
m.toggleCapsLock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
newState = <-ch
|
||||||
|
assert.False(t, newState.CapsLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Close(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().Close().Return(nil).Once()
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch1 := m.Subscribe("client1")
|
||||||
|
ch2 := m.Subscribe("client2")
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch1:
|
||||||
|
assert.False(t, ok, "channel 1 should be closed")
|
||||||
|
default:
|
||||||
|
t.Error("channel 1 should be closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch2:
|
||||||
|
assert.False(t, ok, "channel 2 should be closed")
|
||||||
|
default:
|
||||||
|
t.Error("channel 2 should be closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, m.subscribers, 0)
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsKeyboard(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
devName string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"keyboard in name", "AT Translated Set 2 keyboard", true},
|
||||||
|
{"kbd in name", "USB kbd", true},
|
||||||
|
{"input and key", "input key device", true},
|
||||||
|
{"random device", "Mouse", false},
|
||||||
|
{"empty name", "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().Name().Return(tt.devName, nil).Once()
|
||||||
|
|
||||||
|
result := isKeyboard(mockDevice)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsKeyboard_ErrorHandling(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().Name().Return("", errors.New("device error")).Once()
|
||||||
|
|
||||||
|
result := isKeyboard(mockDevice)
|
||||||
|
assert.False(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_MonitorCapsLock(t *testing.T) {
|
||||||
|
t.Run("caps lock key press toggles state", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
|
||||||
|
capsLockEvent := &evdev.InputEvent{
|
||||||
|
Type: evKeyType,
|
||||||
|
Code: keyCapslockKey,
|
||||||
|
Value: keyStateOn,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(capsLockEvent, nil).Once()
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("stop")).Maybe()
|
||||||
|
mockDevice.EXPECT().Close().Return(nil).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test")
|
||||||
|
|
||||||
|
go m.monitorCapsLock()
|
||||||
|
|
||||||
|
state := <-ch
|
||||||
|
assert.True(t, state.CapsLock)
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsClosedError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"nil error", nil, false},
|
||||||
|
{"closed error", errors.New("device closed"), true},
|
||||||
|
{"bad file descriptor", errors.New("bad file descriptor"), true},
|
||||||
|
{"other error", errors.New("some other error"), false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isClosedError(tt.err)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotifySubscribers(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
mockDevice.EXPECT().Close().Return(nil).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
device: mockDevice,
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
subscribers: make(map[string]chan State),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch1 := m.Subscribe("client1")
|
||||||
|
ch2 := m.Subscribe("client2")
|
||||||
|
|
||||||
|
newState := State{Available: true, CapsLock: true}
|
||||||
|
go m.notifySubscribers(newState)
|
||||||
|
|
||||||
|
state1 := <-ch1
|
||||||
|
state2 := <-ch2
|
||||||
|
|
||||||
|
assert.Equal(t, newState, state1)
|
||||||
|
assert.Equal(t, newState, state2)
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadInitialCapsLockState(t *testing.T) {
|
||||||
|
t.Run("caps lock is on", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
ledState := evdev.StateMap{
|
||||||
|
ledCapslockKey: true,
|
||||||
|
}
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
|
result := readInitialCapsLockState(mockDevice)
|
||||||
|
assert.True(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("caps lock is off", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
ledState := evdev.StateMap{
|
||||||
|
ledCapslockKey: false,
|
||||||
|
}
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
|
result := readInitialCapsLockState(mockDevice)
|
||||||
|
assert.False(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error reading LED state", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(nil, errors.New("read error")).Once()
|
||||||
|
|
||||||
|
result := readInitialCapsLockState(mockDevice)
|
||||||
|
assert.False(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasInputGroupAccess(t *testing.T) {
|
||||||
|
result := hasInputGroupAccess()
|
||||||
|
t.Logf("hasInputGroupAccess: %v", result)
|
||||||
|
}
|
||||||
6
core/internal/server/evdev/models.go
Normal file
6
core/internal/server/evdev/models.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Available bool `json:"available"`
|
||||||
|
CapsLock bool `json:"capsLock"`
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
||||||
@@ -165,6 +166,20 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(req.Method, "evdev.") {
|
||||||
|
if evdevManager == nil {
|
||||||
|
models.RespondError(conn, req.ID, "evdev manager not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
evdevReq := evdev.Request{
|
||||||
|
ID: req.ID,
|
||||||
|
Method: req.Method,
|
||||||
|
Params: req.Params,
|
||||||
|
}
|
||||||
|
evdev.HandleRequest(conn, evdevReq, evdevManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "ping":
|
case "ping":
|
||||||
models.Respond(conn, req.ID, "pong")
|
models.Respond(conn, req.ID, "pong")
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
||||||
@@ -28,7 +29,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIVersion = 17
|
const APIVersion = 18
|
||||||
|
|
||||||
type Capabilities struct {
|
type Capabilities struct {
|
||||||
Capabilities []string `json:"capabilities"`
|
Capabilities []string `json:"capabilities"`
|
||||||
@@ -54,6 +55,7 @@ var dwlManager *dwl.Manager
|
|||||||
var extWorkspaceManager *extworkspace.Manager
|
var extWorkspaceManager *extworkspace.Manager
|
||||||
var brightnessManager *brightness.Manager
|
var brightnessManager *brightness.Manager
|
||||||
var wlrOutputManager *wlroutput.Manager
|
var wlrOutputManager *wlroutput.Manager
|
||||||
|
var evdevManager *evdev.Manager
|
||||||
var wlContext *wlcontext.SharedContext
|
var wlContext *wlcontext.SharedContext
|
||||||
|
|
||||||
var capabilitySubscribers = make(map[string]chan ServerInfo)
|
var capabilitySubscribers = make(map[string]chan ServerInfo)
|
||||||
@@ -292,6 +294,19 @@ func InitializeWlrOutputManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitializeEvdevManager() error {
|
||||||
|
manager, err := evdev.InitializeManager()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to initialize evdev manager: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
evdevManager = manager
|
||||||
|
|
||||||
|
log.Info("Evdev manager initialized")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleConnection(conn net.Conn) {
|
func handleConnection(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -358,6 +373,10 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "wlroutput")
|
caps = append(caps, "wlroutput")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if evdevManager != nil {
|
||||||
|
caps = append(caps, "evdev")
|
||||||
|
}
|
||||||
|
|
||||||
return Capabilities{Capabilities: caps}
|
return Capabilities{Capabilities: caps}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,6 +423,10 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "wlroutput")
|
caps = append(caps, "wlroutput")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if evdevManager != nil {
|
||||||
|
caps = append(caps, "evdev")
|
||||||
|
}
|
||||||
|
|
||||||
return ServerInfo{
|
return ServerInfo{
|
||||||
APIVersion: APIVersion,
|
APIVersion: APIVersion,
|
||||||
Capabilities: caps,
|
Capabilities: caps,
|
||||||
@@ -918,6 +941,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldSubscribe("evdev") && evdevManager != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
evdevChan := evdevManager.Subscribe(clientID + "-evdev")
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer evdevManager.Unsubscribe(clientID + "-evdev")
|
||||||
|
|
||||||
|
initialState := evdevManager.GetState()
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "evdev", Data: initialState}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case state, ok := <-evdevChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "evdev", Data: state}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(eventChan)
|
close(eventChan)
|
||||||
@@ -974,6 +1029,9 @@ func cleanupManagers() {
|
|||||||
if wlrOutputManager != nil {
|
if wlrOutputManager != nil {
|
||||||
wlrOutputManager.Close()
|
wlrOutputManager.Close()
|
||||||
}
|
}
|
||||||
|
if evdevManager != nil {
|
||||||
|
evdevManager.Close()
|
||||||
|
}
|
||||||
if wlContext != nil {
|
if wlContext != nil {
|
||||||
wlContext.Close()
|
wlContext.Close()
|
||||||
}
|
}
|
||||||
@@ -1122,6 +1180,9 @@ func Start(printDocs bool) error {
|
|||||||
log.Info(" - transform : Transform value (optional)")
|
log.Info(" - transform : Transform value (optional)")
|
||||||
log.Info(" - scale : Scale value (optional)")
|
log.Info(" - scale : Scale value (optional)")
|
||||||
log.Info(" - adaptiveSync : Adaptive sync state (optional)")
|
log.Info(" - adaptiveSync : Adaptive sync state (optional)")
|
||||||
|
log.Info("Evdev:")
|
||||||
|
log.Info(" evdev.getState - Get current evdev state (caps lock)")
|
||||||
|
log.Info(" evdev.subscribe - Subscribe to evdev state changes (streaming)")
|
||||||
log.Info("")
|
log.Info("")
|
||||||
}
|
}
|
||||||
log.Info("Initializing managers...")
|
log.Info("Initializing managers...")
|
||||||
@@ -1194,10 +1255,8 @@ func Start(printDocs bool) error {
|
|||||||
fatalErrChan := make(chan error, 1)
|
fatalErrChan := make(chan error, 1)
|
||||||
if wlrOutputManager != nil {
|
if wlrOutputManager != nil {
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
err := <-wlrOutputManager.FatalError()
|
||||||
case err := <-wlrOutputManager.FatalError():
|
fatalErrChan <- fmt.Errorf("WlrOutput fatal error: %w", err)
|
||||||
fatalErrChan <- fmt.Errorf("WlrOutput fatal error: %w", err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1209,6 +1268,14 @@ func Start(printDocs bool) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := InitializeEvdevManager(); err != nil {
|
||||||
|
log.Debugf("Evdev manager unavailable: %v", err)
|
||||||
|
} else {
|
||||||
|
notifyCapabilityChange()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if wlContext != nil {
|
if wlContext != nil {
|
||||||
wlContext.Start()
|
wlContext.Start()
|
||||||
log.Info("Wayland event dispatcher started")
|
log.Info("Wayland event dispatcher started")
|
||||||
|
|||||||
@@ -600,6 +600,14 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: SettingsData.getFilteredScreens("osd")
|
||||||
|
|
||||||
|
delegate: CapsLockOSD {
|
||||||
|
modelData: item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: hyprlandOverviewLoader
|
id: hyprlandOverviewLoader
|
||||||
active: CompositorService.isHyprland
|
active: CompositorService.isHyprland
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
id: passwordLayout
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenterOffset: 50
|
anchors.verticalCenterOffset: 50
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
@@ -740,6 +741,35 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.top: passwordLayout.bottom
|
||||||
|
anchors.topMargin: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: passwordLayout.horizontalCenter
|
||||||
|
spacing: 4
|
||||||
|
opacity: DMSService.capsLockState ? 1 : 0
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "shift_lock"
|
||||||
|
size: 14
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Caps Lock is on"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
37
quickshell/Modules/OSD/CapsLockOSD.qml
Normal file
37
quickshell/Modules/OSD/CapsLockOSD.qml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankOSD {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
osdWidth: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
osdHeight: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
autoHideInterval: 2000
|
||||||
|
enableMouseInteraction: false
|
||||||
|
|
||||||
|
property bool lastCapsLockState: false
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DMSService
|
||||||
|
|
||||||
|
function onCapsLockStateChanged() {
|
||||||
|
if (lastCapsLockState !== DMSService.capsLockState) {
|
||||||
|
root.show()
|
||||||
|
}
|
||||||
|
lastCapsLockState = DMSService.capsLockState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
lastCapsLockState = DMSService.capsLockState
|
||||||
|
}
|
||||||
|
|
||||||
|
content: DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: DMSService.capsLockState ? "shift_lock" : "shift_lock_off"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,8 +49,11 @@ Singleton {
|
|||||||
signal brightnessDeviceUpdate(var device)
|
signal brightnessDeviceUpdate(var device)
|
||||||
signal extWorkspaceStateUpdate(var data)
|
signal extWorkspaceStateUpdate(var data)
|
||||||
signal wlrOutputStateUpdate(var data)
|
signal wlrOutputStateUpdate(var data)
|
||||||
|
signal evdevStateUpdate(var data)
|
||||||
|
|
||||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput"]
|
property bool capsLockState: false
|
||||||
|
|
||||||
|
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -349,6 +352,11 @@ Singleton {
|
|||||||
extWorkspaceStateUpdate(data)
|
extWorkspaceStateUpdate(data)
|
||||||
} else if (service === "wlroutput") {
|
} else if (service === "wlroutput") {
|
||||||
wlrOutputStateUpdate(data)
|
wlrOutputStateUpdate(data)
|
||||||
|
} else if (service === "evdev") {
|
||||||
|
if (data.capsLock !== undefined) {
|
||||||
|
capsLockState = data.capsLock
|
||||||
|
}
|
||||||
|
evdevStateUpdate(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user