summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
committerNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
commita2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch)
tree1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server
parent34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff)
downloadRaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz
RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/handlers.go51
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/manager.go48
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/models.go8
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/agent.go341
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker.go21
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker_test.go220
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers.go237
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers_test.go41
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/manager.go588
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/subscription_broker.go86
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types.go78
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types_test.go210
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go510
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go135
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go122
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go135
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go124
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go66
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go95
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go389
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go11
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go267
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go278
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go180
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go184
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go237
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go260
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/handlers.go22
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/manager.go49
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/models.go5
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/handlers.go325
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager.go1911
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager_test.go531
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/types.go196
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go479
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go235
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go450
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go487
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go711
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go390
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go363
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go184
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go95
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go245
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go295
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go176
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go397
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go13
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go132
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/handlers.go237
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/manager.go392
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/types.go52
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/handlers.go138
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager.go522
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager_test.go366
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/types.go176
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers.go20
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers_test.go130
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager.go413
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager_test.go344
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/models.go6
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/handlers.go134
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager.go598
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager_test.go392
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/types.go169
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions.go112
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions_test.go145
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/constants.go23
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers.go154
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers_test.go581
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager.go206
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager_test.go143
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver.go349
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver_test.go102
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types.go66
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types_test.go70
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/handlers.go61
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/manager.go175
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/types.go28
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/actions.go88
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/constants.go10
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers.go156
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers_test.go500
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager.go723
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager_test.go296
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor.go155
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor_test.go298
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types.go76
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types_test.go50
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/matugen_handler.go79
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types.go47
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types_test.go52
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/API.md552
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_iwd.go306
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager.go897
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager_test.go355
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend.go80
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_iwd_networkd.go245
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_test.go135
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd.go238
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_signals.go355
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_test.go212
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_unimplemented.go133
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_wifi.go662
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd.go301
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_ethernet.go120
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_signals.go68
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_test.go147
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_unimplemented.go99
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager.go405
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet.go402
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet_test.go134
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml.go203
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml_test.go169
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals.go530
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals_test.go255
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state.go277
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state_test.go82
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_test.go154
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn.go1394
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn_test.go138
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi.go1173
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi_test.go202
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/broker.go22
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/connection_test.go109
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect.go89
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect_test.go41
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers.go630
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers_test.go269
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/helpers.go53
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager.go626
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager_test.go191
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/monitor.go1
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority.go187
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority_test.go50
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/subscription_broker.go125
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/testing.go14
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types.go267
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types_test.go178
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_qrcode.go59
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_test.go148
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wired_test.go51
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params.go113
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params_test.go154
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go27
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go196
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go69
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go53
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go77
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go74
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go25
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go66
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go64
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go17
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/router.go250
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server.go1758
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server_test.go180
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/handlers.go154
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/manager.go462
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/types.go23
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/handlers.go27
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/install.go52
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list.go54
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list_installed.go149
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/search.go53
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/types.go47
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/uninstall.go63
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/update.go57
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/manager.go93
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/recovery.go262
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go167
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go120
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go50
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go182
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go1256
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go414
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go122
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go387
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go216
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go330
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go200
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go128
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go282
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go492
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go414
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go192
186 files changed, 0 insertions, 44551 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/handlers.go
deleted file mode 100644
index 007ab3e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/handlers.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package apppicker
-
-import (
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "apppicker.open", "browser.open":
- handleOpen(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, "unknown method")
- }
-}
-
-func handleOpen(conn net.Conn, req models.Request, manager *Manager) {
- log.Infof("AppPicker: Received %s request with params: %+v", req.Method, req.Params)
-
- target, ok := models.Get[string](req, "target")
- if !ok {
- target, ok = models.Get[string](req, "url")
- if !ok {
- log.Warnf("AppPicker: Invalid target parameter in request")
- models.RespondError(conn, req.ID, "invalid target parameter")
- return
- }
- }
-
- event := OpenEvent{
- Target: target,
- RequestType: models.GetOr(req, "requestType", "url"),
- MimeType: models.GetOr(req, "mimeType", ""),
- }
-
- if categories, ok := models.Get[[]any](req, "categories"); ok {
- event.Categories = make([]string, 0, len(categories))
- for _, cat := range categories {
- if catStr, ok := cat.(string); ok {
- event.Categories = append(event.Categories, catStr)
- }
- }
- }
-
- log.Infof("AppPicker: Broadcasting event: %+v", event)
- manager.RequestOpen(event)
- models.Respond(conn, req.ID, "ok")
- log.Infof("AppPicker: Request handled successfully")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/manager.go
deleted file mode 100644
index bde0846..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/manager.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package apppicker
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type Manager struct {
- subscribers syncmap.Map[string, chan OpenEvent]
- closeOnce sync.Once
-}
-
-func NewManager() *Manager {
- return &Manager{}
-}
-
-func (m *Manager) Subscribe(id string) chan OpenEvent {
- ch := make(chan OpenEvent, 16)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) RequestOpen(event OpenEvent) {
- m.subscribers.Range(func(key string, ch chan OpenEvent) bool {
- select {
- case ch <- event:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) Close() {
- m.closeOnce.Do(func() {
- m.subscribers.Range(func(key string, ch chan OpenEvent) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/models.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/models.go
deleted file mode 100644
index 8861461..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/apppicker/models.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package apppicker
-
-type OpenEvent struct {
- Target string `json:"target"`
- MimeType string `json:"mimeType,omitempty"`
- Categories []string `json:"categories,omitempty"`
- RequestType string `json:"requestType"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/agent.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/agent.go
deleted file mode 100644
index b630a19..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/agent.go
+++ /dev/null
@@ -1,341 +0,0 @@
-package bluez
-
-import (
- "context"
- "errors"
- "fmt"
- "strconv"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- bluezService = "org.bluez"
- agentManagerPath = "/org/bluez"
- agentManagerIface = "org.bluez.AgentManager1"
- agent1Iface = "org.bluez.Agent1"
- device1Iface = "org.bluez.Device1"
- agentPath = "/com/danklinux/bluez/agent"
- agentCapability = "KeyboardDisplay"
-)
-
-const introspectXML = `
-<node>
- <interface name="org.bluez.Agent1">
- <method name="Release"/>
- <method name="RequestPinCode">
- <arg direction="in" type="o" name="device"/>
- <arg direction="out" type="s" name="pincode"/>
- </method>
- <method name="RequestPasskey">
- <arg direction="in" type="o" name="device"/>
- <arg direction="out" type="u" name="passkey"/>
- </method>
- <method name="DisplayPinCode">
- <arg direction="in" type="o" name="device"/>
- <arg direction="in" type="s" name="pincode"/>
- </method>
- <method name="DisplayPasskey">
- <arg direction="in" type="o" name="device"/>
- <arg direction="in" type="u" name="passkey"/>
- <arg direction="in" type="q" name="entered"/>
- </method>
- <method name="RequestConfirmation">
- <arg direction="in" type="o" name="device"/>
- <arg direction="in" type="u" name="passkey"/>
- </method>
- <method name="RequestAuthorization">
- <arg direction="in" type="o" name="device"/>
- </method>
- <method name="AuthorizeService">
- <arg direction="in" type="o" name="device"/>
- <arg direction="in" type="s" name="uuid"/>
- </method>
- <method name="Cancel"/>
- </interface>
- <interface name="org.freedesktop.DBus.Introspectable">
- <method name="Introspect">
- <arg direction="out" type="s" name="data"/>
- </method>
- </interface>
-</node>`
-
-type BluezAgent struct {
- conn *dbus.Conn
- broker PromptBroker
-}
-
-func NewBluezAgent(broker PromptBroker) (*BluezAgent, error) {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("system bus connection failed: %w", err)
- }
-
- agent := &BluezAgent{
- conn: conn,
- broker: broker,
- }
-
- if err := conn.Export(agent, dbus.ObjectPath(agentPath), agent1Iface); err != nil {
- conn.Close()
- return nil, fmt.Errorf("agent export failed: %w", err)
- }
-
- if err := conn.Export(agent, dbus.ObjectPath(agentPath), "org.freedesktop.DBus.Introspectable"); err != nil {
- conn.Close()
- return nil, fmt.Errorf("introspection export failed: %w", err)
- }
-
- mgr := conn.Object(bluezService, dbus.ObjectPath(agentManagerPath))
- if err := mgr.Call(agentManagerIface+".RegisterAgent", 0, dbus.ObjectPath(agentPath), agentCapability).Err; err != nil {
- conn.Close()
- return nil, fmt.Errorf("agent registration failed: %w", err)
- }
-
- if err := mgr.Call(agentManagerIface+".RequestDefaultAgent", 0, dbus.ObjectPath(agentPath)).Err; err != nil {
- log.Debugf("[BluezAgent] not default agent: %v", err)
- }
-
- log.Infof("[BluezAgent] registered at %s with capability %s", agentPath, agentCapability)
- return agent, nil
-}
-
-func (a *BluezAgent) Close() {
- if a.conn == nil {
- return
- }
- mgr := a.conn.Object(bluezService, dbus.ObjectPath(agentManagerPath))
- mgr.Call(agentManagerIface+".UnregisterAgent", 0, dbus.ObjectPath(agentPath))
- a.conn.Close()
-}
-
-func (a *BluezAgent) Release() *dbus.Error {
- log.Infof("[BluezAgent] Release called")
- return nil
-}
-
-func (a *BluezAgent) RequestPinCode(device dbus.ObjectPath) (string, *dbus.Error) {
- log.Infof("[BluezAgent] RequestPinCode: device=%s", device)
-
- secrets, err := a.promptFor(device, "pin", []string{"pin"}, nil)
- if err != nil {
- log.Warnf("[BluezAgent] RequestPinCode failed: %v", err)
- return "", a.errorFrom(err)
- }
-
- pin := secrets["pin"]
- log.Infof("[BluezAgent] RequestPinCode returning PIN (len=%d)", len(pin))
- return pin, nil
-}
-
-func (a *BluezAgent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
- log.Infof("[BluezAgent] RequestPasskey: device=%s", device)
-
- secrets, err := a.promptFor(device, "passkey", []string{"passkey"}, nil)
- if err != nil {
- log.Warnf("[BluezAgent] RequestPasskey failed: %v", err)
- return 0, a.errorFrom(err)
- }
-
- passkey, err := strconv.ParseUint(secrets["passkey"], 10, 32)
- if err != nil {
- log.Warnf("[BluezAgent] invalid passkey format: %v", err)
- return 0, dbus.MakeFailedError(fmt.Errorf("invalid passkey: %w", err))
- }
-
- log.Infof("[BluezAgent] RequestPasskey returning: %d", passkey)
- return uint32(passkey), nil
-}
-
-func (a *BluezAgent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error {
- log.Infof("[BluezAgent] DisplayPinCode: device=%s, pin=%s", device, pincode)
-
- _, err := a.promptFor(device, "display-pin", []string{}, &pincode)
- if err != nil {
- log.Warnf("[BluezAgent] DisplayPinCode acknowledgment failed: %v", err)
- }
-
- return nil
-}
-
-func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error {
- log.Infof("[BluezAgent] DisplayPasskey: device=%s, passkey=%06d, entered=%d", device, passkey, entered)
-
- if entered == 0 {
- passkeyStr := strconv.FormatUint(uint64(passkey), 10)
- _, err := a.promptFor(device, "display-passkey", []string{}, &passkeyStr)
- if err != nil {
- log.Warnf("[BluezAgent] DisplayPasskey acknowledgment failed: %v", err)
- }
- }
-
- return nil
-}
-
-func (a *BluezAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
- log.Infof("[BluezAgent] RequestConfirmation: device=%s, passkey=%06d", device, passkey)
-
- passkeyStr := strconv.FormatUint(uint64(passkey), 10)
- secrets, err := a.promptFor(device, "confirm", []string{"decision"}, &passkeyStr)
- if err != nil {
- log.Warnf("[BluezAgent] RequestConfirmation failed: %v", err)
- return a.errorFrom(err)
- }
-
- if secrets["decision"] != "yes" && secrets["decision"] != "accept" {
- log.Debugf("[BluezAgent] RequestConfirmation rejected by user")
- return dbus.NewError("org.bluez.Error.Rejected", nil)
- }
-
- log.Infof("[BluezAgent] RequestConfirmation accepted")
- return nil
-}
-
-func (a *BluezAgent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error {
- log.Infof("[BluezAgent] RequestAuthorization: device=%s", device)
-
- secrets, err := a.promptFor(device, "authorize", []string{"decision"}, nil)
- if err != nil {
- log.Warnf("[BluezAgent] RequestAuthorization failed: %v", err)
- return a.errorFrom(err)
- }
-
- if secrets["decision"] != "yes" && secrets["decision"] != "accept" {
- log.Debugf("[BluezAgent] RequestAuthorization rejected by user")
- return dbus.NewError("org.bluez.Error.Rejected", nil)
- }
-
- log.Infof("[BluezAgent] RequestAuthorization accepted")
- return nil
-}
-
-func (a *BluezAgent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error {
- log.Infof("[BluezAgent] AuthorizeService: device=%s, uuid=%s", device, uuid)
-
- secrets, err := a.promptFor(device, "authorize-service:"+uuid, []string{"decision"}, nil)
- if err != nil {
- log.Warnf("[BluezAgent] AuthorizeService failed: %v", err)
- return a.errorFrom(err)
- }
-
- if secrets["decision"] != "yes" && secrets["decision"] != "accept" {
- log.Debugf("[BluezAgent] AuthorizeService rejected by user")
- return dbus.NewError("org.bluez.Error.Rejected", nil)
- }
-
- log.Infof("[BluezAgent] AuthorizeService accepted")
- return nil
-}
-
-func (a *BluezAgent) Cancel() *dbus.Error {
- log.Infof("[BluezAgent] Cancel called")
- return nil
-}
-
-func (a *BluezAgent) Introspect() (string, *dbus.Error) {
- return introspectXML, nil
-}
-
-func (a *BluezAgent) promptFor(device dbus.ObjectPath, requestType string, fields []string, displayValue *string) (map[string]string, error) {
- if a.broker == nil {
- return nil, fmt.Errorf("broker not initialized")
- }
-
- deviceName, deviceAddr := a.getDeviceInfo(device)
- hints := []string{}
- if displayValue != nil {
- hints = append(hints, *displayValue)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
- defer cancel()
-
- var passkey *uint32
- if requestType == "confirm" || requestType == "display-passkey" {
- if displayValue != nil {
- if pk, err := strconv.ParseUint(*displayValue, 10, 32); err == nil {
- pk32 := uint32(pk)
- passkey = &pk32
- }
- }
- }
-
- token, err := a.broker.Ask(ctx, PromptRequest{
- DevicePath: string(device),
- DeviceName: deviceName,
- DeviceAddr: deviceAddr,
- RequestType: requestType,
- Fields: fields,
- Hints: hints,
- Passkey: passkey,
- })
- if err != nil {
- return nil, fmt.Errorf("prompt creation failed: %w", err)
- }
-
- log.Infof("[BluezAgent] waiting for user response (token=%s)", token)
- reply, err := a.broker.Wait(ctx, token)
- if err != nil {
- if errors.Is(err, errdefs.ErrSecretPromptTimeout) {
- return nil, err
- }
- if reply.Cancel || errors.Is(err, errdefs.ErrSecretPromptCancelled) {
- return nil, errdefs.ErrSecretPromptCancelled
- }
- return nil, err
- }
-
- if !reply.Accept && len(fields) > 0 {
- return nil, errdefs.ErrSecretPromptCancelled
- }
-
- return reply.Secrets, nil
-}
-
-func (a *BluezAgent) getDeviceInfo(device dbus.ObjectPath) (string, string) {
- obj := a.conn.Object(bluezService, device)
-
- var name, alias, addr string
-
- nameVar, err := obj.GetProperty(device1Iface + ".Name")
- if err == nil {
- if n, ok := nameVar.Value().(string); ok {
- name = n
- }
- }
-
- aliasVar, err := obj.GetProperty(device1Iface + ".Alias")
- if err == nil {
- if a, ok := aliasVar.Value().(string); ok {
- alias = a
- }
- }
-
- addrVar, err := obj.GetProperty(device1Iface + ".Address")
- if err == nil {
- if a, ok := addrVar.Value().(string); ok {
- addr = a
- }
- }
-
- if alias != "" {
- return alias, addr
- }
- if name != "" {
- return name, addr
- }
- return addr, addr
-}
-
-func (a *BluezAgent) errorFrom(err error) *dbus.Error {
- if errors.Is(err, errdefs.ErrSecretPromptTimeout) {
- return dbus.NewError("org.bluez.Error.Canceled", nil)
- }
- if errors.Is(err, errdefs.ErrSecretPromptCancelled) {
- return dbus.NewError("org.bluez.Error.Canceled", nil)
- }
- return dbus.MakeFailedError(err)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker.go
deleted file mode 100644
index f817707..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package bluez
-
-import (
- "context"
- "crypto/rand"
- "encoding/hex"
-)
-
-type PromptBroker interface {
- Ask(ctx context.Context, req PromptRequest) (token string, err error)
- Wait(ctx context.Context, token string) (PromptReply, error)
- Resolve(token string, reply PromptReply) error
-}
-
-func generateToken() (string, error) {
- bytes := make([]byte, 16)
- if _, err := rand.Read(bytes); err != nil {
- return "", err
- }
- return hex.EncodeToString(bytes), nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker_test.go
deleted file mode 100644
index 9eebe61..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/broker_test.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package bluez
-
-import (
- "context"
- "testing"
- "time"
-)
-
-func TestSubscriptionBrokerAskWait(t *testing.T) {
- promptReceived := false
- broker := NewSubscriptionBroker(func(p PairingPrompt) {
- promptReceived = true
- if p.Token == "" {
- t.Error("expected token to be non-empty")
- }
- if p.DeviceName != "TestDevice" {
- t.Errorf("expected DeviceName=TestDevice, got %s", p.DeviceName)
- }
- })
-
- ctx := context.Background()
- req := PromptRequest{
- DevicePath: "/org/bluez/test",
- DeviceName: "TestDevice",
- DeviceAddr: "AA:BB:CC:DD:EE:FF",
- RequestType: "pin",
- Fields: []string{"pin"},
- }
-
- token, err := broker.Ask(ctx, req)
- if err != nil {
- t.Fatalf("Ask failed: %v", err)
- }
-
- if token == "" {
- t.Fatal("expected non-empty token")
- }
-
- if !promptReceived {
- t.Fatal("expected prompt broadcast to be called")
- }
-
- go func() {
- time.Sleep(50 * time.Millisecond)
- broker.Resolve(token, PromptReply{
- Secrets: map[string]string{"pin": "1234"},
- Accept: true,
- })
- }()
-
- reply, err := broker.Wait(ctx, token)
- if err != nil {
- t.Fatalf("Wait failed: %v", err)
- }
-
- if reply.Secrets["pin"] != "1234" {
- t.Errorf("expected pin=1234, got %s", reply.Secrets["pin"])
- }
-
- if !reply.Accept {
- t.Error("expected Accept=true")
- }
-}
-
-func TestSubscriptionBrokerTimeout(t *testing.T) {
- broker := NewSubscriptionBroker(nil)
-
- ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
- defer cancel()
-
- req := PromptRequest{
- DevicePath: "/org/bluez/test",
- DeviceName: "TestDevice",
- RequestType: "passkey",
- Fields: []string{"passkey"},
- }
-
- token, err := broker.Ask(ctx, req)
- if err != nil {
- t.Fatalf("Ask failed: %v", err)
- }
-
- _, err = broker.Wait(ctx, token)
- if err == nil {
- t.Fatal("expected timeout error")
- }
-}
-
-func TestSubscriptionBrokerCancel(t *testing.T) {
- broker := NewSubscriptionBroker(nil)
-
- ctx := context.Background()
- req := PromptRequest{
- DevicePath: "/org/bluez/test",
- DeviceName: "TestDevice",
- RequestType: "confirm",
- Fields: []string{"decision"},
- }
-
- token, err := broker.Ask(ctx, req)
- if err != nil {
- t.Fatalf("Ask failed: %v", err)
- }
-
- go func() {
- time.Sleep(50 * time.Millisecond)
- broker.Resolve(token, PromptReply{
- Cancel: true,
- })
- }()
-
- _, err = broker.Wait(ctx, token)
- if err == nil {
- t.Fatal("expected cancelled error")
- }
-}
-
-func TestSubscriptionBrokerUnknownToken(t *testing.T) {
- broker := NewSubscriptionBroker(nil)
-
- ctx := context.Background()
- _, err := broker.Wait(ctx, "invalid-token")
- if err == nil {
- t.Fatal("expected error for unknown token")
- }
-}
-
-func TestGenerateToken(t *testing.T) {
- token1, err := generateToken()
- if err != nil {
- t.Fatalf("generateToken failed: %v", err)
- }
-
- token2, err := generateToken()
- if err != nil {
- t.Fatalf("generateToken failed: %v", err)
- }
-
- if token1 == token2 {
- t.Error("expected unique tokens")
- }
-
- if len(token1) != 32 {
- t.Errorf("expected token length 32, got %d", len(token1))
- }
-}
-
-func TestSubscriptionBrokerResolveUnknownToken(t *testing.T) {
- broker := NewSubscriptionBroker(nil)
-
- err := broker.Resolve("unknown-token", PromptReply{
- Secrets: map[string]string{"test": "value"},
- })
- if err == nil {
- t.Fatal("expected error for unknown token")
- }
-}
-
-func TestSubscriptionBrokerMultipleRequests(t *testing.T) {
- broker := NewSubscriptionBroker(nil)
- ctx := context.Background()
-
- req1 := PromptRequest{
- DevicePath: "/org/bluez/test1",
- DeviceName: "Device1",
- RequestType: "pin",
- Fields: []string{"pin"},
- }
-
- req2 := PromptRequest{
- DevicePath: "/org/bluez/test2",
- DeviceName: "Device2",
- RequestType: "passkey",
- Fields: []string{"passkey"},
- }
-
- token1, err := broker.Ask(ctx, req1)
- if err != nil {
- t.Fatalf("Ask1 failed: %v", err)
- }
-
- token2, err := broker.Ask(ctx, req2)
- if err != nil {
- t.Fatalf("Ask2 failed: %v", err)
- }
-
- if token1 == token2 {
- t.Error("expected different tokens")
- }
-
- go func() {
- time.Sleep(50 * time.Millisecond)
- broker.Resolve(token1, PromptReply{
- Secrets: map[string]string{"pin": "1234"},
- Accept: true,
- })
- broker.Resolve(token2, PromptReply{
- Secrets: map[string]string{"passkey": "567890"},
- Accept: true,
- })
- }()
-
- reply1, err := broker.Wait(ctx, token1)
- if err != nil {
- t.Fatalf("Wait1 failed: %v", err)
- }
-
- reply2, err := broker.Wait(ctx, token2)
- if err != nil {
- t.Fatalf("Wait2 failed: %v", err)
- }
-
- if reply1.Secrets["pin"] != "1234" {
- t.Errorf("expected pin=1234, got %s", reply1.Secrets["pin"])
- }
-
- if reply2.Secrets["passkey"] != "567890" {
- t.Errorf("expected passkey=567890, got %s", reply2.Secrets["passkey"])
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers.go
deleted file mode 100644
index be5a1c4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package bluez
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-type BluetoothEvent struct {
- Type string `json:"type"`
- Data BluetoothState `json:"data"`
-}
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "bluetooth.getState":
- handleGetState(conn, req, manager)
- case "bluetooth.startDiscovery":
- handleStartDiscovery(conn, req, manager)
- case "bluetooth.stopDiscovery":
- handleStopDiscovery(conn, req, manager)
- case "bluetooth.setPowered":
- handleSetPowered(conn, req, manager)
- case "bluetooth.pair":
- handlePairDevice(conn, req, manager)
- case "bluetooth.connect":
- handleConnectDevice(conn, req, manager)
- case "bluetooth.disconnect":
- handleDisconnectDevice(conn, req, manager)
- case "bluetooth.remove":
- handleRemoveDevice(conn, req, manager)
- case "bluetooth.trust":
- handleTrustDevice(conn, req, manager)
- case "bluetooth.untrust":
- handleUntrustDevice(conn, req, manager)
- case "bluetooth.subscribe":
- handleSubscribe(conn, req, manager)
- case "bluetooth.pairing.submit":
- handlePairingSubmit(conn, req, manager)
- case "bluetooth.pairing.cancel":
- handlePairingCancel(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleStartDiscovery(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.StartDiscovery(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "discovery started"})
-}
-
-func handleStopDiscovery(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.StopDiscovery(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "discovery stopped"})
-}
-
-func handleSetPowered(conn net.Conn, req models.Request, manager *Manager) {
- powered, err := params.Bool(req.Params, "powered")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetPowered(powered); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "powered state updated"})
-}
-
-func handlePairDevice(conn net.Conn, req models.Request, manager *Manager) {
- devicePath, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.PairDevice(devicePath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing initiated"})
-}
-
-func handleConnectDevice(conn net.Conn, req models.Request, manager *Manager) {
- devicePath, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.ConnectDevice(devicePath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
-}
-
-func handleDisconnectDevice(conn net.Conn, req models.Request, manager *Manager) {
- devicePath, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.DisconnectDevice(devicePath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"})
-}
-
-func handleRemoveDevice(conn net.Conn, req models.Request, manager *Manager) {
- devicePath, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.RemoveDevice(devicePath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device removed"})
-}
-
-func handleTrustDevice(conn net.Conn, req models.Request, manager *Manager) {
- devicePath, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.TrustDevice(devicePath, true); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device trusted"})
-}
-
-func handleUntrustDevice(conn net.Conn, req models.Request, manager *Manager) {
- devicePath, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.TrustDevice(devicePath, false); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device untrusted"})
-}
-
-func handlePairingSubmit(conn net.Conn, req models.Request, manager *Manager) {
- token, err := params.String(req.Params, "token")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- secrets := params.StringMapOpt(req.Params, "secrets")
- accept := params.BoolOpt(req.Params, "accept", false)
-
- if err := manager.SubmitPairing(token, secrets, accept); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing response submitted"})
-}
-
-func handlePairingCancel(conn net.Conn, req models.Request, manager *Manager) {
- token, err := params.String(req.Params, "token")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.CancelPairing(token); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing cancelled"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- event := BluetoothEvent{
- Type: "state_changed",
- Data: initialState,
- }
-
- if err := json.NewEncoder(conn).Encode(models.Response[BluetoothEvent]{
- ID: req.ID,
- Result: &event,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- event := BluetoothEvent{
- Type: "state_changed",
- Data: state,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[BluetoothEvent]{
- Result: &event,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers_test.go
deleted file mode 100644
index 1846eb0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/handlers_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package bluez
-
-import (
- "context"
- "testing"
- "time"
-)
-
-func TestBrokerIntegration(t *testing.T) {
- broker := NewSubscriptionBroker(nil)
- ctx := context.Background()
-
- req := PromptRequest{
- DevicePath: "/org/bluez/test",
- DeviceName: "TestDevice",
- RequestType: "pin",
- Fields: []string{"pin"},
- }
-
- token, err := broker.Ask(ctx, req)
- if err != nil {
- t.Fatalf("Ask failed: %v", err)
- }
-
- go func() {
- time.Sleep(50 * time.Millisecond)
- broker.Resolve(token, PromptReply{
- Secrets: map[string]string{"pin": "1234"},
- Accept: true,
- })
- }()
-
- reply, err := broker.Wait(ctx, token)
- if err != nil {
- t.Fatalf("Wait failed: %v", err)
- }
-
- if reply.Secrets["pin"] != "1234" {
- t.Errorf("expected pin=1234, got %s", reply.Secrets["pin"])
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/manager.go
deleted file mode 100644
index 4408d25..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/manager.go
+++ /dev/null
@@ -1,588 +0,0 @@
-package bluez
-
-import (
- "fmt"
- "strings"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- adapter1Iface = "org.bluez.Adapter1"
- objectMgrIface = "org.freedesktop.DBus.ObjectManager"
- propertiesIface = "org.freedesktop.DBus.Properties"
-)
-
-func NewManager() (*Manager, error) {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("system bus connection failed: %w", err)
- }
-
- m := &Manager{
- state: &BluetoothState{
- Powered: false,
- Discovering: false,
- Devices: []Device{},
- PairedDevices: []Device{},
- ConnectedDevices: []Device{},
- },
- stateMutex: sync.RWMutex{},
-
- stopChan: make(chan struct{}),
- dbusConn: conn,
- signals: make(chan *dbus.Signal, 256),
- dirty: make(chan struct{}, 1),
- eventQueue: make(chan func(), 32),
- }
-
- broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
- m.promptBroker = broker
-
- adapter, err := m.findAdapter()
- if err != nil {
- conn.Close()
- return nil, fmt.Errorf("no bluetooth adapter found: %w", err)
- }
- m.adapterPath = adapter
-
- if err := m.initialize(); err != nil {
- conn.Close()
- return nil, err
- }
-
- if err := m.startAgent(); err != nil {
- conn.Close()
- return nil, fmt.Errorf("agent start failed: %w", err)
- }
-
- if err := m.startSignalPump(); err != nil {
- m.Close()
- return nil, err
- }
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- m.eventWg.Add(1)
- go m.eventWorker()
-
- return m, nil
-}
-
-func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
- obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/"))
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
-
- if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
- return "", err
- }
-
- for path, interfaces := range objects {
- if _, ok := interfaces[adapter1Iface]; ok {
- log.Infof("[BluezManager] found adapter: %s", path)
- return path, nil
- }
- }
-
- return "", fmt.Errorf("no adapter found")
-}
-
-func (m *Manager) initialize() error {
- if err := m.updateAdapterState(); err != nil {
- return err
- }
-
- if err := m.updateDevices(); err != nil {
- return err
- }
-
- return nil
-}
-
-func (m *Manager) updateAdapterState() error {
- obj := m.dbusConn.Object(bluezService, m.adapterPath)
-
- poweredVar, err := obj.GetProperty(adapter1Iface + ".Powered")
- if err != nil {
- return err
- }
-
- discoveringVar, err := obj.GetProperty(adapter1Iface + ".Discovering")
- if err != nil {
- return err
- }
-
- m.stateMutex.Lock()
- m.state.Powered = dbusutil.AsOr(poweredVar, false)
- m.state.Discovering = dbusutil.AsOr(discoveringVar, false)
- m.stateMutex.Unlock()
-
- return nil
-}
-
-func (m *Manager) updateDevices() error {
- obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/"))
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
-
- if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
- return err
- }
-
- devices := []Device{}
- paired := []Device{}
- connected := []Device{}
-
- for path, interfaces := range objects {
- devProps, ok := interfaces[device1Iface]
- if !ok {
- continue
- }
-
- if !strings.HasPrefix(string(path), string(m.adapterPath)+"/") {
- continue
- }
-
- dev := m.deviceFromProps(string(path), devProps)
- devices = append(devices, dev)
-
- if dev.Paired {
- paired = append(paired, dev)
- }
- if dev.Connected {
- connected = append(connected, dev)
- }
- }
-
- m.stateMutex.Lock()
- m.state.Devices = devices
- m.state.PairedDevices = paired
- m.state.ConnectedDevices = connected
- m.stateMutex.Unlock()
-
- return nil
-}
-
-func (m *Manager) deviceFromProps(path string, props map[string]dbus.Variant) Device {
- return Device{
- Path: path,
- Address: dbusutil.GetOr(props, "Address", ""),
- Name: dbusutil.GetOr(props, "Name", ""),
- Alias: dbusutil.GetOr(props, "Alias", ""),
- Paired: dbusutil.GetOr(props, "Paired", false),
- Trusted: dbusutil.GetOr(props, "Trusted", false),
- Blocked: dbusutil.GetOr(props, "Blocked", false),
- Connected: dbusutil.GetOr(props, "Connected", false),
- Class: dbusutil.GetOr(props, "Class", uint32(0)),
- Icon: dbusutil.GetOr(props, "Icon", ""),
- RSSI: dbusutil.GetOr(props, "RSSI", int16(0)),
- LegacyPairing: dbusutil.GetOr(props, "LegacyPairing", false),
- }
-}
-
-func (m *Manager) startAgent() error {
- if m.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- agent, err := NewBluezAgent(m.promptBroker)
- if err != nil {
- return err
- }
-
- m.agent = agent
- return nil
-}
-
-func (m *Manager) startSignalPump() error {
- m.dbusConn.Signal(m.signals)
-
- if err := m.dbusConn.AddMatchSignal(
- dbus.WithMatchInterface(propertiesIface),
- dbus.WithMatchMember("PropertiesChanged"),
- ); err != nil {
- return err
- }
-
- if err := m.dbusConn.AddMatchSignal(
- dbus.WithMatchInterface(objectMgrIface),
- dbus.WithMatchMember("InterfacesAdded"),
- ); err != nil {
- return err
- }
-
- if err := m.dbusConn.AddMatchSignal(
- dbus.WithMatchInterface(objectMgrIface),
- dbus.WithMatchMember("InterfacesRemoved"),
- ); err != nil {
- return err
- }
-
- m.sigWG.Add(1)
- go func() {
- defer m.sigWG.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case sig, ok := <-m.signals:
- if !ok {
- return
- }
- if sig == nil {
- continue
- }
- m.handleSignal(sig)
- }
- }
- }()
-
- return nil
-}
-
-func (m *Manager) handleSignal(sig *dbus.Signal) {
- switch sig.Name {
- case propertiesIface + ".PropertiesChanged":
- if len(sig.Body) < 2 {
- return
- }
-
- iface, ok := sig.Body[0].(string)
- if !ok {
- return
- }
-
- changed, ok := sig.Body[1].(map[string]dbus.Variant)
- if !ok {
- return
- }
-
- switch iface {
- case adapter1Iface:
- if strings.HasPrefix(string(sig.Path), string(m.adapterPath)) {
- m.handleAdapterPropertiesChanged(changed)
- }
- case device1Iface:
- m.handleDevicePropertiesChanged(sig.Path, changed)
- }
-
- case objectMgrIface + ".InterfacesAdded":
- m.notifySubscribers()
-
- case objectMgrIface + ".InterfacesRemoved":
- m.notifySubscribers()
- }
-}
-
-func (m *Manager) handleAdapterPropertiesChanged(changed map[string]dbus.Variant) {
- m.stateMutex.Lock()
- dirty := false
-
- if powered, ok := dbusutil.Get[bool](changed, "Powered"); ok {
- m.state.Powered = powered
- dirty = true
- }
- if discovering, ok := dbusutil.Get[bool](changed, "Discovering"); ok {
- m.state.Discovering = discovering
- dirty = true
- }
-
- m.stateMutex.Unlock()
-
- if dirty {
- m.notifySubscribers()
- }
-}
-
-func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed map[string]dbus.Variant) {
- paired, hasPaired := dbusutil.Get[bool](changed, "Paired")
- _, hasConnected := changed["Connected"]
- _, hasTrusted := changed["Trusted"]
-
- if hasPaired {
- devicePath := string(path)
- if paired {
- _, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
- if wasPending {
- select {
- case m.eventQueue <- func() {
- time.Sleep(300 * time.Millisecond)
- log.Infof("[Bluetooth] Auto-trusting newly paired device: %s", devicePath)
- if err := m.TrustDevice(devicePath, true); err != nil {
- log.Warnf("[Bluetooth] Auto-trust failed: %v", err)
- }
- log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
- if err := m.ConnectDevice(devicePath); err != nil {
- log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
- }
- }:
- default:
- }
- }
- } else {
- m.pendingPairings.Delete(devicePath)
- }
- }
-
- if hasPaired || hasConnected || hasTrusted {
- select {
- case m.eventQueue <- func() {
- time.Sleep(100 * time.Millisecond)
- m.updateDevices()
- m.notifySubscribers()
- }:
- default:
- }
- }
-}
-
-func (m *Manager) eventWorker() {
- defer m.eventWg.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case event := <-m.eventQueue:
- event()
- }
- }
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 200 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
- m.updateDevices()
-
- currentState := m.snapshotState()
-
- if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan BluetoothState) bool {
- select {
- case ch <- currentState:
- default:
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotifiedState = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) GetState() BluetoothState {
- return m.snapshotState()
-}
-
-func (m *Manager) snapshotState() BluetoothState {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
-
- s := *m.state
- s.Devices = append([]Device(nil), m.state.Devices...)
- s.PairedDevices = append([]Device(nil), m.state.PairedDevices...)
- s.ConnectedDevices = append([]Device(nil), m.state.ConnectedDevices...)
- return s
-}
-
-func (m *Manager) Subscribe(id string) chan BluetoothState {
- ch := make(chan BluetoothState, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if ch, ok := m.subscribers.LoadAndDelete(id); ok {
- close(ch)
- }
-}
-
-func (m *Manager) SubscribePairing(id string) chan PairingPrompt {
- ch := make(chan PairingPrompt, 16)
- m.pairingSubscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) UnsubscribePairing(id string) {
- if ch, ok := m.pairingSubscribers.LoadAndDelete(id); ok {
- close(ch)
- }
-}
-
-func (m *Manager) broadcastPairingPrompt(prompt PairingPrompt) {
- m.pairingSubscribers.Range(func(key string, ch chan PairingPrompt) bool {
- select {
- case ch <- prompt:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) SubmitPairing(token string, secrets map[string]string, accept bool) error {
- if m.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- return m.promptBroker.Resolve(token, PromptReply{
- Secrets: secrets,
- Accept: accept,
- Cancel: false,
- })
-}
-
-func (m *Manager) CancelPairing(token string) error {
- if m.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- return m.promptBroker.Resolve(token, PromptReply{
- Cancel: true,
- })
-}
-
-func (m *Manager) StartDiscovery() error {
- obj := m.dbusConn.Object(bluezService, m.adapterPath)
- return obj.Call(adapter1Iface+".StartDiscovery", 0).Err
-}
-
-func (m *Manager) StopDiscovery() error {
- obj := m.dbusConn.Object(bluezService, m.adapterPath)
- return obj.Call(adapter1Iface+".StopDiscovery", 0).Err
-}
-
-func (m *Manager) SetPowered(powered bool) error {
- obj := m.dbusConn.Object(bluezService, m.adapterPath)
- return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err
-}
-
-func (m *Manager) PairDevice(devicePath string) error {
- m.pendingPairings.Store(devicePath, true)
-
- obj := m.dbusConn.Object(bluezService, dbus.ObjectPath(devicePath))
- err := obj.Call(device1Iface+".Pair", 0).Err
-
- if err != nil {
- m.pendingPairings.Delete(devicePath)
- }
-
- return err
-}
-
-func (m *Manager) ConnectDevice(devicePath string) error {
- obj := m.dbusConn.Object(bluezService, dbus.ObjectPath(devicePath))
- return obj.Call(device1Iface+".Connect", 0).Err
-}
-
-func (m *Manager) DisconnectDevice(devicePath string) error {
- obj := m.dbusConn.Object(bluezService, dbus.ObjectPath(devicePath))
- return obj.Call(device1Iface+".Disconnect", 0).Err
-}
-
-func (m *Manager) RemoveDevice(devicePath string) error {
- obj := m.dbusConn.Object(bluezService, m.adapterPath)
- return obj.Call(adapter1Iface+".RemoveDevice", 0, dbus.ObjectPath(devicePath)).Err
-}
-
-func (m *Manager) TrustDevice(devicePath string, trusted bool) error {
- obj := m.dbusConn.Object(bluezService, dbus.ObjectPath(devicePath))
- return obj.Call(propertiesIface+".Set", 0, device1Iface, "Trusted", dbus.MakeVariant(trusted)).Err
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.notifierWg.Wait()
- m.eventWg.Wait()
-
- m.sigWG.Wait()
-
- if m.signals != nil {
- m.dbusConn.RemoveSignal(m.signals)
- close(m.signals)
- }
-
- if m.agent != nil {
- m.agent.Close()
- }
-
- m.subscribers.Range(func(key string, ch chan BluetoothState) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.pairingSubscribers.Range(func(key string, ch chan PairingPrompt) bool {
- close(ch)
- m.pairingSubscribers.Delete(key)
- return true
- })
-
- if m.dbusConn != nil {
- m.dbusConn.Close()
- }
-}
-
-func stateChanged(old, new *BluetoothState) bool {
- if old.Powered != new.Powered {
- return true
- }
- if old.Discovering != new.Discovering {
- return true
- }
- if len(old.Devices) != len(new.Devices) {
- return true
- }
- if len(old.PairedDevices) != len(new.PairedDevices) {
- return true
- }
- if len(old.ConnectedDevices) != len(new.ConnectedDevices) {
- return true
- }
- for i := range old.Devices {
- if old.Devices[i].Path != new.Devices[i].Path {
- return true
- }
- if old.Devices[i].Paired != new.Devices[i].Paired {
- return true
- }
- if old.Devices[i].Connected != new.Devices[i].Connected {
- return true
- }
- }
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/subscription_broker.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/subscription_broker.go
deleted file mode 100644
index ea04a19..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/subscription_broker.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package bluez
-
-import (
- "context"
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type SubscriptionBroker struct {
- pending syncmap.Map[string, chan PromptReply]
- requests syncmap.Map[string, PromptRequest]
- broadcastPrompt func(PairingPrompt)
-}
-
-func NewSubscriptionBroker(broadcastPrompt func(PairingPrompt)) PromptBroker {
- return &SubscriptionBroker{
- broadcastPrompt: broadcastPrompt,
- }
-}
-
-func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string, error) {
- token, err := generateToken()
- if err != nil {
- return "", err
- }
-
- replyChan := make(chan PromptReply, 1)
- b.pending.Store(token, replyChan)
- b.requests.Store(token, req)
-
- if b.broadcastPrompt != nil {
- prompt := PairingPrompt{
- Token: token,
- DevicePath: req.DevicePath,
- DeviceName: req.DeviceName,
- DeviceAddr: req.DeviceAddr,
- RequestType: req.RequestType,
- Fields: req.Fields,
- Hints: req.Hints,
- Passkey: req.Passkey,
- }
- b.broadcastPrompt(prompt)
- }
-
- return token, nil
-}
-
-func (b *SubscriptionBroker) Wait(ctx context.Context, token string) (PromptReply, error) {
- replyChan, exists := b.pending.Load(token)
- if !exists {
- return PromptReply{}, fmt.Errorf("unknown token: %s", token)
- }
-
- select {
- case <-ctx.Done():
- b.cleanup(token)
- return PromptReply{}, errdefs.ErrSecretPromptTimeout
- case reply := <-replyChan:
- b.cleanup(token)
- if reply.Cancel {
- return reply, errdefs.ErrSecretPromptCancelled
- }
- return reply, nil
- }
-}
-
-func (b *SubscriptionBroker) Resolve(token string, reply PromptReply) error {
- replyChan, exists := b.pending.Load(token)
- if !exists {
- return fmt.Errorf("unknown or expired token: %s", token)
- }
-
- select {
- case replyChan <- reply:
- return nil
- default:
- return fmt.Errorf("failed to deliver reply for token: %s", token)
- }
-}
-
-func (b *SubscriptionBroker) cleanup(token string) {
- b.pending.Delete(token)
- b.requests.Delete(token)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types.go
deleted file mode 100644
index 663acca..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package bluez
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/godbus/dbus/v5"
-)
-
-type BluetoothState struct {
- Powered bool `json:"powered"`
- Discovering bool `json:"discovering"`
- Devices []Device `json:"devices"`
- PairedDevices []Device `json:"pairedDevices"`
- ConnectedDevices []Device `json:"connectedDevices"`
-}
-
-type Device struct {
- Path string `json:"path"`
- Address string `json:"address"`
- Name string `json:"name"`
- Alias string `json:"alias"`
- Paired bool `json:"paired"`
- Trusted bool `json:"trusted"`
- Blocked bool `json:"blocked"`
- Connected bool `json:"connected"`
- Class uint32 `json:"class"`
- Icon string `json:"icon"`
- RSSI int16 `json:"rssi"`
- LegacyPairing bool `json:"legacyPairing"`
-}
-
-type PromptRequest struct {
- DevicePath string `json:"devicePath"`
- DeviceName string `json:"deviceName"`
- DeviceAddr string `json:"deviceAddr"`
- RequestType string `json:"requestType"`
- Fields []string `json:"fields"`
- Hints []string `json:"hints"`
- Passkey *uint32 `json:"passkey,omitempty"`
-}
-
-type PromptReply struct {
- Secrets map[string]string `json:"secrets"`
- Accept bool `json:"accept"`
- Cancel bool `json:"cancel"`
-}
-
-type PairingPrompt struct {
- Token string `json:"token"`
- DevicePath string `json:"devicePath"`
- DeviceName string `json:"deviceName"`
- DeviceAddr string `json:"deviceAddr"`
- RequestType string `json:"requestType"`
- Fields []string `json:"fields"`
- Hints []string `json:"hints"`
- Passkey *uint32 `json:"passkey,omitempty"`
-}
-
-type Manager struct {
- state *BluetoothState
- stateMutex sync.RWMutex
- subscribers syncmap.Map[string, chan BluetoothState]
- stopChan chan struct{}
- dbusConn *dbus.Conn
- signals chan *dbus.Signal
- sigWG sync.WaitGroup
- agent *BluezAgent
- promptBroker PromptBroker
- pairingSubscribers syncmap.Map[string, chan PairingPrompt]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotifiedState *BluetoothState
- adapterPath dbus.ObjectPath
- pendingPairings syncmap.Map[string, bool]
- eventQueue chan func()
- eventWg sync.WaitGroup
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types_test.go
deleted file mode 100644
index ab5b857..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/bluez/types_test.go
+++ /dev/null
@@ -1,210 +0,0 @@
-package bluez
-
-import (
- "encoding/json"
- "testing"
-)
-
-func TestBluetoothStateJSON(t *testing.T) {
- state := BluetoothState{
- Powered: true,
- Discovering: false,
- Devices: []Device{
- {
- Path: "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
- Address: "AA:BB:CC:DD:EE:FF",
- Name: "TestDevice",
- Alias: "My Device",
- Paired: true,
- Trusted: false,
- Connected: true,
- Class: 0x240418,
- Icon: "audio-headset",
- RSSI: -50,
- },
- },
- PairedDevices: []Device{
- {
- Path: "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
- Address: "AA:BB:CC:DD:EE:FF",
- Paired: true,
- },
- },
- ConnectedDevices: []Device{
- {
- Path: "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
- Address: "AA:BB:CC:DD:EE:FF",
- Connected: true,
- },
- },
- }
-
- data, err := json.Marshal(state)
- if err != nil {
- t.Fatalf("failed to marshal state: %v", err)
- }
-
- var decoded BluetoothState
- if err := json.Unmarshal(data, &decoded); err != nil {
- t.Fatalf("failed to unmarshal state: %v", err)
- }
-
- if decoded.Powered != state.Powered {
- t.Errorf("expected Powered=%v, got %v", state.Powered, decoded.Powered)
- }
-
- if len(decoded.Devices) != 1 {
- t.Fatalf("expected 1 device, got %d", len(decoded.Devices))
- }
-
- if decoded.Devices[0].Address != "AA:BB:CC:DD:EE:FF" {
- t.Errorf("expected address AA:BB:CC:DD:EE:FF, got %s", decoded.Devices[0].Address)
- }
-}
-
-func TestDeviceJSON(t *testing.T) {
- device := Device{
- Path: "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
- Address: "AA:BB:CC:DD:EE:FF",
- Name: "TestDevice",
- Alias: "My Device",
- Paired: true,
- Trusted: true,
- Blocked: false,
- Connected: true,
- Class: 0x240418,
- Icon: "audio-headset",
- RSSI: -50,
- LegacyPairing: false,
- }
-
- data, err := json.Marshal(device)
- if err != nil {
- t.Fatalf("failed to marshal device: %v", err)
- }
-
- var decoded Device
- if err := json.Unmarshal(data, &decoded); err != nil {
- t.Fatalf("failed to unmarshal device: %v", err)
- }
-
- if decoded.Address != device.Address {
- t.Errorf("expected Address=%s, got %s", device.Address, decoded.Address)
- }
-
- if decoded.Name != device.Name {
- t.Errorf("expected Name=%s, got %s", device.Name, decoded.Name)
- }
-
- if decoded.Paired != device.Paired {
- t.Errorf("expected Paired=%v, got %v", device.Paired, decoded.Paired)
- }
-
- if decoded.RSSI != device.RSSI {
- t.Errorf("expected RSSI=%d, got %d", device.RSSI, decoded.RSSI)
- }
-}
-
-func TestPairingPromptJSON(t *testing.T) {
- passkey := uint32(123456)
- prompt := PairingPrompt{
- Token: "test-token",
- DevicePath: "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
- DeviceName: "TestDevice",
- DeviceAddr: "AA:BB:CC:DD:EE:FF",
- RequestType: "confirm",
- Fields: []string{"decision"},
- Hints: []string{},
- Passkey: &passkey,
- }
-
- data, err := json.Marshal(prompt)
- if err != nil {
- t.Fatalf("failed to marshal prompt: %v", err)
- }
-
- var decoded PairingPrompt
- if err := json.Unmarshal(data, &decoded); err != nil {
- t.Fatalf("failed to unmarshal prompt: %v", err)
- }
-
- if decoded.Token != prompt.Token {
- t.Errorf("expected Token=%s, got %s", prompt.Token, decoded.Token)
- }
-
- if decoded.DeviceName != prompt.DeviceName {
- t.Errorf("expected DeviceName=%s, got %s", prompt.DeviceName, decoded.DeviceName)
- }
-
- if decoded.Passkey == nil {
- t.Fatal("expected non-nil Passkey")
- }
-
- if *decoded.Passkey != *prompt.Passkey {
- t.Errorf("expected Passkey=%d, got %d", *prompt.Passkey, *decoded.Passkey)
- }
-}
-
-func TestPromptReplyJSON(t *testing.T) {
- reply := PromptReply{
- Secrets: map[string]string{
- "pin": "1234",
- "passkey": "567890",
- },
- Accept: true,
- Cancel: false,
- }
-
- data, err := json.Marshal(reply)
- if err != nil {
- t.Fatalf("failed to marshal reply: %v", err)
- }
-
- var decoded PromptReply
- if err := json.Unmarshal(data, &decoded); err != nil {
- t.Fatalf("failed to unmarshal reply: %v", err)
- }
-
- if decoded.Secrets["pin"] != reply.Secrets["pin"] {
- t.Errorf("expected pin=%s, got %s", reply.Secrets["pin"], decoded.Secrets["pin"])
- }
-
- if decoded.Accept != reply.Accept {
- t.Errorf("expected Accept=%v, got %v", reply.Accept, decoded.Accept)
- }
-}
-
-func TestPromptRequestJSON(t *testing.T) {
- passkey := uint32(123456)
- req := PromptRequest{
- DevicePath: "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
- DeviceName: "TestDevice",
- DeviceAddr: "AA:BB:CC:DD:EE:FF",
- RequestType: "confirm",
- Fields: []string{"decision"},
- Hints: []string{"hint1", "hint2"},
- Passkey: &passkey,
- }
-
- data, err := json.Marshal(req)
- if err != nil {
- t.Fatalf("failed to marshal request: %v", err)
- }
-
- var decoded PromptRequest
- if err := json.Unmarshal(data, &decoded); err != nil {
- t.Fatalf("failed to unmarshal request: %v", err)
- }
-
- if decoded.DevicePath != req.DevicePath {
- t.Errorf("expected DevicePath=%s, got %s", req.DevicePath, decoded.DevicePath)
- }
-
- if decoded.RequestType != req.RequestType {
- t.Errorf("expected RequestType=%s, got %s", req.RequestType, decoded.RequestType)
- }
-
- if len(decoded.Fields) != len(req.Fields) {
- t.Errorf("expected %d fields, got %d", len(req.Fields), len(decoded.Fields))
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go
deleted file mode 100644
index 00cf2d9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go
+++ /dev/null
@@ -1,510 +0,0 @@
-package brightness
-
-import (
- "encoding/binary"
- "fmt"
- "math"
- "os"
- "strings"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "golang.org/x/sys/unix"
-)
-
-const (
- I2C_SLAVE = 0x0703
- DDCCI_ADDR = 0x37
- DDCCI_VCP_GET = 0x01
- DDCCI_VCP_SET = 0x03
- VCP_BRIGHTNESS = 0x10
- DDC_SOURCE_ADDR = 0x51
-)
-
-func NewDDCBackend() (*DDCBackend, error) {
- b := &DDCBackend{
- scanInterval: 30 * time.Second,
- debounceTimers: make(map[string]*time.Timer),
- debouncePending: make(map[string]ddcPendingSet),
- }
-
- if err := b.scanI2CDevices(); err != nil {
- return nil, err
- }
-
- return b, nil
-}
-
-func (b *DDCBackend) scanI2CDevices() error {
- return b.scanI2CDevicesInternal(false)
-}
-
-func (b *DDCBackend) ForceRescan() error {
- return b.scanI2CDevicesInternal(true)
-}
-
-func (b *DDCBackend) scanI2CDevicesInternal(force bool) error {
- b.scanMutex.Lock()
- defer b.scanMutex.Unlock()
-
- if !force && time.Since(b.lastScan) < b.scanInterval {
- return nil
- }
-
- activeBuses := make(map[int]bool)
-
- for i := 0; i < 32; i++ {
- busPath := fmt.Sprintf("/dev/i2c-%d", i)
- if _, err := os.Stat(busPath); os.IsNotExist(err) {
- continue
- }
-
- if isIgnorableI2CBus(i) {
- log.Debugf("Skipping ignorable i2c-%d", i)
- continue
- }
-
- activeBuses[i] = true
- id := fmt.Sprintf("ddc:i2c-%d", i)
-
- dev, err := b.probeDDCDevice(i)
- if err != nil || dev == nil {
- continue
- }
-
- dev.id = id
- b.devices.Store(id, dev)
- log.Debugf("found DDC device on i2c-%d", i)
- }
-
- b.devices.Range(func(id string, dev *ddcDevice) bool {
- if !activeBuses[dev.bus] {
- b.devices.Delete(id)
- log.Debugf("removed DDC device %s (bus no longer exists)", id)
- }
- return true
- })
-
- b.lastScan = time.Now()
-
- return nil
-}
-
-func (b *DDCBackend) probeDDCDevice(bus int) (*ddcDevice, error) {
- busPath := fmt.Sprintf("/dev/i2c-%d", bus)
-
- fd, err := syscall.Open(busPath, syscall.O_RDWR, 0)
- if err != nil {
- return nil, err
- }
- defer syscall.Close(fd)
-
- if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(DDCCI_ADDR)); errno != 0 {
- return nil, errno
- }
-
- dummy := make([]byte, 32)
- syscall.Read(fd, dummy) //nolint:errcheck
-
- writebuf := []byte{0x00}
- n, err := syscall.Write(fd, writebuf)
- if err == nil && n == len(writebuf) {
- name := b.getDDCName(bus)
- dev := &ddcDevice{
- bus: bus,
- addr: DDCCI_ADDR,
- name: name,
- }
- b.readInitialBrightness(fd, dev)
- return dev, nil
- }
-
- readbuf := make([]byte, 4)
- n, err = syscall.Read(fd, readbuf)
- if err != nil || n == 0 {
- return nil, fmt.Errorf("x37 unresponsive")
- }
-
- name := b.getDDCName(bus)
-
- dev := &ddcDevice{
- bus: bus,
- addr: DDCCI_ADDR,
- name: name,
- }
- b.readInitialBrightness(fd, dev)
- return dev, nil
-}
-
-func (b *DDCBackend) getDDCName(bus int) string {
- sysfsPath := fmt.Sprintf("/sys/class/i2c-adapter/i2c-%d/name", bus)
- data, err := os.ReadFile(sysfsPath)
- if err != nil {
- return fmt.Sprintf("I2C-%d", bus)
- }
-
- name := strings.TrimSpace(string(data))
- if name == "" {
- name = fmt.Sprintf("I2C-%d", bus)
- }
-
- return name
-}
-
-func (b *DDCBackend) readInitialBrightness(fd int, dev *ddcDevice) {
- cap, err := b.getVCPFeature(fd, VCP_BRIGHTNESS)
- if err != nil {
- log.Debugf("failed to read initial brightness for %s: %v", dev.name, err)
- return
- }
-
- dev.max = cap.max
- dev.lastBrightness = cap.current
- log.Debugf("initialized %s with brightness %d/%d", dev.name, cap.current, cap.max)
-}
-
-func (b *DDCBackend) GetDevices() ([]Device, error) {
- if err := b.scanI2CDevices(); err != nil {
- log.Debugf("DDC scan error: %v", err)
- }
-
- devices := make([]Device, 0)
-
- b.devices.Range(func(id string, dev *ddcDevice) bool {
- devices = append(devices, Device{
- Class: ClassDDC,
- ID: id,
- Name: dev.name,
- Current: dev.lastBrightness,
- Max: dev.max,
- CurrentPercent: dev.lastBrightness,
- Backend: "ddc",
- })
- return true
- })
-
- return devices, nil
-}
-
-func (b *DDCBackend) SetBrightness(id string, value int, exponential bool, callback func()) error {
- return b.SetBrightnessWithExponent(id, value, exponential, 1.2, callback)
-}
-
-func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential bool, exponent float64, callback func()) error {
- _, ok := b.devices.Load(id)
-
- if !ok {
- if err := b.scanI2CDevicesInternal(true); err != nil {
- log.Debugf("rescan failed for %s: %v", id, err)
- }
- _, ok = b.devices.Load(id)
- }
-
- if !ok {
- return fmt.Errorf("device not found: %s", id)
- }
-
- if value < 0 {
- return fmt.Errorf("value out of range: %d", value)
- }
-
- b.debounceMutex.Lock()
- b.debouncePending[id] = ddcPendingSet{
- percent: value,
- callback: callback,
- }
-
- if timer, exists := b.debounceTimers[id]; exists {
- timer.Reset(200 * time.Millisecond)
- } else {
- b.debounceWg.Add(1)
- b.debounceTimers[id] = time.AfterFunc(200*time.Millisecond, func() {
- defer b.debounceWg.Done()
- b.debounceMutex.Lock()
- pending, exists := b.debouncePending[id]
- if exists {
- delete(b.debouncePending, id)
- }
- b.debounceMutex.Unlock()
-
- if !exists {
- return
- }
-
- err := b.setBrightnessImmediateWithExponent(id, pending.percent)
- if err != nil {
- log.Debugf("Failed to set brightness for %s: %v", id, err)
- }
-
- if pending.callback != nil {
- pending.callback()
- }
- })
- }
- b.debounceMutex.Unlock()
-
- return nil
-}
-
-func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) error {
- dev, ok := b.devices.Load(id)
-
- if !ok {
- if err := b.scanI2CDevicesInternal(true); err != nil {
- log.Debugf("rescan failed for %s: %v", id, err)
- }
- dev, ok = b.devices.Load(id)
- }
-
- if !ok {
- return fmt.Errorf("device not found: %s", id)
- }
-
- busPath := fmt.Sprintf("/dev/i2c-%d", dev.bus)
-
- if _, err := os.Stat(busPath); os.IsNotExist(err) {
- b.devices.Delete(id)
- log.Debugf("removed stale DDC device %s (bus no longer exists)", id)
- return fmt.Errorf("device disconnected: %s", id)
- }
-
- fd, err := syscall.Open(busPath, syscall.O_RDWR, 0)
- if err != nil {
- b.devices.Delete(id)
- log.Debugf("removed DDC device %s (open failed: %v)", id, err)
- return fmt.Errorf("open i2c device: %w", err)
- }
- defer syscall.Close(fd)
-
- if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(dev.addr)); errno != 0 {
- return fmt.Errorf("set i2c slave addr: %w", errno)
- }
-
- max := dev.max
- if max == 0 {
- cap, err := b.getVCPFeature(fd, VCP_BRIGHTNESS)
- if err != nil {
- return fmt.Errorf("get current capability: %w", err)
- }
- max = cap.max
- dev.max = max
- b.devices.Store(id, dev)
- }
-
- if err := b.setVCPFeature(fd, VCP_BRIGHTNESS, value); err != nil {
- return fmt.Errorf("set vcp feature: %w", err)
- }
-
- log.Debugf("set %s to %d/%d", id, value, max)
-
- dev.max = max
- dev.lastBrightness = value
- b.devices.Store(id, dev)
-
- return nil
-}
-
-func (b *DDCBackend) getVCPFeature(fd int, vcp byte) (*ddcCapability, error) {
- for flushTry := 0; flushTry < 3; flushTry++ {
- dummy := make([]byte, 32)
- n, _ := syscall.Read(fd, dummy)
- if n == 0 {
- break
- }
- time.Sleep(20 * time.Millisecond)
- }
-
- data := []byte{
- DDCCI_VCP_GET,
- vcp,
- }
-
- payload := []byte{
- DDC_SOURCE_ADDR,
- byte(len(data)) | 0x80,
- }
- payload = append(payload, data...)
- payload = append(payload, ddcciChecksum(payload))
-
- n, err := syscall.Write(fd, payload)
- if err != nil || n != len(payload) {
- return nil, fmt.Errorf("write i2c: %w", err)
- }
-
- time.Sleep(50 * time.Millisecond)
-
- pollFds := []unix.PollFd{
- {
- Fd: int32(fd),
- Events: unix.POLLIN,
- },
- }
-
- pollTimeout := 200
- pollResult, err := unix.Poll(pollFds, pollTimeout)
- if err != nil {
- return nil, fmt.Errorf("poll i2c: %w", err)
- }
- if pollResult == 0 {
- return nil, fmt.Errorf("poll timeout after %dms", pollTimeout)
- }
- if pollFds[0].Revents&unix.POLLIN == 0 {
- return nil, fmt.Errorf("poll returned but POLLIN not set")
- }
-
- response := make([]byte, 12)
- n, err = syscall.Read(fd, response)
- if err != nil || n < 8 {
- return nil, fmt.Errorf("read i2c: %w", err)
- }
-
- if response[0] != 0x6E || response[2] != 0x02 {
- return nil, fmt.Errorf("invalid ddc response")
- }
-
- resultCode := response[3]
- if resultCode != 0x00 {
- return nil, fmt.Errorf("vcp feature not supported")
- }
-
- responseVCP := response[4]
- if responseVCP != vcp {
- return nil, fmt.Errorf("vcp mismatch: wanted 0x%02x, got 0x%02x", vcp, responseVCP)
- }
-
- maxHigh := response[6]
- maxLow := response[7]
- currentHigh := response[8]
- currentLow := response[9]
-
- max := int(binary.BigEndian.Uint16([]byte{maxHigh, maxLow}))
- current := int(binary.BigEndian.Uint16([]byte{currentHigh, currentLow}))
-
- return &ddcCapability{
- vcp: vcp,
- max: max,
- current: current,
- }, nil
-}
-
-func ddcciChecksum(payload []byte) byte {
- sum := byte(0x6E)
- for _, b := range payload {
- sum ^= b
- }
- return sum
-}
-
-func (b *DDCBackend) setVCPFeature(fd int, vcp byte, value int) error {
- data := []byte{
- DDCCI_VCP_SET,
- vcp,
- byte(value >> 8),
- byte(value & 0xFF),
- }
-
- payload := []byte{
- DDC_SOURCE_ADDR,
- byte(len(data)) | 0x80,
- }
- payload = append(payload, data...)
- payload = append(payload, ddcciChecksum(payload))
-
- if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(DDCCI_ADDR)); errno != 0 {
- return fmt.Errorf("set i2c slave for write: %w", errno)
- }
-
- n, err := syscall.Write(fd, payload)
- if err != nil || n != len(payload) {
- return fmt.Errorf("write i2c: wrote %d/%d: %w", n, len(payload), err)
- }
-
- time.Sleep(50 * time.Millisecond)
-
- return nil
-}
-
-func (b *DDCBackend) percentToValue(percent int, max int, exponential bool) int {
- const minValue = 1
-
- if percent == 0 {
- return minValue
- }
-
- usableRange := max - minValue
- var value int
-
- if exponential {
- const exponent = 2.0
- normalizedPercent := float64(percent) / 100.0
- hardwarePercent := math.Pow(normalizedPercent, 1.0/exponent)
- value = minValue + int(math.Round(hardwarePercent*float64(usableRange)))
- } else {
- value = minValue + ((percent - 1) * usableRange / 99)
- }
-
- if value < minValue {
- value = minValue
- }
- if value > max {
- value = max
- }
-
- return value
-}
-
-func (b *DDCBackend) valueToPercent(value int, max int, exponential bool) int {
- const minValue = 1
-
- if max == 0 {
- return 0
- }
-
- if value <= minValue {
- return 1
- }
-
- usableRange := max - minValue
- if usableRange == 0 {
- return 100
- }
-
- var percent int
-
- if exponential {
- const exponent = 2.0
- linearPercent := 1 + ((value - minValue) * 99 / usableRange)
- normalizedLinear := float64(linearPercent) / 100.0
- expPercent := math.Pow(normalizedLinear, exponent)
- percent = int(math.Round(expPercent * 100.0))
- } else {
- percent = 1 + ((value - minValue) * 99 / usableRange)
- }
-
- if percent > 100 {
- percent = 100
- }
- if percent < 1 {
- percent = 1
- }
-
- return percent
-}
-
-func (b *DDCBackend) WaitPending() {
- done := make(chan struct{})
- go func() {
- b.debounceWg.Wait()
- close(done)
- }()
-
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- log.Debug("WaitPending timed out waiting for DDC writes")
- }
-}
-
-func (b *DDCBackend) Close() {
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go
deleted file mode 100644
index 12fc535..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package brightness
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-// isIgnorableI2CBus checks if an I2C bus should be skipped during DDC probing.
-// Based on ddcutil's sysfs_is_ignorable_i2c_device() (sysfs_base.c:1441)
-func isIgnorableI2CBus(busno int) bool {
- name := getI2CDeviceSysfsName(busno)
- driver := getI2CSysfsDriver(busno)
-
- if name != "" && isIgnorableI2CDeviceName(name, driver) {
- log.Debugf("i2c-%d: ignoring '%s' (driver: %s)", busno, name, driver)
- return true
- }
-
- // Only probe display adapters (0x03xxxx) and docking stations (0x0axxxx)
- class := getI2CDeviceSysfsClass(busno)
- if class != 0 {
- classHigh := class & 0xFFFF0000
- ignorable := (classHigh != 0x030000 && classHigh != 0x0A0000)
- if ignorable {
- log.Debugf("i2c-%d: ignoring class 0x%08x", busno, class)
- }
- return ignorable
- }
-
- return false
-}
-
-// Based on ddcutil's ignorable_i2c_device_sysfs_name() (sysfs_base.c:1408)
-func isIgnorableI2CDeviceName(name, driver string) bool {
- ignorablePrefixes := []string{
- "SMBus",
- "Synopsys DesignWare",
- "soc:i2cdsi",
- "smu",
- "mac-io",
- "u4",
- "AMDGPU SMU", // AMD Navi2+ - probing hangs GPU
- }
-
- for _, prefix := range ignorablePrefixes {
- if strings.HasPrefix(name, prefix) {
- return true
- }
- }
-
- // nouveau driver: only nvkm-* buses are valid
- if driver == "nouveau" && !strings.HasPrefix(name, "nvkm-") {
- return true
- }
-
- return false
-}
-
-// Based on ddcutil's get_i2c_device_sysfs_name() (sysfs_base.c:1175)
-func getI2CDeviceSysfsName(busno int) string {
- path := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/name", busno)
- data, err := os.ReadFile(path)
- if err != nil {
- return ""
- }
- return strings.TrimSpace(string(data))
-}
-
-// Based on ddcutil's get_i2c_device_sysfs_class() (sysfs_base.c:1380)
-func getI2CDeviceSysfsClass(busno int) uint32 {
- classPath := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device/class", busno)
- data, err := os.ReadFile(classPath)
- if err != nil {
- classPath = fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device/device/device/class", busno)
- data, err = os.ReadFile(classPath)
- if err != nil {
- return 0
- }
- }
-
- classStr := strings.TrimSpace(string(data))
- classStr = strings.TrimPrefix(classStr, "0x")
-
- class, err := strconv.ParseUint(classStr, 16, 32)
- if err != nil {
- return 0
- }
-
- return uint32(class)
-}
-
-// Based on ddcutil's get_i2c_sysfs_driver_by_busno() (sysfs_base.c:1284)
-func getI2CSysfsDriver(busno int) string {
- devicePath := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno)
- adapterPath, err := findI2CAdapter(devicePath)
- if err != nil {
- return ""
- }
-
- driverLink := filepath.Join(adapterPath, "driver")
- target, err := os.Readlink(driverLink)
- if err != nil {
- return ""
- }
-
- return filepath.Base(target)
-}
-
-func findI2CAdapter(devicePath string) (string, error) {
- currentPath := devicePath
-
- for depth := 0; depth < 10; depth++ {
- if _, err := os.Stat(filepath.Join(currentPath, "name")); err == nil {
- return currentPath, nil
- }
-
- deviceLink := filepath.Join(currentPath, "device")
- target, err := os.Readlink(deviceLink)
- if err != nil {
- break
- }
-
- if !filepath.IsAbs(target) {
- target = filepath.Join(filepath.Dir(currentPath), target)
- }
- currentPath = filepath.Clean(target)
- }
-
- return "", fmt.Errorf("could not find adapter for %s", devicePath)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go
deleted file mode 100644
index dc3751f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package brightness
-
-import (
- "testing"
-)
-
-func TestIsIgnorableI2CDeviceName(t *testing.T) {
- tests := []struct {
- name string
- deviceName string
- driver string
- want bool
- }{
- {
- name: "AMDGPU SMU should be ignored",
- deviceName: "AMDGPU SMU",
- driver: "amdgpu",
- want: true,
- },
- {
- name: "SMBus should be ignored",
- deviceName: "SMBus I801 adapter",
- driver: "",
- want: true,
- },
- {
- name: "Synopsys DesignWare should be ignored",
- deviceName: "Synopsys DesignWare I2C adapter",
- driver: "",
- want: true,
- },
- {
- name: "smu prefix should be ignored (Mac G5)",
- deviceName: "smu-i2c-controller",
- driver: "",
- want: true,
- },
- {
- name: "Regular NVIDIA DDC should not be ignored",
- deviceName: "NVIDIA i2c adapter 1",
- driver: "nvidia",
- want: false,
- },
- {
- name: "nouveau nvkm bus should not be ignored",
- deviceName: "nvkm-0000:01:00.0-bus-0000",
- driver: "nouveau",
- want: false,
- },
- {
- name: "nouveau non-nvkm bus should be ignored",
- deviceName: "nouveau-other-bus",
- driver: "nouveau",
- want: true,
- },
- {
- name: "Regular AMD display adapter should not be ignored",
- deviceName: "AMDGPU DM i2c hw bus 0",
- driver: "amdgpu",
- want: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := isIgnorableI2CDeviceName(tt.deviceName, tt.driver)
- if got != tt.want {
- t.Errorf("isIgnorableI2CDeviceName(%q, %q) = %v, want %v",
- tt.deviceName, tt.driver, got, tt.want)
- }
- })
- }
-}
-
-func TestClassFiltering(t *testing.T) {
- tests := []struct {
- name string
- class uint32
- want bool
- }{
- {
- name: "Display adapter class should not be ignored",
- class: 0x030000,
- want: false,
- },
- {
- name: "Docking station class should not be ignored",
- class: 0x0a0000,
- want: false,
- },
- {
- name: "Display adapter with subclass should not be ignored",
- class: 0x030001,
- want: false,
- },
- {
- name: "SMBus class should be ignored",
- class: 0x0c0500,
- want: true,
- },
- {
- name: "Bridge class should be ignored",
- class: 0x060400,
- want: true,
- },
- {
- name: "Generic system peripheral should be ignored",
- class: 0x088000,
- want: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- classHigh := tt.class & 0xFFFF0000
- ignorable := (classHigh != 0x030000 && classHigh != 0x0A0000)
- if ignorable != tt.want {
- t.Errorf("class 0x%08x: ignorable = %v, want %v", tt.class, ignorable, tt.want)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go
deleted file mode 100644
index 7f85510..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package brightness
-
-import (
- "testing"
-)
-
-func TestDDCBackend_PercentConversions(t *testing.T) {
- tests := []struct {
- name string
- max int
- percent int
- wantValue int
- }{
- {
- name: "0% should map to minValue=1",
- max: 100,
- percent: 0,
- wantValue: 1,
- },
- {
- name: "1% should be 1",
- max: 100,
- percent: 1,
- wantValue: 1,
- },
- {
- name: "50% should be ~50",
- max: 100,
- percent: 50,
- wantValue: 50,
- },
- {
- name: "100% should be max",
- max: 100,
- percent: 100,
- wantValue: 100,
- },
- }
-
- b := &DDCBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.percentToValue(tt.percent, tt.max, false)
- diff := got - tt.wantValue
- if diff < 0 {
- diff = -diff
- }
- if diff > 1 {
- t.Errorf("percentToValue() = %v, want %v (±1)", got, tt.wantValue)
- }
- })
- }
-}
-
-func TestDDCBackend_ValueToPercent(t *testing.T) {
- tests := []struct {
- name string
- max int
- value int
- wantPercent int
- tolerance int
- }{
- {
- name: "zero value should be 1%",
- max: 100,
- value: 0,
- wantPercent: 1,
- tolerance: 0,
- },
- {
- name: "min value should be 1%",
- max: 100,
- value: 1,
- wantPercent: 1,
- tolerance: 0,
- },
- {
- name: "mid value should be ~50%",
- max: 100,
- value: 50,
- wantPercent: 50,
- tolerance: 2,
- },
- {
- name: "max value should be 100%",
- max: 100,
- value: 100,
- wantPercent: 100,
- tolerance: 0,
- },
- }
-
- b := &DDCBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.valueToPercent(tt.value, tt.max, false)
- diff := got - tt.wantPercent
- if diff < 0 {
- diff = -diff
- }
- if diff > tt.tolerance {
- t.Errorf("valueToPercent() = %v, want %v (±%d)", got, tt.wantPercent, tt.tolerance)
- }
- })
- }
-}
-
-func TestDDCBackend_RoundTrip(t *testing.T) {
- b := &DDCBackend{}
-
- tests := []struct {
- name string
- max int
- percent int
- }{
- {"1%", 100, 1},
- {"25%", 100, 25},
- {"50%", 100, 50},
- {"75%", 100, 75},
- {"100%", 100, 100},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- value := b.percentToValue(tt.percent, tt.max, false)
- gotPercent := b.valueToPercent(value, tt.max, false)
-
- if diff := tt.percent - gotPercent; diff < -1 || diff > 1 {
- t.Errorf("round trip failed: wanted %d%%, got %d%% (value=%d)", tt.percent, gotPercent, value)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go
deleted file mode 100644
index c71163e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package brightness
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
- switch req.Method {
- case "brightness.getState":
- handleGetState(conn, req, m)
- case "brightness.setBrightness":
- handleSetBrightness(conn, req, m)
- case "brightness.increment":
- handleIncrement(conn, req, m)
- case "brightness.decrement":
- handleDecrement(conn, req, m)
- case "brightness.rescan":
- handleRescan(conn, req, m)
- case "brightness.subscribe":
- handleSubscribe(conn, req, m)
- default:
- models.RespondError(conn, req.ID, "unknown method: "+req.Method)
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, m *Manager) {
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleSetBrightness(conn net.Conn, req models.Request, m *Manager) {
- device, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- percent, err := params.Int(req.Params, "percent")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- exponential := params.BoolOpt(req.Params, "exponential", false)
- exponent := params.FloatOpt(req.Params, "exponent", 1.2)
-
- if err := m.SetBrightnessWithExponent(device, percent, exponential, exponent); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleIncrement(conn net.Conn, req models.Request, m *Manager) {
- device, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- step := params.IntOpt(req.Params, "step", 10)
- exponential := params.BoolOpt(req.Params, "exponential", false)
- exponent := params.FloatOpt(req.Params, "exponent", 1.2)
-
- if err := m.IncrementBrightnessWithExponent(device, step, exponential, exponent); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleDecrement(conn net.Conn, req models.Request, m *Manager) {
- device, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- step := params.IntOpt(req.Params, "step", 10)
- exponential := params.BoolOpt(req.Params, "exponential", false)
- exponent := params.FloatOpt(req.Params, "exponent", 1.2)
-
- if err := m.IncrementBrightnessWithExponent(device, -step, exponential, exponent); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleRescan(conn net.Conn, req models.Request, m *Manager) {
- m.Rescan()
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, m *Manager) {
- clientID := fmt.Sprintf("brightness-%d", req.ID)
-
- ch := m.Subscribe(clientID)
- defer m.Unsubscribe(clientID)
-
- initialState := m.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range ch {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go
deleted file mode 100644
index 8449ab5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package brightness
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-type DBusConn interface {
- Object(dest string, path dbus.ObjectPath) dbus.BusObject
- Close() error
-}
-
-type LogindBackend struct {
- conn DBusConn
-}
-
-func NewLogindBackend() (*LogindBackend, error) {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("connect to system bus: %w", err)
- }
-
- obj := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1/session/auto")
- call := obj.Call("org.freedesktop.DBus.Peer.Ping", 0)
- if call.Err != nil {
- conn.Close()
- return nil, fmt.Errorf("logind not available: %w", call.Err)
- }
-
- conn.Close()
-
- return &LogindBackend{}, nil
-}
-
-func NewLogindBackendWithConn(conn DBusConn) *LogindBackend {
- return &LogindBackend{
- conn: conn,
- }
-}
-
-func (b *LogindBackend) SetBrightness(subsystem, name string, brightness uint32) error {
- if b.conn == nil {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("connect to system bus: %w", err)
- }
- b.conn = conn
- }
-
- obj := b.conn.Object("org.freedesktop.login1", "/org/freedesktop/login1/session/auto")
- call := obj.Call("org.freedesktop.login1.Session.SetBrightness", 0, subsystem, name, brightness)
- if call.Err != nil {
- return fmt.Errorf("dbus call failed: %w", call.Err)
- }
-
- log.Debugf("logind: set %s/%s to %d", subsystem, name, brightness)
- return nil
-}
-
-func (b *LogindBackend) Close() {
- if b.conn != nil {
- b.conn.Close()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go
deleted file mode 100644
index 3d4c2ee..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package brightness
-
-import (
- "errors"
- "testing"
-
- mocks_brightness "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/brightness"
- mock_dbus "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/mock"
-)
-
-func TestLogindBackend_SetBrightness_Success(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", dbus.Flags(0), "backlight", "nvidia_0", uint32(75)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := backend.SetBrightness("backlight", "nvidia_0", 75)
- if err != nil {
- t.Errorf("SetBrightness() error = %v, want nil", err)
- }
-}
-
-func TestLogindBackend_SetBrightness_DBusError(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- dbusErr := errors.New("permission denied")
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(&dbus.Call{Err: dbusErr}).
- Once()
-
- err := backend.SetBrightness("backlight", "test_device", 50)
- if err == nil {
- t.Error("SetBrightness() error = nil, want error")
- }
-}
-
-func TestLogindBackend_SetBrightness_LEDDevice(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", dbus.Flags(0), "leds", "test_led", uint32(128)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := backend.SetBrightness("leds", "test_led", 128)
- if err != nil {
- t.Errorf("SetBrightness() error = %v, want nil", err)
- }
-}
-
-func TestLogindBackend_Close(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Close().
- Return(nil).
- Once()
-
- backend.Close()
-}
-
-func TestLogindBackend_Close_NilConn(t *testing.T) {
- backend := &LogindBackend{conn: nil}
- backend.Close()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go
deleted file mode 100644
index 4cf7dd4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go
+++ /dev/null
@@ -1,389 +0,0 @@
-package brightness
-
-import (
- "fmt"
- "sort"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-func NewManager() (*Manager, error) {
- return NewManagerWithOptions(false)
-}
-
-func NewManagerWithOptions(exponential bool) (*Manager, error) {
- m := &Manager{
- stopChan: make(chan struct{}),
- exponential: exponential,
- }
-
- go m.initLogind()
- go m.initSysfs()
- go m.initDDC()
-
- return m, nil
-}
-
-func (m *Manager) initLogind() {
- log.Debug("Initializing logind backend...")
- logind, err := NewLogindBackend()
- if err != nil {
- log.Infof("Logind backend not available: %v", err)
- log.Info("Will use direct sysfs access for brightness control")
- return
- }
-
- m.logindBackend = logind
- m.logindReady = true
- log.Info("Logind backend initialized - will use for brightness control")
-}
-
-func (m *Manager) initSysfs() {
- log.Debug("Initializing sysfs backend...")
- sysfs, err := NewSysfsBackend()
- if err != nil {
- log.Warnf("Failed to initialize sysfs backend: %v", err)
- return
- }
-
- devices, err := sysfs.GetDevices()
- if err != nil {
- log.Warnf("Failed to get initial sysfs devices: %v", err)
- m.sysfsBackend = sysfs
- m.sysfsReady = true
- m.updateState()
- m.initUdev()
- return
- }
-
- log.Infof("Sysfs backend initialized with %d devices", len(devices))
- for _, d := range devices {
- log.Debugf(" - %s: %s (%d%%)", d.ID, d.Name, d.CurrentPercent)
- }
-
- m.sysfsBackend = sysfs
- m.sysfsReady = true
- m.updateState()
- m.initUdev()
-}
-
-func (m *Manager) initUdev() {
- m.udevMonitor = NewUdevMonitor(m)
-}
-
-func (m *Manager) initDDC() {
- ddc, err := NewDDCBackend()
- if err != nil {
- log.Debugf("Failed to initialize DDC backend: %v", err)
- return
- }
-
- m.ddcBackend = ddc
- m.ddcReady = true
- log.Info("DDC backend initialized")
-
- m.updateState()
-}
-
-func (m *Manager) Rescan() {
- log.Debug("Rescanning brightness devices...")
-
- if m.ddcReady && m.ddcBackend != nil {
- if err := m.ddcBackend.ForceRescan(); err != nil {
- log.Debugf("DDC force rescan failed: %v", err)
- }
- }
-
- if m.sysfsReady && m.sysfsBackend != nil {
- if err := m.sysfsBackend.Rescan(); err != nil {
- log.Debugf("Sysfs rescan failed: %v", err)
- }
- }
-
- m.updateState()
-}
-
-func sortDevices(devices []Device) {
- sort.Slice(devices, func(i, j int) bool {
- classOrder := map[DeviceClass]int{
- ClassBacklight: 0,
- ClassDDC: 1,
- ClassLED: 2,
- }
-
- orderI := classOrder[devices[i].Class]
- orderJ := classOrder[devices[j].Class]
-
- if orderI != orderJ {
- return orderI < orderJ
- }
-
- return devices[i].Name < devices[j].Name
- })
-}
-
-func stateChanged(old, new State) bool {
- if len(old.Devices) != len(new.Devices) {
- return true
- }
-
- oldMap := make(map[string]Device)
- for _, d := range old.Devices {
- oldMap[d.ID] = d
- }
-
- for _, newDev := range new.Devices {
- oldDev, exists := oldMap[newDev.ID]
- if !exists {
- return true
- }
- if oldDev.Current != newDev.Current || oldDev.Max != newDev.Max {
- return true
- }
- }
-
- return false
-}
-
-func (m *Manager) updateState() {
- allDevices := make([]Device, 0)
-
- if m.sysfsReady && m.sysfsBackend != nil {
- devices, err := m.sysfsBackend.GetDevices()
- if err != nil {
- log.Debugf("Failed to get sysfs devices: %v", err)
- }
- if err == nil {
- allDevices = append(allDevices, devices...)
- }
- }
-
- if m.ddcReady && m.ddcBackend != nil {
- devices, err := m.ddcBackend.GetDevices()
- if err != nil {
- log.Debugf("Failed to get DDC devices: %v", err)
- }
- if err == nil {
- allDevices = append(allDevices, devices...)
- }
- }
-
- sortDevices(allDevices)
-
- m.stateMutex.Lock()
- oldState := m.state
- newState := State{Devices: allDevices}
-
- if !stateChanged(oldState, newState) {
- m.stateMutex.Unlock()
- return
- }
-
- m.state = newState
- m.stateMutex.Unlock()
- log.Debugf("State changed, notifying subscribers")
- m.NotifySubscribers()
-}
-
-func (m *Manager) SetBrightness(deviceID string, percent int) error {
- return m.SetBrightnessWithMode(deviceID, percent, m.exponential)
-}
-
-func (m *Manager) SetBrightnessWithMode(deviceID string, percent int, exponential bool) error {
- return m.SetBrightnessWithExponent(deviceID, percent, exponential, 1.2)
-}
-
-func (m *Manager) SetBrightnessWithExponent(deviceID string, percent int, exponential bool, exponent float64) error {
- if percent < 0 {
- return fmt.Errorf("percent out of range: %d", percent)
- }
-
- log.Debugf("SetBrightness: %s to %d%%", deviceID, percent)
-
- m.stateMutex.Lock()
- currentState := m.state
- var found bool
- var deviceClass DeviceClass
- var deviceIndex int
-
- log.Debugf("Current state has %d devices", len(currentState.Devices))
-
- for i, dev := range currentState.Devices {
- if dev.ID == deviceID {
- found = true
- deviceClass = dev.Class
- deviceIndex = i
- break
- }
- }
-
- if !found {
- m.stateMutex.Unlock()
- log.Debugf("Device not found in state: %s", deviceID)
- return fmt.Errorf("device not found: %s", deviceID)
- }
-
- newDevices := make([]Device, len(currentState.Devices))
- copy(newDevices, currentState.Devices)
- newDevices[deviceIndex].CurrentPercent = percent
- m.state = State{Devices: newDevices}
- m.stateMutex.Unlock()
-
- var err error
- if deviceClass == ClassDDC {
- log.Debugf("Calling DDC backend for %s", deviceID)
- err = m.ddcBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent, func() {
- m.updateState()
- m.debouncedBroadcast(deviceID)
- })
- } else if m.logindReady && m.logindBackend != nil {
- log.Debugf("Calling logind backend for %s", deviceID)
- err = m.setViaSysfsWithLogindWithExponent(deviceID, percent, exponential, exponent)
- } else {
- log.Debugf("Calling sysfs backend for %s", deviceID)
- err = m.sysfsBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
- }
-
- if err != nil {
- m.updateState()
- return fmt.Errorf("failed to set brightness: %w", err)
- }
-
- if deviceClass != ClassDDC {
- log.Debugf("Queueing broadcast for %s", deviceID)
- m.debouncedBroadcast(deviceID)
- }
- return nil
-}
-
-func (m *Manager) IncrementBrightness(deviceID string, step int) error {
- return m.IncrementBrightnessWithMode(deviceID, step, m.exponential)
-}
-
-func (m *Manager) IncrementBrightnessWithMode(deviceID string, step int, exponential bool) error {
- return m.IncrementBrightnessWithExponent(deviceID, step, exponential, 1.2)
-}
-
-func (m *Manager) IncrementBrightnessWithExponent(deviceID string, step int, exponential bool, exponent float64) error {
- m.stateMutex.RLock()
- currentState := m.state
- m.stateMutex.RUnlock()
-
- var currentPercent int
- var found bool
-
- for _, dev := range currentState.Devices {
- if dev.ID == deviceID {
- currentPercent = dev.CurrentPercent
- found = true
- break
- }
- }
-
- if !found {
- return fmt.Errorf("device not found: %s", deviceID)
- }
-
- newPercent := currentPercent + step
- if newPercent > 100 {
- newPercent = 100
- }
- if newPercent < 0 {
- newPercent = 0
- }
-
- return m.SetBrightnessWithExponent(deviceID, newPercent, exponential, exponent)
-}
-
-func (m *Manager) DecrementBrightness(deviceID string, step int) error {
- return m.IncrementBrightness(deviceID, -step)
-}
-
-func (m *Manager) setViaSysfsWithLogindWithExponent(deviceID string, percent int, exponential bool, exponent float64) error {
- parts := strings.SplitN(deviceID, ":", 2)
- if len(parts) != 2 {
- return fmt.Errorf("invalid device id: %s", deviceID)
- }
-
- subsystem := parts[0]
- name := parts[1]
-
- dev, err := m.sysfsBackend.GetDevice(deviceID)
- if err != nil {
- return err
- }
-
- value := m.sysfsBackend.PercentToValueWithExponent(percent, dev, exponential, exponent)
-
- if m.logindBackend == nil {
- return m.sysfsBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
- }
-
- err = m.logindBackend.SetBrightness(subsystem, name, uint32(value))
- if err != nil {
- log.Debugf("logind SetBrightness failed, falling back to direct sysfs: %v", err)
- return m.sysfsBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
- }
-
- log.Debugf("set %s to %d%% (%d/%d) via logind", deviceID, percent, value, dev.maxBrightness)
- return nil
-}
-
-func (m *Manager) debouncedBroadcast(deviceID string) {
- m.broadcastMutex.Lock()
- defer m.broadcastMutex.Unlock()
-
- m.broadcastPending = true
- m.pendingDeviceID = deviceID
-
- if m.broadcastTimer == nil {
- m.broadcastTimer = time.AfterFunc(150*time.Millisecond, func() {
- m.broadcastMutex.Lock()
- pending := m.broadcastPending
- deviceID := m.pendingDeviceID
- m.broadcastPending = false
- m.pendingDeviceID = ""
- m.broadcastMutex.Unlock()
-
- if !pending || deviceID == "" {
- return
- }
-
- m.broadcastDeviceUpdate(deviceID)
- })
- } else {
- m.broadcastTimer.Reset(150 * time.Millisecond)
- }
-}
-
-func (m *Manager) broadcastDeviceUpdate(deviceID string) {
- m.stateMutex.RLock()
- var targetDevice *Device
- for _, dev := range m.state.Devices {
- if dev.ID == deviceID {
- devCopy := dev
- targetDevice = &devCopy
- break
- }
- }
- m.stateMutex.RUnlock()
-
- if targetDevice == nil {
- log.Debugf("Device not found for broadcast: %s", deviceID)
- return
- }
-
- update := DeviceUpdate{Device: *targetDevice}
-
- log.Debugf("Broadcasting device update: %s at %d%%", deviceID, targetDevice.CurrentPercent)
-
- m.updateSubscribers.Range(func(key string, ch chan DeviceUpdate) bool {
- select {
- case ch <- update:
- default:
- }
- return true
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go
deleted file mode 100644
index a8ae0f8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package brightness
-
-import (
- "testing"
-)
-
-// Manager tests can be added here as needed
-func TestManager_Placeholder(t *testing.T) {
- // Placeholder test to keep the test file valid
- t.Skip("No tests implemented yet")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go
deleted file mode 100644
index 1ff38f9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go
+++ /dev/null
@@ -1,267 +0,0 @@
-package brightness
-
-import (
- "fmt"
- "math"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-func NewSysfsBackend() (*SysfsBackend, error) {
- b := &SysfsBackend{
- basePath: "/sys/class",
- classes: []string{"backlight", "leds"},
- }
-
- if err := b.scanDevices(); err != nil {
- return nil, err
- }
-
- return b, nil
-}
-
-func (b *SysfsBackend) scanDevices() error {
- for _, class := range b.classes {
- classPath := filepath.Join(b.basePath, class)
- entries, err := os.ReadDir(classPath)
- if err != nil {
- if os.IsNotExist(err) {
- continue
- }
- return fmt.Errorf("read %s: %w", classPath, err)
- }
-
- for _, entry := range entries {
- devicePath := filepath.Join(classPath, entry.Name())
-
- stat, err := os.Stat(devicePath)
- if err != nil || !stat.IsDir() {
- continue
- }
- maxPath := filepath.Join(devicePath, "max_brightness")
-
- maxData, err := os.ReadFile(maxPath)
- if err != nil {
- log.Debugf("skip %s/%s: no max_brightness", class, entry.Name())
- continue
- }
-
- maxBrightness, err := strconv.Atoi(strings.TrimSpace(string(maxData)))
- if err != nil || maxBrightness <= 0 {
- log.Debugf("skip %s/%s: invalid max_brightness", class, entry.Name())
- continue
- }
-
- deviceClass := ClassBacklight
- minValue := 1
- if class == "leds" {
- deviceClass = ClassLED
- minValue = 0
- }
-
- deviceID := fmt.Sprintf("%s:%s", class, entry.Name())
- b.deviceCache.Store(deviceID, &sysfsDevice{
- class: deviceClass,
- id: deviceID,
- name: entry.Name(),
- maxBrightness: maxBrightness,
- minValue: minValue,
- })
-
- log.Debugf("found %s device: %s (max=%d)", class, entry.Name(), maxBrightness)
- }
- }
-
- return nil
-}
-
-func shouldSuppressDevice(name string) bool {
- if strings.HasSuffix(name, "::lan") {
- return true
- }
-
- keyboardLEDs := []string{
- "::scrolllock",
- "::capslock",
- "::numlock",
- "::kana",
- "::compose",
- }
-
- for _, suffix := range keyboardLEDs {
- if strings.HasSuffix(name, suffix) {
- return true
- }
- }
-
- return false
-}
-
-func (b *SysfsBackend) Rescan() error {
- return b.scanDevices()
-}
-
-func (b *SysfsBackend) GetDevices() ([]Device, error) {
- devices := make([]Device, 0)
-
- b.deviceCache.Range(func(key string, dev *sysfsDevice) bool {
- if shouldSuppressDevice(dev.name) {
- return true
- }
-
- parts := strings.SplitN(dev.id, ":", 2)
- if len(parts) != 2 {
- return true
- }
-
- class := parts[0]
- name := parts[1]
-
- devicePath := filepath.Join(b.basePath, class, name)
- brightnessPath := filepath.Join(devicePath, "brightness")
-
- brightnessData, err := os.ReadFile(brightnessPath)
- if err != nil {
- log.Debugf("failed to read brightness for %s: %v", dev.id, err)
- return true
- }
-
- current, err := strconv.Atoi(strings.TrimSpace(string(brightnessData)))
- if err != nil {
- log.Debugf("failed to parse brightness for %s: %v", dev.id, err)
- return true
- }
-
- percent := b.ValueToPercent(current, dev, false)
-
- devices = append(devices, Device{
- Class: dev.class,
- ID: dev.id,
- Name: dev.name,
- Current: current,
- Max: dev.maxBrightness,
- CurrentPercent: percent,
- Backend: "sysfs",
- })
- return true
- })
-
- return devices, nil
-}
-
-func (b *SysfsBackend) GetDevice(id string) (*sysfsDevice, error) {
- dev, ok := b.deviceCache.Load(id)
- if !ok {
- return nil, fmt.Errorf("device not found: %s", id)
- }
-
- return dev, nil
-}
-
-func (b *SysfsBackend) SetBrightness(id string, percent int, exponential bool) error {
- return b.SetBrightnessWithExponent(id, percent, exponential, 1.2)
-}
-
-func (b *SysfsBackend) SetBrightnessWithExponent(id string, percent int, exponential bool, exponent float64) error {
- dev, err := b.GetDevice(id)
- if err != nil {
- return err
- }
-
- if percent < 0 {
- return fmt.Errorf("percent out of range: %d", percent)
- }
-
- value := b.PercentToValueWithExponent(percent, dev, exponential, exponent)
-
- parts := strings.SplitN(id, ":", 2)
- if len(parts) != 2 {
- return fmt.Errorf("invalid device id: %s", id)
- }
-
- class := parts[0]
- name := parts[1]
-
- devicePath := filepath.Join(b.basePath, class, name)
- brightnessPath := filepath.Join(devicePath, "brightness")
-
- data := []byte(fmt.Sprintf("%d", value))
- if err := os.WriteFile(brightnessPath, data, 0o644); err != nil {
- return fmt.Errorf("write brightness: %w", err)
- }
-
- log.Debugf("set %s to %d%% (%d/%d) via direct sysfs", id, percent, value, dev.maxBrightness)
-
- return nil
-}
-
-func (b *SysfsBackend) PercentToValue(percent int, dev *sysfsDevice, exponential bool) int {
- return b.PercentToValueWithExponent(percent, dev, exponential, 1.2)
-}
-
-func (b *SysfsBackend) PercentToValueWithExponent(percent int, dev *sysfsDevice, exponential bool, exponent float64) int {
- if percent == 0 {
- return dev.minValue
- }
-
- usableRange := dev.maxBrightness - dev.minValue
- var value int
-
- if exponential {
- normalizedPercent := float64(percent-1) / 99.0
- hardwarePercent := math.Pow(normalizedPercent, exponent)
- value = dev.minValue + int(math.Round(hardwarePercent*float64(usableRange)))
- } else {
- value = dev.minValue + ((percent - 1) * usableRange / 99)
- }
-
- if value < dev.minValue {
- value = dev.minValue
- }
- if value > dev.maxBrightness {
- value = dev.maxBrightness
- }
-
- return value
-}
-
-func (b *SysfsBackend) ValueToPercent(value int, dev *sysfsDevice, exponential bool) int {
- return b.ValueToPercentWithExponent(value, dev, exponential, 1.2)
-}
-
-func (b *SysfsBackend) ValueToPercentWithExponent(value int, dev *sysfsDevice, exponential bool, exponent float64) int {
- if value <= dev.minValue {
- if dev.minValue == 0 && value == 0 {
- return 0
- }
- return 1
- }
-
- usableRange := dev.maxBrightness - dev.minValue
- if usableRange == 0 {
- return 100
- }
-
- var percent int
-
- if exponential {
- hardwarePercent := float64(value-dev.minValue) / float64(usableRange)
- normalizedPercent := math.Pow(hardwarePercent, 1.0/exponent)
- percent = 1 + int(math.Round(normalizedPercent*99.0))
- } else {
- percent = 1 + int(math.Round(float64(value-dev.minValue)*99.0/float64(usableRange)))
- }
-
- if percent > 100 {
- percent = 100
- }
- if percent < 1 {
- percent = 1
- }
-
- return percent
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go
deleted file mode 100644
index 6ba84b8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go
+++ /dev/null
@@ -1,278 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- mocks_brightness "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/brightness"
- mock_dbus "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/mock"
-)
-
-func TestManager_SetBrightness_LogindSuccess(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- mockLogind := NewLogindBackendWithConn(mockConn)
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: mockLogind,
- sysfsBackend: sysfs,
- logindReady: true,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:test_backlight",
- Name: "test_backlight",
- Current: 50,
- Max: 100,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, "backlight", "test_backlight", uint32(75)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := m.SetBrightness("backlight:test_backlight", 75)
- if err != nil {
- t.Errorf("SetBrightness() with logind error = %v, want nil", err)
- }
-
- data, _ := os.ReadFile(filepath.Join(backlightDir, "brightness"))
- if string(data) == "75\n" {
- t.Error("Direct sysfs write occurred when logind should have been used")
- }
-}
-
-func TestManager_SetBrightness_LogindFailsFallbackToSysfs(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- mockLogind := NewLogindBackendWithConn(mockConn)
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: mockLogind,
- sysfsBackend: sysfs,
- logindReady: true,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:test_backlight",
- Name: "test_backlight",
- Current: 50,
- Max: 100,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, "backlight", "test_backlight", mock.Anything).
- Return(&dbus.Call{Err: dbus.ErrMsgNoObject}).
- Once()
-
- err := m.SetBrightness("backlight:test_backlight", 75)
- if err != nil {
- t.Errorf("SetBrightness() with fallback error = %v, want nil", err)
- }
-
- data, _ := os.ReadFile(filepath.Join(backlightDir, "brightness"))
- brightness := string(data)
- if brightness != "75" {
- t.Errorf("Fallback sysfs write did not occur, got brightness = %q, want %q", brightness, "75")
- }
-}
-
-func TestManager_SetBrightness_NoLogind(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: nil,
- sysfsBackend: sysfs,
- logindReady: false,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:test_backlight",
- Name: "test_backlight",
- Current: 50,
- Max: 100,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- err := m.SetBrightness("backlight:test_backlight", 75)
- if err != nil {
- t.Errorf("SetBrightness() without logind error = %v, want nil", err)
- }
-
- data, _ := os.ReadFile(filepath.Join(backlightDir, "brightness"))
- brightness := string(data)
- if brightness != "75" {
- t.Errorf("Direct sysfs write = %q, want %q", brightness, "75")
- }
-}
-
-func TestManager_SetBrightness_LEDWithLogind(t *testing.T) {
- tmpDir := t.TempDir()
-
- ledsDir := filepath.Join(tmpDir, "leds", "test_led")
- if err := os.MkdirAll(ledsDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(ledsDir, "max_brightness"), []byte("255\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(ledsDir, "brightness"), []byte("128\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- mockLogind := NewLogindBackendWithConn(mockConn)
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"leds"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: mockLogind,
- sysfsBackend: sysfs,
- logindReady: true,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassLED,
- ID: "leds:test_led",
- Name: "test_led",
- Current: 128,
- Max: 255,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, "leds", "test_led", uint32(0)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := m.SetBrightness("leds:test_led", 0)
- if err != nil {
- t.Errorf("SetBrightness() LED with logind error = %v, want nil", err)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go
deleted file mode 100644
index 8061513..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestSysfsBackend_PercentConversions(t *testing.T) {
- tests := []struct {
- name string
- device *sysfsDevice
- percent int
- wantValue int
- tolerance int
- }{
- {
- name: "backlight 0% should be minValue=1",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 0,
- wantValue: 1,
- tolerance: 0,
- },
- {
- name: "backlight 1% should be minValue=1",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 1,
- wantValue: 1,
- tolerance: 0,
- },
- {
- name: "backlight 50% should be ~50",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 50,
- wantValue: 50,
- tolerance: 1,
- },
- {
- name: "backlight 100% should be max",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 100,
- wantValue: 100,
- tolerance: 0,
- },
- {
- name: "led 0% should be 0",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 0,
- wantValue: 0,
- tolerance: 0,
- },
- {
- name: "led 1% should be ~2-3",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 1,
- wantValue: 2,
- tolerance: 3,
- },
- {
- name: "led 50% should be ~127",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 50,
- wantValue: 127,
- tolerance: 2,
- },
- {
- name: "led 100% should be max",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 100,
- wantValue: 255,
- tolerance: 0,
- },
- }
-
- b := &SysfsBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.PercentToValue(tt.percent, tt.device, false)
- diff := got - tt.wantValue
- if diff < 0 {
- diff = -diff
- }
- if diff > tt.tolerance {
- t.Errorf("percentToValue() = %v, want %v (±%d)", got, tt.wantValue, tt.tolerance)
- }
-
- gotPercent := b.ValueToPercent(got, tt.device, false)
- if tt.percent > 1 && gotPercent == 0 {
- t.Errorf("valueToPercent() returned 0 for non-zero input (percent=%d, got value=%d)", tt.percent, got)
- }
- })
- }
-}
-
-func TestSysfsBackend_ValueToPercent(t *testing.T) {
- tests := []struct {
- name string
- device *sysfsDevice
- value int
- wantPercent int
- }{
- {
- name: "backlight min value",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- value: 1,
- wantPercent: 1,
- },
- {
- name: "backlight max value",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- value: 100,
- wantPercent: 100,
- },
- {
- name: "led zero",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- value: 0,
- wantPercent: 0,
- },
- }
-
- b := &SysfsBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.ValueToPercent(tt.value, tt.device, false)
- if got != tt.wantPercent {
- t.Errorf("valueToPercent() = %v, want %v", got, tt.wantPercent)
- }
- })
- }
-}
-
-func TestSysfsBackend_ScanDevices(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
-
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- ledsDir := filepath.Join(tmpDir, "leds", "test_led")
- if err := os.MkdirAll(ledsDir, 0o755); err != nil {
- t.Fatal(err)
- }
-
- if err := os.WriteFile(filepath.Join(ledsDir, "max_brightness"), []byte("255\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(ledsDir, "brightness"), []byte("128\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- b := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight", "leds"},
- }
-
- if err := b.scanDevices(); err != nil {
- t.Fatalf("scanDevices() error = %v", err)
- }
-
- backlightID := "backlight:test_backlight"
- if _, ok := b.deviceCache.Load(backlightID); !ok {
- t.Errorf("backlight device not found")
- }
-
- ledID := "leds:test_led"
- if _, ok := b.deviceCache.Load(ledID); !ok {
- t.Errorf("LED device not found")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go
deleted file mode 100644
index 81e00cf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package brightness
-
-import (
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type DeviceClass string
-
-const (
- ClassBacklight DeviceClass = "backlight"
- ClassLED DeviceClass = "leds"
- ClassDDC DeviceClass = "ddc"
-)
-
-type Device struct {
- Class DeviceClass `json:"class"`
- ID string `json:"id"`
- Name string `json:"name"`
- Current int `json:"current"`
- Max int `json:"max"`
- CurrentPercent int `json:"currentPercent"`
- Backend string `json:"backend"`
-}
-
-type State struct {
- Devices []Device `json:"devices"`
-}
-
-type DeviceUpdate struct {
- Device Device `json:"device"`
-}
-
-type Manager struct {
- logindBackend *LogindBackend
- sysfsBackend *SysfsBackend
- ddcBackend *DDCBackend
- udevMonitor *UdevMonitor
-
- logindReady bool
- sysfsReady bool
- ddcReady bool
-
- exponential bool
-
- stateMutex sync.RWMutex
- state State
-
- subscribers syncmap.Map[string, chan State]
- updateSubscribers syncmap.Map[string, chan DeviceUpdate]
-
- broadcastMutex sync.Mutex
- broadcastTimer *time.Timer
- broadcastPending bool
- pendingDeviceID string
-
- stopChan chan struct{}
-}
-
-type SysfsBackend struct {
- basePath string
- classes []string
-
- deviceCache syncmap.Map[string, *sysfsDevice]
-}
-
-type sysfsDevice struct {
- class DeviceClass
- id string
- name string
- maxBrightness int
- minValue int
-}
-
-type DDCBackend struct {
- devices syncmap.Map[string, *ddcDevice]
-
- scanMutex sync.Mutex
- lastScan time.Time
- scanInterval time.Duration
-
- debounceMutex sync.Mutex
- debounceTimers map[string]*time.Timer
- debouncePending map[string]ddcPendingSet
- debounceWg sync.WaitGroup
-}
-
-type ddcPendingSet struct {
- percent int
- callback func()
-}
-
-type ddcDevice struct {
- bus int
- addr int
- id string
- name string
- max int
- lastBrightness int
-}
-
-type ddcCapability struct {
- vcp byte
- max int
- current int
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 16)
-
- m.subscribers.Store(id, ch)
-
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
-
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
-
- }
-
-}
-
-func (m *Manager) SubscribeUpdates(id string) chan DeviceUpdate {
- ch := make(chan DeviceUpdate, 16)
- m.updateSubscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) UnsubscribeUpdates(id string) {
- if val, ok := m.updateSubscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) NotifySubscribers() {
- m.stateMutex.RLock()
- state := m.state
- m.stateMutex.RUnlock()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- state:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- return m.state
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
- m.updateSubscribers.Range(func(key string, ch chan DeviceUpdate) bool {
- close(ch)
- m.updateSubscribers.Delete(key)
- return true
- })
-
- if m.udevMonitor != nil {
- m.udevMonitor.Close()
- }
-
- if m.logindBackend != nil {
- m.logindBackend.Close()
- }
-
- if m.ddcBackend != nil {
- m.ddcBackend.Close()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go
deleted file mode 100644
index b7f02a8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/pilebones/go-udev/netlink"
-)
-
-const (
- udevRecvBufSize = 8 * 1024 * 1024
- udevMaxRetries = 5
- udevBaseDelay = 2 * time.Second
- udevMaxDelay = 60 * time.Second
-)
-
-type UdevMonitor struct {
- stop chan struct{}
- rescanMutex sync.Mutex
- rescanTimer *time.Timer
- rescanPending bool
-}
-
-func NewUdevMonitor(manager *Manager) *UdevMonitor {
- m := &UdevMonitor{
- stop: make(chan struct{}),
- }
-
- go m.run(manager)
- return m
-}
-
-func (m *UdevMonitor) run(manager *Manager) {
- matcher := &netlink.RuleDefinitions{
- Rules: []netlink.RuleDefinition{
- {Env: map[string]string{"SUBSYSTEM": "backlight"}},
- {Env: map[string]string{"SUBSYSTEM": "drm"}},
- {Env: map[string]string{"SUBSYSTEM": "i2c"}},
- },
- }
- if err := matcher.Compile(); err != nil {
- log.Errorf("Failed to compile udev matcher: %v", err)
- return
- }
-
- failures := 0
- for {
- if err := m.monitorLoop(manager, matcher); err != nil {
- log.Errorf("Udev monitor error: %v", err)
- }
-
- select {
- case <-m.stop:
- return
- default:
- }
-
- failures++
- if failures > udevMaxRetries {
- log.Errorf("Udev monitor exceeded %d retries, giving up", udevMaxRetries)
- return
- }
-
- delay := min(udevBaseDelay*time.Duration(1<<(failures-1)), udevMaxDelay)
- log.Infof("Udev monitor reconnecting in %v (attempt %d/%d)", delay, failures, udevMaxRetries)
-
- select {
- case <-m.stop:
- return
- case <-time.After(delay):
- }
- }
-}
-
-func (m *UdevMonitor) monitorLoop(manager *Manager, matcher *netlink.RuleDefinitions) error {
- conn := &netlink.UEventConn{}
- if err := conn.Connect(netlink.UdevEvent); err != nil {
- return err
- }
- defer conn.Close()
-
- if err := syscall.SetsockoptInt(conn.Fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, udevRecvBufSize); err != nil {
- log.Warnf("Failed to set udev socket receive buffer: %v", err)
- }
-
- events := make(chan netlink.UEvent)
- errs := make(chan error)
- conn.Monitor(events, errs, matcher)
-
- log.Info("Udev monitor started for backlight/drm/i2c events")
-
- for {
- select {
- case <-m.stop:
- return nil
- case err := <-errs:
- return err
- case event := <-events:
- m.handleEvent(manager, event)
- }
- }
-}
-
-func (m *UdevMonitor) handleEvent(manager *Manager, event netlink.UEvent) {
- subsystem := event.Env["SUBSYSTEM"]
- devpath := event.Env["DEVPATH"]
-
- if subsystem == "" || devpath == "" {
- return
- }
-
- sysname := filepath.Base(devpath)
- action := string(event.Action)
-
- switch subsystem {
- case "drm", "i2c":
- m.handleDisplayEvent(manager, action, subsystem, sysname)
- case "backlight":
- m.handleBacklightEvent(manager, action, sysname)
- }
-}
-
-func (m *UdevMonitor) handleDisplayEvent(manager *Manager, action, subsystem, sysname string) {
- switch action {
- case "add", "remove", "change":
- log.Debugf("Udev %s event: %s:%s - queueing DDC rescan", action, subsystem, sysname)
- m.debouncedRescan(manager)
- }
-}
-
-func (m *UdevMonitor) debouncedRescan(manager *Manager) {
- m.rescanMutex.Lock()
- defer m.rescanMutex.Unlock()
-
- m.rescanPending = true
-
- if m.rescanTimer != nil {
- m.rescanTimer.Reset(2 * time.Second)
- return
- }
-
- m.rescanTimer = time.AfterFunc(2*time.Second, func() {
- m.rescanMutex.Lock()
- pending := m.rescanPending
- m.rescanPending = false
- m.rescanMutex.Unlock()
-
- if !pending {
- return
- }
-
- log.Debug("Executing debounced DDC rescan")
- manager.Rescan()
- })
-}
-
-func (m *UdevMonitor) handleBacklightEvent(manager *Manager, action, sysname string) {
- switch action {
- case "change":
- m.handleChange(manager, "backlight", sysname)
- case "add", "remove":
- log.Debugf("Udev %s event: backlight:%s - triggering rescan", action, sysname)
- manager.Rescan()
- }
-}
-
-func (m *UdevMonitor) handleChange(manager *Manager, subsystem, sysname string) {
- deviceID := subsystem + ":" + sysname
-
- if manager.sysfsBackend == nil {
- return
- }
-
- brightnessPath := filepath.Join(manager.sysfsBackend.basePath, subsystem, sysname, "brightness")
- data, err := os.ReadFile(brightnessPath)
- if err != nil {
- log.Debugf("Udev change event for %s but failed to read brightness: %v", deviceID, err)
- return
- }
-
- brightness, err := strconv.Atoi(strings.TrimSpace(string(data)))
- if err != nil {
- log.Debugf("Failed to parse brightness for %s: %v", deviceID, err)
- return
- }
-
- manager.handleUdevBrightnessChange(deviceID, brightness)
-}
-
-func (m *UdevMonitor) Close() {
- close(m.stop)
-}
-
-func (m *Manager) handleUdevBrightnessChange(deviceID string, rawBrightness int) {
- if m.sysfsBackend == nil {
- return
- }
-
- dev, err := m.sysfsBackend.GetDevice(deviceID)
- if err != nil {
- log.Debugf("Udev event for unknown device %s: %v", deviceID, err)
- return
- }
-
- percent := m.sysfsBackend.ValueToPercent(rawBrightness, dev, false)
-
- m.stateMutex.Lock()
- var found bool
- for i, d := range m.state.Devices {
- if d.ID != deviceID {
- continue
- }
- found = true
- if d.Current == rawBrightness {
- m.stateMutex.Unlock()
- return
- }
- m.state.Devices[i].Current = rawBrightness
- m.state.Devices[i].CurrentPercent = percent
- break
- }
- m.stateMutex.Unlock()
-
- if !found {
- log.Debugf("Udev event for device not in state: %s", deviceID)
- return
- }
-
- log.Debugf("Udev brightness change: %s -> %d (%d%%)", deviceID, rawBrightness, percent)
- m.broadcastDeviceUpdate(deviceID)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go
deleted file mode 100644
index a5c406b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/pilebones/go-udev/netlink"
-)
-
-func setupTestManager(t *testing.T) (*Manager, string) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "intel_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("1000\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("500\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- sysfsBackend: sysfs,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:intel_backlight",
- Name: "intel_backlight",
- Current: 500,
- Max: 1000,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- return m, tmpDir
-}
-
-func TestHandleUdevBrightnessChange_UpdatesState(t *testing.T) {
- m, _ := setupTestManager(t)
-
- m.handleUdevBrightnessChange("backlight:intel_backlight", 750)
-
- state := m.GetState()
- if len(state.Devices) != 1 {
- t.Fatalf("expected 1 device, got %d", len(state.Devices))
- }
-
- dev := state.Devices[0]
- if dev.Current != 750 {
- t.Errorf("expected Current=750, got %d", dev.Current)
- }
- if dev.CurrentPercent != 75 {
- t.Errorf("expected CurrentPercent=75, got %d", dev.CurrentPercent)
- }
-}
-
-func TestHandleUdevBrightnessChange_NoChangeWhenSameValue(t *testing.T) {
- m, _ := setupTestManager(t)
-
- updateCh := m.SubscribeUpdates("test")
- defer m.UnsubscribeUpdates("test")
-
- m.handleUdevBrightnessChange("backlight:intel_backlight", 500)
-
- select {
- case <-updateCh:
- t.Error("should not broadcast when brightness unchanged")
- case <-time.After(50 * time.Millisecond):
- }
-}
-
-func TestHandleUdevBrightnessChange_BroadcastsOnChange(t *testing.T) {
- m, _ := setupTestManager(t)
-
- updateCh := m.SubscribeUpdates("test")
- defer m.UnsubscribeUpdates("test")
-
- m.handleUdevBrightnessChange("backlight:intel_backlight", 750)
-
- select {
- case update := <-updateCh:
- if update.Device.Current != 750 {
- t.Errorf("broadcast had wrong Current: got %d, want 750", update.Device.Current)
- }
- case <-time.After(100 * time.Millisecond):
- t.Error("expected broadcast on brightness change")
- }
-}
-
-func TestHandleUdevBrightnessChange_UnknownDevice(t *testing.T) {
- m, _ := setupTestManager(t)
-
- m.handleUdevBrightnessChange("backlight:unknown_device", 500)
-
- state := m.GetState()
- if len(state.Devices) != 1 {
- t.Errorf("state should be unchanged, got %d devices", len(state.Devices))
- }
-}
-
-func TestHandleUdevBrightnessChange_NilSysfsBackend(t *testing.T) {
- m := &Manager{
- sysfsBackend: nil,
- stopChan: make(chan struct{}),
- }
-
- m.handleUdevBrightnessChange("backlight:test", 500)
-}
-
-func TestHandleUdevBrightnessChange_DeviceNotInState(t *testing.T) {
- m, _ := setupTestManager(t)
-
- m.sysfsBackend.deviceCache.Store("backlight:other_device", &sysfsDevice{
- class: ClassBacklight,
- id: "backlight:other_device",
- name: "other_device",
- maxBrightness: 100,
- minValue: 1,
- })
-
- m.handleUdevBrightnessChange("backlight:other_device", 50)
-
- state := m.GetState()
- for _, d := range state.Devices {
- if d.ID == "backlight:other_device" {
- t.Error("device should not be added to state via udev change event")
- }
- }
-}
-
-func TestHandleEvent_ChangeAction(t *testing.T) {
- m, tmpDir := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- brightnessPath := filepath.Join(tmpDir, "backlight", "intel_backlight", "brightness")
- if err := os.WriteFile(brightnessPath, []byte("800\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- event := netlink.UEvent{
- Action: netlink.CHANGE,
- Env: map[string]string{
- "SUBSYSTEM": "backlight",
- "DEVPATH": "/devices/pci0000:00/0000:00:02.0/drm/card0/card0-eDP-1/intel_backlight",
- },
- }
-
- um.handleEvent(m, event)
-
- state := m.GetState()
- if state.Devices[0].Current != 800 {
- t.Errorf("expected Current=800 after change event, got %d", state.Devices[0].Current)
- }
-}
-
-func TestHandleEvent_MissingEnvVars(t *testing.T) {
- m, _ := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- event := netlink.UEvent{
- Action: netlink.CHANGE,
- Env: map[string]string{},
- }
-
- um.handleEvent(m, event)
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged with missing env vars")
- }
-}
-
-func TestHandleEvent_MissingSubsystem(t *testing.T) {
- m, _ := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- event := netlink.UEvent{
- Action: netlink.CHANGE,
- Env: map[string]string{
- "DEVPATH": "/devices/foo/bar",
- },
- }
-
- um.handleEvent(m, event)
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged with missing SUBSYSTEM")
- }
-}
-
-func TestHandleChange_BrightnessFileNotFound(t *testing.T) {
- m, _ := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- um.handleChange(m, "backlight", "nonexistent_device")
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged when brightness file not found")
- }
-}
-
-func TestHandleChange_InvalidBrightnessValue(t *testing.T) {
- m, tmpDir := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- brightnessPath := filepath.Join(tmpDir, "backlight", "intel_backlight", "brightness")
- if err := os.WriteFile(brightnessPath, []byte("not_a_number\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- um.handleChange(m, "backlight", "intel_backlight")
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged with invalid brightness value")
- }
-}
-
-func TestUdevMonitor_Close(t *testing.T) {
- um := &UdevMonitor{stop: make(chan struct{})}
-
- um.Close()
-
- select {
- case <-um.stop:
- default:
- t.Error("stop channel should be closed")
- }
-}
-
-func TestHandleChange_NilSysfsBackend(t *testing.T) {
- m := &Manager{
- sysfsBackend: nil,
- stopChan: make(chan struct{}),
- }
- um := &UdevMonitor{stop: make(chan struct{})}
-
- um.handleChange(m, "backlight", "test_device")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/handlers.go
deleted file mode 100644
index 0b4159a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/handlers.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package browser
-
-import (
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "browser.open":
- url, ok := models.Get[string](req, "url")
- if !ok {
- models.RespondError(conn, req.ID, "invalid url parameter")
- return
- }
- manager.RequestOpen(url)
- models.Respond(conn, req.ID, "ok")
- default:
- models.RespondError(conn, req.ID, "unknown method")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/manager.go
deleted file mode 100644
index 5306759..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/manager.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package browser
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type Manager struct {
- subscribers syncmap.Map[string, chan OpenEvent]
- closeOnce sync.Once
-}
-
-func NewManager() *Manager {
- return &Manager{}
-}
-
-func (m *Manager) Subscribe(id string) chan OpenEvent {
- ch := make(chan OpenEvent, 16)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) RequestOpen(url string) {
- event := OpenEvent{URL: url}
- m.subscribers.Range(func(key string, ch chan OpenEvent) bool {
- select {
- case ch <- event:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) Close() {
- m.closeOnce.Do(func() {
- m.subscribers.Range(func(key string, ch chan OpenEvent) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/models.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/models.go
deleted file mode 100644
index fc63519..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/browser/models.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package browser
-
-type OpenEvent struct {
- URL string `json:"url"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/handlers.go
deleted file mode 100644
index 1ccc710..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/handlers.go
+++ /dev/null
@@ -1,325 +0,0 @@
-package clipboard
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
- switch req.Method {
- case "clipboard.getState":
- handleGetState(conn, req, m)
- case "clipboard.getHistory":
- handleGetHistory(conn, req, m)
- case "clipboard.getEntry":
- handleGetEntry(conn, req, m)
- case "clipboard.deleteEntry":
- handleDeleteEntry(conn, req, m)
- case "clipboard.clearHistory":
- handleClearHistory(conn, req, m)
- case "clipboard.copy":
- handleCopy(conn, req, m)
- case "clipboard.copyEntry":
- handleCopyEntry(conn, req, m)
- case "clipboard.paste":
- handlePaste(conn, req, m)
- case "clipboard.subscribe":
- handleSubscribe(conn, req, m)
- case "clipboard.search":
- handleSearch(conn, req, m)
- case "clipboard.getConfig":
- handleGetConfig(conn, req, m)
- case "clipboard.setConfig":
- handleSetConfig(conn, req, m)
- case "clipboard.store":
- handleStore(conn, req, m)
- case "clipboard.pinEntry":
- handlePinEntry(conn, req, m)
- case "clipboard.unpinEntry":
- handleUnpinEntry(conn, req, m)
- case "clipboard.getPinnedEntries":
- handleGetPinnedEntries(conn, req, m)
- case "clipboard.getPinnedCount":
- handleGetPinnedCount(conn, req, m)
- case "clipboard.copyFile":
- handleCopyFile(conn, req, m)
- default:
- models.RespondError(conn, req.ID, "unknown method: "+req.Method)
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, m *Manager) {
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleGetHistory(conn net.Conn, req models.Request, m *Manager) {
- history := m.GetHistory()
- for i := range history {
- history[i].Data = nil
- }
- models.Respond(conn, req.ID, history)
-}
-
-func handleGetEntry(conn net.Conn, req models.Request, m *Manager) {
- id, err := params.Int(req.Params, "id")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- entry, err := m.GetEntry(uint64(id))
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, entry)
-}
-
-func handleDeleteEntry(conn net.Conn, req models.Request, m *Manager) {
- id, err := params.Int(req.Params, "id")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := m.DeleteEntry(uint64(id)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry deleted"})
-}
-
-func handleClearHistory(conn net.Conn, req models.Request, m *Manager) {
- m.ClearHistory()
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "history cleared"})
-}
-
-func handleCopy(conn net.Conn, req models.Request, m *Manager) {
- text, err := params.String(req.Params, "text")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := m.CopyText(text); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied to clipboard"})
-}
-
-func handleCopyEntry(conn net.Conn, req models.Request, m *Manager) {
- id, err := params.Int(req.Params, "id")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- entry, err := m.GetEntry(uint64(id))
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- filePath := m.EntryToFile(entry)
- if filePath != "" {
- if err := m.CopyFile(filePath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, map[string]any{
- "success": true,
- "filePath": filePath,
- })
- return
- }
-
- if err := m.SetClipboard(entry.Data, entry.MimeType); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if entry.Pinned {
- if err := m.CreateHistoryEntryFromPinned(entry); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- } else {
- if err := m.TouchEntry(uint64(id)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied to clipboard"})
-}
-
-func handlePaste(conn net.Conn, req models.Request, m *Manager) {
- text, err := m.PasteText()
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, map[string]string{"text": text})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, m *Manager) {
- clientID := fmt.Sprintf("clipboard-%d", req.ID)
-
- ch := m.Subscribe(clientID)
- defer m.Unsubscribe(clientID)
-
- initialState := m.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range ch {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
-
-func handleSearch(conn net.Conn, req models.Request, m *Manager) {
- p := SearchParams{
- Query: params.StringOpt(req.Params, "query", ""),
- MimeType: params.StringOpt(req.Params, "mimeType", ""),
- Limit: params.IntOpt(req.Params, "limit", 50),
- Offset: params.IntOpt(req.Params, "offset", 0),
- }
-
- if img, ok := models.Get[bool](req, "isImage"); ok {
- p.IsImage = &img
- }
- if b, ok := models.Get[float64](req, "before"); ok {
- v := int64(b)
- p.Before = &v
- }
- if a, ok := models.Get[float64](req, "after"); ok {
- v := int64(a)
- p.After = &v
- }
-
- models.Respond(conn, req.ID, m.Search(p))
-}
-
-func handleGetConfig(conn net.Conn, req models.Request, m *Manager) {
- models.Respond(conn, req.ID, m.GetConfig())
-}
-
-func handleSetConfig(conn net.Conn, req models.Request, m *Manager) {
- cfg := m.GetConfig()
-
- if v, ok := models.Get[float64](req, "maxHistory"); ok {
- cfg.MaxHistory = int(v)
- }
- if v, ok := models.Get[float64](req, "maxEntrySize"); ok {
- cfg.MaxEntrySize = int64(v)
- }
- if v, ok := models.Get[float64](req, "autoClearDays"); ok {
- cfg.AutoClearDays = int(v)
- }
- if v, ok := models.Get[bool](req, "clearAtStartup"); ok {
- cfg.ClearAtStartup = v
- }
- if v, ok := models.Get[bool](req, "disabled"); ok {
- cfg.Disabled = v
- }
- if v, ok := models.Get[float64](req, "maxPinned"); ok {
- cfg.MaxPinned = int(v)
- }
-
- if err := m.SetConfig(cfg); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "config updated"})
-}
-
-func handleStore(conn net.Conn, req models.Request, m *Manager) {
- data, err := params.String(req.Params, "data")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- mimeType := params.StringOpt(req.Params, "mimeType", "text/plain;charset=utf-8")
-
- if err := m.StoreData([]byte(data), mimeType); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "stored"})
-}
-
-func handlePinEntry(conn net.Conn, req models.Request, m *Manager) {
- id, err := params.Int(req.Params, "id")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := m.PinEntry(uint64(id)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry pinned"})
-}
-
-func handleUnpinEntry(conn net.Conn, req models.Request, m *Manager) {
- id, err := params.Int(req.Params, "id")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := m.UnpinEntry(uint64(id)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry unpinned"})
-}
-
-func handleGetPinnedEntries(conn net.Conn, req models.Request, m *Manager) {
- pinned := m.GetPinnedEntries()
- models.Respond(conn, req.ID, pinned)
-}
-
-func handleGetPinnedCount(conn net.Conn, req models.Request, m *Manager) {
- count := m.GetPinnedCount()
- models.Respond(conn, req.ID, map[string]int{"count": count})
-}
-
-func handleCopyFile(conn net.Conn, req models.Request, m *Manager) {
- filePath, err := params.String(req.Params, "filePath")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := m.CopyFile(filePath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied"})
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager.go
deleted file mode 100644
index f5f528b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager.go
+++ /dev/null
@@ -1,1911 +0,0 @@
-package clipboard
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "image"
- _ "image/gif"
- _ "image/jpeg"
- _ "image/png"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "slices"
- "strings"
- "syscall"
- "time"
-
- "hash/fnv"
-
- "github.com/fsnotify/fsnotify"
- "github.com/godbus/dbus/v5"
- _ "golang.org/x/image/bmp"
- _ "golang.org/x/image/tiff"
- _ "golang.org/x/image/webp"
-
- bolt "go.etcd.io/bbolt"
-
- clipboardstore "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-// These mime types won't be stored in history
-var sensitiveMimeTypes = []string{
- "x-kde-passwordManagerHint",
-}
-
-func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error) {
- display := wlCtx.Display()
- dbPath, err := clipboardstore.GetDBPath()
- if err != nil {
- return nil, fmt.Errorf("failed to get db path: %w", err)
- }
-
- configPath, _ := getConfigPath()
-
- m := &Manager{
- config: config,
- configPath: configPath,
- display: display,
- wlCtx: wlCtx,
- stopChan: make(chan struct{}),
- subscribers: make(map[string]chan State),
- dirty: make(chan struct{}, 1),
- offerMimeTypes: make(map[any][]string),
- offerRegistry: make(map[uint32]any),
- dbPath: dbPath,
- }
-
- if !config.Disabled {
- if err := m.setupRegistry(); err != nil {
- return nil, err
- }
- }
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- go m.watchConfig()
-
- db, err := openDB(dbPath)
- if err != nil {
- return nil, fmt.Errorf("failed to open db: %w", err)
- }
- m.db = db
-
- if err := m.migrateHashes(); err != nil {
- log.Errorf("Failed to migrate hashes: %v", err)
- }
-
- if !config.Disabled {
- if config.ClearAtStartup {
- if err := m.clearHistoryInternal(); err != nil {
- log.Errorf("Failed to clear history at startup: %v", err)
- }
- }
-
- if config.AutoClearDays > 0 {
- if err := m.clearOldEntries(config.AutoClearDays); err != nil {
- log.Errorf("Failed to clear old entries: %v", err)
- }
- }
- }
-
- m.alive = true
- m.updateState()
-
- if !config.Disabled && m.dataControlMgr != nil && m.seat != nil {
- m.setupDataDeviceSync()
- }
-
- return m, nil
-}
-
-func openDB(path string) (*bolt.DB, error) {
- db, err := bolt.Open(path, 0o644, &bolt.Options{
- Timeout: 1 * time.Second,
- })
- if err != nil {
- return nil, err
- }
-
- err = db.Update(func(tx *bolt.Tx) error {
- _, err := tx.CreateBucketIfNotExists([]byte("clipboard"))
- return err
- })
- if err != nil {
- db.Close()
- return nil, err
- }
-
- return db, nil
-}
-
-func (m *Manager) post(fn func()) {
- m.wlCtx.Post(fn)
-}
-
-func (m *Manager) setupRegistry() error {
- ctx := m.display.Context()
-
- registry, err := m.display.GetRegistry()
- if err != nil {
- return fmt.Errorf("failed to get registry: %w", err)
- }
- m.registry = registry
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- switch e.Interface {
- case "ext_data_control_manager_v1":
- if e.Version < 1 {
- return
- }
- dataControlMgr := ext_data_control.NewExtDataControlManagerV1(ctx)
- if err := registry.Bind(e.Name, e.Interface, e.Version, dataControlMgr); err != nil {
- log.Errorf("Failed to bind ext_data_control_manager_v1: %v", err)
- return
- }
- m.dataControlMgr = dataControlMgr
- log.Info("Bound ext_data_control_manager_v1")
- case "wl_seat":
- seat := wlclient.NewSeat(ctx)
- if err := registry.Bind(e.Name, e.Interface, e.Version, seat); err != nil {
- log.Errorf("Failed to bind wl_seat: %v", err)
- return
- }
- m.seat = seat
- m.seatName = e.Name
- log.Info("Bound wl_seat")
- }
- })
-
- m.display.Roundtrip()
- m.display.Roundtrip()
-
- if m.dataControlMgr == nil {
- return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
- }
-
- if m.seat == nil {
- return fmt.Errorf("no seat available")
- }
-
- return nil
-}
-
-func (m *Manager) setupDataDeviceSync() {
- if m.dataControlMgr == nil || m.seat == nil {
- return
- }
-
- ctx := m.display.Context()
- dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
-
- dataDevice := ext_data_control.NewExtDataControlDeviceV1(ctx)
-
- dataDevice.SetDataOfferHandler(func(e ext_data_control.ExtDataControlDeviceV1DataOfferEvent) {
- if e.Id == nil {
- return
- }
-
- m.offerMutex.Lock()
- m.offerRegistry[e.Id.ID()] = e.Id
- m.offerMimeTypes[e.Id] = make([]string, 0)
- m.offerMutex.Unlock()
-
- e.Id.SetOfferHandler(func(me ext_data_control.ExtDataControlOfferV1OfferEvent) {
- m.offerMutex.Lock()
- m.offerMimeTypes[e.Id] = append(m.offerMimeTypes[e.Id], me.MimeType)
- m.offerMutex.Unlock()
- })
- })
-
- dataDevice.SetSelectionHandler(func(e ext_data_control.ExtDataControlDeviceV1SelectionEvent) {
- if !m.initialized {
- m.initialized = true
- return
- }
-
- var offer any
- if e.Id != nil {
- offer = e.Id
- } else if e.OfferId != 0 {
- m.offerMutex.RLock()
- offer = m.offerRegistry[e.OfferId]
- m.offerMutex.RUnlock()
- }
-
- m.ownerLock.Lock()
- wasOwner := m.isOwner
- m.ownerLock.Unlock()
-
- if offer == nil {
- return
- }
-
- if wasOwner {
- return
- }
-
- prevOffer := m.currentOffer
- m.currentOffer = offer
-
- if prevOffer != nil && prevOffer != offer {
- m.offerMutex.Lock()
- delete(m.offerMimeTypes, prevOffer)
- m.offerMutex.Unlock()
- }
-
- m.offerMutex.RLock()
- mimes := m.offerMimeTypes[offer]
- m.offerMutex.RUnlock()
-
- m.mimeTypes = mimes
-
- if len(mimes) == 0 {
- return
- }
-
- if m.hasSensitiveMimeType(mimes) {
- return
- }
-
- preferredMime := m.selectMimeType(mimes)
- if preferredMime == "" {
- return
- }
-
- typedOffer := offer.(*ext_data_control.ExtDataControlOfferV1)
-
- r, w, err := os.Pipe()
- if err != nil {
- return
- }
-
- if err := typedOffer.Receive(preferredMime, int(w.Fd())); err != nil {
- r.Close()
- w.Close()
- return
- }
- w.Close()
-
- go m.readAndStore(r, preferredMime)
- })
-
- if err := dataMgr.GetDataDeviceWithProxy(dataDevice, m.seat); err != nil {
- log.Errorf("Failed to send get_data_device request: %v", err)
- return
- }
-
- m.dataDevice = dataDevice
-
- if err := ctx.Dispatch(); err != nil {
- log.Errorf("Failed to dispatch initial events: %v", err)
- return
- }
-
- log.Info("Data device setup complete")
-}
-
-func (m *Manager) readAndStore(r *os.File, mimeType string) {
- defer r.Close()
-
- cfg := m.getConfig()
-
- done := make(chan []byte, 1)
- go func() {
- data, _ := io.ReadAll(r)
- done <- data
- }()
-
- var data []byte
- select {
- case data = <-done:
- case <-time.After(500 * time.Millisecond):
- return
- }
-
- if len(data) == 0 || int64(len(data)) > cfg.MaxEntrySize {
- return
- }
- if len(bytes.TrimSpace(data)) == 0 {
- return
- }
-
- if !cfg.Disabled && m.db != nil {
- m.storeClipboardEntry(data, mimeType)
- }
-
- m.updateState()
- m.notifySubscribers()
-}
-
-func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
- if mimeType == "text/uri-list" {
- if imgData, imgMime, ok := m.tryReadImageFromURI(data); ok {
- data = imgData
- mimeType = imgMime
- }
- }
-
- entry := Entry{
- Data: data,
- MimeType: mimeType,
- Size: len(data),
- Timestamp: time.Now(),
- IsImage: m.isImageMimeType(mimeType),
- }
-
- switch {
- case entry.IsImage:
- entry.Preview = m.imagePreview(data, mimeType)
- case mimeType == "text/uri-list":
- entry.Preview, entry.IsImage = m.uriListPreview(data)
- default:
- entry.Preview = m.textPreview(data)
- }
-
- if err := m.storeEntry(entry); err != nil {
- log.Errorf("Failed to store clipboard entry: %v", err)
- }
-}
-
-func (m *Manager) storeEntry(entry Entry) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- entry.Hash = computeHash(entry.Data)
-
- return m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
-
- if err := m.deduplicateInTx(b, entry.Hash); err != nil {
- return err
- }
-
- id, err := b.NextSequence()
- if err != nil {
- return err
- }
-
- entry.ID = id
-
- encoded, err := encodeEntry(entry)
- if err != nil {
- return err
- }
-
- if err := b.Put(itob(id), encoded); err != nil {
- return err
- }
-
- return m.trimLengthInTx(b)
- })
-}
-
-func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
- c := b.Cursor()
- for k, v := c.Last(); k != nil; k, v = c.Prev() {
- if extractHash(v) != hash {
- continue
- }
- entry, err := decodeEntry(v)
- if err == nil && entry.Pinned {
- continue
- }
- if err := b.Delete(k); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
- if m.config.MaxHistory < 0 {
- return nil
- }
- c := b.Cursor()
- var count int
- for k, v := c.Last(); k != nil; k, v = c.Prev() {
- entry, err := decodeEntry(v)
- if err == nil && entry.Pinned {
- continue
- }
- if count < m.config.MaxHistory {
- count++
- continue
- }
- if err := b.Delete(k); err != nil {
- return err
- }
- }
- return nil
-}
-
-func encodeEntry(e Entry) ([]byte, error) {
- buf := new(bytes.Buffer)
-
- binary.Write(buf, binary.BigEndian, e.ID)
- binary.Write(buf, binary.BigEndian, uint32(len(e.Data)))
- buf.Write(e.Data)
- binary.Write(buf, binary.BigEndian, uint32(len(e.MimeType)))
- buf.WriteString(e.MimeType)
- binary.Write(buf, binary.BigEndian, uint32(len(e.Preview)))
- buf.WriteString(e.Preview)
- binary.Write(buf, binary.BigEndian, int32(e.Size))
- binary.Write(buf, binary.BigEndian, e.Timestamp.Unix())
- if e.IsImage {
- buf.WriteByte(1)
- } else {
- buf.WriteByte(0)
- }
- binary.Write(buf, binary.BigEndian, e.Hash)
- if e.Pinned {
- buf.WriteByte(1)
- } else {
- buf.WriteByte(0)
- }
-
- return buf.Bytes(), nil
-}
-
-func decodeEntry(data []byte) (Entry, error) {
- buf := bytes.NewReader(data)
- var e Entry
-
- binary.Read(buf, binary.BigEndian, &e.ID)
-
- var dataLen uint32
- binary.Read(buf, binary.BigEndian, &dataLen)
- e.Data = make([]byte, dataLen)
- buf.Read(e.Data)
-
- var mimeLen uint32
- binary.Read(buf, binary.BigEndian, &mimeLen)
- mimeBytes := make([]byte, mimeLen)
- buf.Read(mimeBytes)
- e.MimeType = string(mimeBytes)
-
- var prevLen uint32
- binary.Read(buf, binary.BigEndian, &prevLen)
- prevBytes := make([]byte, prevLen)
- buf.Read(prevBytes)
- e.Preview = string(prevBytes)
-
- var size int32
- binary.Read(buf, binary.BigEndian, &size)
- e.Size = int(size)
-
- var timestamp int64
- binary.Read(buf, binary.BigEndian, &timestamp)
- e.Timestamp = time.Unix(timestamp, 0)
-
- var isImage byte
- binary.Read(buf, binary.BigEndian, &isImage)
- e.IsImage = isImage == 1
-
- if buf.Len() >= 8 {
- binary.Read(buf, binary.BigEndian, &e.Hash)
- }
-
- if buf.Len() >= 1 {
- var pinnedByte byte
- binary.Read(buf, binary.BigEndian, &pinnedByte)
- e.Pinned = pinnedByte == 1
- }
-
- return e, nil
-}
-
-func itob(v uint64) []byte {
- b := make([]byte, 8)
- binary.BigEndian.PutUint64(b, v)
- return b
-}
-
-func computeHash(data []byte) uint64 {
- h := fnv.New64a()
- h.Write(data)
- return h.Sum64()
-}
-
-func extractHash(data []byte) uint64 {
- if len(data) < 9 {
- return 0
- }
- return binary.BigEndian.Uint64(data[len(data)-9 : len(data)-1])
-}
-
-func (m *Manager) hasSensitiveMimeType(mimes []string) bool {
- return slices.ContainsFunc(mimes, func(mime string) bool {
- return slices.Contains(sensitiveMimeTypes, mime)
- })
-}
-
-func (m *Manager) selectMimeType(mimes []string) string {
- preferredTypes := []string{
- "text/uri-list",
- "text/plain;charset=utf-8",
- "text/plain",
- "UTF8_STRING",
- "STRING",
- "TEXT",
- "image/png",
- "image/jpeg",
- "image/gif",
- "image/bmp",
- "image/tiff",
- }
-
- for _, pref := range preferredTypes {
- for _, mime := range mimes {
- if mime == pref {
- return mime
- }
- }
- }
-
- // Skip useless MIME types when falling back
- for _, mime := range mimes {
- switch mime {
- case "application/vnd.portal.filetransfer":
- continue
- default:
- return mime
- }
- }
-
- return ""
-}
-
-func (m *Manager) isImageMimeType(mime string) bool {
- return strings.HasPrefix(mime, "image/")
-}
-
-func (m *Manager) textPreview(data []byte) string {
- text := string(data)
- text = strings.TrimSpace(text)
- text = strings.Join(strings.Fields(text), " ")
-
- if len(text) > 100 {
- return text[:100] + "…"
- }
- return text
-}
-
-func (m *Manager) imagePreview(data []byte, format string) string {
- config, imgFmt, err := image.DecodeConfig(bytes.NewReader(data))
- if err != nil {
- return fmt.Sprintf("[[ image %s %s ]]", sizeStr(len(data)), format)
- }
- return fmt.Sprintf("[[ image %s %s %dx%d ]]", sizeStr(len(data)), imgFmt, config.Width, config.Height)
-}
-
-func (m *Manager) uriListPreview(data []byte) (string, bool) {
- text := strings.TrimSpace(string(data))
- uris := strings.Split(text, "\r\n")
- if len(uris) == 0 {
- uris = strings.Split(text, "\n")
- }
-
- if len(uris) > 1 {
- return fmt.Sprintf("[[ %d files ]]", len(uris)), false
- }
-
- if len(uris) == 1 && strings.HasPrefix(uris[0], "file://") {
- filePath := strings.TrimPrefix(uris[0], "file://")
- info, err := os.Stat(filePath)
- if err != nil || info.IsDir() {
- return m.textPreview(data), false
- }
-
- cfg := m.getConfig()
- if info.Size() <= cfg.MaxEntrySize {
- if imgData, err := os.ReadFile(filePath); err == nil {
- if config, imgFmt, err := image.DecodeConfig(bytes.NewReader(imgData)); err == nil {
- return fmt.Sprintf("[[ file %s %s %dx%d ]]", filepath.Base(filePath), imgFmt, config.Width, config.Height), true
- }
- }
- }
- return fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)), false
- }
-
- return m.textPreview(data), false
-}
-
-func (m *Manager) tryReadImageFromURI(data []byte) ([]byte, string, bool) {
- text := strings.TrimSpace(string(data))
- uris := strings.Split(text, "\r\n")
- if len(uris) == 0 {
- uris = strings.Split(text, "\n")
- }
-
- if len(uris) != 1 || !strings.HasPrefix(uris[0], "file://") {
- return nil, "", false
- }
-
- filePath := strings.TrimPrefix(uris[0], "file://")
- info, err := os.Stat(filePath)
- if err != nil || info.IsDir() {
- return nil, "", false
- }
-
- cfg := m.getConfig()
- if info.Size() > cfg.MaxEntrySize {
- return nil, "", false
- }
-
- imgData, err := os.ReadFile(filePath)
- if err != nil {
- return nil, "", false
- }
-
- _, imgFmt, err := image.DecodeConfig(bytes.NewReader(imgData))
- if err != nil {
- return nil, "", false
- }
-
- return imgData, "image/" + imgFmt, true
-}
-
-func sizeStr(size int) string {
- units := []string{"B", "KiB", "MiB"}
- var i int
- fsize := float64(size)
- for fsize >= 1024 && i < len(units)-1 {
- fsize /= 1024
- i++
- }
- return fmt.Sprintf("%.0f %s", fsize, units[i])
-}
-
-func (m *Manager) updateState() {
- history := m.GetHistory()
-
- for i := range history {
- history[i].Data = nil
- }
-
- var current *Entry
- if len(history) > 0 {
- c := history[0]
- c.Data = nil
- current = &c
- }
-
- newState := &State{
- Enabled: m.alive,
- History: history,
- Current: current,
- }
-
- m.stateMutex.Lock()
- m.state = newState
- m.stateMutex.Unlock()
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
-
- for range m.dirty {
- state := m.GetState()
-
- if m.lastState != nil && stateEqual(m.lastState, &state) {
- continue
- }
-
- m.lastState = &state
-
- m.subMutex.RLock()
- subs := make([]chan State, 0, len(m.subscribers))
- for _, ch := range m.subscribers {
- subs = append(subs, ch)
- }
- m.subMutex.RUnlock()
-
- for _, ch := range subs {
- select {
- case ch <- state:
- default:
- }
- }
- }
-}
-
-func stateEqual(a, b *State) bool {
- if a == nil || b == nil {
- return false
- }
- if a.Enabled != b.Enabled {
- return false
- }
- if len(a.History) != len(b.History) {
- return false
- }
- return true
-}
-
-func (m *Manager) GetHistory() []Entry {
- if m.db == nil {
- return nil
- }
-
- cfg := m.getConfig()
- var cutoff time.Time
- if cfg.AutoClearDays > 0 {
- cutoff = time.Now().AddDate(0, 0, -cfg.AutoClearDays)
- }
-
- var history []Entry
- var stale []uint64
-
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- c := b.Cursor()
-
- for k, v := c.Last(); k != nil; k, v = c.Prev() {
- entry, err := decodeEntry(v)
- if err != nil {
- continue
- }
- if !cutoff.IsZero() && entry.Timestamp.Before(cutoff) {
- stale = append(stale, entry.ID)
- continue
- }
- history = append(history, entry)
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to read clipboard history: %v", err)
- }
-
- if len(stale) > 0 {
- go m.deleteStaleEntries(stale)
- }
-
- return history
-}
-
-func (m *Manager) deleteStaleEntries(ids []uint64) {
- if m.db == nil {
- return
- }
-
- if err := m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- for _, id := range ids {
- if err := b.Delete(itob(id)); err != nil {
- log.Errorf("Failed to delete stale entry %d: %v", id, err)
- }
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to delete stale entries: %v", err)
- }
-}
-
-func (m *Manager) GetEntry(id uint64) (*Entry, error) {
- if m.db == nil {
- return nil, fmt.Errorf("database not available")
- }
-
- var entry Entry
- var found bool
-
- err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- v := b.Get(itob(id))
- if v == nil {
- return nil
- }
-
- var err error
- entry, err = decodeEntry(v)
- if err != nil {
- return err
- }
- found = true
- return nil
- })
-
- if err != nil {
- return nil, err
- }
- if !found {
- return nil, fmt.Errorf("entry not found")
- }
-
- return &entry, nil
-}
-
-func (m *Manager) DeleteEntry(id uint64) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- err := m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- return b.Delete(itob(id))
- })
-
- if err == nil {
- m.updateState()
- m.notifySubscribers()
- }
-
- return err
-}
-
-func (m *Manager) TouchEntry(id uint64) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- entry, err := m.GetEntry(id)
- if err != nil {
- return err
- }
-
- entry.Timestamp = time.Now()
-
- if err := m.storeEntry(*entry); err != nil {
- return err
- }
-
- m.updateState()
- m.notifySubscribers()
-
- return nil
-}
-
-func (m *Manager) CreateHistoryEntryFromPinned(pinnedEntry *Entry) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- // Create a new unpinned entry with the same data
- newEntry := Entry{
- Data: pinnedEntry.Data,
- MimeType: pinnedEntry.MimeType,
- Size: pinnedEntry.Size,
- Timestamp: time.Now(),
- IsImage: pinnedEntry.IsImage,
- Preview: pinnedEntry.Preview,
- Pinned: false,
- }
-
- if err := m.storeEntryWithoutDedup(newEntry); err != nil {
- return err
- }
-
- m.updateState()
- m.notifySubscribers()
-
- return nil
-}
-
-func (m *Manager) storeEntryWithoutDedup(entry Entry) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- entry.Hash = computeHash(entry.Data)
-
- return m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
-
- id, err := b.NextSequence()
- if err != nil {
- return err
- }
-
- entry.ID = id
-
- encoded, err := encodeEntry(entry)
- if err != nil {
- return err
- }
-
- if err := b.Put(itob(id), encoded); err != nil {
- return err
- }
-
- return m.trimLengthInTx(b)
- })
-}
-
-func (m *Manager) ClearHistory() {
- if m.db == nil {
- return
- }
-
- // Delete only non-pinned entries
- if err := m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
-
- var toDelete [][]byte
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, err := decodeEntry(v)
- if err != nil || !entry.Pinned {
- toDelete = append(toDelete, k)
- }
- }
-
- for _, k := range toDelete {
- if err := b.Delete(k); err != nil {
- return err
- }
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to clear clipboard history: %v", err)
- return
- }
-
- pinnedCount := 0
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b != nil {
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, _ := decodeEntry(v)
- if entry.Pinned {
- pinnedCount++
- }
- }
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to count pinned entries: %v", err)
- }
-
- if pinnedCount == 0 {
- if err := m.compactDB(); err != nil {
- log.Errorf("Failed to compact database: %v", err)
- }
- }
-
- m.updateState()
- m.notifySubscribers()
-}
-
-func (m *Manager) compactDB() error {
- m.db.Close()
-
- tmpPath := m.dbPath + ".compact"
- defer os.Remove(tmpPath)
-
- srcDB, err := bolt.Open(m.dbPath, 0o644, &bolt.Options{ReadOnly: true, Timeout: time.Second})
- if err != nil {
- m.db, _ = bolt.Open(m.dbPath, 0o644, &bolt.Options{Timeout: time.Second})
- return fmt.Errorf("open source: %w", err)
- }
-
- dstDB, err := bolt.Open(tmpPath, 0o644, &bolt.Options{Timeout: time.Second})
- if err != nil {
- srcDB.Close()
- m.db, _ = bolt.Open(m.dbPath, 0o644, &bolt.Options{Timeout: time.Second})
- return fmt.Errorf("open destination: %w", err)
- }
-
- if err := bolt.Compact(dstDB, srcDB, 0); err != nil {
- srcDB.Close()
- dstDB.Close()
- m.db, _ = bolt.Open(m.dbPath, 0o644, &bolt.Options{Timeout: time.Second})
- return fmt.Errorf("compact: %w", err)
- }
-
- srcDB.Close()
- dstDB.Close()
-
- if err := os.Rename(tmpPath, m.dbPath); err != nil {
- m.db, _ = bolt.Open(m.dbPath, 0o644, &bolt.Options{Timeout: time.Second})
- return fmt.Errorf("rename: %w", err)
- }
-
- m.db, err = bolt.Open(m.dbPath, 0o644, &bolt.Options{Timeout: time.Second})
- if err != nil {
- return fmt.Errorf("reopen: %w", err)
- }
-
- return nil
-}
-
-func (m *Manager) SetClipboard(data []byte, mimeType string) error {
- if int64(len(data)) > m.config.MaxEntrySize {
- return fmt.Errorf("data too large")
- }
-
- dataCopy := make([]byte, len(data))
- copy(dataCopy, data)
-
- m.post(func() {
- if m.dataControlMgr == nil || m.dataDevice == nil {
- log.Error("Data control manager or device not initialized")
- return
- }
-
- dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
-
- source, err := dataMgr.CreateDataSource()
- if err != nil {
- log.Errorf("Failed to create data source: %v", err)
- return
- }
-
- if err := source.Offer(mimeType); err != nil {
- log.Errorf("Failed to offer mime type: %v", err)
- return
- }
-
- source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
- fd := e.Fd
- defer syscall.Close(fd)
-
- file := os.NewFile(uintptr(fd), "clipboard-pipe")
- defer file.Close()
-
- if _, err := file.Write(dataCopy); err != nil {
- log.Errorf("Failed to write clipboard data: %v", err)
- }
- })
-
- source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
- m.ownerLock.Lock()
- m.isOwner = false
- m.ownerLock.Unlock()
- })
-
- m.currentSource = source
- m.sourceMutex.Lock()
- m.sourceMimeTypes = []string{mimeType}
- m.sourceMutex.Unlock()
-
- m.ownerLock.Lock()
- m.isOwner = true
- m.ownerLock.Unlock()
-
- device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
- if err := device.SetSelection(source); err != nil {
- log.Errorf("Failed to set selection: %v", err)
- }
- })
-
- return nil
-}
-
-func (m *Manager) CopyText(text string) error {
- if err := m.SetClipboard([]byte(text), "text/plain;charset=utf-8"); err != nil {
- return err
- }
-
- entry := Entry{
- Data: []byte(text),
- MimeType: "text/plain;charset=utf-8",
- Size: len(text),
- Timestamp: time.Now(),
- IsImage: false,
- Preview: m.textPreview([]byte(text)),
- }
-
- if err := m.storeEntry(entry); err != nil {
- log.Errorf("Failed to store clipboard entry: %v", err)
- }
-
- m.updateState()
- m.notifySubscribers()
-
- return nil
-}
-
-func (m *Manager) PasteText() (string, error) {
- history := m.GetHistory()
- if len(history) == 0 {
- return "", fmt.Errorf("no clipboard data available")
- }
-
- entry := history[0]
- if entry.IsImage {
- return "", fmt.Errorf("clipboard contains image, not text")
- }
-
- fullEntry, err := m.GetEntry(entry.ID)
- if err != nil {
- return "", err
- }
-
- return string(fullEntry.Data), nil
-}
-
-func (m *Manager) Close() {
- if !m.alive {
- return
- }
-
- m.alive = false
- close(m.stopChan)
-
- close(m.dirty)
- m.notifierWg.Wait()
-
- m.subMutex.Lock()
- for _, ch := range m.subscribers {
- close(ch)
- }
- m.subscribers = make(map[string]chan State)
- m.subMutex.Unlock()
-
- if m.currentSource != nil {
- source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
- source.Destroy()
- }
-
- if m.dataDevice != nil {
- device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
- device.Destroy()
- }
-
- if m.dataControlMgr != nil {
- mgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
- mgr.Destroy()
- }
-
- if m.registry != nil {
- m.registry.Destroy()
- }
-
- if m.db != nil {
- m.db.Close()
- }
-}
-
-func (m *Manager) clearHistoryInternal() error {
- return m.db.Update(func(tx *bolt.Tx) error {
- if err := tx.DeleteBucket([]byte("clipboard")); err != nil {
- return err
- }
- _, err := tx.CreateBucket([]byte("clipboard"))
- return err
- })
-}
-
-func (m *Manager) clearOldEntries(days int) error {
- cutoff := time.Now().AddDate(0, 0, -days)
-
- return m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
-
- var toDelete [][]byte
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, err := decodeEntry(v)
- if err != nil {
- continue
- }
- // Skip pinned entries
- if entry.Pinned {
- continue
- }
- if entry.Timestamp.Before(cutoff) {
- toDelete = append(toDelete, k)
- }
- }
-
- for _, k := range toDelete {
- if err := b.Delete(k); err != nil {
- return err
- }
- }
- return nil
- })
-}
-
-func (m *Manager) migrateHashes() error {
- if m.db == nil {
- return nil
- }
-
- var needsMigration bool
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- if extractHash(v) == 0 {
- needsMigration = true
- return nil
- }
- }
- return nil
- }); err != nil {
- return err
- }
-
- if !needsMigration {
- return nil
- }
-
- log.Info("Migrating clipboard entries to add hashes...")
-
- return m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
-
- var updates []struct {
- key []byte
- entry Entry
- }
-
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, err := decodeEntry(v)
- if err != nil {
- continue
- }
- if entry.Hash != 0 {
- continue
- }
- entry.Hash = computeHash(entry.Data)
- keyCopy := make([]byte, len(k))
- copy(keyCopy, k)
- updates = append(updates, struct {
- key []byte
- entry Entry
- }{keyCopy, entry})
- }
-
- for _, u := range updates {
- encoded, err := encodeEntry(u.entry)
- if err != nil {
- continue
- }
- if err := b.Put(u.key, encoded); err != nil {
- return err
- }
- }
-
- log.Infof("Migrated %d clipboard entries", len(updates))
- return nil
- })
-}
-
-func (m *Manager) Search(params SearchParams) SearchResult {
- if m.db == nil {
- return SearchResult{}
- }
-
- if params.Limit <= 0 {
- params.Limit = 50
- }
- if params.Limit > 500 {
- params.Limit = 500
- }
-
- query := strings.ToLower(params.Query)
- mimeFilter := strings.ToLower(params.MimeType)
-
- var all []Entry
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
-
- c := b.Cursor()
- for k, v := c.Last(); k != nil; k, v = c.Prev() {
- entry, err := decodeEntry(v)
- if err != nil {
- continue
- }
-
- if params.IsImage != nil && entry.IsImage != *params.IsImage {
- continue
- }
-
- if mimeFilter != "" && !strings.Contains(strings.ToLower(entry.MimeType), mimeFilter) {
- continue
- }
-
- if params.Before != nil && entry.Timestamp.Unix() >= *params.Before {
- continue
- }
-
- if params.After != nil && entry.Timestamp.Unix() <= *params.After {
- continue
- }
-
- if query != "" && !strings.Contains(strings.ToLower(entry.Preview), query) {
- continue
- }
-
- entry.Data = nil
- all = append(all, entry)
- }
- return nil
- }); err != nil {
- log.Errorf("Search failed: %v", err)
- }
-
- total := len(all)
-
- start := params.Offset
- if start > total {
- start = total
- }
- end := start + params.Limit
- if end > total {
- end = total
- }
-
- return SearchResult{
- Entries: all[start:end],
- Total: total,
- HasMore: end < total,
- }
-}
-
-func (m *Manager) GetConfig() Config {
- return m.config
-}
-
-func (m *Manager) SetConfig(cfg Config) error {
- m.configMutex.Lock()
- m.config = cfg
- m.configMutex.Unlock()
-
- m.updateState()
- m.notifySubscribers()
-
- return SaveConfig(cfg)
-}
-
-func (m *Manager) getConfig() Config {
- m.configMutex.RLock()
- defer m.configMutex.RUnlock()
- return m.config
-}
-
-func (m *Manager) watchConfig() {
- if m.configPath == "" {
- return
- }
-
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- log.Warnf("Failed to create config watcher: %v", err)
- return
- }
- defer watcher.Close()
-
- configDir := filepath.Dir(m.configPath)
- if err := watcher.Add(configDir); err != nil {
- log.Warnf("Failed to watch config directory: %v", err)
- return
- }
-
- for {
- select {
- case <-m.stopChan:
- return
- case event, ok := <-watcher.Events:
- if !ok {
- return
- }
- if event.Name != m.configPath {
- continue
- }
- if event.Op&(fsnotify.Write|fsnotify.Create) == 0 {
- continue
- }
- newCfg := LoadConfig()
- m.applyConfigChange(newCfg)
- case err, ok := <-watcher.Errors:
- if !ok {
- return
- }
- log.Warnf("Config watcher error: %v", err)
- }
- }
-}
-
-func (m *Manager) applyConfigChange(newCfg Config) {
- m.configMutex.Lock()
- oldCfg := m.config
- m.config = newCfg
- m.configMutex.Unlock()
-
- switch {
- case newCfg.Disabled && !oldCfg.Disabled:
- log.Info("Clipboard tracking disabled")
- case !newCfg.Disabled && oldCfg.Disabled:
- log.Info("Clipboard tracking enabled")
- }
-
- m.updateState()
- m.notifySubscribers()
-}
-
-func (m *Manager) StoreData(data []byte, mimeType string) error {
- cfg := m.getConfig()
-
- if cfg.Disabled {
- return fmt.Errorf("clipboard tracking disabled")
- }
-
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- if len(data) == 0 {
- return nil
- }
-
- if int64(len(data)) > cfg.MaxEntrySize {
- return fmt.Errorf("data too large")
- }
-
- if len(bytes.TrimSpace(data)) == 0 {
- return nil
- }
-
- entry := Entry{
- Data: data,
- MimeType: mimeType,
- Size: len(data),
- Timestamp: time.Now(),
- IsImage: m.isImageMimeType(mimeType),
- }
-
- switch {
- case entry.IsImage:
- entry.Preview = m.imagePreview(data, mimeType)
- case mimeType == "text/uri-list":
- entry.Preview, entry.IsImage = m.uriListPreview(data)
- default:
- entry.Preview = m.textPreview(data)
- }
-
- if err := m.storeEntry(entry); err != nil {
- return err
- }
-
- m.updateState()
- m.notifySubscribers()
-
- return nil
-}
-
-func (m *Manager) PinEntry(id uint64) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- entryToPin, err := m.GetEntry(id)
- if err != nil {
- return err
- }
-
- var hashExists bool
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, err := decodeEntry(v)
- if err != nil || !entry.Pinned {
- continue
- }
- if entry.Hash == entryToPin.Hash {
- hashExists = true
- return nil
- }
- }
- return nil
- }); err != nil {
- return err
- }
-
- if hashExists {
- return nil
- }
-
- // Check pinned count
- cfg := m.getConfig()
- pinnedCount := 0
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, err := decodeEntry(v)
- if err == nil && entry.Pinned {
- pinnedCount++
- }
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to count pinned entries: %v", err)
- }
-
- if pinnedCount >= cfg.MaxPinned {
- return fmt.Errorf("maximum pinned entries reached (%d)", cfg.MaxPinned)
- }
-
- err = m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- v := b.Get(itob(id))
- if v == nil {
- return fmt.Errorf("entry not found")
- }
-
- entry, err := decodeEntry(v)
- if err != nil {
- return err
- }
-
- entry.Pinned = true
- encoded, err := encodeEntry(entry)
- if err != nil {
- return err
- }
-
- return b.Put(itob(id), encoded)
- })
-
- if err == nil {
- m.updateState()
- m.notifySubscribers()
- }
-
- return err
-}
-
-func (m *Manager) UnpinEntry(id uint64) error {
- if m.db == nil {
- return fmt.Errorf("database not available")
- }
-
- err := m.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- v := b.Get(itob(id))
- if v == nil {
- return fmt.Errorf("entry not found")
- }
-
- entry, err := decodeEntry(v)
- if err != nil {
- return err
- }
-
- entry.Pinned = false
- encoded, err := encodeEntry(entry)
- if err != nil {
- return err
- }
-
- return b.Put(itob(id), encoded)
- })
-
- if err == nil {
- m.updateState()
- m.notifySubscribers()
- }
-
- return err
-}
-
-func (m *Manager) GetPinnedEntries() []Entry {
- if m.db == nil {
- return nil
- }
-
- var pinned []Entry
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
-
- c := b.Cursor()
- for k, v := c.Last(); k != nil; k, v = c.Prev() {
- entry, err := decodeEntry(v)
- if err != nil {
- continue
- }
- if entry.Pinned {
- entry.Data = nil
- pinned = append(pinned, entry)
- }
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to get pinned entries: %v", err)
- }
-
- return pinned
-}
-
-func (m *Manager) GetPinnedCount() int {
- if m.db == nil {
- return 0
- }
-
- count := 0
- if err := m.db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("clipboard"))
- if b == nil {
- return nil
- }
-
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- entry, err := decodeEntry(v)
- if err == nil && entry.Pinned {
- count++
- }
- }
- return nil
- }); err != nil {
- log.Errorf("Failed to count pinned entries: %v", err)
- }
-
- return count
-}
-
-func (m *Manager) CopyFile(filePath string) error {
- fileInfo, err := os.Stat(filePath)
- if err != nil {
- return fmt.Errorf("file not found: %w", err)
- }
-
- cfg := m.getConfig()
- if fileInfo.Size() > cfg.MaxEntrySize {
- return fmt.Errorf("file too large: %d > %d", fileInfo.Size(), cfg.MaxEntrySize)
- }
-
- fileData, err := os.ReadFile(filePath)
- if err != nil {
- return fmt.Errorf("read file: %w", err)
- }
-
- exportedPath, err := m.ExportFileForFlatpak(filePath)
- if err != nil {
- exportedPath = filePath
- }
- fileURI := "file://" + exportedPath
-
- if imgData, imgMime, ok := m.tryReadImageFromURI([]byte("file://" + filePath)); ok {
- entry := Entry{
- Data: imgData,
- MimeType: imgMime,
- Size: len(imgData),
- Timestamp: time.Now(),
- IsImage: true,
- Preview: m.imagePreview(imgData, imgMime),
- }
- if err := m.storeEntry(entry); err != nil {
- log.Errorf("Failed to store file entry: %v", err)
- }
- } else {
- entry := Entry{
- Data: fileData,
- MimeType: "text/uri-list",
- Size: len(fileData),
- Timestamp: time.Now(),
- IsImage: false,
- Preview: fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)),
- }
- if err := m.storeEntry(entry); err != nil {
- log.Errorf("Failed to store file entry: %v", err)
- }
- }
-
- m.updateState()
- m.notifySubscribers()
-
- _, imgMime, imgErr := image.DecodeConfig(bytes.NewReader(fileData))
-
- m.post(func() {
- if m.dataControlMgr == nil || m.dataDevice == nil {
- log.Error("Data control manager or device not initialized")
- return
- }
-
- dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
- source, err := dataMgr.CreateDataSource()
- if err != nil {
- log.Errorf("Failed to create data source: %v", err)
- return
- }
-
- type offer struct {
- mime string
- data []byte
- }
- offers := []offer{
- {"x-special/gnome-copied-files", []byte("copy\n" + fileURI)},
- {"text/uri-list", []byte(fileURI + "\r\n")},
- {"text/plain", []byte(filePath)},
- }
-
- if imgErr == nil {
- imgMimeType := "image/" + imgMime
- offers = append(offers, offer{imgMimeType, fileData})
- }
-
- offerData := make(map[string][]byte)
- for _, o := range offers {
- if err := source.Offer(o.mime); err != nil {
- log.Errorf("Failed to offer %s: %v", o.mime, err)
- return
- }
- offerData[o.mime] = o.data
- }
-
- source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
- fd := e.Fd
- defer syscall.Close(fd)
- file := os.NewFile(uintptr(fd), "clipboard-pipe")
- defer file.Close()
- if data, ok := offerData[e.MimeType]; ok {
- file.Write(data)
- }
- })
-
- source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
- m.ownerLock.Lock()
- m.isOwner = false
- m.ownerLock.Unlock()
- })
-
- m.currentSource = source
-
- m.ownerLock.Lock()
- m.isOwner = true
- m.ownerLock.Unlock()
-
- device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
- if err := device.SetSelection(source); err != nil {
- log.Errorf("Failed to set selection: %v", err)
- }
- })
-
- return nil
-}
-
-func (m *Manager) EntryToFile(entry *Entry) string {
- switch {
- case entry.MimeType == "text/uri-list":
- data := strings.TrimSpace(string(entry.Data))
- lines := strings.Split(data, "\n")
- if len(lines) == 0 {
- return ""
- }
- uri := strings.TrimSuffix(strings.TrimSpace(lines[0]), "\r")
- if path, ok := strings.CutPrefix(uri, "file://"); ok {
- return path
- }
- case entry.IsImage:
- ext := ".png"
- if suffix, ok := strings.CutPrefix(entry.MimeType, "image/"); ok {
- ext = "." + suffix
- }
- cacheDir, err := os.UserCacheDir()
- if err != nil {
- return ""
- }
- clipDir := filepath.Join(cacheDir, "dms", "clipboard")
- if err := os.MkdirAll(clipDir, 0o755); err != nil {
- return ""
- }
- filePath := filepath.Join(clipDir, fmt.Sprintf("%d%s", time.Now().UnixNano(), ext))
- if os.WriteFile(filePath, entry.Data, 0o644) != nil {
- return ""
- }
- return filePath
- }
- return ""
-}
-
-func (m *Manager) ExportFileForFlatpak(filePath string) (string, error) {
- if _, err := os.Stat(filePath); err != nil {
- return "", fmt.Errorf("file not found: %w", err)
- }
-
- if m.dbusConn == nil {
- conn, err := dbus.ConnectSessionBus()
- if err != nil {
- return "", fmt.Errorf("connect session bus: %w", err)
- }
- if !conn.SupportsUnixFDs() {
- conn.Close()
- return "", fmt.Errorf("D-Bus connection does not support Unix FD passing")
- }
- m.dbusConn = conn
- }
-
- file, err := os.Open(filePath)
- if err != nil {
- return "", fmt.Errorf("open file: %w", err)
- }
- fd := int(file.Fd())
-
- portal := m.dbusConn.Object("org.freedesktop.portal.Documents", "/org/freedesktop/portal/documents")
-
- var docIds []string
- var extra map[string]dbus.Variant
- flags := uint32(0)
-
- err = portal.Call(
- "org.freedesktop.portal.Documents.AddFull",
- 0,
- []dbus.UnixFD{dbus.UnixFD(fd)},
- flags,
- "",
- []string{},
- ).Store(&docIds, &extra)
-
- file.Close()
-
- if err != nil {
- return "", fmt.Errorf("AddFull: %w", err)
- }
-
- if len(docIds) == 0 {
- return "", fmt.Errorf("no doc IDs returned")
- }
-
- docId := docIds[0]
-
- for _, app := range getInstalledFlatpaks() {
- _ = portal.Call(
- "org.freedesktop.portal.Documents.GrantPermissions",
- 0,
- docId,
- app,
- []string{"read"},
- ).Err
- }
-
- uid := os.Getuid()
- basename := filepath.Base(filePath)
- exportedPath := fmt.Sprintf("/run/user/%d/doc/%s/%s", uid, docId, basename)
-
- return exportedPath, nil
-}
-
-func getInstalledFlatpaks() []string {
- out, err := exec.Command("flatpak", "list", "--app", "--columns=application").Output()
- if err != nil {
- return nil
- }
-
- var apps []string
- for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
- if app := strings.TrimSpace(line); app != "" {
- apps = append(apps, app)
- }
- }
- return apps
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager_test.go
deleted file mode 100644
index db45bca..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/manager_test.go
+++ /dev/null
@@ -1,531 +0,0 @@
-package clipboard
-
-import (
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-
- mocks_wlcontext "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlcontext"
-)
-
-func TestEncodeDecodeEntry_Roundtrip(t *testing.T) {
- original := Entry{
- ID: 12345,
- Data: []byte("hello world"),
- MimeType: "text/plain;charset=utf-8",
- Preview: "hello world",
- Size: 11,
- Timestamp: time.Now().Truncate(time.Second),
- IsImage: false,
- }
-
- encoded, err := encodeEntry(original)
- assert.NoError(t, err)
-
- decoded, err := decodeEntry(encoded)
- assert.NoError(t, err)
-
- assert.Equal(t, original.ID, decoded.ID)
- assert.Equal(t, original.Data, decoded.Data)
- assert.Equal(t, original.MimeType, decoded.MimeType)
- assert.Equal(t, original.Preview, decoded.Preview)
- assert.Equal(t, original.Size, decoded.Size)
- assert.Equal(t, original.Timestamp.Unix(), decoded.Timestamp.Unix())
- assert.Equal(t, original.IsImage, decoded.IsImage)
-}
-
-func TestEncodeDecodeEntry_Image(t *testing.T) {
- original := Entry{
- ID: 99999,
- Data: []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
- MimeType: "image/png",
- Preview: "[[ image 8 B png 100x100 ]]",
- Size: 8,
- Timestamp: time.Now().Truncate(time.Second),
- IsImage: true,
- }
-
- encoded, err := encodeEntry(original)
- assert.NoError(t, err)
-
- decoded, err := decodeEntry(encoded)
- assert.NoError(t, err)
-
- assert.Equal(t, original.ID, decoded.ID)
- assert.Equal(t, original.Data, decoded.Data)
- assert.True(t, decoded.IsImage)
- assert.Equal(t, original.Preview, decoded.Preview)
-}
-
-func TestEncodeDecodeEntry_EmptyData(t *testing.T) {
- original := Entry{
- ID: 1,
- Data: []byte{},
- MimeType: "text/plain",
- Preview: "",
- Size: 0,
- Timestamp: time.Now().Truncate(time.Second),
- IsImage: false,
- }
-
- encoded, err := encodeEntry(original)
- assert.NoError(t, err)
-
- decoded, err := decodeEntry(encoded)
- assert.NoError(t, err)
-
- assert.Equal(t, original.ID, decoded.ID)
- assert.Empty(t, decoded.Data)
-}
-
-func TestEncodeDecodeEntry_LargeData(t *testing.T) {
- largeData := make([]byte, 100000)
- for i := range largeData {
- largeData[i] = byte(i % 256)
- }
-
- original := Entry{
- ID: 777,
- Data: largeData,
- MimeType: "application/octet-stream",
- Preview: "binary data...",
- Size: len(largeData),
- Timestamp: time.Now().Truncate(time.Second),
- IsImage: false,
- }
-
- encoded, err := encodeEntry(original)
- assert.NoError(t, err)
-
- decoded, err := decodeEntry(encoded)
- assert.NoError(t, err)
-
- assert.Equal(t, original.Data, decoded.Data)
- assert.Equal(t, original.Size, decoded.Size)
-}
-
-func TestStateEqual_BothNil(t *testing.T) {
- assert.False(t, stateEqual(nil, nil))
-}
-
-func TestStateEqual_OneNil(t *testing.T) {
- s := &State{Enabled: true}
- assert.False(t, stateEqual(s, nil))
- assert.False(t, stateEqual(nil, s))
-}
-
-func TestStateEqual_EnabledDiffers(t *testing.T) {
- a := &State{Enabled: true, History: []Entry{}}
- b := &State{Enabled: false, History: []Entry{}}
- assert.False(t, stateEqual(a, b))
-}
-
-func TestStateEqual_HistoryLengthDiffers(t *testing.T) {
- a := &State{Enabled: true, History: []Entry{{ID: 1}}}
- b := &State{Enabled: true, History: []Entry{}}
- assert.False(t, stateEqual(a, b))
-}
-
-func TestStateEqual_BothEqual(t *testing.T) {
- a := &State{Enabled: true, History: []Entry{{ID: 1}, {ID: 2}}}
- b := &State{Enabled: true, History: []Entry{{ID: 3}, {ID: 4}}}
- assert.True(t, stateEqual(a, b))
-}
-
-func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
- m := &Manager{
- subscribers: make(map[string]chan State),
- dirty: make(chan struct{}, 1),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- subID := string(rune('a' + id))
- ch := m.Subscribe(subID)
- assert.NotNil(t, ch)
- time.Sleep(time.Millisecond)
- m.Unsubscribe(subID)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentGetState(t *testing.T) {
- m := &Manager{
- state: &State{
- Enabled: true,
- History: []Entry{{ID: 1}, {ID: 2}},
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- s := m.GetState()
- _ = s.Enabled
- _ = len(s.History)
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.stateMutex.Lock()
- m.state = &State{
- Enabled: j%2 == 0,
- History: []Entry{{ID: uint64(j)}},
- }
- m.stateMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentConfigAccess(t *testing.T) {
- m := &Manager{
- config: DefaultConfig(),
- }
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- cfg := m.getConfig()
- _ = cfg.MaxHistory
- _ = cfg.MaxEntrySize
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.configMutex.Lock()
- m.config.MaxHistory = 50 + j
- m.config.MaxEntrySize = int64(1024 * j)
- m.configMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
- m := &Manager{
- dirty: make(chan struct{}, 1),
- }
-
- for i := 0; i < 10; i++ {
- m.notifySubscribers()
- }
-
- assert.Len(t, m.dirty, 1)
-}
-
-func TestManager_ConcurrentOfferAccess(t *testing.T) {
- m := &Manager{
- offerMimeTypes: make(map[any][]string),
- offerRegistry: make(map[uint32]any),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- m.offerMutex.Lock()
- m.offerRegistry[key] = struct{}{}
- m.offerMimeTypes[key] = []string{"text/plain"}
- m.offerMutex.Unlock()
-
- m.offerMutex.RLock()
- _ = m.offerRegistry[key]
- _ = m.offerMimeTypes[key]
- m.offerMutex.RUnlock()
-
- m.offerMutex.Lock()
- delete(m.offerRegistry, key)
- delete(m.offerMimeTypes, key)
- m.offerMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentPersistAccess(t *testing.T) {
- m := &Manager{
- persistData: make(map[string][]byte),
- persistMimeTypes: []string{},
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
- const iterations = 50
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.persistMutex.RLock()
- _ = m.persistData
- _ = m.persistMimeTypes
- m.persistMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.persistMutex.Lock()
- m.persistMimeTypes = []string{"text/plain", "text/html"}
- m.persistData = map[string][]byte{
- "text/plain": []byte("test"),
- }
- m.persistMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentOwnerAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.ownerLock.Lock()
- _ = m.isOwner
- m.ownerLock.Unlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.ownerLock.Lock()
- m.isOwner = j%2 == 0
- m.ownerLock.Unlock()
- }
- }()
- }
-
- wg.Wait()
-}
-
-func TestItob(t *testing.T) {
- tests := []struct {
- input uint64
- expected []byte
- }{
- {0, []byte{0, 0, 0, 0, 0, 0, 0, 0}},
- {1, []byte{0, 0, 0, 0, 0, 0, 0, 1}},
- {256, []byte{0, 0, 0, 0, 0, 0, 1, 0}},
- {0xFFFFFFFFFFFFFFFF, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
- }
-
- for _, tt := range tests {
- result := itob(tt.input)
- assert.Equal(t, tt.expected, result)
- }
-}
-
-func TestSizeStr(t *testing.T) {
- tests := []struct {
- input int
- expected string
- }{
- {0, "0 B"},
- {100, "100 B"},
- {1024, "1 KiB"},
- {2048, "2 KiB"},
- {1048576, "1 MiB"},
- {5242880, "5 MiB"},
- }
-
- for _, tt := range tests {
- result := sizeStr(tt.input)
- assert.Equal(t, tt.expected, result)
- }
-}
-
-func TestSelectMimeType(t *testing.T) {
- m := &Manager{}
-
- tests := []struct {
- mimes []string
- expected string
- }{
- {[]string{"text/plain;charset=utf-8", "text/html"}, "text/plain;charset=utf-8"},
- {[]string{"text/html", "text/plain"}, "text/plain"},
- {[]string{"text/html", "image/png"}, "image/png"},
- {[]string{"image/png", "image/jpeg"}, "image/png"},
- {[]string{"image/png"}, "image/png"},
- {[]string{"application/octet-stream"}, "application/octet-stream"},
- {[]string{}, ""},
- }
-
- for _, tt := range tests {
- result := m.selectMimeType(tt.mimes)
- assert.Equal(t, tt.expected, result)
- }
-}
-
-func TestIsImageMimeType(t *testing.T) {
- m := &Manager{}
-
- assert.True(t, m.isImageMimeType("image/png"))
- assert.True(t, m.isImageMimeType("image/jpeg"))
- assert.True(t, m.isImageMimeType("image/gif"))
- assert.False(t, m.isImageMimeType("text/plain"))
- assert.False(t, m.isImageMimeType("application/json"))
-}
-
-func TestTextPreview(t *testing.T) {
- m := &Manager{}
-
- short := m.textPreview([]byte("hello world"))
- assert.Equal(t, "hello world", short)
-
- withWhitespace := m.textPreview([]byte(" hello world "))
- assert.Equal(t, "hello world", withWhitespace)
-
- longText := make([]byte, 200)
- for i := range longText {
- longText[i] = 'a'
- }
- preview := m.textPreview(longText)
- assert.True(t, len(preview) > 100)
- assert.Contains(t, preview, "…")
-}
-
-func TestDefaultConfig(t *testing.T) {
- cfg := DefaultConfig()
- assert.Equal(t, 100, cfg.MaxHistory)
- assert.Equal(t, int64(5*1024*1024), cfg.MaxEntrySize)
- assert.Equal(t, 0, cfg.AutoClearDays)
- assert.False(t, cfg.ClearAtStartup)
- assert.False(t, cfg.Disabled)
-}
-
-func TestManager_PostDelegatesToWlContext(t *testing.T) {
- mockCtx := mocks_wlcontext.NewMockWaylandContext(t)
-
- var called atomic.Bool
- mockCtx.EXPECT().Post(mock.AnythingOfType("func()")).Run(func(fn func()) {
- called.Store(true)
- fn()
- }).Once()
-
- m := &Manager{
- wlCtx: mockCtx,
- }
-
- executed := false
- m.post(func() {
- executed = true
- })
-
- assert.True(t, called.Load())
- assert.True(t, executed)
-}
-
-func TestManager_PostExecutesFunctionViaContext(t *testing.T) {
- mockCtx := mocks_wlcontext.NewMockWaylandContext(t)
-
- var capturedFn func()
- mockCtx.EXPECT().Post(mock.AnythingOfType("func()")).Run(func(fn func()) {
- capturedFn = fn
- }).Times(3)
-
- m := &Manager{
- wlCtx: mockCtx,
- }
-
- counter := 0
- m.post(func() { counter++ })
- m.post(func() { counter += 10 })
- m.post(func() { counter += 100 })
-
- assert.NotNil(t, capturedFn)
- capturedFn()
- assert.Equal(t, 100, counter)
-}
-
-func TestManager_ConcurrentPostWithMock(t *testing.T) {
- mockCtx := mocks_wlcontext.NewMockWaylandContext(t)
-
- var postCount atomic.Int32
- mockCtx.EXPECT().Post(mock.AnythingOfType("func()")).Run(func(fn func()) {
- postCount.Add(1)
- }).Times(100)
-
- m := &Manager{
- wlCtx: mockCtx,
- }
-
- var wg sync.WaitGroup
- for i := 0; i < 10; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < 10; j++ {
- m.post(func() {})
- }
- }()
- }
-
- wg.Wait()
- assert.Equal(t, int32(100), postCount.Load())
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/types.go
deleted file mode 100644
index c754561..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/clipboard/types.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package clipboard
-
-import (
- "encoding/json"
- "os"
- "path/filepath"
- "sync"
- "time"
-
- "github.com/godbus/dbus/v5"
- bolt "go.etcd.io/bbolt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-type Config struct {
- MaxHistory int `json:"maxHistory"`
- MaxEntrySize int64 `json:"maxEntrySize"`
- AutoClearDays int `json:"autoClearDays"`
- ClearAtStartup bool `json:"clearAtStartup"`
- Disabled bool `json:"disabled"`
- MaxPinned int `json:"maxPinned"`
-}
-
-func DefaultConfig() Config {
- return Config{
- MaxHistory: 100,
- MaxEntrySize: 5 * 1024 * 1024,
- AutoClearDays: 0,
- ClearAtStartup: false,
- MaxPinned: 25,
- }
-}
-
-func getConfigPath() (string, error) {
- configDir, err := os.UserConfigDir()
- if err != nil {
- return "", err
- }
- return filepath.Join(configDir, "DankMaterialShell", "clsettings.json"), nil
-}
-
-func LoadConfig() Config {
- cfg := DefaultConfig()
-
- path, err := getConfigPath()
- if err != nil {
- return cfg
- }
-
- data, err := os.ReadFile(path)
- if err != nil {
- return cfg
- }
-
- if err := json.Unmarshal(data, &cfg); err != nil {
- return DefaultConfig()
- }
- return cfg
-}
-
-func SaveConfig(cfg Config) error {
- path, err := getConfigPath()
- if err != nil {
- return err
- }
-
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
- return err
- }
-
- data, err := json.MarshalIndent(cfg, "", " ")
- if err != nil {
- return err
- }
-
- return os.WriteFile(path, data, 0o644)
-}
-
-type SearchParams struct {
- Query string `json:"query"`
- MimeType string `json:"mimeType"`
- IsImage *bool `json:"isImage"`
- Limit int `json:"limit"`
- Offset int `json:"offset"`
- Before *int64 `json:"before"`
- After *int64 `json:"after"`
-}
-
-type SearchResult struct {
- Entries []Entry `json:"entries"`
- Total int `json:"total"`
- HasMore bool `json:"hasMore"`
-}
-
-type Entry struct {
- ID uint64 `json:"id"`
- Data []byte `json:"data,omitempty"`
- MimeType string `json:"mimeType"`
- Preview string `json:"preview"`
- Size int `json:"size"`
- Timestamp time.Time `json:"timestamp"`
- IsImage bool `json:"isImage"`
- Hash uint64 `json:"hash,omitempty"`
- Pinned bool `json:"pinned"`
-}
-
-type State struct {
- Enabled bool `json:"enabled"`
- History []Entry `json:"history"`
- Current *Entry `json:"current,omitempty"`
-}
-
-type Manager struct {
- config Config
- configMutex sync.RWMutex
- configPath string
-
- display wlclient.WaylandDisplay
- wlCtx wlcontext.WaylandContext
-
- registry *wlclient.Registry
- dataControlMgr any
- seat *wlclient.Seat
- dataDevice any
- currentOffer any
- currentSource any
- seatName uint32
- mimeTypes []string
- offerMimeTypes map[any][]string
- offerMutex sync.RWMutex
- offerRegistry map[uint32]any
-
- sourceMimeTypes []string
- sourceMutex sync.RWMutex
-
- persistData map[string][]byte
- persistMimeTypes []string
- persistMutex sync.RWMutex
-
- isOwner bool
- ownerLock sync.Mutex
-
- initialized bool
-
- alive bool
- stopChan chan struct{}
-
- db *bolt.DB
- dbPath string
-
- state *State
- stateMutex sync.RWMutex
-
- subscribers map[string]chan State
- subMutex sync.RWMutex
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastState *State
-
- dbusConn *dbus.Conn
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{}
- }
- return *m.state
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
- m.subMutex.Lock()
- m.subscribers[id] = ch
- m.subMutex.Unlock()
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- m.subMutex.Lock()
- if ch, ok := m.subscribers[id]; ok {
- close(ch)
- delete(m.subscribers, id)
- }
- m.subMutex.Unlock()
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go
deleted file mode 100644
index 2f9b98b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go
+++ /dev/null
@@ -1,479 +0,0 @@
-package cups
-
-import (
- "errors"
- "fmt"
- "net"
- "net/url"
- "os/exec"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
-)
-
-func isAuthError(err error) bool {
- if err == nil {
- return false
- }
-
- var httpErr ipp.HTTPError
- if errors.As(err, &httpErr) {
- return httpErr.Code == 401 || httpErr.Code == 403
- }
-
- var ippErr ipp.IPPError
- if errors.As(err, &ippErr) {
- return ippErr.Status == ipp.StatusErrorForbidden ||
- ippErr.Status == ipp.StatusErrorNotAuthenticated ||
- ippErr.Status == ipp.StatusErrorNotAuthorized
- }
-
- return false
-}
-
-func (m *Manager) GetPrinters() ([]Printer, error) {
- attributes := []string{
- ipp.AttributePrinterName,
- ipp.AttributePrinterUriSupported,
- ipp.AttributePrinterState,
- ipp.AttributePrinterStateReasons,
- ipp.AttributePrinterLocation,
- ipp.AttributePrinterInfo,
- ipp.AttributePrinterMakeAndModel,
- ipp.AttributePrinterIsAcceptingJobs,
- }
-
- printerAttrs, err := m.client.GetPrinters(attributes)
- if err != nil {
- if isNoPrintersError(err) {
- return []Printer{}, nil
- }
- return nil, err
- }
-
- printers := make([]Printer, 0, len(printerAttrs))
- for _, attrs := range printerAttrs {
- printer := Printer{
- Name: getStringAttr(attrs, ipp.AttributePrinterName),
- URI: getStringAttr(attrs, ipp.AttributePrinterUriSupported),
- State: parsePrinterState(attrs),
- StateReason: getStringAttr(attrs, ipp.AttributePrinterStateReasons),
- Location: getStringAttr(attrs, ipp.AttributePrinterLocation),
- Info: getStringAttr(attrs, ipp.AttributePrinterInfo),
- MakeModel: getStringAttr(attrs, ipp.AttributePrinterMakeAndModel),
- Accepting: getBoolAttr(attrs, ipp.AttributePrinterIsAcceptingJobs),
- }
-
- if printer.Name != "" {
- printers = append(printers, printer)
- }
- }
-
- return printers, nil
-}
-
-func (m *Manager) GetJobs(printerName string, whichJobs string) ([]Job, error) {
- attributes := []string{
- ipp.AttributeJobID,
- ipp.AttributeJobName,
- ipp.AttributeJobState,
- ipp.AttributeJobPrinterURI,
- ipp.AttributeJobOriginatingUserName,
- ipp.AttributeJobKilobyteOctets,
- "time-at-creation",
- }
-
- jobAttrs, err := m.client.GetJobs(printerName, "", whichJobs, false, 0, 0, attributes)
- if err != nil {
- return nil, err
- }
-
- jobs := make([]Job, 0, len(jobAttrs))
- for _, attrs := range jobAttrs {
- job := Job{
- ID: getIntAttr(attrs, ipp.AttributeJobID),
- Name: getStringAttr(attrs, ipp.AttributeJobName),
- State: parseJobState(attrs),
- User: getStringAttr(attrs, ipp.AttributeJobOriginatingUserName),
- Size: getIntAttr(attrs, ipp.AttributeJobKilobyteOctets) * 1024,
- }
-
- if uri := getStringAttr(attrs, ipp.AttributeJobPrinterURI); uri != "" {
- parts := strings.Split(uri, "/")
- if len(parts) > 0 {
- job.Printer = parts[len(parts)-1]
- }
- }
-
- if ts := getIntAttr(attrs, "time-at-creation"); ts > 0 {
- job.TimeCreated = time.Unix(int64(ts), 0)
- }
-
- if job.ID != 0 {
- jobs = append(jobs, job)
- }
- }
-
- return jobs, nil
-}
-
-func (m *Manager) CancelJob(jobID int) error {
- err := m.client.CancelJob(jobID, false)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.JobCancelPurge(jobID, false)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) PausePrinter(printerName string) error {
- err := m.client.PausePrinter(printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetEnabled(printerName, false)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) ResumePrinter(printerName string) error {
- err := m.client.ResumePrinter(printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetEnabled(printerName, true)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) PurgeJobs(printerName string) error {
- err := m.client.CancelAllJob(printerName, true)
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func resolveIPFromURI(uri string) string {
- parsed, err := url.Parse(uri)
- if err != nil {
- return ""
- }
- host := parsed.Hostname()
- if host == "" {
- return ""
- }
- if ip := net.ParseIP(host); ip != nil {
- return ip.String()
- }
- addrs, err := net.LookupIP(host)
- if err != nil || len(addrs) == 0 {
- return ""
- }
- for _, addr := range addrs {
- if v4 := addr.To4(); v4 != nil {
- return v4.String()
- }
- }
- return addrs[0].String()
-}
-
-func (m *Manager) GetDevices() ([]Device, error) {
- if m.pkHelper != nil {
- devices, err := m.pkHelper.DevicesGet(10, 0, nil, nil)
- if err != nil {
- return nil, err
- }
- for i := range devices {
- if devices[i].Class == "network" {
- devices[i].IP = resolveIPFromURI(devices[i].URI)
- }
- }
- return devices, nil
- }
-
- deviceAttrs, err := m.client.GetDevices()
- if err != nil {
- return nil, err
- }
-
- devices := make([]Device, 0, len(deviceAttrs))
- for uri, attrs := range deviceAttrs {
- device := Device{
- URI: uri,
- Class: getStringAttr(attrs, "device-class"),
- Info: getStringAttr(attrs, "device-info"),
- MakeModel: getStringAttr(attrs, "device-make-and-model"),
- ID: getStringAttr(attrs, "device-id"),
- Location: getStringAttr(attrs, "device-location"),
- }
- if device.Class == "network" {
- device.IP = resolveIPFromURI(uri)
- }
- devices = append(devices, device)
- }
-
- return devices, nil
-}
-
-func (m *Manager) GetPPDs() ([]PPD, error) {
- ppdAttrs, err := m.client.GetPPDs()
- if err != nil {
- return nil, err
- }
-
- ppds := make([]PPD, 0, len(ppdAttrs))
- for name, attrs := range ppdAttrs {
- ppd := PPD{
- Name: name,
- NaturalLanguage: getStringAttr(attrs, "ppd-natural-language"),
- MakeModel: getStringAttr(attrs, ipp.AttributePPDMakeAndModel),
- DeviceID: getStringAttr(attrs, "ppd-device-id"),
- Product: getStringAttr(attrs, "ppd-product"),
- PSVersion: getStringAttr(attrs, "ppd-psversion"),
- Type: getStringAttr(attrs, "ppd-type"),
- }
- ppds = append(ppds, ppd)
- }
-
- return ppds, nil
-}
-
-func (m *Manager) GetClasses() ([]PrinterClass, error) {
- attributes := []string{
- ipp.AttributePrinterName,
- ipp.AttributePrinterUriSupported,
- ipp.AttributePrinterState,
- ipp.AttributeMemberURIs,
- ipp.AttributeMemberNames,
- ipp.AttributePrinterLocation,
- ipp.AttributePrinterInfo,
- }
-
- classAttrs, err := m.client.GetClasses(attributes)
- if err != nil {
- return nil, err
- }
-
- classes := make([]PrinterClass, 0, len(classAttrs))
- for _, attrs := range classAttrs {
- class := PrinterClass{
- Name: getStringAttr(attrs, ipp.AttributePrinterName),
- URI: getStringAttr(attrs, ipp.AttributePrinterUriSupported),
- State: parsePrinterState(attrs),
- Location: getStringAttr(attrs, ipp.AttributePrinterLocation),
- Info: getStringAttr(attrs, ipp.AttributePrinterInfo),
- Members: getStringSliceAttr(attrs, ipp.AttributeMemberNames),
- }
- classes = append(classes, class)
- }
-
- return classes, nil
-}
-
-func createPrinterViaLpadmin(name, deviceURI, ppd, information, location string) error {
- args := []string{"-p", name, "-E", "-v", deviceURI, "-m", ppd}
- if information != "" {
- args = append(args, "-D", information)
- }
- if location != "" {
- args = append(args, "-L", location)
- }
- out, err := exec.Command("lpadmin", args...).CombinedOutput()
- if err != nil {
- return fmt.Errorf("lpadmin failed: %s: %w", strings.TrimSpace(string(out)), err)
- }
- return nil
-}
-
-func deletePrinterViaLpadmin(name string) error {
- out, err := exec.Command("lpadmin", "-x", name).CombinedOutput()
- if err != nil {
- return fmt.Errorf("lpadmin failed: %s: %w", strings.TrimSpace(string(out)), err)
- }
- return nil
-}
-
-func (m *Manager) CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error {
- usedPkHelper := false
-
- err := m.client.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location)
- if isAuthError(err) && m.pkHelper != nil {
- if err = m.pkHelper.PrinterAdd(name, deviceURI, ppd, information, location); err != nil {
- // pkHelper failed (e.g., no polkit agent), try lpadmin as last resort.
- // lpadmin -E enables the printer, so no further setup needed.
- if lpadminErr := createPrinterViaLpadmin(name, deviceURI, ppd, information, location); lpadminErr != nil {
- return err
- }
- m.RefreshState()
- return nil
- }
- usedPkHelper = true
- } else if err != nil {
- return err
- }
-
- if usedPkHelper {
- m.pkHelper.PrinterSetEnabled(name, true) //nolint:errcheck
- m.pkHelper.PrinterSetAcceptJobs(name, true, "") //nolint:errcheck
- } else {
- if err := m.client.ResumePrinter(name); isAuthError(err) && m.pkHelper != nil {
- m.pkHelper.PrinterSetEnabled(name, true) //nolint:errcheck
- }
- if err := m.client.AcceptJobs(name); isAuthError(err) && m.pkHelper != nil {
- m.pkHelper.PrinterSetAcceptJobs(name, true, "") //nolint:errcheck
- }
- }
-
- m.RefreshState()
- return nil
-}
-
-func (m *Manager) DeletePrinter(printerName string) error {
- err := m.client.DeletePrinter(printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterDelete(printerName)
- if err != nil {
- // pkHelper failed, try lpadmin as last resort
- if lpadminErr := deletePrinterViaLpadmin(printerName); lpadminErr == nil {
- err = nil
- }
- }
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) AcceptJobs(printerName string) error {
- err := m.client.AcceptJobs(printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetAcceptJobs(printerName, true, "")
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) RejectJobs(printerName string) error {
- err := m.client.RejectJobs(printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetAcceptJobs(printerName, false, "")
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) SetPrinterShared(printerName string, shared bool) error {
- err := m.client.SetPrinterIsShared(printerName, shared)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetShared(printerName, shared)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) SetPrinterLocation(printerName, location string) error {
- err := m.client.SetPrinterLocation(printerName, location)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetLocation(printerName, location)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) SetPrinterInfo(printerName, info string) error {
- err := m.client.SetPrinterInformation(printerName, info)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.PrinterSetInfo(printerName, info)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) MoveJob(jobID int, destPrinter string) error {
- err := m.client.MoveJob(jobID, destPrinter)
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) PrintTestPage(printerName string) (int, error) {
- jobID, err := m.client.PrintTestPage(printerName, strings.NewReader(config.TestPage), len(config.TestPage))
- if err == nil {
- m.RefreshState()
- }
- return jobID, err
-}
-
-func (m *Manager) AddPrinterToClass(className, printerName string) error {
- err := m.client.AddPrinterToClass(className, printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.ClassAddPrinter(className, printerName)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) RemovePrinterFromClass(className, printerName string) error {
- err := m.client.DeletePrinterFromClass(className, printerName)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.ClassDeletePrinter(className, printerName)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) DeleteClass(className string) error {
- err := m.client.DeleteClass(className)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.ClassDelete(className)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) RestartJob(jobID int) error {
- err := m.client.RestartJob(jobID)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.JobRestart(jobID)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
-
-func (m *Manager) HoldJob(jobID int, holdUntil string) error {
- err := m.client.HoldJobUntil(jobID, holdUntil)
- if isAuthError(err) && m.pkHelper != nil {
- err = m.pkHelper.JobSetHoldUntil(jobID, holdUntil)
- }
- if err == nil {
- m.RefreshState()
- }
- return err
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go
deleted file mode 100644
index ad16a8a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go
+++ /dev/null
@@ -1,235 +0,0 @@
-package cups_test
-
-import (
- "testing"
-
- mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
- mocks_pkhelper "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups_pkhelper"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-func authErr() error {
- return ipp.IPPError{Status: ipp.StatusErrorForbidden}
-}
-
-func TestManager_CancelJob_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CancelJob(1, false).Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().JobCancelPurge(1, false).Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.CancelJob(1))
-}
-
-func TestManager_CancelJob_PkHelperError(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CancelJob(1, false).Return(authErr())
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().JobCancelPurge(1, false).Return(assert.AnError)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.Error(t, m.CancelJob(1))
-}
-
-func TestManager_PausePrinter_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().PausePrinter("printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetEnabled("printer1", false).Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.PausePrinter("printer1"))
-}
-
-func TestManager_ResumePrinter_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().ResumePrinter("printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetEnabled("printer1", true).Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.ResumePrinter("printer1"))
-}
-
-func TestManager_GetDevices_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return([]cups.Device{
- {URI: "usb://HP/LaserJet", Class: "direct"},
- }, nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- got, err := m.GetDevices()
- assert.NoError(t, err)
- assert.Len(t, got, 1)
- assert.Equal(t, "usb://HP/LaserJet", got[0].URI)
-}
-
-func TestManager_GetDevices_PkHelperError(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return(nil, assert.AnError)
-
- m := cups.NewTestManager(mockClient, mockPk)
- _, err := m.GetDevices()
- assert.Error(t, err)
-}
-
-func TestManager_CreatePrinter_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterAdd("newprinter", "usb://HP", "generic.ppd", "info", "location").Return(nil)
- mockPk.EXPECT().PrinterSetEnabled("newprinter", true).Return(nil)
- mockPk.EXPECT().PrinterSetAcceptJobs("newprinter", true, "").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location"))
-}
-
-func TestManager_DeletePrinter_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeletePrinter("printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterDelete("printer1").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.DeletePrinter("printer1"))
-}
-
-func TestManager_AcceptJobs_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().AcceptJobs("printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetAcceptJobs("printer1", true, "").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.AcceptJobs("printer1"))
-}
-
-func TestManager_RejectJobs_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().RejectJobs("printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetAcceptJobs("printer1", false, "").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.RejectJobs("printer1"))
-}
-
-func TestManager_SetPrinterShared_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetShared("printer1", true).Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.SetPrinterShared("printer1", true))
-}
-
-func TestManager_SetPrinterLocation_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetLocation("printer1", "Office").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.SetPrinterLocation("printer1", "Office"))
-}
-
-func TestManager_SetPrinterInfo_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().PrinterSetInfo("printer1", "Main Printer").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer"))
-}
-
-func TestManager_AddPrinterToClass_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().ClassAddPrinter("office", "printer1").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.AddPrinterToClass("office", "printer1"))
-}
-
-func TestManager_RemovePrinterFromClass_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().ClassDeletePrinter("office", "printer1").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.RemovePrinterFromClass("office", "printer1"))
-}
-
-func TestManager_DeleteClass_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeleteClass("office").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().ClassDelete("office").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.DeleteClass("office"))
-}
-
-func TestManager_RestartJob_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().RestartJob(1).Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().JobRestart(1).Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.RestartJob(1))
-}
-
-func TestManager_HoldJob_WithPkHelper(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(authErr())
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- mockPk := mocks_pkhelper.NewMockPkHelper(t)
- mockPk.EXPECT().JobSetHoldUntil(1, "indefinite").Return(nil)
-
- m := cups.NewTestManager(mockClient, mockPk)
- assert.NoError(t, m.HoldJob(1, "indefinite"))
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go
deleted file mode 100644
index 70f4f21..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go
+++ /dev/null
@@ -1,450 +0,0 @@
-package cups
-
-import (
- "errors"
- "testing"
- "time"
-
- mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-func TestManager_GetPrinters(t *testing.T) {
- tests := []struct {
- name string
- mockRet map[string]ipp.Attributes
- mockErr error
- want int
- wantErr bool
- }{
- {
- name: "success",
- mockRet: map[string]ipp.Attributes{
- "printer1": {
- ipp.AttributePrinterName: []ipp.Attribute{{Value: "printer1"}},
- ipp.AttributePrinterUriSupported: []ipp.Attribute{{Value: "ipp://localhost/printers/printer1"}},
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
- ipp.AttributePrinterStateReasons: []ipp.Attribute{{Value: "none"}},
- ipp.AttributePrinterLocation: []ipp.Attribute{{Value: "Office"}},
- ipp.AttributePrinterInfo: []ipp.Attribute{{Value: "Test Printer"}},
- ipp.AttributePrinterMakeAndModel: []ipp.Attribute{{Value: "Generic"}},
- ipp.AttributePrinterIsAcceptingJobs: []ipp.Attribute{{Value: true}},
- },
- },
- mockErr: nil,
- want: 1,
- wantErr: false,
- },
- {
- name: "error",
- mockRet: nil,
- mockErr: errors.New("test error"),
- want: 0,
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(tt.mockRet, tt.mockErr)
-
- m := &Manager{
- client: mockClient,
- }
-
- got, err := m.GetPrinters()
- if tt.wantErr {
- assert.Error(t, err)
- } else {
- assert.NoError(t, err)
- assert.Equal(t, tt.want, len(got))
- if len(got) > 0 {
- assert.Equal(t, "printer1", got[0].Name)
- assert.Equal(t, "idle", got[0].State)
- assert.Equal(t, "Office", got[0].Location)
- assert.True(t, got[0].Accepting)
- }
- }
- })
- }
-}
-
-func TestManager_GetJobs(t *testing.T) {
- tests := []struct {
- name string
- mockRet map[int]ipp.Attributes
- mockErr error
- want int
- wantErr bool
- }{
- {
- name: "success",
- mockRet: map[int]ipp.Attributes{
- 1: {
- ipp.AttributeJobID: []ipp.Attribute{{Value: 1}},
- ipp.AttributeJobName: []ipp.Attribute{{Value: "test-job"}},
- ipp.AttributeJobState: []ipp.Attribute{{Value: 5}},
- ipp.AttributeJobPrinterURI: []ipp.Attribute{{Value: "ipp://localhost/printers/printer1"}},
- ipp.AttributeJobOriginatingUserName: []ipp.Attribute{{Value: "testuser"}},
- ipp.AttributeJobKilobyteOctets: []ipp.Attribute{{Value: 10}},
- "time-at-creation": []ipp.Attribute{{Value: 1609459200}},
- },
- },
- mockErr: nil,
- want: 1,
- wantErr: false,
- },
- {
- name: "error",
- mockRet: nil,
- mockErr: errors.New("test error"),
- want: 0,
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetJobs("printer1", "", "not-completed", false, 0, 0, mock.Anything).
- Return(tt.mockRet, tt.mockErr)
-
- m := &Manager{
- client: mockClient,
- }
-
- got, err := m.GetJobs("printer1", "not-completed")
- if tt.wantErr {
- assert.Error(t, err)
- } else {
- assert.NoError(t, err)
- assert.Equal(t, tt.want, len(got))
- if len(got) > 0 {
- assert.Equal(t, 1, got[0].ID)
- assert.Equal(t, "test-job", got[0].Name)
- assert.Equal(t, "processing", got[0].State)
- assert.Equal(t, "testuser", got[0].User)
- assert.Equal(t, "printer1", got[0].Printer)
- assert.Equal(t, 10240, got[0].Size)
- assert.Equal(t, time.Unix(1609459200, 0), got[0].TimeCreated)
- }
- }
- })
- }
-}
-
-func TestManager_CancelJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CancelJob(1, false).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.CancelJob(1))
-}
-
-func TestManager_PausePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().PausePrinter("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.PausePrinter("printer1"))
-}
-
-func TestManager_ResumePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.ResumePrinter("printer1"))
-}
-
-func TestManager_PurgeJobs(t *testing.T) {
- tests := []struct {
- name string
- mockErr error
- wantErr bool
- }{
- {
- name: "success",
- mockErr: nil,
- wantErr: false,
- },
- {
- name: "error",
- mockErr: errors.New("test error"),
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CancelAllJob("printer1", true).Return(tt.mockErr)
- if !tt.wantErr {
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
- }
-
- m := NewTestManager(mockClient, nil)
-
- err := m.PurgeJobs("printer1")
- if tt.wantErr {
- assert.Error(t, err)
- } else {
- assert.NoError(t, err)
- }
- })
- }
-}
-
-func TestManager_GetDevices(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{
- "usb://HP/LaserJet": {
- "device-class": []ipp.Attribute{{Value: "direct"}},
- "device-info": []ipp.Attribute{{Value: "HP LaserJet"}},
- "device-make-and-model": []ipp.Attribute{{Value: "HP LaserJet 1020"}},
- },
- }, nil)
-
- m := &Manager{client: mockClient}
- got, err := m.GetDevices()
- assert.NoError(t, err)
- assert.Len(t, got, 1)
- assert.Equal(t, "usb://HP/LaserJet", got[0].URI)
- assert.Equal(t, "direct", got[0].Class)
-}
-
-func TestManager_GetPPDs(t *testing.T) {
- tests := []struct {
- name string
- mockRet map[string]ipp.Attributes
- mockErr error
- want int
- wantErr bool
- }{
- {
- name: "success",
- mockRet: map[string]ipp.Attributes{
- "drv:///sample.drv/generic.ppd": {
- "ppd-make-and-model": []ipp.Attribute{{Value: "Generic PostScript"}},
- "ppd-type": []ipp.Attribute{{Value: "ppd"}},
- },
- },
- mockErr: nil,
- want: 1,
- wantErr: false,
- },
- {
- name: "error",
- mockRet: nil,
- mockErr: errors.New("test error"),
- want: 0,
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetPPDs().Return(tt.mockRet, tt.mockErr)
-
- m := &Manager{client: mockClient}
-
- got, err := m.GetPPDs()
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
- assert.NoError(t, err)
- assert.Equal(t, tt.want, len(got))
- })
- }
-}
-
-func TestManager_GetClasses(t *testing.T) {
- tests := []struct {
- name string
- mockRet map[string]ipp.Attributes
- mockErr error
- want int
- wantErr bool
- }{
- {
- name: "success",
- mockRet: map[string]ipp.Attributes{
- "office": {
- ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}},
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
- ipp.AttributeMemberNames: []ipp.Attribute{{Value: "printer1"}, {Value: "printer2"}},
- },
- },
- mockErr: nil,
- want: 1,
- wantErr: false,
- },
- {
- name: "error",
- mockRet: nil,
- mockErr: errors.New("test error"),
- want: 0,
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetClasses(mock.Anything).Return(tt.mockRet, tt.mockErr)
-
- m := &Manager{client: mockClient}
-
- got, err := m.GetClasses()
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
- assert.NoError(t, err)
- assert.Equal(t, tt.want, len(got))
- if len(got) > 0 {
- assert.Equal(t, "office", got[0].Name)
- assert.Equal(t, 2, len(got[0].Members))
- }
- })
- }
-}
-
-func TestManager_CreatePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(nil)
- mockClient.EXPECT().ResumePrinter("newprinter").Return(nil)
- mockClient.EXPECT().AcceptJobs("newprinter").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location"))
-}
-
-func TestManager_DeletePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeletePrinter("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.DeletePrinter("printer1"))
-}
-
-func TestManager_AcceptJobs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().AcceptJobs("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.AcceptJobs("printer1"))
-}
-
-func TestManager_RejectJobs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().RejectJobs("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.RejectJobs("printer1"))
-}
-
-func TestManager_SetPrinterShared(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.SetPrinterShared("printer1", true))
-}
-
-func TestManager_SetPrinterLocation(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.SetPrinterLocation("printer1", "Office"))
-}
-
-func TestManager_SetPrinterInfo(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer"))
-}
-
-func TestManager_MoveJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().MoveJob(1, "printer2").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- err := m.MoveJob(1, "printer2")
- assert.NoError(t, err)
-}
-
-func TestManager_PrintTestPage(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- jobID, err := m.PrintTestPage("printer1")
- assert.NoError(t, err)
- assert.Equal(t, 42, jobID)
-}
-
-func TestManager_AddPrinterToClass(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.AddPrinterToClass("office", "printer1"))
-}
-
-func TestManager_RemovePrinterFromClass(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.RemovePrinterFromClass("office", "printer1"))
-}
-
-func TestManager_DeleteClass(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeleteClass("office").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.DeleteClass("office"))
-}
-
-func TestManager_RestartJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().RestartJob(1).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.RestartJob(1))
-}
-
-func TestManager_HoldJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- assert.NoError(t, m.HoldJob(1, "indefinite"))
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go
deleted file mode 100644
index 57b45d5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go
+++ /dev/null
@@ -1,487 +0,0 @@
-package cups
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-type CUPSEvent struct {
- Type string `json:"type"`
- Data CUPSState `json:"data"`
-}
-
-type TestPageResult struct {
- Success bool `json:"success"`
- JobID int `json:"jobId"`
- Message string `json:"message"`
-}
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "cups.subscribe":
- handleSubscribe(conn, req, manager)
- case "cups.getPrinters":
- handleGetPrinters(conn, req, manager)
- case "cups.getJobs":
- handleGetJobs(conn, req, manager)
- case "cups.pausePrinter":
- handlePausePrinter(conn, req, manager)
- case "cups.resumePrinter":
- handleResumePrinter(conn, req, manager)
- case "cups.cancelJob":
- handleCancelJob(conn, req, manager)
- case "cups.purgeJobs":
- handlePurgeJobs(conn, req, manager)
- case "cups.getDevices":
- handleGetDevices(conn, req, manager)
- case "cups.getPPDs":
- handleGetPPDs(conn, req, manager)
- case "cups.getClasses":
- handleGetClasses(conn, req, manager)
- case "cups.createPrinter":
- handleCreatePrinter(conn, req, manager)
- case "cups.deletePrinter":
- handleDeletePrinter(conn, req, manager)
- case "cups.acceptJobs":
- handleAcceptJobs(conn, req, manager)
- case "cups.rejectJobs":
- handleRejectJobs(conn, req, manager)
- case "cups.setPrinterShared":
- handleSetPrinterShared(conn, req, manager)
- case "cups.setPrinterLocation":
- handleSetPrinterLocation(conn, req, manager)
- case "cups.setPrinterInfo":
- handleSetPrinterInfo(conn, req, manager)
- case "cups.moveJob":
- handleMoveJob(conn, req, manager)
- case "cups.printTestPage":
- handlePrintTestPage(conn, req, manager)
- case "cups.addPrinterToClass":
- handleAddPrinterToClass(conn, req, manager)
- case "cups.removePrinterFromClass":
- handleRemovePrinterFromClass(conn, req, manager)
- case "cups.deleteClass":
- handleDeleteClass(conn, req, manager)
- case "cups.restartJob":
- handleRestartJob(conn, req, manager)
- case "cups.holdJob":
- handleHoldJob(conn, req, manager)
- case "cups.testConnection":
- handleTestConnection(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetPrinters(conn net.Conn, req models.Request, manager *Manager) {
- printers, err := manager.GetPrinters()
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, printers)
-}
-
-func handleGetJobs(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.String(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- jobs, err := manager.GetJobs(printerName, "not-completed")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, jobs)
-}
-
-func handlePausePrinter(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.String(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.PausePrinter(printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "paused"})
-}
-
-func handleResumePrinter(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.String(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.ResumePrinter(printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "resumed"})
-}
-
-func handleCancelJob(conn net.Conn, req models.Request, manager *Manager) {
- jobID, err := params.Int(req.Params, "jobID")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.CancelJob(jobID); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job canceled"})
-}
-
-func handlePurgeJobs(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.String(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.PurgeJobs(printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "jobs canceled"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- event := CUPSEvent{
- Type: "state_changed",
- Data: initialState,
- }
-
- if err := json.NewEncoder(conn).Encode(models.Response[CUPSEvent]{
- ID: req.ID,
- Result: &event,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- event := CUPSEvent{
- Type: "state_changed",
- Data: state,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[CUPSEvent]{
- Result: &event,
- }); err != nil {
- return
- }
- }
-}
-
-func handleGetDevices(conn net.Conn, req models.Request, manager *Manager) {
- devices, err := manager.GetDevices()
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, devices)
-}
-
-func handleGetPPDs(conn net.Conn, req models.Request, manager *Manager) {
- ppds, err := manager.GetPPDs()
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, ppds)
-}
-
-func handleGetClasses(conn net.Conn, req models.Request, manager *Manager) {
- classes, err := manager.GetClasses()
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, classes)
-}
-
-func handleCreatePrinter(conn net.Conn, req models.Request, manager *Manager) {
- name, err := params.StringNonEmpty(req.Params, "name")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- deviceURI, err := params.StringNonEmpty(req.Params, "deviceURI")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- ppd, err := params.StringNonEmpty(req.Params, "ppd")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- shared := params.BoolOpt(req.Params, "shared", false)
- errorPolicy := params.StringOpt(req.Params, "errorPolicy", "")
- information := params.StringOpt(req.Params, "information", "")
- location := params.StringOpt(req.Params, "location", "")
-
- if err := manager.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer created"})
-}
-
-func handleDeletePrinter(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.DeletePrinter(printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer deleted"})
-}
-
-func handleAcceptJobs(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.AcceptJobs(printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "accepting jobs"})
-}
-
-func handleRejectJobs(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.RejectJobs(printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "rejecting jobs"})
-}
-
-func handleSetPrinterShared(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- shared, err := params.Bool(req.Params, "shared")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetPrinterShared(printerName, shared); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sharing updated"})
-}
-
-func handleSetPrinterLocation(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- location, err := params.String(req.Params, "location")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetPrinterLocation(printerName, location); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location updated"})
-}
-
-func handleSetPrinterInfo(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- info, err := params.String(req.Params, "info")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetPrinterInfo(printerName, info); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "info updated"})
-}
-
-func handleMoveJob(conn net.Conn, req models.Request, manager *Manager) {
- jobID, err := params.Int(req.Params, "jobID")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- destPrinter, err := params.StringNonEmpty(req.Params, "destPrinter")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.MoveJob(jobID, destPrinter); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job moved"})
-}
-
-func handlePrintTestPage(conn net.Conn, req models.Request, manager *Manager) {
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- jobID, err := manager.PrintTestPage(printerName)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"})
-}
-
-func handleAddPrinterToClass(conn net.Conn, req models.Request, manager *Manager) {
- className, err := params.StringNonEmpty(req.Params, "className")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.AddPrinterToClass(className, printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer added to class"})
-}
-
-func handleRemovePrinterFromClass(conn net.Conn, req models.Request, manager *Manager) {
- className, err := params.StringNonEmpty(req.Params, "className")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- printerName, err := params.StringNonEmpty(req.Params, "printerName")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.RemovePrinterFromClass(className, printerName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer removed from class"})
-}
-
-func handleDeleteClass(conn net.Conn, req models.Request, manager *Manager) {
- className, err := params.StringNonEmpty(req.Params, "className")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.DeleteClass(className); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "class deleted"})
-}
-
-func handleRestartJob(conn net.Conn, req models.Request, manager *Manager) {
- jobID, err := params.Int(req.Params, "jobID")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.RestartJob(jobID); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job restarted"})
-}
-
-func handleHoldJob(conn net.Conn, req models.Request, manager *Manager) {
- jobID, err := params.Int(req.Params, "jobID")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- holdUntil := params.StringOpt(req.Params, "holdUntil", "indefinite")
-
- if err := manager.HoldJob(jobID, holdUntil); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job held"})
-}
-
-func handleTestConnection(conn net.Conn, req models.Request, manager *Manager) {
- host, err := params.StringNonEmpty(req.Params, "host")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- port := params.IntOpt(req.Params, "port", 631)
- protocol := params.StringOpt(req.Params, "protocol", "ipp")
-
- result, err := manager.TestRemotePrinter(host, port, protocol)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go
deleted file mode 100644
index b73931e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go
+++ /dev/null
@@ -1,711 +0,0 @@
-package cups
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "net"
- "testing"
- "time"
-
- mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-type mockConn struct {
- *bytes.Buffer
-}
-
-func (m *mockConn) Close() error { return nil }
-func (m *mockConn) LocalAddr() net.Addr { return nil }
-func (m *mockConn) RemoteAddr() net.Addr { return nil }
-func (m *mockConn) SetDeadline(t time.Time) error { return nil }
-func (m *mockConn) SetReadDeadline(t time.Time) error { return nil }
-func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
-
-func TestHandleGetPrinters(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{
- "printer1": {
- ipp.AttributePrinterName: []ipp.Attribute{{Value: "printer1"}},
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
- ipp.AttributePrinterUriSupported: []ipp.Attribute{{Value: "ipp://localhost/printers/printer1"}},
- },
- }, nil)
-
- m := &Manager{
- client: mockClient,
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.getPrinters",
- }
-
- handleGetPrinters(conn, req, m)
-
- var resp models.Response[[]Printer]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.Equal(t, 1, len(*resp.Result))
-}
-
-func TestHandleGetPrinters_Error(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(nil, errors.New("test error"))
-
- m := &Manager{
- client: mockClient,
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.getPrinters",
- }
-
- handleGetPrinters(conn, req, m)
-
- var resp models.Response[any]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.Nil(t, resp.Result)
- assert.NotNil(t, resp.Error)
-}
-
-func TestHandleGetJobs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetJobs("printer1", "", "not-completed", false, 0, 0, mock.Anything).
- Return(map[int]ipp.Attributes{
- 1: {
- ipp.AttributeJobID: []ipp.Attribute{{Value: 1}},
- ipp.AttributeJobName: []ipp.Attribute{{Value: "job1"}},
- ipp.AttributeJobState: []ipp.Attribute{{Value: 5}},
- },
- }, nil)
-
- m := &Manager{
- client: mockClient,
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.getJobs",
- Params: map[string]any{
- "printerName": "printer1",
- },
- }
-
- handleGetJobs(conn, req, m)
-
- var resp models.Response[[]Job]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.Equal(t, 1, len(*resp.Result))
-}
-
-func TestHandleGetJobs_MissingParam(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- m := &Manager{
- client: mockClient,
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.getJobs",
- Params: map[string]any{},
- }
-
- handleGetJobs(conn, req, m)
-
- var resp models.Response[any]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.Nil(t, resp.Result)
- assert.NotNil(t, resp.Error)
-}
-
-func TestHandlePausePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().PausePrinter("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.pausePrinter",
- Params: map[string]any{
- "printerName": "printer1",
- },
- }
-
- handlePausePrinter(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleResumePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.resumePrinter",
- Params: map[string]any{
- "printerName": "printer1",
- },
- }
-
- handleResumePrinter(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleCancelJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CancelJob(1, false).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.cancelJob",
- Params: map[string]any{
- "jobID": float64(1),
- },
- }
-
- handleCancelJob(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandlePurgeJobs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CancelAllJob("printer1", true).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.purgeJobs",
- Params: map[string]any{
- "printerName": "printer1",
- },
- }
-
- handlePurgeJobs(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleRequest_UnknownMethod(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- m := &Manager{
- client: mockClient,
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.unknownMethod",
- }
-
- HandleRequest(conn, req, m)
-
- var resp models.Response[any]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.Nil(t, resp.Result)
- assert.NotNil(t, resp.Error)
-}
-
-func TestHandleGetDevices(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{
- "usb://HP/LaserJet": {
- "device-class": []ipp.Attribute{{Value: "direct"}},
- "device-info": []ipp.Attribute{{Value: "HP LaserJet"}},
- },
- }, nil)
-
- m := &Manager{client: mockClient}
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{ID: 1, Method: "cups.getDevices"}
- handleGetDevices(conn, req, m)
-
- var resp models.Response[[]Device]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.Equal(t, 1, len(*resp.Result))
-}
-
-func TestHandleGetPPDs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetPPDs().Return(map[string]ipp.Attributes{
- "generic.ppd": {
- "ppd-make-and-model": []ipp.Attribute{{Value: "Generic"}},
- },
- }, nil)
-
- m := &Manager{client: mockClient}
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{ID: 1, Method: "cups.getPPDs"}
- handleGetPPDs(conn, req, m)
-
- var resp models.Response[[]PPD]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.Equal(t, 1, len(*resp.Result))
-}
-
-func TestHandleGetClasses(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().GetClasses(mock.Anything).Return(map[string]ipp.Attributes{
- "office": {
- ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}},
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
- },
- }, nil)
-
- m := &Manager{client: mockClient}
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{ID: 1, Method: "cups.getClasses"}
- handleGetClasses(conn, req, m)
-
- var resp models.Response[[]PrinterClass]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.Equal(t, 1, len(*resp.Result))
-}
-
-func TestHandleCreatePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", false, "", "", "").Return(nil)
- mockClient.EXPECT().ResumePrinter("newprinter").Return(nil)
- mockClient.EXPECT().AcceptJobs("newprinter").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.createPrinter",
- Params: map[string]any{
- "name": "newprinter",
- "deviceURI": "usb://HP",
- "ppd": "generic.ppd",
- },
- }
- handleCreatePrinter(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleCreatePrinter_MissingParams(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- m := &Manager{client: mockClient}
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{ID: 1, Method: "cups.createPrinter", Params: map[string]any{}}
- handleCreatePrinter(conn, req, m)
-
- var resp models.Response[any]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.Nil(t, resp.Result)
- assert.NotNil(t, resp.Error)
-}
-
-func TestHandleDeletePrinter(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeletePrinter("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.deletePrinter",
- Params: map[string]any{"printerName": "printer1"},
- }
- handleDeletePrinter(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleAcceptJobs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().AcceptJobs("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.acceptJobs",
- Params: map[string]any{"printerName": "printer1"},
- }
- handleAcceptJobs(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleRejectJobs(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().RejectJobs("printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.rejectJobs",
- Params: map[string]any{"printerName": "printer1"},
- }
- handleRejectJobs(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleSetPrinterShared(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.setPrinterShared",
- Params: map[string]any{"printerName": "printer1", "shared": true},
- }
- handleSetPrinterShared(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleSetPrinterLocation(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.setPrinterLocation",
- Params: map[string]any{"printerName": "printer1", "location": "Office"},
- }
- handleSetPrinterLocation(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleSetPrinterInfo(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.setPrinterInfo",
- Params: map[string]any{"printerName": "printer1", "info": "Main Printer"},
- }
- handleSetPrinterInfo(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleMoveJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().MoveJob(1, "printer2").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.moveJob",
- Params: map[string]any{"jobID": float64(1), "destPrinter": "printer2"},
- }
- handleMoveJob(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandlePrintTestPage(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.printTestPage",
- Params: map[string]any{"printerName": "printer1"},
- }
- handlePrintTestPage(conn, req, m)
-
- var resp models.Response[TestPageResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, 42, resp.Result.JobID)
-}
-
-func TestHandleAddPrinterToClass(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.addPrinterToClass",
- Params: map[string]any{"className": "office", "printerName": "printer1"},
- }
- handleAddPrinterToClass(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleRemovePrinterFromClass(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.removePrinterFromClass",
- Params: map[string]any{"className": "office", "printerName": "printer1"},
- }
- handleRemovePrinterFromClass(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleDeleteClass(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().DeleteClass("office").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.deleteClass",
- Params: map[string]any{"className": "office"},
- }
- handleDeleteClass(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleRestartJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().RestartJob(1).Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.restartJob",
- Params: map[string]any{"jobID": float64(1)},
- }
- handleRestartJob(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleHoldJob(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.holdJob",
- Params: map[string]any{"jobID": float64(1)},
- }
- handleHoldJob(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
-
-func TestHandleHoldJob_WithHoldUntil(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
- mockClient.EXPECT().HoldJobUntil(1, "no-hold").Return(nil)
- mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
-
- m := NewTestManager(mockClient, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.holdJob",
- Params: map[string]any{"jobID": float64(1), "holdUntil": "no-hold"},
- }
- handleHoldJob(conn, req, m)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go
deleted file mode 100644
index e5b7e8c..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go
+++ /dev/null
@@ -1,390 +0,0 @@
-package cups
-
-import (
- "errors"
- "fmt"
- "os"
- "strconv"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
-)
-
-func NewManager() (*Manager, error) {
- host := os.Getenv("DMS_IPP_HOST")
- if host == "" {
- host = "localhost"
- }
-
- portStr := os.Getenv("DMS_IPP_PORT")
- port := 631
- if portStr != "" {
- if p, err := strconv.Atoi(portStr); err == nil {
- port = p
- }
- }
-
- username := os.Getenv("DMS_IPP_USERNAME")
- password := os.Getenv("DMS_IPP_PASSWORD")
-
- client := ipp.NewCUPSClient(host, port, username, password, false)
- baseURL := fmt.Sprintf("http://%s:%d", host, port)
-
- var pkHelper PkHelper
- if isLocalCUPS(host) {
- var err error
- pkHelper, err = NewPkHelper()
- if err != nil {
- log.Warnf("[CUPS] Failed to initialize pkhelper: %v", err)
- }
- }
-
- m := &Manager{
- state: &CUPSState{
- Printers: make(map[string]*Printer),
- },
- client: client,
- pkHelper: pkHelper,
- baseURL: baseURL,
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- if err := m.updateState(); err != nil {
- return nil, err
- }
-
- if isLocalCUPS(host) {
- m.subscription = NewDBusSubscriptionManager(client, baseURL)
- log.Infof("[CUPS] Using D-Bus notifications for local CUPS")
- } else {
- m.subscription = NewSubscriptionManager(client, baseURL)
- log.Infof("[CUPS] Using IPPGET notifications for remote CUPS")
- }
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- return m, nil
-}
-
-func isLocalCUPS(host string) bool {
- switch host {
- case "localhost", "127.0.0.1", "::1", "":
- return true
- }
- return false
-}
-
-func (m *Manager) eventHandler() {
- defer m.eventWG.Done()
-
- if m.subscription == nil {
- return
- }
-
- for {
- select {
- case <-m.stopChan:
- return
- case event, ok := <-m.subscription.Events():
- if !ok {
- return
- }
- log.Debugf("[CUPS] Received event: %s (printer: %s, job: %d)",
- event.EventName, event.PrinterName, event.JobID)
-
- if err := m.updateState(); err != nil {
- log.Warnf("[CUPS] Failed to update state after event: %v", err)
- } else {
- m.notifySubscribers()
- }
- }
- }
-}
-
-func (m *Manager) updateState() error {
- printers, err := m.GetPrinters()
- if err != nil {
- if isNoPrintersError(err) {
- m.stateMutex.Lock()
- m.state.Printers = make(map[string]*Printer)
- m.stateMutex.Unlock()
- return nil
- }
- return err
- }
-
- printerMap := make(map[string]*Printer, len(printers))
- for _, printer := range printers {
- jobs, err := m.GetJobs(printer.Name, "not-completed")
- if err != nil {
- return err
- }
-
- printer.Jobs = jobs
- printerMap[printer.Name] = &printer
- }
-
- m.stateMutex.Lock()
- m.state.Printers = printerMap
- m.stateMutex.Unlock()
-
- return nil
-}
-
-func isNoPrintersError(err error) bool {
- if err == nil {
- return false
- }
-
- var ippErr ipp.IPPError
- if errors.As(err, &ippErr) {
- return ippErr.Status == 1030
- }
-
- return false
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.snapshotState()
-
- if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- select {
- case ch <- currentState:
- default:
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotifiedState = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) RefreshState() {
- if err := m.updateState(); err != nil {
- log.Warnf("[CUPS] Failed to refresh state: %v", err)
- return
- }
- m.notifySubscribers()
-}
-
-func (m *Manager) GetState() CUPSState {
- return m.snapshotState()
-}
-
-func (m *Manager) snapshotState() CUPSState {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
-
- s := CUPSState{
- Printers: make(map[string]*Printer, len(m.state.Printers)),
- }
- for name, printer := range m.state.Printers {
- printerCopy := *printer
- s.Printers[name] = &printerCopy
- }
- return s
-}
-
-func (m *Manager) Subscribe(id string) chan CUPSState {
- ch := make(chan CUPSState, 64)
-
- wasEmpty := true
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- wasEmpty = false
- return false
- })
-
- m.subscribers.Store(id, ch)
-
- if wasEmpty && m.subscription != nil {
- if err := m.subscription.Start(); err != nil {
- log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
- } else {
- m.eventWG.Add(1)
- go m.eventHandler()
- }
- }
-
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-
- isEmpty := true
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- isEmpty = false
- return false
- })
-
- if isEmpty && m.subscription != nil {
- m.subscription.Stop()
- m.eventWG.Wait()
- }
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
-
- if m.subscription != nil {
- m.subscription.Stop()
- }
-
- m.eventWG.Wait()
- m.notifierWg.Wait()
-
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-}
-
-func stateChanged(old, new *CUPSState) bool {
- if len(old.Printers) != len(new.Printers) {
- return true
- }
- for name, oldPrinter := range old.Printers {
- newPrinter, exists := new.Printers[name]
- if !exists {
- return true
- }
- if oldPrinter.State != newPrinter.State ||
- oldPrinter.StateReason != newPrinter.StateReason ||
- oldPrinter.Accepting != newPrinter.Accepting ||
- len(oldPrinter.Jobs) != len(newPrinter.Jobs) {
- return true
- }
- }
- return false
-}
-
-func parsePrinterState(attrs ipp.Attributes) string {
- if stateAttr, ok := attrs[ipp.AttributePrinterState]; ok && len(stateAttr) > 0 {
- if state, ok := stateAttr[0].Value.(int); ok {
- switch state {
- case 3:
- return "idle"
- case 4:
- return "processing"
- case 5:
- return "stopped"
- default:
- return fmt.Sprintf("%d", state)
- }
- }
- }
- return "unknown"
-}
-
-func parseJobState(attrs ipp.Attributes) string {
- if stateAttr, ok := attrs[ipp.AttributeJobState]; ok && len(stateAttr) > 0 {
- if state, ok := stateAttr[0].Value.(int); ok {
- switch state {
- case 3:
- return "pending"
- case 4:
- return "pending-held"
- case 5:
- return "processing"
- case 6:
- return "processing-stopped"
- case 7:
- return "canceled"
- case 8:
- return "aborted"
- case 9:
- return "completed"
- default:
- return fmt.Sprintf("%d", state)
- }
- }
- }
- return "unknown"
-}
-
-func getStringAttr(attrs ipp.Attributes, key string) string {
- if attr, ok := attrs[key]; ok && len(attr) > 0 {
- if val, ok := attr[0].Value.(string); ok {
- return val
- }
- return fmt.Sprintf("%v", attr[0].Value)
- }
- return ""
-}
-
-func getIntAttr(attrs ipp.Attributes, key string) int {
- if attr, ok := attrs[key]; ok && len(attr) > 0 {
- if val, ok := attr[0].Value.(int); ok {
- return val
- }
- }
- return 0
-}
-
-func getBoolAttr(attrs ipp.Attributes, key string) bool {
- if attr, ok := attrs[key]; ok && len(attr) > 0 {
- if val, ok := attr[0].Value.(bool); ok {
- return val
- }
- }
- return false
-}
-
-func getStringSliceAttr(attrs ipp.Attributes, key string) []string {
- attr, ok := attrs[key]
- if !ok {
- return nil
- }
-
- result := make([]string, 0, len(attr))
- for _, a := range attr {
- if val, ok := a.Value.(string); ok {
- result = append(result, val)
- }
- }
- return result
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go
deleted file mode 100644
index 7f09e67..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go
+++ /dev/null
@@ -1,363 +0,0 @@
-package cups
-
-import (
- "testing"
-
- mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNewManager(t *testing.T) {
- m := &Manager{
- state: &CUPSState{
- Printers: make(map[string]*Printer),
- },
- client: nil,
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- assert.NotNil(t, m)
- assert.NotNil(t, m.state)
-}
-
-func TestManager_GetState(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- m := &Manager{
- state: &CUPSState{
- Printers: map[string]*Printer{
- "test-printer": {
- Name: "test-printer",
- State: "idle",
- },
- },
- },
- client: mockClient,
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- state := m.GetState()
- assert.Equal(t, 1, len(state.Printers))
- assert.Equal(t, "test-printer", state.Printers["test-printer"].Name)
-}
-
-func TestManager_Subscribe(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- m := &Manager{
- state: &CUPSState{
- Printers: make(map[string]*Printer),
- },
- client: mockClient,
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- ch := m.Subscribe("test-client")
- assert.NotNil(t, ch)
-
- count := 0
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- count++
- return true
- })
- assert.Equal(t, 1, count)
-
- m.Unsubscribe("test-client")
- count = 0
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- count++
- return true
- })
- assert.Equal(t, 0, count)
-}
-
-func TestManager_Close(t *testing.T) {
- mockClient := mocks_cups.NewMockCUPSClientInterface(t)
-
- m := &Manager{
- state: &CUPSState{
- Printers: make(map[string]*Printer),
- },
- client: mockClient,
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- m.eventWG.Add(1)
- go func() {
- defer m.eventWG.Done()
- <-m.stopChan
- }()
-
- m.notifierWg.Add(1)
- go func() {
- defer m.notifierWg.Done()
- <-m.stopChan
- }()
-
- m.Close()
- count := 0
- m.subscribers.Range(func(key string, ch chan CUPSState) bool {
- count++
- return true
- })
- assert.Equal(t, 0, count)
-}
-
-func TestStateChanged(t *testing.T) {
- tests := []struct {
- name string
- oldState *CUPSState
- newState *CUPSState
- want bool
- }{
- {
- name: "no change",
- oldState: &CUPSState{
- Printers: map[string]*Printer{
- "p1": {Name: "p1", State: "idle"},
- },
- },
- newState: &CUPSState{
- Printers: map[string]*Printer{
- "p1": {Name: "p1", State: "idle"},
- },
- },
- want: false,
- },
- {
- name: "state changed",
- oldState: &CUPSState{
- Printers: map[string]*Printer{
- "p1": {Name: "p1", State: "idle"},
- },
- },
- newState: &CUPSState{
- Printers: map[string]*Printer{
- "p1": {Name: "p1", State: "processing"},
- },
- },
- want: true,
- },
- {
- name: "printer added",
- oldState: &CUPSState{
- Printers: map[string]*Printer{},
- },
- newState: &CUPSState{
- Printers: map[string]*Printer{
- "p1": {Name: "p1", State: "idle"},
- },
- },
- want: true,
- },
- {
- name: "printer removed",
- oldState: &CUPSState{
- Printers: map[string]*Printer{
- "p1": {Name: "p1", State: "idle"},
- },
- },
- newState: &CUPSState{
- Printers: map[string]*Printer{},
- },
- want: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := stateChanged(tt.oldState, tt.newState)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestParsePrinterState(t *testing.T) {
- tests := []struct {
- name string
- attrs ipp.Attributes
- want string
- }{
- {
- name: "idle",
- attrs: ipp.Attributes{
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
- },
- want: "idle",
- },
- {
- name: "processing",
- attrs: ipp.Attributes{
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 4}},
- },
- want: "processing",
- },
- {
- name: "stopped",
- attrs: ipp.Attributes{
- ipp.AttributePrinterState: []ipp.Attribute{{Value: 5}},
- },
- want: "stopped",
- },
- {
- name: "unknown",
- attrs: ipp.Attributes{},
- want: "unknown",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := parsePrinterState(tt.attrs)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestParseJobState(t *testing.T) {
- tests := []struct {
- name string
- attrs ipp.Attributes
- want string
- }{
- {
- name: "pending",
- attrs: ipp.Attributes{
- ipp.AttributeJobState: []ipp.Attribute{{Value: 3}},
- },
- want: "pending",
- },
- {
- name: "processing",
- attrs: ipp.Attributes{
- ipp.AttributeJobState: []ipp.Attribute{{Value: 5}},
- },
- want: "processing",
- },
- {
- name: "completed",
- attrs: ipp.Attributes{
- ipp.AttributeJobState: []ipp.Attribute{{Value: 9}},
- },
- want: "completed",
- },
- {
- name: "unknown",
- attrs: ipp.Attributes{},
- want: "unknown",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := parseJobState(tt.attrs)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestGetStringAttr(t *testing.T) {
- tests := []struct {
- name string
- attrs ipp.Attributes
- key string
- want string
- }{
- {
- name: "string value",
- attrs: ipp.Attributes{
- "test-key": []ipp.Attribute{{Value: "test-value"}},
- },
- key: "test-key",
- want: "test-value",
- },
- {
- name: "missing key",
- attrs: ipp.Attributes{},
- key: "missing",
- want: "",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := getStringAttr(tt.attrs, tt.key)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestGetIntAttr(t *testing.T) {
- tests := []struct {
- name string
- attrs ipp.Attributes
- key string
- want int
- }{
- {
- name: "int value",
- attrs: ipp.Attributes{
- "test-key": []ipp.Attribute{{Value: 42}},
- },
- key: "test-key",
- want: 42,
- },
- {
- name: "missing key",
- attrs: ipp.Attributes{},
- key: "missing",
- want: 0,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := getIntAttr(tt.attrs, tt.key)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestGetBoolAttr(t *testing.T) {
- tests := []struct {
- name string
- attrs ipp.Attributes
- key string
- want bool
- }{
- {
- name: "true value",
- attrs: ipp.Attributes{
- "test-key": []ipp.Attribute{{Value: true}},
- },
- key: "test-key",
- want: true,
- },
- {
- name: "false value",
- attrs: ipp.Attributes{
- "test-key": []ipp.Attribute{{Value: false}},
- },
- key: "test-key",
- want: false,
- },
- {
- name: "missing key",
- attrs: ipp.Attributes{},
- key: "missing",
- want: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := getBoolAttr(tt.attrs, tt.key)
- assert.Equal(t, tt.want, got)
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go
deleted file mode 100644
index 81dd458..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package cups
-
-import (
- "fmt"
- "strings"
-
- "github.com/godbus/dbus/v5"
-)
-
-const (
- pkHelperDest = "org.opensuse.CupsPkHelper.Mechanism"
- pkHelperPath = "/"
- pkHelperInterface = "org.opensuse.CupsPkHelper.Mechanism"
-)
-
-type PkHelper interface {
- DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error)
- PrinterAdd(name, uri, ppd, info, location string) error
- PrinterDelete(name string) error
- PrinterSetEnabled(name string, enabled bool) error
- PrinterSetAcceptJobs(name string, enabled bool, reason string) error
- PrinterSetInfo(name, info string) error
- PrinterSetLocation(name, location string) error
- PrinterSetShared(name string, shared bool) error
- ClassAddPrinter(className, printerName string) error
- ClassDeletePrinter(className, printerName string) error
- ClassDelete(className string) error
- JobCancelPurge(jobID int, purge bool) error
- JobRestart(jobID int) error
- JobSetHoldUntil(jobID int, holdUntil string) error
-}
-
-type DBusPkHelper struct {
- conn *dbus.Conn
- obj dbus.BusObject
-}
-
-func NewPkHelper() (*DBusPkHelper, error) {
- conn, err := dbus.SystemBus()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to system bus: %w", err)
- }
-
- return &DBusPkHelper{
- conn: conn,
- obj: conn.Object(pkHelperDest, pkHelperPath),
- }, nil
-}
-
-func (p *DBusPkHelper) DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error) {
- if includeSchemes == nil {
- includeSchemes = []string{}
- }
- if excludeSchemes == nil {
- excludeSchemes = []string{}
- }
-
- var errStr string
- var devicesMap map[string]string
-
- call := p.obj.Call(pkHelperInterface+".DevicesGet", 0, int32(timeout), int32(limit), includeSchemes, excludeSchemes)
- if call.Err != nil {
- return nil, call.Err
- }
- if err := call.Store(&errStr, &devicesMap); err != nil {
- return nil, err
- }
- if errStr != "" {
- return nil, fmt.Errorf("%s", errStr)
- }
-
- return parseDevicesMap(devicesMap), nil
-}
-
-func parseDevicesMap(devicesMap map[string]string) []Device {
- devicesByIndex := make(map[string]*Device)
-
- for key, value := range devicesMap {
- idx := strings.LastIndex(key, ":")
- if idx == -1 {
- continue
- }
-
- attr := key[:idx]
- index := key[idx+1:]
-
- dev, ok := devicesByIndex[index]
- if !ok {
- dev = &Device{}
- devicesByIndex[index] = dev
- }
-
- switch attr {
- case "device-uri":
- dev.URI = value
- case "device-class":
- dev.Class = value
- case "device-info":
- dev.Info = value
- case "device-make-and-model":
- dev.MakeModel = value
- case "device-id":
- dev.ID = value
- case "device-location":
- dev.Location = value
- }
- }
-
- devices := make([]Device, 0, len(devicesByIndex))
- for _, dev := range devicesByIndex {
- if dev.URI != "" {
- devices = append(devices, *dev)
- }
- }
- return devices
-}
-
-func (p *DBusPkHelper) PrinterAdd(name, uri, ppd, info, location string) error {
- return p.callSimple("PrinterAdd", name, uri, ppd, info, location)
-}
-
-func (p *DBusPkHelper) PrinterDelete(name string) error {
- return p.callSimple("PrinterDelete", name)
-}
-
-func (p *DBusPkHelper) PrinterSetEnabled(name string, enabled bool) error {
- return p.callSimple("PrinterSetEnabled", name, enabled)
-}
-
-func (p *DBusPkHelper) PrinterSetAcceptJobs(name string, enabled bool, reason string) error {
- return p.callSimple("PrinterSetAcceptJobs", name, enabled, reason)
-}
-
-func (p *DBusPkHelper) PrinterSetInfo(name, info string) error {
- return p.callSimple("PrinterSetInfo", name, info)
-}
-
-func (p *DBusPkHelper) PrinterSetLocation(name, location string) error {
- return p.callSimple("PrinterSetLocation", name, location)
-}
-
-func (p *DBusPkHelper) PrinterSetShared(name string, shared bool) error {
- return p.callSimple("PrinterSetShared", name, shared)
-}
-
-func (p *DBusPkHelper) ClassAddPrinter(className, printerName string) error {
- return p.callSimple("ClassAddPrinter", className, printerName)
-}
-
-func (p *DBusPkHelper) ClassDeletePrinter(className, printerName string) error {
- return p.callSimple("ClassDeletePrinter", className, printerName)
-}
-
-func (p *DBusPkHelper) ClassDelete(className string) error {
- return p.callSimple("ClassDelete", className)
-}
-
-func (p *DBusPkHelper) JobCancelPurge(jobID int, purge bool) error {
- return p.callSimple("JobCancelPurge", int32(jobID), purge)
-}
-
-func (p *DBusPkHelper) JobRestart(jobID int) error {
- return p.callSimple("JobRestart", int32(jobID))
-}
-
-func (p *DBusPkHelper) JobSetHoldUntil(jobID int, holdUntil string) error {
- return p.callSimple("JobSetHoldUntil", int32(jobID), holdUntil)
-}
-
-func (p *DBusPkHelper) callSimple(method string, args ...any) error {
- var errStr string
-
- call := p.obj.Call(pkHelperInterface+"."+method, 0, args...)
- if call.Err != nil {
- return call.Err
- }
- if err := call.Store(&errStr); err != nil {
- return err
- }
- if errStr != "" {
- return fmt.Errorf("%s", errStr)
- }
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go
deleted file mode 100644
index 596f1d0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package cups
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestParseDevicesMap(t *testing.T) {
- tests := []struct {
- name string
- input map[string]string
- wantLen int
- wantURIs []string
- }{
- {
- name: "empty",
- input: map[string]string{},
- wantLen: 0,
- wantURIs: nil,
- },
- {
- name: "single_device",
- input: map[string]string{
- "device-uri:0": "usb://HP/LaserJet",
- "device-class:0": "direct",
- "device-info:0": "HP LaserJet",
- "device-make-and-model:0": "HP LaserJet 1020",
- "device-id:0": "MFG:HP;MDL:LaserJet",
- },
- wantLen: 1,
- wantURIs: []string{"usb://HP/LaserJet"},
- },
- {
- name: "multiple_devices",
- input: map[string]string{
- "device-uri:0": "usb://HP/LaserJet",
- "device-class:0": "direct",
- "device-info:0": "HP LaserJet",
- "device-uri:1": "socket://192.168.1.100",
- "device-class:1": "network",
- "device-info:1": "Network Printer",
- },
- wantLen: 2,
- wantURIs: []string{"usb://HP/LaserJet", "socket://192.168.1.100"},
- },
- {
- name: "malformed_key",
- input: map[string]string{
- "no-colon-here": "value",
- },
- wantLen: 0,
- wantURIs: nil,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := parseDevicesMap(tt.input)
- assert.Len(t, got, tt.wantLen)
-
- if tt.wantURIs != nil {
- gotURIs := make(map[string]bool)
- for _, d := range got {
- gotURIs[d.URI] = true
- }
- for _, uri := range tt.wantURIs {
- assert.True(t, gotURIs[uri], "expected URI %s not found", uri)
- }
- }
- })
- }
-}
-
-func TestParseDevicesMap_Attributes(t *testing.T) {
- input := map[string]string{
- "device-uri:0": "usb://HP/LaserJet",
- "device-class:0": "direct",
- "device-info:0": "HP LaserJet",
- "device-make-and-model:0": "HP LaserJet 1020",
- "device-id:0": "MFG:HP;MDL:LaserJet",
- "device-location:0": "USB Port",
- }
-
- got := parseDevicesMap(input)
- assert.Len(t, got, 1)
-
- dev := got[0]
- assert.Equal(t, "usb://HP/LaserJet", dev.URI)
- assert.Equal(t, "direct", dev.Class)
- assert.Equal(t, "HP LaserJet", dev.Info)
- assert.Equal(t, "HP LaserJet 1020", dev.MakeModel)
- assert.Equal(t, "MFG:HP;MDL:LaserJet", dev.ID)
- assert.Equal(t, "USB Port", dev.Location)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go
deleted file mode 100644
index 6b369b4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go
+++ /dev/null
@@ -1,245 +0,0 @@
-package cups
-
-import (
- "fmt"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
-)
-
-type SubscriptionManager struct {
- client CUPSClientInterface
- subscriptionID int
- sequenceNumber int
- eventChan chan SubscriptionEvent
- stopChan chan struct{}
- wg sync.WaitGroup
- baseURL string
- running bool
- mu sync.Mutex
-}
-
-func NewSubscriptionManager(client CUPSClientInterface, baseURL string) *SubscriptionManager {
- return &SubscriptionManager{
- client: client,
- eventChan: make(chan SubscriptionEvent, 100),
- stopChan: make(chan struct{}),
- baseURL: baseURL,
- }
-}
-
-func (sm *SubscriptionManager) Start() error {
- sm.mu.Lock()
- if sm.running {
- sm.mu.Unlock()
- return fmt.Errorf("subscription manager already running")
- }
- sm.running = true
- sm.mu.Unlock()
-
- subID, err := sm.createSubscription()
- if err != nil {
- sm.mu.Lock()
- sm.running = false
- sm.mu.Unlock()
- return fmt.Errorf("failed to create subscription: %w", err)
- }
-
- sm.subscriptionID = subID
- log.Infof("[CUPS] Created IPP subscription with ID %d", subID)
-
- sm.wg.Add(1)
- go sm.notificationLoop()
-
- return nil
-}
-
-func (sm *SubscriptionManager) createSubscription() (int, error) {
- req := ipp.NewRequest(ipp.OperationCreatePrinterSubscriptions, 1)
- req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
- req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
-
- // Subscription attributes go in SubscriptionAttributes (subscription-attributes-tag in IPP)
- req.SubscriptionAttributes = map[string]any{
- "notify-events": []string{
- "printer-state-changed",
- "printer-added",
- "printer-deleted",
- "job-created",
- "job-completed",
- "job-state-changed",
- },
- "notify-pull-method": "ippget",
- "notify-lease-duration": 0,
- }
-
- // Send to root IPP endpoint
- resp, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
- if err != nil {
- return 0, fmt.Errorf("SendRequest failed: %w", err)
- }
-
- // Check for IPP errors
- if err := resp.CheckForErrors(); err != nil {
- return 0, fmt.Errorf("IPP error: %w", err)
- }
-
- // Subscription ID comes back in SubscriptionAttributes
- if len(resp.SubscriptionAttributes) > 0 {
- if idAttr, ok := resp.SubscriptionAttributes[0]["notify-subscription-id"]; ok && len(idAttr) > 0 {
- if val, ok := idAttr[0].Value.(int); ok {
- return val, nil
- }
- }
- }
-
- return 0, fmt.Errorf("no subscription ID returned")
-}
-
-func (sm *SubscriptionManager) notificationLoop() {
- defer sm.wg.Done()
-
- backoff := 1 * time.Second
-
- for {
- select {
- case <-sm.stopChan:
- return
- default:
- }
-
- gotAny, err := sm.fetchNotificationsWithWait()
- if err != nil {
- log.Warnf("[CUPS] Error fetching notifications: %v", err)
- jitter := time.Duration(50+(time.Now().UnixNano()%200)) * time.Millisecond
- sleepTime := backoff + jitter
- if sleepTime > 30*time.Second {
- sleepTime = 30 * time.Second
- }
- select {
- case <-sm.stopChan:
- return
- case <-time.After(sleepTime):
- }
- if backoff < 30*time.Second {
- backoff *= 2
- }
- continue
- }
-
- backoff = 1 * time.Second
-
- if gotAny {
- continue
- }
-
- select {
- case <-sm.stopChan:
- return
- case <-time.After(2 * time.Second):
- }
- }
-}
-
-func (sm *SubscriptionManager) fetchNotificationsWithWait() (bool, error) {
- req := ipp.NewRequest(ipp.OperationGetNotifications, 1)
- req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
- req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
- req.OperationAttributes["notify-subscription-ids"] = sm.subscriptionID
- if sm.sequenceNumber > 0 {
- req.OperationAttributes["notify-sequence-numbers"] = sm.sequenceNumber
- }
-
- resp, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
- if err != nil {
- return false, err
- }
-
- gotAny := false
- for _, eventGroup := range resp.SubscriptionAttributes {
- if seqAttr, ok := eventGroup["notify-sequence-number"]; ok && len(seqAttr) > 0 {
- if seqNum, ok := seqAttr[0].Value.(int); ok {
- sm.sequenceNumber = seqNum + 1
- }
- }
-
- event := sm.parseEvent(eventGroup)
- gotAny = true
- select {
- case sm.eventChan <- event:
- case <-sm.stopChan:
- return gotAny, nil
- default:
- log.Warn("[CUPS] Event channel full, dropping event")
- }
- }
-
- return gotAny, nil
-}
-
-func (sm *SubscriptionManager) parseEvent(attrs ipp.Attributes) SubscriptionEvent {
- event := SubscriptionEvent{
- SubscribedAt: time.Now(),
- }
-
- if attr, ok := attrs["notify-subscribed-event"]; ok && len(attr) > 0 {
- if val, ok := attr[0].Value.(string); ok {
- event.EventName = val
- }
- }
-
- if attr, ok := attrs["printer-name"]; ok && len(attr) > 0 {
- if val, ok := attr[0].Value.(string); ok {
- event.PrinterName = val
- }
- }
-
- if attr, ok := attrs["notify-job-id"]; ok && len(attr) > 0 {
- if val, ok := attr[0].Value.(int); ok {
- event.JobID = val
- }
- }
-
- return event
-}
-
-func (sm *SubscriptionManager) Events() <-chan SubscriptionEvent {
- return sm.eventChan
-}
-
-func (sm *SubscriptionManager) Stop() {
- sm.mu.Lock()
- if !sm.running {
- sm.mu.Unlock()
- return
- }
- sm.running = false
- sm.mu.Unlock()
-
- close(sm.stopChan)
- sm.wg.Wait()
-
- if sm.subscriptionID != 0 {
- sm.cancelSubscription()
- sm.subscriptionID = 0
- sm.sequenceNumber = 0
- }
-
- sm.stopChan = make(chan struct{})
-}
-
-func (sm *SubscriptionManager) cancelSubscription() {
- req := ipp.NewRequest(ipp.OperationCancelSubscription, 1)
- req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
- req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
- req.OperationAttributes["notify-subscription-id"] = sm.subscriptionID
-
- _, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
- if err != nil {
- log.Warnf("[CUPS] Failed to cancel subscription %d: %v", sm.subscriptionID, err)
- } else {
- log.Infof("[CUPS] Cancelled subscription %d", sm.subscriptionID)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go
deleted file mode 100644
index b205a6b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go
+++ /dev/null
@@ -1,295 +0,0 @@
-package cups
-
-import (
- "fmt"
- "strings"
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/godbus/dbus/v5"
-)
-
-type DBusSubscriptionManager struct {
- client CUPSClientInterface
- subscriptionID int
- eventChan chan SubscriptionEvent
- stopChan chan struct{}
- wg sync.WaitGroup
- baseURL string
- running bool
- mu sync.Mutex
- conn *dbus.Conn
-}
-
-func NewDBusSubscriptionManager(client CUPSClientInterface, baseURL string) *DBusSubscriptionManager {
- return &DBusSubscriptionManager{
- client: client,
- eventChan: make(chan SubscriptionEvent, 100),
- stopChan: make(chan struct{}),
- baseURL: baseURL,
- }
-}
-
-func (sm *DBusSubscriptionManager) Start() error {
- sm.mu.Lock()
- if sm.running {
- sm.mu.Unlock()
- return fmt.Errorf("subscription manager already running")
- }
- sm.running = true
- sm.mu.Unlock()
-
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- sm.mu.Lock()
- sm.running = false
- sm.mu.Unlock()
- return fmt.Errorf("connect to system bus: %w", err)
- }
- sm.conn = conn
-
- subID, err := sm.createDBusSubscription()
- if err != nil {
- sm.conn.Close()
- sm.mu.Lock()
- sm.running = false
- sm.mu.Unlock()
- return fmt.Errorf("failed to create D-Bus subscription: %w", err)
- }
-
- sm.subscriptionID = subID
- log.Infof("[CUPS] Created D-Bus subscription with ID %d", subID)
-
- if err := sm.conn.AddMatchSignal(
- dbus.WithMatchInterface("org.cups.cupsd.Notifier"),
- ); err != nil {
- sm.cancelSubscription()
- sm.conn.Close()
- sm.mu.Lock()
- sm.running = false
- sm.mu.Unlock()
- return fmt.Errorf("failed to add D-Bus match: %w", err)
- }
-
- sm.wg.Add(1)
- go sm.dbusListenerLoop()
-
- return nil
-}
-
-func (sm *DBusSubscriptionManager) createDBusSubscription() (int, error) {
- req := ipp.NewRequest(ipp.OperationCreatePrinterSubscriptions, 2)
- req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
- req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
-
- req.SubscriptionAttributes = map[string]any{
- "notify-events": []string{
- "printer-state-changed",
- "printer-added",
- "printer-deleted",
- "job-created",
- "job-completed",
- "job-state-changed",
- },
- "notify-recipient-uri": "dbus:/",
- "notify-lease-duration": 86400,
- }
-
- resp, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
- if err != nil {
- return 0, fmt.Errorf("SendRequest failed: %w", err)
- }
-
- if err := resp.CheckForErrors(); err != nil {
- return 0, fmt.Errorf("IPP error: %w", err)
- }
-
- if len(resp.SubscriptionAttributes) > 0 {
- if idAttr, ok := resp.SubscriptionAttributes[0]["notify-subscription-id"]; ok && len(idAttr) > 0 {
- if val, ok := idAttr[0].Value.(int); ok {
- return val, nil
- }
- }
- }
-
- return 0, fmt.Errorf("no subscription ID returned")
-}
-
-func (sm *DBusSubscriptionManager) dbusListenerLoop() {
- defer sm.wg.Done()
-
- signalChan := make(chan *dbus.Signal, 10)
- sm.conn.Signal(signalChan)
- defer sm.conn.RemoveSignal(signalChan)
-
- for {
- select {
- case <-sm.stopChan:
- return
- case sig := <-signalChan:
- if sig == nil {
- continue
- }
-
- event := sm.parseDBusSignal(sig)
- if event.EventName == "" {
- continue
- }
-
- select {
- case sm.eventChan <- event:
- case <-sm.stopChan:
- return
- default:
- log.Warn("[CUPS] Event channel full, dropping event")
- }
- }
- }
-}
-
-func (sm *DBusSubscriptionManager) parseDBusSignal(sig *dbus.Signal) SubscriptionEvent {
- event := SubscriptionEvent{}
-
- switch sig.Name {
- case "org.cups.cupsd.Notifier.JobStateChanged":
- if len(sig.Body) >= 6 {
- if text, ok := sig.Body[0].(string); ok {
- event.EventName = "job-state-changed"
- parts := strings.Split(text, " ")
- if len(parts) >= 2 {
- event.PrinterName = parts[0]
- }
- }
- if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
- if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
- event.PrinterName = printerURI[idx+1:]
- }
- }
- if jobID, ok := sig.Body[3].(uint32); ok {
- event.JobID = int(jobID)
- }
- }
-
- case "org.cups.cupsd.Notifier.JobCreated":
- if len(sig.Body) >= 6 {
- if text, ok := sig.Body[0].(string); ok {
- event.EventName = "job-created"
- parts := strings.Split(text, " ")
- if len(parts) >= 2 {
- event.PrinterName = parts[0]
- }
- }
- if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
- if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
- event.PrinterName = printerURI[idx+1:]
- }
- }
- if jobID, ok := sig.Body[3].(uint32); ok {
- event.JobID = int(jobID)
- }
- }
-
- case "org.cups.cupsd.Notifier.JobCompleted":
- if len(sig.Body) >= 6 {
- if text, ok := sig.Body[0].(string); ok {
- event.EventName = "job-completed"
- parts := strings.Split(text, " ")
- if len(parts) >= 2 {
- event.PrinterName = parts[0]
- }
- }
- if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
- if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
- event.PrinterName = printerURI[idx+1:]
- }
- }
- if jobID, ok := sig.Body[3].(uint32); ok {
- event.JobID = int(jobID)
- }
- }
-
- case "org.cups.cupsd.Notifier.PrinterStateChanged":
- if len(sig.Body) >= 6 {
- if text, ok := sig.Body[0].(string); ok {
- event.EventName = "printer-state-changed"
- parts := strings.Split(text, " ")
- if len(parts) >= 2 {
- event.PrinterName = parts[0]
- }
- }
- if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
- if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
- event.PrinterName = printerURI[idx+1:]
- }
- }
- }
-
- case "org.cups.cupsd.Notifier.PrinterAdded":
- if len(sig.Body) >= 6 {
- if text, ok := sig.Body[0].(string); ok {
- event.EventName = "printer-added"
- parts := strings.Split(text, " ")
- if len(parts) >= 2 {
- event.PrinterName = parts[0]
- }
- }
- }
-
- case "org.cups.cupsd.Notifier.PrinterDeleted":
- if len(sig.Body) >= 6 {
- if text, ok := sig.Body[0].(string); ok {
- event.EventName = "printer-deleted"
- parts := strings.Split(text, " ")
- if len(parts) >= 2 {
- event.PrinterName = parts[0]
- }
- }
- }
- }
-
- return event
-}
-
-func (sm *DBusSubscriptionManager) Events() <-chan SubscriptionEvent {
- return sm.eventChan
-}
-
-func (sm *DBusSubscriptionManager) Stop() {
- sm.mu.Lock()
- if !sm.running {
- sm.mu.Unlock()
- return
- }
- sm.running = false
- sm.mu.Unlock()
-
- close(sm.stopChan)
- sm.wg.Wait()
-
- if sm.subscriptionID != 0 {
- sm.cancelSubscription()
- sm.subscriptionID = 0
- }
-
- if sm.conn != nil {
- sm.conn.Close()
- sm.conn = nil
- }
-
- sm.stopChan = make(chan struct{})
-}
-
-func (sm *DBusSubscriptionManager) cancelSubscription() {
- req := ipp.NewRequest(ipp.OperationCancelSubscription, 1)
- req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
- req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
- req.OperationAttributes["notify-subscription-id"] = sm.subscriptionID
-
- _, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
- if err != nil {
- log.Warnf("[CUPS] Failed to cancel subscription %d: %v", sm.subscriptionID, err)
- } else {
- log.Infof("[CUPS] Cancelled subscription %d", sm.subscriptionID)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go
deleted file mode 100644
index d0203e9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go
+++ /dev/null
@@ -1,176 +0,0 @@
-package cups
-
-import (
- "errors"
- "fmt"
- "net"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
-)
-
-var validProtocols = map[string]bool{
- "ipp": true,
- "ipps": true,
- "lpd": true,
- "socket": true,
-}
-
-func validateTestConnectionParams(host string, port int, protocol string) error {
- if host == "" {
- return errors.New("host is required")
- }
- if strings.ContainsAny(host, " \t\n\r/\\") {
- return errors.New("host contains invalid characters")
- }
- if port < 1 || port > 65535 {
- return errors.New("port must be between 1 and 65535")
- }
- if protocol != "" && !validProtocols[protocol] {
- return errors.New("protocol must be one of: ipp, ipps, lpd, socket")
- }
- return nil
-}
-
-const probeTimeout = 10 * time.Second
-
-func probeRemotePrinter(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- addr := net.JoinHostPort(host, fmt.Sprintf("%d", port))
-
- // Fast fail: TCP reachability check
- conn, err := net.DialTimeout("tcp", addr, probeTimeout)
- if err != nil {
- return &RemotePrinterInfo{
- Reachable: false,
- Error: fmt.Sprintf("cannot reach %s: %s", addr, err.Error()),
- }, nil
- }
- conn.Close()
-
- // Create a temporary IPP client pointing at the remote host.
- // The TCP dial above provides fast-fail for unreachable hosts.
- // The IPP adapter's ResponseHeaderTimeout (90s) bounds stalling servers.
- client := ipp.NewIPPClient(host, port, "", "", useTLS)
-
- // Try /ipp/print first (modern driverless printers), then / (legacy)
- info, err := probeIPPEndpoint(client, host, port, useTLS, "/ipp/print")
- if err != nil {
- // If we got an auth error, the printer exists but requires credentials.
- // Report it as reachable with the URI that triggered the auth challenge.
- if isAuthError(err) {
- proto := "ipp"
- if useTLS {
- proto = "ipps"
- }
- return &RemotePrinterInfo{
- Reachable: true,
- URI: fmt.Sprintf("%s://%s:%d/ipp/print", proto, host, port),
- Info: "authentication required",
- }, nil
- }
- info, err = probeIPPEndpoint(client, host, port, useTLS, "/")
- }
- if err != nil {
- if isAuthError(err) {
- proto := "ipp"
- if useTLS {
- proto = "ipps"
- }
- return &RemotePrinterInfo{
- Reachable: true,
- URI: fmt.Sprintf("%s://%s:%d/", proto, host, port),
- Info: "authentication required",
- }, nil
- }
- // TCP reachable but not an IPP printer
- return &RemotePrinterInfo{
- Reachable: true,
- Error: fmt.Sprintf("host is reachable but does not appear to be an IPP printer: %s", err.Error()),
- }, nil
- }
-
- return info, nil
-}
-
-func probeIPPEndpoint(client *ipp.IPPClient, host string, port int, useTLS bool, resourcePath string) (*RemotePrinterInfo, error) {
- proto := "ipp"
- if useTLS {
- proto = "ipps"
- }
- printerURI := fmt.Sprintf("%s://%s:%d%s", proto, host, port, resourcePath)
-
- httpProto := "http"
- if useTLS {
- httpProto = "https"
- }
- httpURL := fmt.Sprintf("%s://%s:%d%s", httpProto, host, port, resourcePath)
-
- req := ipp.NewRequest(ipp.OperationGetPrinterAttributes, 1)
- req.OperationAttributes[ipp.AttributePrinterURI] = printerURI
- req.OperationAttributes[ipp.AttributeRequestedAttributes] = []string{
- ipp.AttributePrinterName,
- ipp.AttributePrinterMakeAndModel,
- ipp.AttributePrinterState,
- ipp.AttributePrinterInfo,
- ipp.AttributePrinterUriSupported,
- }
-
- resp, err := client.SendRequest(httpURL, req, nil)
- if err != nil {
- return nil, err
- }
-
- if len(resp.PrinterAttributes) == 0 {
- return nil, errors.New("no printer attributes returned")
- }
-
- attrs := resp.PrinterAttributes[0]
-
- return &RemotePrinterInfo{
- Reachable: true,
- MakeModel: getStringAttr(attrs, ipp.AttributePrinterMakeAndModel),
- Name: getStringAttr(attrs, ipp.AttributePrinterName),
- Info: getStringAttr(attrs, ipp.AttributePrinterInfo),
- State: parsePrinterState(attrs),
- URI: printerURI,
- }, nil
-}
-
-// TestRemotePrinter validates inputs and probes a remote printer via IPP.
-// For lpd/socket protocols, only TCP reachability is tested.
-func (m *Manager) TestRemotePrinter(host string, port int, protocol string) (*RemotePrinterInfo, error) {
- if protocol == "" {
- protocol = "ipp"
- }
-
- if err := validateTestConnectionParams(host, port, protocol); err != nil {
- return nil, err
- }
-
- // For non-IPP protocols, only check TCP reachability
- if protocol == "lpd" || protocol == "socket" {
- addr := net.JoinHostPort(host, fmt.Sprintf("%d", port))
- conn, err := net.DialTimeout("tcp", addr, probeTimeout)
- if err != nil {
- return &RemotePrinterInfo{
- Reachable: false,
- Error: fmt.Sprintf("cannot reach %s: %s", addr, err.Error()),
- }, nil
- }
- conn.Close()
- return &RemotePrinterInfo{
- Reachable: true,
- URI: fmt.Sprintf("%s://%s:%d", protocol, host, port),
- }, nil
- }
-
- useTLS := protocol == "ipps"
-
- probeFn := m.probeRemoteFn
- if probeFn == nil {
- probeFn = probeRemotePrinter
- }
-
- return probeFn(host, port, useTLS)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go
deleted file mode 100644
index b835028..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go
+++ /dev/null
@@ -1,397 +0,0 @@
-package cups
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/stretchr/testify/assert"
-)
-
-func TestValidateTestConnectionParams(t *testing.T) {
- tests := []struct {
- name string
- host string
- port int
- protocol string
- wantErr string
- }{
- {
- name: "valid ipp",
- host: "192.168.0.5",
- port: 631,
- protocol: "ipp",
- wantErr: "",
- },
- {
- name: "valid ipps",
- host: "printer.local",
- port: 443,
- protocol: "ipps",
- wantErr: "",
- },
- {
- name: "valid lpd",
- host: "10.0.0.1",
- port: 515,
- protocol: "lpd",
- wantErr: "",
- },
- {
- name: "valid socket",
- host: "10.0.0.1",
- port: 9100,
- protocol: "socket",
- wantErr: "",
- },
- {
- name: "empty host",
- host: "",
- port: 631,
- protocol: "ipp",
- wantErr: "host is required",
- },
- {
- name: "port too low",
- host: "192.168.0.5",
- port: 0,
- protocol: "ipp",
- wantErr: "port must be between 1 and 65535",
- },
- {
- name: "port too high",
- host: "192.168.0.5",
- port: 70000,
- protocol: "ipp",
- wantErr: "port must be between 1 and 65535",
- },
- {
- name: "invalid protocol",
- host: "192.168.0.5",
- port: 631,
- protocol: "ftp",
- wantErr: "protocol must be one of: ipp, ipps, lpd, socket",
- },
- {
- name: "empty protocol treated as ipp",
- host: "192.168.0.5",
- port: 631,
- protocol: "",
- wantErr: "",
- },
- {
- name: "host with slash",
- host: "192.168.0.5/admin",
- port: 631,
- protocol: "ipp",
- wantErr: "host contains invalid characters",
- },
- {
- name: "host with space",
- host: "192.168.0.5 ",
- port: 631,
- protocol: "ipp",
- wantErr: "host contains invalid characters",
- },
- {
- name: "host with newline",
- host: "192.168.0.5\n",
- port: 631,
- protocol: "ipp",
- wantErr: "host contains invalid characters",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := validateTestConnectionParams(tt.host, tt.port, tt.protocol)
- if tt.wantErr == "" {
- assert.NoError(t, err)
- } else {
- assert.EqualError(t, err, tt.wantErr)
- }
- })
- }
-}
-
-func TestManager_TestRemotePrinter_Validation(t *testing.T) {
- m := NewTestManager(nil, nil)
-
- tests := []struct {
- name string
- host string
- port int
- protocol string
- wantErr string
- }{
- {
- name: "empty host returns error",
- host: "",
- port: 631,
- protocol: "ipp",
- wantErr: "host is required",
- },
- {
- name: "invalid port returns error",
- host: "192.168.0.5",
- port: 0,
- protocol: "ipp",
- wantErr: "port must be between 1 and 65535",
- },
- {
- name: "invalid protocol returns error",
- host: "192.168.0.5",
- port: 631,
- protocol: "ftp",
- wantErr: "protocol must be one of: ipp, ipps, lpd, socket",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := m.TestRemotePrinter(tt.host, tt.port, tt.protocol)
- assert.EqualError(t, err, tt.wantErr)
- })
- }
-}
-
-func TestManager_TestRemotePrinter_IPP(t *testing.T) {
- tests := []struct {
- name string
- protocol string
- probeRet *RemotePrinterInfo
- probeErr error
- wantTLS bool
- wantReach bool
- wantModel string
- }{
- {
- name: "successful ipp probe",
- protocol: "ipp",
- probeRet: &RemotePrinterInfo{
- Reachable: true,
- MakeModel: "HP OfficeJet 8010",
- Name: "OfficeJet",
- State: "idle",
- URI: "ipp://192.168.0.5:631/ipp/print",
- },
- wantTLS: false,
- wantReach: true,
- wantModel: "HP OfficeJet 8010",
- },
- {
- name: "successful ipps probe",
- protocol: "ipps",
- probeRet: &RemotePrinterInfo{
- Reachable: true,
- MakeModel: "HP OfficeJet 8010",
- URI: "ipps://192.168.0.5:631/ipp/print",
- },
- wantTLS: true,
- wantReach: true,
- wantModel: "HP OfficeJet 8010",
- },
- {
- name: "unreachable host",
- protocol: "ipp",
- probeRet: &RemotePrinterInfo{
- Reachable: false,
- Error: "cannot reach 192.168.0.5:631: connection refused",
- },
- wantReach: false,
- },
- {
- name: "empty protocol defaults to ipp",
- protocol: "",
- probeRet: &RemotePrinterInfo{
- Reachable: true,
- MakeModel: "Test Printer",
- },
- wantTLS: false,
- wantReach: true,
- wantModel: "Test Printer",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var capturedTLS bool
- m := NewTestManager(nil, nil)
- m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- capturedTLS = useTLS
- return tt.probeRet, tt.probeErr
- }
-
- result, err := m.TestRemotePrinter("192.168.0.5", 631, tt.protocol)
- if tt.probeErr != nil {
- assert.Error(t, err)
- return
- }
- assert.NoError(t, err)
- assert.Equal(t, tt.wantReach, result.Reachable)
- assert.Equal(t, tt.wantModel, result.MakeModel)
- assert.Equal(t, tt.wantTLS, capturedTLS)
- })
- }
-}
-
-func TestManager_TestRemotePrinter_AuthRequired(t *testing.T) {
- m := NewTestManager(nil, nil)
- m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- // Simulate what happens when the printer returns HTTP 401
- return probeRemotePrinterWithAuthError(host, port, useTLS)
- }
-
- result, err := m.TestRemotePrinter("192.168.0.107", 631, "ipp")
- assert.NoError(t, err)
- assert.True(t, result.Reachable)
- assert.Equal(t, "authentication required", result.Info)
- assert.Contains(t, result.URI, "ipp://192.168.0.107:631")
-}
-
-// probeRemotePrinterWithAuthError simulates a probe where the printer
-// returns HTTP 401 on both endpoints.
-func probeRemotePrinterWithAuthError(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- // This simulates what probeRemotePrinter does when both endpoints
- // return auth errors. We test the auth detection logic directly.
- err := ipp.HTTPError{Code: 401}
- if isAuthError(err) {
- proto := "ipp"
- if useTLS {
- proto = "ipps"
- }
- return &RemotePrinterInfo{
- Reachable: true,
- URI: fmt.Sprintf("%s://%s:%d/ipp/print", proto, host, port),
- Info: "authentication required",
- }, nil
- }
- return nil, err
-}
-
-func TestManager_TestRemotePrinter_NonIPPProtocol(t *testing.T) {
- m := NewTestManager(nil, nil)
- probeCalled := false
- m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- probeCalled = true
- return nil, nil
- }
-
- // These will fail at TCP dial (no real server), but the important
- // thing is that probeRemoteFn is NOT called for lpd/socket.
- m.TestRemotePrinter("192.168.0.5", 9100, "socket")
- assert.False(t, probeCalled, "probe function should not be called for socket protocol")
-
- m.TestRemotePrinter("192.168.0.5", 515, "lpd")
- assert.False(t, probeCalled, "probe function should not be called for lpd protocol")
-}
-
-func TestHandleTestConnection_Success(t *testing.T) {
- m := NewTestManager(nil, nil)
- m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- return &RemotePrinterInfo{
- Reachable: true,
- MakeModel: "HP OfficeJet 8010",
- Name: "OfficeJet",
- State: "idle",
- URI: "ipp://192.168.0.5:631/ipp/print",
- }, nil
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.testConnection",
- Params: map[string]any{
- "host": "192.168.0.5",
- },
- }
-
- handleTestConnection(conn, req, m)
-
- var resp models.Response[RemotePrinterInfo]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Reachable)
- assert.Equal(t, "HP OfficeJet 8010", resp.Result.MakeModel)
-}
-
-func TestHandleTestConnection_MissingHost(t *testing.T) {
- m := NewTestManager(nil, nil)
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.testConnection",
- Params: map[string]any{},
- }
-
- handleTestConnection(conn, req, m)
-
- var resp models.Response[any]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.Nil(t, resp.Result)
- assert.NotNil(t, resp.Error)
-}
-
-func TestHandleTestConnection_CustomPortAndProtocol(t *testing.T) {
- m := NewTestManager(nil, nil)
- m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- assert.Equal(t, 9631, port)
- assert.True(t, useTLS)
- return &RemotePrinterInfo{Reachable: true, URI: "ipps://192.168.0.5:9631/ipp/print"}, nil
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.testConnection",
- Params: map[string]any{
- "host": "192.168.0.5",
- "port": float64(9631),
- "protocol": "ipps",
- },
- }
-
- handleTestConnection(conn, req, m)
-
- var resp models.Response[RemotePrinterInfo]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Reachable)
-}
-
-func TestHandleRequest_TestConnection(t *testing.T) {
- m := NewTestManager(nil, nil)
- m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) {
- return &RemotePrinterInfo{Reachable: true}, nil
- }
-
- buf := &bytes.Buffer{}
- conn := &mockConn{Buffer: buf}
-
- req := models.Request{
- ID: 1,
- Method: "cups.testConnection",
- Params: map[string]any{"host": "192.168.0.5"},
- }
-
- HandleRequest(conn, req, m)
-
- var resp models.Response[RemotePrinterInfo]
- err := json.NewDecoder(buf).Decode(&resp)
- assert.NoError(t, err)
- assert.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Reachable)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go
deleted file mode 100644
index 4820e34..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package cups
-
-func NewTestManager(client CUPSClientInterface, pkHelper PkHelper) *Manager {
- return &Manager{
- client: client,
- pkHelper: pkHelper,
- state: &CUPSState{
- Printers: make(map[string]*Printer),
- },
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go
deleted file mode 100644
index 52e9d2c..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package cups
-
-import (
- "io"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type CUPSState struct {
- Printers map[string]*Printer `json:"printers"`
-}
-
-type Printer struct {
- Name string `json:"name"`
- URI string `json:"uri"`
- State string `json:"state"`
- StateReason string `json:"stateReason"`
- Location string `json:"location"`
- Info string `json:"info"`
- MakeModel string `json:"makeModel"`
- Accepting bool `json:"accepting"`
- Jobs []Job `json:"jobs"`
-}
-
-type Job struct {
- ID int `json:"id"`
- Name string `json:"name"`
- State string `json:"state"`
- Printer string `json:"printer"`
- User string `json:"user"`
- Size int `json:"size"`
- TimeCreated time.Time `json:"timeCreated"`
-}
-
-type Device struct {
- URI string `json:"uri"`
- Class string `json:"class"`
- Info string `json:"info"`
- MakeModel string `json:"makeModel"`
- ID string `json:"id"`
- Location string `json:"location"`
- IP string `json:"ip,omitempty"`
-}
-
-type PPD struct {
- Name string `json:"name"`
- NaturalLanguage string `json:"naturalLanguage"`
- MakeModel string `json:"makeModel"`
- DeviceID string `json:"deviceId"`
- Product string `json:"product"`
- PSVersion string `json:"psVersion"`
- Type string `json:"type"`
-}
-
-type RemotePrinterInfo struct {
- Reachable bool `json:"reachable"`
- MakeModel string `json:"makeModel"`
- Name string `json:"name"`
- Info string `json:"info"`
- State string `json:"state"`
- URI string `json:"uri"`
- Error string `json:"error,omitempty"`
-}
-
-type PrinterClass struct {
- Name string `json:"name"`
- URI string `json:"uri"`
- State string `json:"state"`
- Members []string `json:"members"`
- Location string `json:"location"`
- Info string `json:"info"`
-}
-
-type Manager struct {
- state *CUPSState
- client CUPSClientInterface
- pkHelper PkHelper
- subscription SubscriptionManagerInterface
- stateMutex sync.RWMutex
- subscribers syncmap.Map[string, chan CUPSState]
- stopChan chan struct{}
- eventWG sync.WaitGroup
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotifiedState *CUPSState
- baseURL string
- probeRemoteFn func(host string, port int, useTLS bool) (*RemotePrinterInfo, error)
-}
-
-type SubscriptionManagerInterface interface {
- Start() error
- Stop()
- Events() <-chan SubscriptionEvent
-}
-
-type CUPSClientInterface interface {
- GetPrinters(attributes []string) (map[string]ipp.Attributes, error)
- GetJobs(printer, class string, whichJobs string, myJobs bool, firstJobId, limit int, attributes []string) (map[int]ipp.Attributes, error)
- CancelJob(jobID int, purge bool) error
- PausePrinter(printer string) error
- ResumePrinter(printer string) error
- CancelAllJob(printer string, purge bool) error
- SendRequest(url string, req *ipp.Request, additionalResponseData io.Writer) (*ipp.Response, error)
-
- GetDevices() (map[string]ipp.Attributes, error)
- GetPPDs() (map[string]ipp.Attributes, error)
- GetClasses(attributes []string) (map[string]ipp.Attributes, error)
- CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error
- DeletePrinter(printer string) error
- AcceptJobs(printer string) error
- RejectJobs(printer string) error
- SetPrinterIsShared(printer string, shared bool) error
- SetPrinterLocation(printer, location string) error
- SetPrinterInformation(printer, information string) error
- MoveJob(jobID int, destPrinter string) error
- PrintTestPage(printer string, testPageData io.Reader, size int) (int, error)
- AddPrinterToClass(class, printer string) error
- DeletePrinterFromClass(class, printer string) error
- DeleteClass(class string) error
- RestartJob(jobID int) error
- HoldJobUntil(jobID int, holdUntil string) error
-}
-
-type SubscriptionEvent struct {
- EventName string
- PrinterName string
- JobID int
- SubscribedAt time.Time
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/handlers.go
deleted file mode 100644
index e625d79..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/handlers.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package dbus
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-type objectParams struct {
- bus string
- dest string
- path string
- iface string
-}
-
-func extractObjectParams(p map[string]any, requirePath bool) (objectParams, error) {
- bus, err := params.String(p, "bus")
- if err != nil {
- return objectParams{}, err
- }
- dest, err := params.String(p, "dest")
- if err != nil {
- return objectParams{}, err
- }
-
- var path string
- if requirePath {
- path, err = params.String(p, "path")
- if err != nil {
- return objectParams{}, err
- }
- } else {
- path = params.StringOpt(p, "path", "/")
- }
-
- iface, err := params.String(p, "interface")
- if err != nil {
- return objectParams{}, err
- }
-
- return objectParams{bus: bus, dest: dest, path: path, iface: iface}, nil
-}
-
-func HandleRequest(conn net.Conn, req models.Request, m *Manager, clientID string) {
- switch req.Method {
- case "dbus.call":
- handleCall(conn, req, m)
- case "dbus.getProperty":
- handleGetProperty(conn, req, m)
- case "dbus.setProperty":
- handleSetProperty(conn, req, m)
- case "dbus.getAllProperties":
- handleGetAllProperties(conn, req, m)
- case "dbus.introspect":
- handleIntrospect(conn, req, m)
- case "dbus.listNames":
- handleListNames(conn, req, m)
- case "dbus.subscribe":
- handleSubscribe(conn, req, m, clientID)
- case "dbus.unsubscribe":
- handleUnsubscribe(conn, req, m)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleCall(conn net.Conn, req models.Request, m *Manager) {
- op, err := extractObjectParams(req.Params, true)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- method, err := params.String(req.Params, "method")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- var args []any
- if argsRaw, ok := params.Any(req.Params, "args"); ok {
- if argsSlice, ok := argsRaw.([]any); ok {
- args = argsSlice
- }
- }
-
- result, err := m.Call(op.bus, op.dest, op.path, op.iface, method, args)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleGetProperty(conn net.Conn, req models.Request, m *Manager) {
- op, err := extractObjectParams(req.Params, true)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- property, err := params.String(req.Params, "property")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- result, err := m.GetProperty(op.bus, op.dest, op.path, op.iface, property)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleSetProperty(conn net.Conn, req models.Request, m *Manager) {
- op, err := extractObjectParams(req.Params, true)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- property, err := params.String(req.Params, "property")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- value, ok := params.Any(req.Params, "value")
- if !ok {
- models.RespondError(conn, req.ID, "missing 'value' parameter")
- return
- }
-
- if err := m.SetProperty(op.bus, op.dest, op.path, op.iface, property, value); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true})
-}
-
-func handleGetAllProperties(conn net.Conn, req models.Request, m *Manager) {
- op, err := extractObjectParams(req.Params, true)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- result, err := m.GetAllProperties(op.bus, op.dest, op.path, op.iface)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleIntrospect(conn net.Conn, req models.Request, m *Manager) {
- bus, err := params.String(req.Params, "bus")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- dest, err := params.String(req.Params, "dest")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- path := params.StringOpt(req.Params, "path", "/")
-
- result, err := m.Introspect(bus, dest, path)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleListNames(conn net.Conn, req models.Request, m *Manager) {
- bus, err := params.String(req.Params, "bus")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- result, err := m.ListNames(bus)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, m *Manager, clientID string) {
- bus, err := params.String(req.Params, "bus")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- sender := params.StringOpt(req.Params, "sender", "")
- path := params.StringOpt(req.Params, "path", "")
- iface := params.StringOpt(req.Params, "interface", "")
- member := params.StringOpt(req.Params, "member", "")
-
- result, err := m.Subscribe(clientID, bus, sender, path, iface, member)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleUnsubscribe(conn net.Conn, req models.Request, m *Manager) {
- subID, err := params.String(req.Params, "subscriptionId")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := m.Unsubscribe(subID); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true})
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/manager.go
deleted file mode 100644
index 86da6bd..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/manager.go
+++ /dev/null
@@ -1,392 +0,0 @@
-package dbus
-
-import (
- "crypto/rand"
- "encoding/hex"
- "fmt"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
- "github.com/godbus/dbus/v5"
-)
-
-func NewManager() (*Manager, error) {
- systemConn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to system bus: %w", err)
- }
-
- sessionConn, err := dbus.ConnectSessionBus()
- if err != nil {
- systemConn.Close()
- return nil, fmt.Errorf("failed to connect to session bus: %w", err)
- }
-
- m := &Manager{
- systemConn: systemConn,
- sessionConn: sessionConn,
- }
-
- go m.processSystemSignals()
- go m.processSessionSignals()
-
- return m, nil
-}
-
-func (m *Manager) getConn(bus string) (*dbus.Conn, error) {
- switch bus {
- case "system":
- if m.systemConn == nil {
- return nil, fmt.Errorf("system bus not connected")
- }
- return m.systemConn, nil
- case "session":
- if m.sessionConn == nil {
- return nil, fmt.Errorf("session bus not connected")
- }
- return m.sessionConn, nil
- default:
- return nil, fmt.Errorf("invalid bus: %s (must be 'system' or 'session')", bus)
- }
-}
-
-func (m *Manager) Call(bus, dest, path, iface, method string, args []any) (*CallResult, error) {
- conn, err := m.getConn(bus)
- if err != nil {
- return nil, err
- }
-
- obj := conn.Object(dest, dbus.ObjectPath(path))
- fullMethod := iface + "." + method
-
- convertedArgs := convertArgs(args)
- call := obj.Call(fullMethod, 0, convertedArgs...)
- if call.Err != nil {
- return nil, fmt.Errorf("dbus call failed: %w", call.Err)
- }
-
- return &CallResult{Values: dbusutil.NormalizeSlice(call.Body)}, nil
-}
-
-func convertArgs(args []any) []any {
- result := make([]any, len(args))
- for i, arg := range args {
- result[i] = convertArg(arg)
- }
- return result
-}
-
-func convertArg(arg any) any {
- switch v := arg.(type) {
- case float64:
- if v == float64(uint32(v)) && v >= 0 && v <= float64(^uint32(0)) {
- return uint32(v)
- }
- if v == float64(int32(v)) {
- return int32(v)
- }
- return v
- case []any:
- return convertArgs(v)
- case map[string]any:
- result := make(map[string]any)
- for k, val := range v {
- result[k] = convertArg(val)
- }
- return result
- default:
- return arg
- }
-}
-
-func (m *Manager) GetProperty(bus, dest, path, iface, property string) (*PropertyResult, error) {
- conn, err := m.getConn(bus)
- if err != nil {
- return nil, err
- }
-
- obj := conn.Object(dest, dbus.ObjectPath(path))
-
- var variant dbus.Variant
- err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, iface, property).Store(&variant)
- if err != nil {
- return nil, fmt.Errorf("failed to get property: %w", err)
- }
-
- return &PropertyResult{Value: dbusutil.Normalize(variant.Value())}, nil
-}
-
-func (m *Manager) SetProperty(bus, dest, path, iface, property string, value any) error {
- conn, err := m.getConn(bus)
- if err != nil {
- return err
- }
-
- obj := conn.Object(dest, dbus.ObjectPath(path))
-
- call := obj.Call("org.freedesktop.DBus.Properties.Set", 0, iface, property, dbus.MakeVariant(value))
- if call.Err != nil {
- return fmt.Errorf("failed to set property: %w", call.Err)
- }
-
- return nil
-}
-
-func (m *Manager) GetAllProperties(bus, dest, path, iface string) (map[string]any, error) {
- conn, err := m.getConn(bus)
- if err != nil {
- return nil, err
- }
-
- obj := conn.Object(dest, dbus.ObjectPath(path))
-
- var props map[string]dbus.Variant
- err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, iface).Store(&props)
- if err != nil {
- return nil, fmt.Errorf("failed to get properties: %w", err)
- }
-
- result := make(map[string]any)
- for k, v := range props {
- result[k] = dbusutil.Normalize(v.Value())
- }
-
- return result, nil
-}
-
-func (m *Manager) Introspect(bus, dest, path string) (*IntrospectResult, error) {
- conn, err := m.getConn(bus)
- if err != nil {
- return nil, err
- }
-
- obj := conn.Object(dest, dbus.ObjectPath(path))
-
- var xml string
- err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xml)
- if err != nil {
- return nil, fmt.Errorf("failed to introspect: %w", err)
- }
-
- return &IntrospectResult{XML: xml}, nil
-}
-
-func (m *Manager) ListNames(bus string) (*ListNamesResult, error) {
- conn, err := m.getConn(bus)
- if err != nil {
- return nil, err
- }
-
- var names []string
- err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names)
- if err != nil {
- return nil, fmt.Errorf("failed to list names: %w", err)
- }
-
- return &ListNamesResult{Names: names}, nil
-}
-
-func (m *Manager) Subscribe(clientID, bus, sender, path, iface, member string) (*SubscribeResult, error) {
- conn, err := m.getConn(bus)
- if err != nil {
- return nil, err
- }
-
- subID := generateSubscriptionID()
-
- parts := []string{"type='signal'"}
- if sender != "" {
- parts = append(parts, fmt.Sprintf("sender='%s'", sender))
- }
- if path != "" {
- parts = append(parts, fmt.Sprintf("path='%s'", path))
- }
- if iface != "" {
- parts = append(parts, fmt.Sprintf("interface='%s'", iface))
- }
- if member != "" {
- parts = append(parts, fmt.Sprintf("member='%s'", member))
- }
- matchRule := strings.Join(parts, ",")
-
- call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule)
- if call.Err != nil {
- return nil, fmt.Errorf("failed to add match rule: %w", call.Err)
- }
-
- sub := &signalSubscription{
- Bus: bus,
- Sender: sender,
- Path: path,
- Interface: iface,
- Member: member,
- ClientID: clientID,
- }
- m.subscriptions.Store(subID, sub)
-
- log.Debugf("dbus: subscribed %s to %s", subID, matchRule)
-
- return &SubscribeResult{SubscriptionID: subID}, nil
-}
-
-func (m *Manager) Unsubscribe(subID string) error {
- sub, ok := m.subscriptions.LoadAndDelete(subID)
- if !ok {
- return fmt.Errorf("subscription not found: %s", subID)
- }
-
- conn, err := m.getConn(sub.Bus)
- if err != nil {
- return err
- }
-
- parts := []string{"type='signal'"}
- if sub.Sender != "" {
- parts = append(parts, fmt.Sprintf("sender='%s'", sub.Sender))
- }
- if sub.Path != "" {
- parts = append(parts, fmt.Sprintf("path='%s'", sub.Path))
- }
- if sub.Interface != "" {
- parts = append(parts, fmt.Sprintf("interface='%s'", sub.Interface))
- }
- if sub.Member != "" {
- parts = append(parts, fmt.Sprintf("member='%s'", sub.Member))
- }
- matchRule := strings.Join(parts, ",")
-
- call := conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, matchRule)
- if call.Err != nil {
- log.Warnf("dbus: failed to remove match rule: %v", call.Err)
- }
-
- log.Debugf("dbus: unsubscribed %s", subID)
-
- return nil
-}
-
-func (m *Manager) UnsubscribeClient(clientID string) {
- var toDelete []string
- m.subscriptions.Range(func(subID string, sub *signalSubscription) bool {
- if sub.ClientID == clientID {
- toDelete = append(toDelete, subID)
- }
- return true
- })
-
- for _, subID := range toDelete {
- _ = m.Unsubscribe(subID)
- }
-}
-
-func (m *Manager) SubscribeSignals(clientID string) chan SignalEvent {
- ch := make(chan SignalEvent, 64)
- existing, loaded := m.signalSubscribers.LoadOrStore(clientID, ch)
- if loaded {
- return existing
- }
- return ch
-}
-
-func (m *Manager) UnsubscribeSignals(clientID string) {
- if ch, ok := m.signalSubscribers.LoadAndDelete(clientID); ok {
- close(ch)
- }
- m.UnsubscribeClient(clientID)
-}
-
-func (m *Manager) processSystemSignals() {
- if m.systemConn == nil {
- return
- }
- ch := make(chan *dbus.Signal, 256)
- m.systemConn.Signal(ch)
-
- for sig := range ch {
- m.dispatchSignal("system", sig)
- }
-}
-
-func (m *Manager) processSessionSignals() {
- if m.sessionConn == nil {
- return
- }
- ch := make(chan *dbus.Signal, 256)
- m.sessionConn.Signal(ch)
-
- for sig := range ch {
- m.dispatchSignal("session", sig)
- }
-}
-
-func (m *Manager) dispatchSignal(bus string, sig *dbus.Signal) {
- path := string(sig.Path)
- iface := ""
- member := sig.Name
-
- if idx := strings.LastIndex(sig.Name, "."); idx != -1 {
- iface = sig.Name[:idx]
- member = sig.Name[idx+1:]
- }
-
- m.subscriptions.Range(func(subID string, sub *signalSubscription) bool {
- if sub.Bus != bus {
- return true
- }
- if sub.Path != "" && sub.Path != path && !strings.HasPrefix(path, sub.Path) {
- return true
- }
- if sub.Interface != "" && sub.Interface != iface {
- return true
- }
- if sub.Member != "" && sub.Member != member {
- return true
- }
-
- event := SignalEvent{
- SubscriptionID: subID,
- Sender: sig.Sender,
- Path: path,
- Interface: iface,
- Member: member,
- Body: dbusutil.NormalizeSlice(sig.Body),
- }
-
- ch, ok := m.signalSubscribers.Load(sub.ClientID)
- if !ok {
- return true
- }
-
- select {
- case ch <- event:
- default:
- log.Warnf("dbus: channel full for %s, dropping signal", subID)
- }
-
- return true
- })
-}
-
-func (m *Manager) Close() {
- m.signalSubscribers.Range(func(clientID string, ch chan SignalEvent) bool {
- close(ch)
- m.signalSubscribers.Delete(clientID)
- return true
- })
-
- if m.systemConn != nil {
- m.systemConn.Close()
- }
- if m.sessionConn != nil {
- m.sessionConn.Close()
- }
-}
-
-func generateSubscriptionID() string {
- b := make([]byte, 8)
- if _, err := rand.Read(b); err != nil {
- log.Warnf("dbus: failed to generate random subscription ID: %v", err)
- }
- return hex.EncodeToString(b)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/types.go
deleted file mode 100644
index 55c45bf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dbus/types.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package dbus
-
-import (
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/godbus/dbus/v5"
-)
-
-type Manager struct {
- systemConn *dbus.Conn
- sessionConn *dbus.Conn
-
- subscriptions syncmap.Map[string, *signalSubscription]
- signalSubscribers syncmap.Map[string, chan SignalEvent]
-}
-
-type signalSubscription struct {
- Bus string
- Sender string
- Path string
- Interface string
- Member string
- ClientID string
-}
-
-type SignalEvent struct {
- SubscriptionID string `json:"subscriptionId"`
- Sender string `json:"sender"`
- Path string `json:"path"`
- Interface string `json:"interface"`
- Member string `json:"member"`
- Body []any `json:"body"`
-}
-
-type CallResult struct {
- Values []any `json:"values"`
-}
-
-type PropertyResult struct {
- Value any `json:"value"`
-}
-
-type IntrospectResult struct {
- XML string `json:"xml"`
-}
-
-type ListNamesResult struct {
- Names []string `json:"names"`
-}
-
-type SubscribeResult struct {
- SubscriptionID string `json:"subscriptionId"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/handlers.go
deleted file mode 100644
index e83cc84..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/handlers.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package dwl
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-type SuccessResult struct {
- Success bool `json:"success"`
- Message string `json:"message"`
-}
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- if manager == nil {
- models.RespondError(conn, req.ID, "dwl manager not initialized")
- return
- }
-
- switch req.Method {
- case "dwl.getState":
- handleGetState(conn, req, manager)
- case "dwl.setTags":
- handleSetTags(conn, req, manager)
- case "dwl.setClientTags":
- handleSetClientTags(conn, req, manager)
- case "dwl.setLayout":
- handleSetLayout(conn, req, manager)
- case "dwl.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- state := manager.GetState()
- models.Respond(conn, req.ID, state)
-}
-
-func handleSetTags(conn net.Conn, req models.Request, manager *Manager) {
- output, ok := models.Get[string](req, "output")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
- return
- }
-
- tagmask, ok := models.Get[float64](req, "tagmask")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'tagmask' parameter")
- return
- }
-
- toggleTagset, ok := models.Get[float64](req, "toggleTagset")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'toggleTagset' parameter")
- return
- }
-
- if err := manager.SetTags(output, uint32(tagmask), uint32(toggleTagset)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"})
-}
-
-func handleSetClientTags(conn net.Conn, req models.Request, manager *Manager) {
- output, ok := models.Get[string](req, "output")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
- return
- }
-
- andTags, ok := models.Get[float64](req, "andTags")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'andTags' parameter")
- return
- }
-
- xorTags, ok := models.Get[float64](req, "xorTags")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'xorTags' parameter")
- return
- }
-
- if err := manager.SetClientTags(output, uint32(andTags), uint32(xorTags)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"})
-}
-
-func handleSetLayout(conn net.Conn, req models.Request, manager *Manager) {
- output, ok := models.Get[string](req, "output")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
- return
- }
-
- index, ok := models.Get[float64](req, "index")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'index' parameter")
- return
- }
-
- if err := manager.SetLayout(output, uint32(index)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager.go
deleted file mode 100644
index 0d21afa..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager.go
+++ /dev/null
@@ -1,522 +0,0 @@
-package dwl
-
-import (
- "fmt"
- "time"
-
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
-)
-
-func NewManager(display wlclient.WaylandDisplay) (*Manager, error) {
- m := &Manager{
- display: display,
- ctx: display.Context(),
- cmdq: make(chan cmd, 128),
- outputSetupReq: make(chan uint32, 16),
- stopChan: make(chan struct{}),
-
- dirty: make(chan struct{}, 1),
- layouts: make([]string, 0),
- }
-
- if err := m.setupRegistry(); err != nil {
- return nil, err
- }
-
- m.updateState()
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- m.wg.Add(1)
- go m.waylandActor()
-
- return m, nil
-}
-
-func (m *Manager) post(fn func()) {
- select {
- case m.cmdq <- cmd{fn: fn}:
- default:
- log.Warn("DWL actor command queue full, dropping command")
- }
-}
-
-func (m *Manager) waylandActor() {
- defer m.wg.Done()
-
- for {
- select {
- case <-m.stopChan:
- return
- case c := <-m.cmdq:
- c.fn()
- case outputID := <-m.outputSetupReq:
- out, exists := m.outputs.Load(outputID)
- if !exists {
- log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID)
- continue
- }
-
- if out.ipcOutput != nil {
- continue
- }
-
- mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2)
- if !ok || mgr == nil {
- log.Errorf("DWL: Manager not available for output %d setup", outputID)
- continue
- }
-
- log.Infof("DWL: Setting up ipcOutput for dynamically added output %d", outputID)
- if err := m.setupOutput(mgr, out.output); err != nil {
- log.Errorf("DWL: Failed to setup output %d: %v", outputID, err)
- } else {
- m.updateState()
- }
- }
- }
-}
-
-func (m *Manager) setupRegistry() error {
- log.Info("DWL: starting registry setup")
-
- registry, err := m.display.GetRegistry()
- if err != nil {
- return fmt.Errorf("failed to get registry: %w", err)
- }
- m.registry = registry
-
- outputs := make([]*wlclient.Output, 0)
- outputRegNames := make(map[uint32]uint32)
- var dwlMgr *dwl_ipc.ZdwlIpcManagerV2
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- switch e.Interface {
- case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
- log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
- manager := dwl_ipc.NewZdwlIpcManagerV2(m.ctx)
- version := e.Version
- if version > 2 {
- version = 2
- }
- if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
- dwlMgr = manager
- log.Info("DWL: manager bound successfully")
-
- // Set handlers immediately after binding, before roundtrips
- manager.SetTagsHandler(func(e dwl_ipc.ZdwlIpcManagerV2TagsEvent) {
- log.Infof("DWL: Tags count: %d", e.Amount)
- m.tagCount = e.Amount
- m.updateState()
- })
-
- manager.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcManagerV2LayoutEvent) {
- log.Infof("DWL: Layout: %s", e.Name)
- m.layouts = append(m.layouts, e.Name)
- m.updateState()
- })
- } else {
- log.Errorf("DWL: failed to bind manager: %v", err)
- }
- case "wl_output":
- log.Debugf("DWL: found wl_output (name=%d)", e.Name)
- output := wlclient.NewOutput(m.ctx)
-
- outState := &outputState{
- registryName: e.Name,
- output: output,
- tags: make([]TagState, 0),
- }
-
- output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
- log.Debugf("DWL: Output name: %s (registry=%d)", ev.Name, e.Name)
- outState.name = ev.Name
- })
-
- output.SetDescriptionHandler(func(ev wlclient.OutputDescriptionEvent) {
- log.Debugf("DWL: Output description: %s", ev.Description)
- })
-
- version := e.Version
- if version > 4 {
- version = 4
- }
- if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
- outputID := output.ID()
- outState.id = outputID
- log.Infof("DWL: Bound wl_output id=%d registry_name=%d", outputID, e.Name)
- outputs = append(outputs, output)
- outputRegNames[outputID] = e.Name
-
- m.outputs.Store(outputID, outState)
-
- if m.manager != nil {
- select {
- case m.outputSetupReq <- outputID:
- log.Debugf("DWL: Queued setup for output %d", outputID)
- default:
- log.Warnf("DWL: Setup queue full, output %d will not be initialized", outputID)
- }
- }
- } else {
- log.Errorf("DWL: Failed to bind wl_output: %v", err)
- }
- }
- })
-
- registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
- m.post(func() {
- var outToRelease *outputState
- m.outputs.Range(func(id uint32, out *outputState) bool {
- if out.registryName == e.Name {
- log.Infof("DWL: Output %d removed", id)
- outToRelease = out
- m.outputs.Delete(id)
- return false
- }
- return true
- })
-
- if outToRelease != nil {
- if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil {
- m.wlMutex.Lock()
- ipcOut.Release()
- m.wlMutex.Unlock()
- log.Debugf("DWL: Released ipcOutput for removed output %d", outToRelease.id)
- }
- m.updateState()
- }
- })
- })
-
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("first roundtrip failed: %w", err)
- }
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("second roundtrip failed: %w", err)
- }
-
- if dwlMgr == nil {
- log.Info("DWL: manager not found in registry")
- return fmt.Errorf("dwl_ipc_manager_v2 not available")
- }
-
- m.manager = dwlMgr
-
- for _, output := range outputs {
- if err := m.setupOutput(dwlMgr, output); err != nil {
- log.Warnf("DWL: Failed to setup output %d: %v", output.ID(), err)
- }
- }
-
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("final roundtrip failed: %w", err)
- }
-
- log.Info("DWL: registry setup complete")
- return nil
-}
-
-func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclient.Output) error {
- m.wlMutex.Lock()
- ipcOutput, err := manager.GetOutput(output)
- m.wlMutex.Unlock()
- if err != nil {
- return fmt.Errorf("failed to get dwl output: %w", err)
- }
-
- outState, exists := m.outputs.Load(output.ID())
- if !exists {
- return fmt.Errorf("output state not found for id %d", output.ID())
- }
- outState.ipcOutput = ipcOutput
-
- ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
- outState.active = e.Active
- })
-
- ipcOutput.SetTagHandler(func(e dwl_ipc.ZdwlIpcOutputV2TagEvent) {
- updated := false
- for i, tag := range outState.tags {
- if tag.Tag == e.Tag {
- outState.tags[i] = TagState{
- Tag: e.Tag,
- State: e.State,
- Clients: e.Clients,
- Focused: e.Focused,
- }
- updated = true
- break
- }
- }
-
- if !updated {
- outState.tags = append(outState.tags, TagState{
- Tag: e.Tag,
- State: e.State,
- Clients: e.Clients,
- Focused: e.Focused,
- })
- }
-
- m.updateState()
- })
-
- ipcOutput.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutEvent) {
- outState.layout = e.Layout
- })
-
- ipcOutput.SetTitleHandler(func(e dwl_ipc.ZdwlIpcOutputV2TitleEvent) {
- outState.title = e.Title
- })
-
- ipcOutput.SetAppidHandler(func(e dwl_ipc.ZdwlIpcOutputV2AppidEvent) {
- outState.appID = e.Appid
- })
-
- ipcOutput.SetLayoutSymbolHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutSymbolEvent) {
- outState.layoutSymbol = e.Layout
- })
-
- ipcOutput.SetKbLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2KbLayoutEvent) {
- outState.kbLayout = e.KbLayout
- })
-
- ipcOutput.SetKeymodeHandler(func(e dwl_ipc.ZdwlIpcOutputV2KeymodeEvent) {
- outState.keymode = e.Keymode
- })
-
- ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
- m.updateState()
- })
-
- return nil
-}
-
-func (m *Manager) updateState() {
- outputs := make(map[string]*OutputState)
- activeOutput := ""
-
- m.outputs.Range(func(key uint32, out *outputState) bool {
- name := out.name
- if name == "" {
- name = fmt.Sprintf("output-%d", out.id)
- }
-
- tagsCopy := make([]TagState, len(out.tags))
- copy(tagsCopy, out.tags)
-
- outputs[name] = &OutputState{
- Name: name,
- Active: out.active,
- Tags: tagsCopy,
- Layout: out.layout,
- LayoutSymbol: out.layoutSymbol,
- Title: out.title,
- AppID: out.appID,
- KbLayout: out.kbLayout,
- Keymode: out.keymode,
- }
-
- if out.active != 0 {
- activeOutput = name
- }
- return true
- })
-
- newState := State{
- Outputs: outputs,
- TagCount: m.tagCount,
- Layouts: m.layouts,
- ActiveOutput: activeOutput,
- }
-
- m.stateMutex.Lock()
- m.state = &newState
- m.stateMutex.Unlock()
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.GetState()
-
- if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- currentState:
- default:
- log.Warn("DWL: subscriber channel full, dropping update")
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotified = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) ensureOutputSetup(out *outputState) error {
- if out.ipcOutput != nil {
- return nil
- }
-
- return fmt.Errorf("output not yet initialized - setup in progress, retry in a moment")
-}
-
-func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error {
- availableOutputs := make([]string, 0)
- var targetOut *outputState
- m.outputs.Range(func(key uint32, out *outputState) bool {
- name := out.name
- if name == "" {
- name = fmt.Sprintf("output-%d", out.id)
- }
- availableOutputs = append(availableOutputs, name)
- if name == outputName {
- targetOut = out
- return false
- }
- return true
- })
-
- if targetOut == nil {
- return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs)
- }
-
- if err := m.ensureOutputSetup(targetOut); err != nil {
- return fmt.Errorf("failed to setup output %s: %w", outputName, err)
- }
-
- ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
- if !ok {
- return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
- }
-
- m.wlMutex.Lock()
- err := ipcOut.SetTags(tagmask, toggleTagset)
- m.wlMutex.Unlock()
- return err
-}
-
-func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error {
- var targetOut *outputState
- m.outputs.Range(func(key uint32, out *outputState) bool {
- name := out.name
- if name == "" {
- name = fmt.Sprintf("output-%d", out.id)
- }
- if name == outputName {
- targetOut = out
- return false
- }
- return true
- })
-
- if targetOut == nil {
- return fmt.Errorf("output not found: %s", outputName)
- }
-
- if err := m.ensureOutputSetup(targetOut); err != nil {
- return fmt.Errorf("failed to setup output %s: %w", outputName, err)
- }
-
- ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
- if !ok {
- return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
- }
-
- m.wlMutex.Lock()
- err := ipcOut.SetClientTags(andTags, xorTags)
- m.wlMutex.Unlock()
- return err
-}
-
-func (m *Manager) SetLayout(outputName string, index uint32) error {
- var targetOut *outputState
- m.outputs.Range(func(key uint32, out *outputState) bool {
- name := out.name
- if name == "" {
- name = fmt.Sprintf("output-%d", out.id)
- }
- if name == outputName {
- targetOut = out
- return false
- }
- return true
- })
-
- if targetOut == nil {
- return fmt.Errorf("output not found: %s", outputName)
- }
-
- if err := m.ensureOutputSetup(targetOut); err != nil {
- return fmt.Errorf("failed to setup output %s: %w", outputName, err)
- }
-
- ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
- if !ok {
- return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
- }
-
- m.wlMutex.Lock()
- err := ipcOut.SetLayout(index)
- m.wlMutex.Unlock()
- return err
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.wg.Wait()
- m.notifierWg.Wait()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.outputs.Range(func(key uint32, out *outputState) bool {
- if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok {
- ipcOut.Release()
- }
- m.outputs.Delete(key)
- return true
- })
-
- if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok {
- mgr.Release()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager_test.go
deleted file mode 100644
index 13af137..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/manager_test.go
+++ /dev/null
@@ -1,366 +0,0 @@
-package dwl
-
-import (
- "errors"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-
- mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
-)
-
-func TestStateChanged_BothNil(t *testing.T) {
- assert.True(t, stateChanged(nil, nil))
-}
-
-func TestStateChanged_OneNil(t *testing.T) {
- s := &State{TagCount: 9}
- assert.True(t, stateChanged(s, nil))
- assert.True(t, stateChanged(nil, s))
-}
-
-func TestStateChanged_TagCountDiffers(t *testing.T) {
- a := &State{TagCount: 9, Outputs: make(map[string]*OutputState), Layouts: []string{}}
- b := &State{TagCount: 10, Outputs: make(map[string]*OutputState), Layouts: []string{}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_LayoutLengthDiffers(t *testing.T) {
- a := &State{TagCount: 9, Layouts: []string{"tile"}, Outputs: make(map[string]*OutputState)}
- b := &State{TagCount: 9, Layouts: []string{"tile", "monocle"}, Outputs: make(map[string]*OutputState)}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_ActiveOutputDiffers(t *testing.T) {
- a := &State{TagCount: 9, ActiveOutput: "eDP-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
- b := &State{TagCount: 9, ActiveOutput: "HDMI-A-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputCountDiffers(t *testing.T) {
- a := &State{
- TagCount: 9,
- Outputs: map[string]*OutputState{"eDP-1": {}},
- Layouts: []string{},
- }
- b := &State{
- TagCount: 9,
- Outputs: map[string]*OutputState{},
- Layouts: []string{},
- }
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputFieldsDiffer(t *testing.T) {
- a := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Active: 1, Layout: 0, Title: "Firefox"},
- },
- }
- b := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Active: 0, Layout: 0, Title: "Firefox"},
- },
- }
- assert.True(t, stateChanged(a, b))
-
- b.Outputs["eDP-1"].Active = 1
- b.Outputs["eDP-1"].Layout = 1
- assert.True(t, stateChanged(a, b))
-
- b.Outputs["eDP-1"].Layout = 0
- b.Outputs["eDP-1"].Title = "Code"
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_TagsDiffer(t *testing.T) {
- a := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}},
- },
- }
- b := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Tags: []TagState{{Tag: 1, State: 2, Clients: 2, Focused: 1}}},
- },
- }
- assert.True(t, stateChanged(a, b))
-
- b.Outputs["eDP-1"].Tags[0].State = 1
- b.Outputs["eDP-1"].Tags[0].Clients = 3
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_Equal(t *testing.T) {
- a := &State{
- TagCount: 9,
- ActiveOutput: "eDP-1",
- Layouts: []string{"tile", "monocle"},
- Outputs: map[string]*OutputState{
- "eDP-1": {
- Name: "eDP-1",
- Active: 1,
- Layout: 0,
- LayoutSymbol: "[]=",
- Title: "Firefox",
- AppID: "firefox",
- KbLayout: "us",
- Keymode: "",
- Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
- },
- },
- }
- b := &State{
- TagCount: 9,
- ActiveOutput: "eDP-1",
- Layouts: []string{"tile", "monocle"},
- Outputs: map[string]*OutputState{
- "eDP-1": {
- Name: "eDP-1",
- Active: 1,
- Layout: 0,
- LayoutSymbol: "[]=",
- Title: "Firefox",
- AppID: "firefox",
- KbLayout: "us",
- Keymode: "",
- Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
- },
- },
- }
- assert.False(t, stateChanged(a, b))
-}
-
-func TestManager_ConcurrentGetState(t *testing.T) {
- m := &Manager{
- state: &State{
- TagCount: 9,
- Layouts: []string{"tile"},
- Outputs: map[string]*OutputState{"eDP-1": {Name: "eDP-1"}},
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- s := m.GetState()
- _ = s.TagCount
- _ = s.Outputs
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.stateMutex.Lock()
- m.state = &State{
- TagCount: uint32(j % 10),
- Layouts: []string{"tile", "monocle"},
- Outputs: map[string]*OutputState{"eDP-1": {Active: uint32(j % 2)}},
- }
- m.stateMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
- m := &Manager{
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- subID := string(rune('a' + id))
- ch := m.Subscribe(subID)
- assert.NotNil(t, ch)
- time.Sleep(time.Millisecond)
- m.Unsubscribe(subID)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &outputState{
- id: key,
- name: "test-output",
- active: uint32(j % 2),
- tags: []TagState{{Tag: uint32(j), State: 1}},
- }
- m.outputs.Store(key, state)
-
- if loaded, ok := m.outputs.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.outputs.Range(func(k uint32, v *outputState) bool {
- _ = v.name
- _ = v.active
- return true
- })
- }
-
- m.outputs.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
- m := &Manager{
- dirty: make(chan struct{}, 1),
- }
-
- for i := 0; i < 10; i++ {
- m.notifySubscribers()
- }
-
- assert.Len(t, m.dirty, 1)
-}
-
-func TestManager_PostQueueFull(t *testing.T) {
- m := &Manager{
- cmdq: make(chan cmd, 2),
- stopChan: make(chan struct{}),
- }
-
- m.post(func() {})
- m.post(func() {})
- m.post(func() {})
- m.post(func() {})
-
- assert.Len(t, m.cmdq, 2)
-}
-
-func TestManager_GetStateNilState(t *testing.T) {
- m := &Manager{}
-
- s := m.GetState()
- assert.NotNil(t, s.Outputs)
- assert.NotNil(t, s.Layouts)
- assert.Equal(t, uint32(0), s.TagCount)
-}
-
-func TestTagState_Fields(t *testing.T) {
- tag := TagState{
- Tag: 1,
- State: 2,
- Clients: 3,
- Focused: 1,
- }
-
- assert.Equal(t, uint32(1), tag.Tag)
- assert.Equal(t, uint32(2), tag.State)
- assert.Equal(t, uint32(3), tag.Clients)
- assert.Equal(t, uint32(1), tag.Focused)
-}
-
-func TestOutputState_Fields(t *testing.T) {
- out := OutputState{
- Name: "eDP-1",
- Active: 1,
- Tags: []TagState{{Tag: 1}},
- Layout: 0,
- LayoutSymbol: "[]=",
- Title: "Firefox",
- AppID: "firefox",
- KbLayout: "us",
- Keymode: "",
- }
-
- assert.Equal(t, "eDP-1", out.Name)
- assert.Equal(t, uint32(1), out.Active)
- assert.Len(t, out.Tags, 1)
- assert.Equal(t, "[]=", out.LayoutSymbol)
-}
-
-func TestStateChanged_NewOutputAppears(t *testing.T) {
- a := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Name: "eDP-1"},
- },
- }
- b := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Name: "eDP-1"},
- "HDMI-A-1": {Name: "HDMI-A-1"},
- },
- }
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_TagsLengthDiffers(t *testing.T) {
- a := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Tags: []TagState{{Tag: 1}}},
- },
- }
- b := &State{
- TagCount: 9,
- Layouts: []string{},
- Outputs: map[string]*OutputState{
- "eDP-1": {Tags: []TagState{{Tag: 1}, {Tag: 2}}},
- },
- }
- assert.True(t, stateChanged(a, b))
-}
-
-func TestNewManager_GetRegistryError(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- mockDisplay.EXPECT().Context().Return(nil)
- mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
-
- _, err := NewManager(mockDisplay)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get registry")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/types.go
deleted file mode 100644
index 2b17537..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/dwl/types.go
+++ /dev/null
@@ -1,176 +0,0 @@
-package dwl
-
-import (
- "sync"
-
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type TagState struct {
- Tag uint32 `json:"tag"`
- State uint32 `json:"state"`
- Clients uint32 `json:"clients"`
- Focused uint32 `json:"focused"`
-}
-
-type OutputState struct {
- Name string `json:"name"`
- Active uint32 `json:"active"`
- Tags []TagState `json:"tags"`
- Layout uint32 `json:"layout"`
- LayoutSymbol string `json:"layoutSymbol"`
- Title string `json:"title"`
- AppID string `json:"appId"`
- KbLayout string `json:"kbLayout"`
- Keymode string `json:"keymode"`
-}
-
-type State struct {
- Outputs map[string]*OutputState `json:"outputs"`
- TagCount uint32 `json:"tagCount"`
- Layouts []string `json:"layouts"`
- ActiveOutput string `json:"activeOutput"`
-}
-
-type cmd struct {
- fn func()
-}
-
-type Manager struct {
- display wlclient.WaylandDisplay
- ctx *wlclient.Context
- registry *wlclient.Registry
- manager any
-
- outputs syncmap.Map[uint32, *outputState]
-
- tagCount uint32
- layouts []string
-
- wlMutex sync.Mutex
- cmdq chan cmd
- outputSetupReq chan uint32
- stopChan chan struct{}
- wg sync.WaitGroup
-
- subscribers syncmap.Map[string, chan State]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotified *State
-
- stateMutex sync.RWMutex
- state *State
-}
-
-type outputState struct {
- id uint32
- registryName uint32
- output *wlclient.Output
- ipcOutput any
- name string
- active uint32
- tags []TagState
- layout uint32
- layoutSymbol string
- title string
- appID string
- kbLayout string
- keymode string
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{
- Outputs: make(map[string]*OutputState),
- Layouts: []string{},
- TagCount: 0,
- }
- }
- stateCopy := *m.state
- return stateCopy
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
-
- m.subscribers.Store(id, ch)
-
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func stateChanged(old, new *State) bool {
- if old == nil || new == nil {
- return true
- }
- if old.TagCount != new.TagCount {
- return true
- }
- if len(old.Layouts) != len(new.Layouts) {
- return true
- }
- if old.ActiveOutput != new.ActiveOutput {
- return true
- }
- if len(old.Outputs) != len(new.Outputs) {
- return true
- }
-
- for name, newOut := range new.Outputs {
- oldOut, exists := old.Outputs[name]
- if !exists {
- return true
- }
- if oldOut.Active != newOut.Active {
- return true
- }
- if oldOut.Layout != newOut.Layout {
- return true
- }
- if oldOut.LayoutSymbol != newOut.LayoutSymbol {
- return true
- }
- if oldOut.Title != newOut.Title {
- return true
- }
- if oldOut.AppID != newOut.AppID {
- return true
- }
- if oldOut.KbLayout != newOut.KbLayout {
- return true
- }
- if oldOut.Keymode != newOut.Keymode {
- return true
- }
- if len(oldOut.Tags) != len(newOut.Tags) {
- return true
- }
- for i, newTag := range newOut.Tags {
- if i >= len(oldOut.Tags) {
- return true
- }
- oldTag := oldOut.Tags[i]
- if oldTag.Tag != newTag.Tag || oldTag.State != newTag.State ||
- oldTag.Clients != newTag.Clients || oldTag.Focused != newTag.Focused {
- return true
- }
- }
- }
-
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers.go
deleted file mode 100644
index 54a7bca..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package evdev
-
-import (
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
- switch req.Method {
- case "evdev.getState":
- handleGetState(conn, req, m)
- default:
- models.RespondError(conn, req.ID, "unknown method: "+req.Method)
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, m *Manager) {
- models.Respond(conn, req.ID, m.GetState())
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers_test.go
deleted file mode 100644
index f9bd21d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/handlers_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-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{
- devices: []EvdevDevice{mockDevice},
- state: State{Available: true, CapsLock: true},
- closeChan: make(chan struct{}),
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "evdev.getState",
- Params: map[string]any{},
- }
-
- 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{
- devices: []EvdevDevice{mockDevice},
- state: State{Available: true, CapsLock: false},
- closeChan: make(chan struct{}),
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 456,
- Method: "evdev.unknownMethod",
- Params: map[string]any{},
- }
-
- 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{
- devices: []EvdevDevice{mockDevice},
- state: State{Available: true, CapsLock: false},
- closeChan: make(chan struct{}),
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 789,
- Method: "evdev.getState",
- Params: map[string]any{},
- }
-
- 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)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager.go
deleted file mode 100644
index b578481..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager.go
+++ /dev/null
@@ -1,413 +0,0 @@
-package evdev
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/fsnotify/fsnotify"
- 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 {
- devices []EvdevDevice
- devicesMutex sync.RWMutex
- monitoredPaths map[string]bool
- state State
- stateMutex sync.RWMutex
- subscribers syncmap.Map[string, chan State]
- closeChan chan struct{}
- closeOnce sync.Once
- watcher *fsnotify.Watcher
-}
-
-func NewManager() (*Manager, error) {
- devices, err := findKeyboards()
- if err != nil {
- return nil, fmt.Errorf("failed to find keyboards: %w", err)
- }
-
- initialCapsLock := readInitialCapsLockState(devices[0])
-
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- log.Warnf("Failed to create fsnotify watcher, hotplug detection disabled: %v", err)
- watcher = nil
- } else if err := watcher.Add("/dev/input"); err != nil {
- log.Warnf("Failed to watch /dev/input, hotplug detection disabled: %v", err)
- watcher.Close()
- watcher = nil
- }
-
- monitoredPaths := make(map[string]bool)
- for _, device := range devices {
- monitoredPaths[device.Path()] = true
- }
-
- m := &Manager{
- devices: devices,
- monitoredPaths: monitoredPaths,
- state: State{Available: true, CapsLock: initialCapsLock},
-
- closeChan: make(chan struct{}),
- watcher: watcher,
- }
-
- for i, device := range devices {
- go m.monitorDevice(device, i)
- }
-
- if watcher != nil {
- go m.watchForNewKeyboards()
- }
-
- 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 findKeyboards() ([]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")
- }
-
- var keyboards []EvdevDevice
- for _, path := range matches {
- device, err := evdev.Open(path)
- if err != nil {
- continue
- }
-
- if !isKeyboard(device) {
- device.Close()
- continue
- }
-
- deviceName, _ := device.Name()
- log.Debugf("Found keyboard: %s at %s", deviceName, path)
- keyboards = append(keyboards, device)
- }
-
- if len(keyboards) == 0 {
- return nil, fmt.Errorf("no keyboard device found")
- }
-
- return keyboards, nil
-}
-
-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
- }
-
- keyStates, err := device.State(evKeyType)
- if err != nil {
- return false
- }
-
- hasKeyA := len(keyStates) > 30
- hasKeyZ := len(keyStates) > 44
- hasEnter := len(keyStates) > 28
-
- return hasKeyA && hasKeyZ && hasEnter && len(keyStates) > 100
-}
-
-func (m *Manager) watchForNewKeyboards() {
- defer func() {
- if r := recover(); r != nil {
- log.Errorf("Panic in keyboard hotplug monitor: %v", r)
- }
- }()
-
- for {
- select {
- case <-m.closeChan:
- return
- case event, ok := <-m.watcher.Events:
- if !ok {
- return
- }
-
- if !strings.HasPrefix(filepath.Base(event.Name), "event") {
- continue
- }
-
- if event.Op&fsnotify.Create == fsnotify.Create {
- time.Sleep(100 * time.Millisecond)
-
- m.devicesMutex.Lock()
- if m.monitoredPaths[event.Name] {
- m.devicesMutex.Unlock()
- continue
- }
-
- device, err := evdev.Open(event.Name)
- if err != nil {
- m.devicesMutex.Unlock()
- continue
- }
-
- if !isKeyboard(device) {
- device.Close()
- m.devicesMutex.Unlock()
- continue
- }
-
- deviceName, _ := device.Name()
- log.Debugf("Hotplugged keyboard: %s at %s", deviceName, event.Name)
-
- m.devices = append(m.devices, device)
- m.monitoredPaths[event.Name] = true
- deviceIndex := len(m.devices) - 1
- m.devicesMutex.Unlock()
-
- go m.monitorDevice(device, deviceIndex)
- } else if event.Op&fsnotify.Remove == fsnotify.Remove {
- m.devicesMutex.Lock()
- if !m.monitoredPaths[event.Name] {
- m.devicesMutex.Unlock()
- continue
- }
-
- delete(m.monitoredPaths, event.Name)
- for i, device := range m.devices {
- if device != nil && device.Path() == event.Name {
- log.Debugf("Keyboard removed: %s", event.Name)
- device.Close()
- m.devices[i] = nil
- break
- }
- }
- m.devicesMutex.Unlock()
- }
-
- case err, ok := <-m.watcher.Errors:
- if !ok {
- return
- }
- log.Warnf("Keyboard hotplug watcher error: %v", err)
- }
- }
-}
-
-func (m *Manager) monitorDevice(device EvdevDevice, deviceIndex int) {
- defer func() {
- if r := recover(); r != nil {
- log.Errorf("Panic in evdev monitor: %v", r)
- }
- }()
-
- for {
- select {
- case <-m.closeChan:
- return
- default:
- }
-
- event, err := device.ReadOne()
- if err != nil {
- if isClosedError(err) {
- return
- }
- 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 {
- time.Sleep(50 * time.Millisecond)
- m.readAndUpdateCapsLockState(deviceIndex)
- } else if event.Type == evLedType && event.Code == ledCapslockKey {
- capsLockState := event.Value == keyStateOn
- m.updateCapsLockStateDirect(capsLockState)
- }
- }
-}
-
-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) readAndUpdateCapsLockState(deviceIndex int) {
- m.devicesMutex.RLock()
- if deviceIndex >= len(m.devices) {
- m.devicesMutex.RUnlock()
- return
- }
- device := m.devices[deviceIndex]
- m.devicesMutex.RUnlock()
-
- ledStates, err := device.State(evLedType)
- if err != nil {
- log.Warnf("Failed to read LED state: %v", err)
- return
- }
-
- if len(ledStates) == 0 {
- log.Debug("No LED state available (empty map)")
-
- // This means the device either:
- // - doesn't support LED reporting at all, or
- // - the kernel returned an empty state
- return
- }
-
- capsLockState := ledStates[ledCapslockKey]
- m.updateCapsLockStateDirect(capsLockState)
-}
-
-func (m *Manager) updateCapsLockStateDirect(capsLockState bool) {
- m.stateMutex.Lock()
- if m.state.CapsLock == capsLockState {
- m.stateMutex.Unlock()
- return
- }
-
- m.state.CapsLock = capsLockState
- newState := m.state
- m.stateMutex.Unlock()
-
- log.Debugf("Caps lock state: %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 {
- ch := make(chan State, 16)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) notifySubscribers(state State) {
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- state:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) Close() {
- m.closeOnce.Do(func() {
- close(m.closeChan)
-
- if m.watcher != nil {
- m.watcher.Close()
- }
-
- m.devicesMutex.Lock()
- for _, device := range m.devices {
- if device == nil {
- continue
- }
- if err := device.Close(); err != nil && !isClosedError(err) {
- log.Warnf("Error closing evdev device: %v", err)
- }
- }
- m.devicesMutex.Unlock()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
- })
-}
-
-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
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager_test.go
deleted file mode 100644
index 667c078..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/manager_test.go
+++ /dev/null
@@ -1,344 +0,0 @@
-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{
- devices: []EvdevDevice{mockDevice},
- state: State{Available: true, CapsLock: false},
- 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{
- devices: []EvdevDevice{mockDevice},
- state: State{Available: true, CapsLock: true},
- 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{
- devices: []EvdevDevice{mockDevice},
- monitoredPaths: make(map[string]bool),
- state: State{Available: true, CapsLock: false},
- 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{
- devices: []EvdevDevice{mockDevice},
- monitoredPaths: make(map[string]bool),
- state: State{Available: true, CapsLock: false},
- closeChan: make(chan struct{}),
- }
-
- ch := m.Subscribe("test-client")
- assert.NotNil(t, ch)
- count := 0
- m.subscribers.Range(func(key string, ch chan State) bool {
- count++
- return true
- })
- assert.Equal(t, 1, count)
-}
-
-func TestManager_Unsubscribe(t *testing.T) {
- mockDevice := mocks.NewMockEvdevDevice(t)
- mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
-
- m := &Manager{
- devices: []EvdevDevice{mockDevice},
- monitoredPaths: make(map[string]bool),
- state: State{Available: true, CapsLock: false},
- closeChan: make(chan struct{}),
- }
-
- ch := m.Subscribe("test-client")
- count := 0
- m.subscribers.Range(func(key string, ch chan State) bool {
- count++
- return true
- })
- assert.Equal(t, 1, count)
-
- m.Unsubscribe("test-client")
- count = 0
- m.subscribers.Range(func(key string, ch chan State) bool {
- count++
- return true
- })
- assert.Equal(t, 0, count)
-
- select {
- case _, ok := <-ch:
- assert.False(t, ok, "channel should be closed")
- default:
- t.Error("channel should be closed")
- }
-}
-
-func TestManager_UpdateCapsLock(t *testing.T) {
- mockDevice := mocks.NewMockEvdevDevice(t)
- mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
-
- m := &Manager{
- devices: []EvdevDevice{mockDevice},
- monitoredPaths: make(map[string]bool),
- state: State{Available: true, CapsLock: false},
- closeChan: make(chan struct{}),
- }
-
- ch := m.Subscribe("test-client")
-
- ledStateOn := evdev.StateMap{ledCapslockKey: true}
- mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledStateOn, nil).Once()
-
- go func() {
- m.readAndUpdateCapsLockState(0)
- }()
-
- newState := <-ch
- assert.True(t, newState.CapsLock)
-
- ledStateOff := evdev.StateMap{ledCapslockKey: false}
- mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledStateOff, nil).Once()
-
- go func() {
- m.readAndUpdateCapsLockState(0)
- }()
-
- 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{
- devices: []EvdevDevice{mockDevice},
- monitoredPaths: make(map[string]bool),
- state: State{Available: true, CapsLock: false},
- 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")
- }
-
- count := 0
- m.subscribers.Range(func(key string, ch chan State) bool {
- count++
- return true
- })
- assert.Equal(t, 0, count)
-
- 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()
-
- if !tt.expected {
- mockDevice.EXPECT().State(evdev.EvType(evKeyType)).Return(evdev.StateMap{}, nil).Maybe()
- }
-
- 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_MonitorDevice(t *testing.T) {
- t.Run("caps lock key press updates state", func(t *testing.T) {
- mockDevice := mocks.NewMockEvdevDevice(t)
-
- capsLockEvent := &evdev.InputEvent{
- Type: evKeyType,
- Code: keyCapslockKey,
- Value: keyStateOn,
- }
-
- ledState := evdev.StateMap{ledCapslockKey: true}
-
- mockDevice.EXPECT().ReadOne().Return(capsLockEvent, nil).Once()
- mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
- mockDevice.EXPECT().ReadOne().Return(nil, errors.New("stop")).Maybe()
- mockDevice.EXPECT().Close().Return(nil).Maybe()
-
- m := &Manager{
- devices: []EvdevDevice{mockDevice},
- state: State{Available: true, CapsLock: false},
- closeChan: make(chan struct{}),
- }
-
- ch := m.Subscribe("test")
-
- go m.monitorDevice(mockDevice, 0)
-
- 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{
- devices: []EvdevDevice{mockDevice},
- monitoredPaths: make(map[string]bool),
- state: State{Available: true, CapsLock: false},
- 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)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/models.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/models.go
deleted file mode 100644
index df8ec9e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/evdev/models.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package evdev
-
-type State struct {
- Available bool `json:"available"`
- CapsLock bool `json:"capsLock"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/handlers.go
deleted file mode 100644
index 3f7e17f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/handlers.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package extworkspace
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-type SuccessResult struct {
- Success bool `json:"success"`
- Message string `json:"message"`
-}
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- if manager == nil {
- models.RespondError(conn, req.ID, "extworkspace manager not initialized")
- return
- }
-
- switch req.Method {
- case "extworkspace.getState":
- handleGetState(conn, req, manager)
- case "extworkspace.activateWorkspace":
- handleActivateWorkspace(conn, req, manager)
- case "extworkspace.deactivateWorkspace":
- handleDeactivateWorkspace(conn, req, manager)
- case "extworkspace.removeWorkspace":
- handleRemoveWorkspace(conn, req, manager)
- case "extworkspace.createWorkspace":
- handleCreateWorkspace(conn, req, manager)
- case "extworkspace.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- state := manager.GetState()
- models.Respond(conn, req.ID, state)
-}
-
-func handleActivateWorkspace(conn net.Conn, req models.Request, manager *Manager) {
- groupID := models.GetOr(req, "groupID", "")
- workspaceID, ok := models.Get[string](req, "workspaceID")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'workspaceID' parameter")
- return
- }
-
- if err := manager.ActivateWorkspace(groupID, workspaceID); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace activated"})
-}
-
-func handleDeactivateWorkspace(conn net.Conn, req models.Request, manager *Manager) {
- groupID := models.GetOr(req, "groupID", "")
- workspaceID, ok := models.Get[string](req, "workspaceID")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'workspaceID' parameter")
- return
- }
-
- if err := manager.DeactivateWorkspace(groupID, workspaceID); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace deactivated"})
-}
-
-func handleRemoveWorkspace(conn net.Conn, req models.Request, manager *Manager) {
- groupID := models.GetOr(req, "groupID", "")
- workspaceID, ok := models.Get[string](req, "workspaceID")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'workspaceID' parameter")
- return
- }
-
- if err := manager.RemoveWorkspace(groupID, workspaceID); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace removed"})
-}
-
-func handleCreateWorkspace(conn net.Conn, req models.Request, manager *Manager) {
- groupID, ok := models.Get[string](req, "groupID")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'groupID' parameter")
- return
- }
-
- workspaceName, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- if err := manager.CreateWorkspace(groupID, workspaceName); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace create requested"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager.go
deleted file mode 100644
index b791b39..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager.go
+++ /dev/null
@@ -1,598 +0,0 @@
-package extworkspace
-
-import (
- "fmt"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-func CheckCapability() bool {
- display, err := wlclient.Connect("")
- if err != nil {
- return false
- }
- defer display.Destroy()
-
- registry, err := display.GetRegistry()
- if err != nil {
- return false
- }
- defer registry.Destroy()
-
- found := false
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
- found = true
- }
- })
-
- // Roundtrip to ensure all registry events are processed
- if err := display.Roundtrip(); err != nil {
- return false
- }
-
- return found
-}
-
-func NewManager(display wlclient.WaylandDisplay) (*Manager, error) {
- m := &Manager{
- display: display,
- ctx: display.Context(),
- cmdq: make(chan cmd, 128),
- stopChan: make(chan struct{}),
-
- dirty: make(chan struct{}, 1),
- }
-
- m.wg.Add(1)
- go m.waylandActor()
-
- if err := m.setupRegistry(); err != nil {
- close(m.stopChan)
- m.wg.Wait()
- return nil, err
- }
-
- m.updateState()
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- return m, nil
-}
-
-func (m *Manager) post(fn func()) {
- select {
- case m.cmdq <- cmd{fn: fn}:
- default:
- log.Warn("ExtWorkspace actor command queue full, dropping command")
- }
-}
-
-func (m *Manager) waylandActor() {
- defer m.wg.Done()
-
- for {
- select {
- case <-m.stopChan:
- return
- case c := <-m.cmdq:
- c.fn()
- }
- }
-}
-
-func (m *Manager) setupRegistry() error {
- log.Info("ExtWorkspace: starting registry setup")
-
- registry, err := m.display.GetRegistry()
- if err != nil {
- return fmt.Errorf("failed to get registry: %w", err)
- }
- m.registry = registry
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- if e.Interface == "wl_output" {
- output := wlclient.NewOutput(m.ctx)
- if err := registry.Bind(e.Name, e.Interface, 4, output); err == nil {
- outputID := output.ID()
-
- output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
- m.outputNames.Store(outputID, ev.Name)
- log.Debugf("ExtWorkspace: Output %d (%s) name received", outputID, ev.Name)
- m.post(func() {
- m.updateState()
- })
- })
- }
- return
- }
-
- if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
- log.Infof("ExtWorkspace: found %s", ext_workspace.ExtWorkspaceManagerV1InterfaceName)
- manager := ext_workspace.NewExtWorkspaceManagerV1(m.ctx)
- version := e.Version
- if version > 1 {
- version = 1
- }
-
- manager.SetWorkspaceGroupHandler(func(e ext_workspace.ExtWorkspaceManagerV1WorkspaceGroupEvent) {
- m.handleWorkspaceGroup(e)
- })
-
- manager.SetWorkspaceHandler(func(e ext_workspace.ExtWorkspaceManagerV1WorkspaceEvent) {
- m.handleWorkspace(e)
- })
-
- manager.SetDoneHandler(func(e ext_workspace.ExtWorkspaceManagerV1DoneEvent) {
- log.Debug("ExtWorkspace: done event received")
- m.post(func() {
- m.updateState()
- })
- })
-
- manager.SetFinishedHandler(func(e ext_workspace.ExtWorkspaceManagerV1FinishedEvent) {
- log.Info("ExtWorkspace: finished event received")
- })
-
- if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
- m.manager = manager
- log.Info("ExtWorkspace: manager bound successfully")
- } else {
- log.Errorf("ExtWorkspace: failed to bind manager: %v", err)
- }
- }
- })
-
- log.Info("ExtWorkspace: registry setup complete (events will be processed async)")
- return nil
-}
-
-func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1WorkspaceGroupEvent) {
- handle := e.WorkspaceGroup
- groupID := handle.ID()
-
- log.Debugf("ExtWorkspace: New workspace group (id=%d)", groupID)
-
- group := &workspaceGroupState{
- id: groupID,
- handle: handle,
- outputIDs: make(map[uint32]bool),
- workspaceIDs: make([]uint32, 0),
- }
-
- m.groups.Store(groupID, group)
-
- handle.SetCapabilitiesHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1CapabilitiesEvent) {
- log.Debugf("ExtWorkspace: Group %d capabilities: %d", groupID, e.Capabilities)
- })
-
- handle.SetOutputEnterHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputEnterEvent) {
- outputID := e.Output.ID()
- log.Debugf("ExtWorkspace: Group %d output enter (output=%d)", groupID, outputID)
-
- m.post(func() {
- group.outputIDs[outputID] = true
- m.updateState()
- })
- })
-
- handle.SetOutputLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputLeaveEvent) {
- outputID := e.Output.ID()
- log.Debugf("ExtWorkspace: Group %d output leave (output=%d)", groupID, outputID)
- m.post(func() {
- delete(group.outputIDs, outputID)
- m.updateState()
- })
- })
-
- handle.SetWorkspaceEnterHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1WorkspaceEnterEvent) {
- workspaceID := e.Workspace.ID()
- log.Debugf("ExtWorkspace: Group %d workspace enter (workspace=%d)", groupID, workspaceID)
-
- m.post(func() {
- if ws, ok := m.workspaces.Load(workspaceID); ok {
- ws.groupID = groupID
- }
-
- group.workspaceIDs = append(group.workspaceIDs, workspaceID)
- m.updateState()
- })
- })
-
- handle.SetWorkspaceLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent) {
- workspaceID := e.Workspace.ID()
- log.Debugf("ExtWorkspace: Group %d workspace leave (workspace=%d)", groupID, workspaceID)
-
- m.post(func() {
- if ws, ok := m.workspaces.Load(workspaceID); ok {
- ws.groupID = 0
- }
-
- for i, id := range group.workspaceIDs {
- if id == workspaceID {
- group.workspaceIDs = append(group.workspaceIDs[:i], group.workspaceIDs[i+1:]...)
- break
- }
- }
- m.updateState()
- })
- })
-
- handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1RemovedEvent) {
- log.Debugf("ExtWorkspace: Group %d removed", groupID)
-
- m.post(func() {
- group.removed = true
-
- m.groups.Delete(groupID)
-
- m.wlMutex.Lock()
- handle.Destroy()
- m.wlMutex.Unlock()
-
- m.updateState()
- })
- })
-}
-
-func (m *Manager) handleWorkspace(e ext_workspace.ExtWorkspaceManagerV1WorkspaceEvent) {
- handle := e.Workspace
- workspaceID := handle.ID()
-
- log.Debugf("ExtWorkspace: New workspace (proxy_id=%d)", workspaceID)
-
- ws := &workspaceState{
- id: workspaceID,
- handle: handle,
- coordinates: make([]uint32, 0),
- }
-
- m.workspaces.Store(workspaceID, ws)
-
- handle.SetIdHandler(func(e ext_workspace.ExtWorkspaceHandleV1IdEvent) {
- log.Debugf("ExtWorkspace: Workspace %d id: %s", workspaceID, e.Id)
- m.post(func() {
- ws.workspaceID = e.Id
- m.updateState()
- })
- })
-
- handle.SetNameHandler(func(e ext_workspace.ExtWorkspaceHandleV1NameEvent) {
- log.Debugf("ExtWorkspace: Workspace %d name: %s", workspaceID, e.Name)
- m.post(func() {
- ws.name = e.Name
- m.updateState()
- })
- })
-
- handle.SetCoordinatesHandler(func(e ext_workspace.ExtWorkspaceHandleV1CoordinatesEvent) {
- coords := make([]uint32, 0)
- for i := 0; i < len(e.Coordinates); i += 4 {
- if i+4 <= len(e.Coordinates) {
- val := uint32(e.Coordinates[i]) |
- uint32(e.Coordinates[i+1])<<8 |
- uint32(e.Coordinates[i+2])<<16 |
- uint32(e.Coordinates[i+3])<<24
- coords = append(coords, val)
- }
- }
- log.Debugf("ExtWorkspace: Workspace %d coordinates: %v", workspaceID, coords)
- m.post(func() {
- ws.coordinates = coords
- m.updateState()
- })
- })
-
- handle.SetStateHandler(func(e ext_workspace.ExtWorkspaceHandleV1StateEvent) {
- log.Debugf("ExtWorkspace: Workspace %d state: %d", workspaceID, e.State)
- m.post(func() {
- ws.state = e.State
- m.updateState()
- })
- })
-
- handle.SetCapabilitiesHandler(func(e ext_workspace.ExtWorkspaceHandleV1CapabilitiesEvent) {
- log.Debugf("ExtWorkspace: Workspace %d capabilities: %d", workspaceID, e.Capabilities)
- })
-
- handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceHandleV1RemovedEvent) {
- log.Debugf("ExtWorkspace: Workspace %d removed", workspaceID)
-
- m.post(func() {
- ws.removed = true
-
- m.workspaces.Delete(workspaceID)
-
- m.wlMutex.Lock()
- handle.Destroy()
- m.wlMutex.Unlock()
-
- m.updateState()
- })
- })
-}
-
-func (m *Manager) updateState() {
- groups := make([]*WorkspaceGroup, 0)
-
- m.groups.Range(func(key uint32, group *workspaceGroupState) bool {
- if group.removed {
- return true
- }
-
- outputs := make([]string, 0)
- for outputID := range group.outputIDs {
- if name, ok := m.outputNames.Load(outputID); ok && name != "" {
- outputs = append(outputs, name)
- }
- }
-
- workspaces := make([]*Workspace, 0)
- for _, wsID := range group.workspaceIDs {
- ws, exists := m.workspaces.Load(wsID)
- if !exists {
- continue
- }
- if ws.removed {
- continue
- }
-
- workspace := &Workspace{
- ID: ws.workspaceID,
- Name: ws.name,
- Coordinates: ws.coordinates,
- State: ws.state,
- Active: ws.state&uint32(ext_workspace.ExtWorkspaceHandleV1StateActive) != 0,
- Urgent: ws.state&uint32(ext_workspace.ExtWorkspaceHandleV1StateUrgent) != 0,
- Hidden: ws.state&uint32(ext_workspace.ExtWorkspaceHandleV1StateHidden) != 0,
- }
- workspaces = append(workspaces, workspace)
- }
-
- groupState := &WorkspaceGroup{
- ID: fmt.Sprintf("group-%d", group.id),
- Outputs: outputs,
- Workspaces: workspaces,
- }
- groups = append(groups, groupState)
- return true
- })
-
- newState := State{
- Groups: groups,
- }
-
- m.stateMutex.Lock()
- m.state = &newState
- m.stateMutex.Unlock()
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.GetState()
-
- if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- currentState:
- default:
- log.Warn("ExtWorkspace: subscriber channel full, dropping update")
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotified = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error {
- errChan := make(chan error, 1)
-
- m.post(func() {
- var targetGroupID uint32
- if groupID != "" {
- var parsedID uint32
- if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil {
- targetGroupID = parsedID
- }
- }
-
- var found bool
- m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
- if targetGroupID != 0 && ws.groupID != targetGroupID {
- return true
- }
- if ws.workspaceID == workspaceID || ws.name == workspaceID {
- m.wlMutex.Lock()
- err := ws.handle.Activate()
- if err == nil {
- err = m.manager.Commit()
- }
- m.wlMutex.Unlock()
- errChan <- err
- found = true
- return false
- }
- return true
- })
-
- if !found {
- errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
- }
- })
-
- return <-errChan
-}
-
-func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error {
- errChan := make(chan error, 1)
-
- m.post(func() {
- var targetGroupID uint32
- if groupID != "" {
- var parsedID uint32
- if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil {
- targetGroupID = parsedID
- }
- }
-
- var found bool
- m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
- if targetGroupID != 0 && ws.groupID != targetGroupID {
- return true
- }
- if ws.workspaceID == workspaceID || ws.name == workspaceID {
- m.wlMutex.Lock()
- err := ws.handle.Deactivate()
- if err == nil {
- err = m.manager.Commit()
- }
- m.wlMutex.Unlock()
- errChan <- err
- found = true
- return false
- }
- return true
- })
-
- if !found {
- errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
- }
- })
-
- return <-errChan
-}
-
-func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error {
- errChan := make(chan error, 1)
-
- m.post(func() {
- var targetGroupID uint32
- if groupID != "" {
- var parsedID uint32
- if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil {
- targetGroupID = parsedID
- }
- }
-
- var found bool
- m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
- if targetGroupID != 0 && ws.groupID != targetGroupID {
- return true
- }
- if ws.workspaceID == workspaceID || ws.name == workspaceID {
- m.wlMutex.Lock()
- err := ws.handle.Remove()
- if err == nil {
- err = m.manager.Commit()
- }
- m.wlMutex.Unlock()
- errChan <- err
- found = true
- return false
- }
- return true
- })
-
- if !found {
- errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
- }
- })
-
- return <-errChan
-}
-
-func (m *Manager) CreateWorkspace(groupID, workspaceName string) error {
- errChan := make(chan error, 1)
-
- m.post(func() {
- var found bool
- m.groups.Range(func(key uint32, group *workspaceGroupState) bool {
- if fmt.Sprintf("group-%d", group.id) == groupID {
- m.wlMutex.Lock()
- err := group.handle.CreateWorkspace(workspaceName)
- if err == nil {
- err = m.manager.Commit()
- }
- m.wlMutex.Unlock()
- errChan <- err
- found = true
- return false
- }
- return true
- })
-
- if !found {
- errChan <- fmt.Errorf("workspace group not found: %s", groupID)
- }
- })
-
- return <-errChan
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.wg.Wait()
- m.notifierWg.Wait()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
- if ws.handle != nil {
- ws.handle.Destroy()
- }
- m.workspaces.Delete(key)
- return true
- })
-
- m.groups.Range(func(key uint32, group *workspaceGroupState) bool {
- if group.handle != nil {
- group.handle.Destroy()
- }
- m.groups.Delete(key)
- return true
- })
-
- if m.manager != nil {
- m.manager.Stop()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager_test.go
deleted file mode 100644
index b20d457..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/manager_test.go
+++ /dev/null
@@ -1,392 +0,0 @@
-package extworkspace
-
-import (
- "errors"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-
- mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
-)
-
-func TestStateChanged_BothNil(t *testing.T) {
- assert.True(t, stateChanged(nil, nil))
-}
-
-func TestStateChanged_OneNil(t *testing.T) {
- s := &State{Groups: []*WorkspaceGroup{}}
- assert.True(t, stateChanged(s, nil))
- assert.True(t, stateChanged(nil, s))
-}
-
-func TestStateChanged_GroupCountDiffers(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{ID: "group-1"}}}
- b := &State{Groups: []*WorkspaceGroup{}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_GroupIDDiffers(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{}, Workspaces: []*Workspace{}}}}
- b := &State{Groups: []*WorkspaceGroup{{ID: "group-2", Outputs: []string{}, Workspaces: []*Workspace{}}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputCountDiffers(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}, Workspaces: []*Workspace{}}}}
- b := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{}, Workspaces: []*Workspace{}}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputNameDiffers(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}, Workspaces: []*Workspace{}}}}
- b := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"HDMI-A-1"}, Workspaces: []*Workspace{}}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_WorkspaceCountDiffers(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{},
- Workspaces: []*Workspace{{ID: "1", Name: "ws1"}},
- }}}
- b := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{},
- Workspaces: []*Workspace{},
- }}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_WorkspaceFieldsDiffer(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{},
- Workspaces: []*Workspace{{
- ID: "1", Name: "ws1", State: 0, Active: false, Urgent: false, Hidden: false,
- }},
- }}}
- b := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{},
- Workspaces: []*Workspace{{
- ID: "2", Name: "ws1", State: 0, Active: false, Urgent: false, Hidden: false,
- }},
- }}}
- assert.True(t, stateChanged(a, b))
-
- b.Groups[0].Workspaces[0].ID = "1"
- b.Groups[0].Workspaces[0].Name = "ws2"
- assert.True(t, stateChanged(a, b))
-
- b.Groups[0].Workspaces[0].Name = "ws1"
- b.Groups[0].Workspaces[0].State = 1
- assert.True(t, stateChanged(a, b))
-
- b.Groups[0].Workspaces[0].State = 0
- b.Groups[0].Workspaces[0].Active = true
- assert.True(t, stateChanged(a, b))
-
- b.Groups[0].Workspaces[0].Active = false
- b.Groups[0].Workspaces[0].Urgent = true
- assert.True(t, stateChanged(a, b))
-
- b.Groups[0].Workspaces[0].Urgent = false
- b.Groups[0].Workspaces[0].Hidden = true
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_WorkspaceCoordinatesDiffer(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{},
- Workspaces: []*Workspace{{
- ID: "1", Name: "ws1", Coordinates: []uint32{0, 0},
- }},
- }}}
- b := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{},
- Workspaces: []*Workspace{{
- ID: "1", Name: "ws1", Coordinates: []uint32{1, 0},
- }},
- }}}
- assert.True(t, stateChanged(a, b))
-
- b.Groups[0].Workspaces[0].Coordinates = []uint32{0}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_Equal(t *testing.T) {
- a := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{"eDP-1", "HDMI-A-1"},
- Workspaces: []*Workspace{
- {ID: "1", Name: "ws1", Coordinates: []uint32{0, 0}, State: 1, Active: true},
- {ID: "2", Name: "ws2", Coordinates: []uint32{1, 0}, State: 0, Active: false},
- },
- }}}
- b := &State{Groups: []*WorkspaceGroup{{
- ID: "group-1",
- Outputs: []string{"eDP-1", "HDMI-A-1"},
- Workspaces: []*Workspace{
- {ID: "1", Name: "ws1", Coordinates: []uint32{0, 0}, State: 1, Active: true},
- {ID: "2", Name: "ws2", Coordinates: []uint32{1, 0}, State: 0, Active: false},
- },
- }}}
- assert.False(t, stateChanged(a, b))
-}
-
-func TestManager_ConcurrentGetState(t *testing.T) {
- m := &Manager{
- state: &State{
- Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}}},
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- s := m.GetState()
- _ = s.Groups
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.stateMutex.Lock()
- m.state = &State{
- Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}}},
- }
- m.stateMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
- m := &Manager{
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- subID := string(rune('a' + id))
- ch := m.Subscribe(subID)
- assert.NotNil(t, ch)
- time.Sleep(time.Millisecond)
- m.Unsubscribe(subID)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapGroupsConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &workspaceGroupState{
- id: key,
- outputIDs: map[uint32]bool{1: true},
- workspaceIDs: []uint32{uint32(j)},
- }
- m.groups.Store(key, state)
-
- if loaded, ok := m.groups.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.groups.Range(func(k uint32, v *workspaceGroupState) bool {
- _ = v.id
- _ = v.outputIDs
- return true
- })
- }
-
- m.groups.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapWorkspacesConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &workspaceState{
- id: key,
- workspaceID: "ws-1",
- name: "workspace",
- state: uint32(j % 4),
- coordinates: []uint32{uint32(j), 0},
- }
- m.workspaces.Store(key, state)
-
- if loaded, ok := m.workspaces.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.workspaces.Range(func(k uint32, v *workspaceState) bool {
- _ = v.name
- _ = v.state
- return true
- })
- }
-
- m.workspaces.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapOutputNamesConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- m.outputNames.Store(key, "eDP-1")
-
- if loaded, ok := m.outputNames.Load(key); ok {
- assert.NotEmpty(t, loaded)
- }
-
- m.outputNames.Range(func(k uint32, v string) bool {
- _ = v
- return true
- })
- }
-
- m.outputNames.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
- m := &Manager{
- dirty: make(chan struct{}, 1),
- }
-
- for i := 0; i < 10; i++ {
- m.notifySubscribers()
- }
-
- assert.Len(t, m.dirty, 1)
-}
-
-func TestManager_PostQueueFull(t *testing.T) {
- m := &Manager{
- cmdq: make(chan cmd, 2),
- stopChan: make(chan struct{}),
- }
-
- m.post(func() {})
- m.post(func() {})
- m.post(func() {})
- m.post(func() {})
-
- assert.Len(t, m.cmdq, 2)
-}
-
-func TestManager_GetStateNilState(t *testing.T) {
- m := &Manager{}
-
- s := m.GetState()
- assert.NotNil(t, s.Groups)
- assert.Empty(t, s.Groups)
-}
-
-func TestWorkspace_Fields(t *testing.T) {
- ws := Workspace{
- ID: "ws-1",
- Name: "workspace 1",
- Coordinates: []uint32{0, 0},
- State: 1,
- Active: true,
- Urgent: false,
- Hidden: false,
- }
-
- assert.Equal(t, "ws-1", ws.ID)
- assert.Equal(t, "workspace 1", ws.Name)
- assert.True(t, ws.Active)
- assert.False(t, ws.Urgent)
- assert.False(t, ws.Hidden)
-}
-
-func TestWorkspaceGroup_Fields(t *testing.T) {
- group := WorkspaceGroup{
- ID: "group-1",
- Outputs: []string{"eDP-1", "HDMI-A-1"},
- Workspaces: []*Workspace{
- {ID: "ws-1", Name: "workspace 1"},
- },
- }
-
- assert.Equal(t, "group-1", group.ID)
- assert.Len(t, group.Outputs, 2)
- assert.Len(t, group.Workspaces, 1)
-}
-
-func TestNewManager_GetRegistryError(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- mockDisplay.EXPECT().Context().Return(nil)
- mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
-
- _, err := NewManager(mockDisplay)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get registry")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/types.go
deleted file mode 100644
index 903a1cb..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/extworkspace/types.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package extworkspace
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type Workspace struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Coordinates []uint32 `json:"coordinates"`
- State uint32 `json:"state"`
- Active bool `json:"active"`
- Urgent bool `json:"urgent"`
- Hidden bool `json:"hidden"`
-}
-
-type WorkspaceGroup struct {
- ID string `json:"id"`
- Outputs []string `json:"outputs"`
- Workspaces []*Workspace `json:"workspaces"`
-}
-
-type State struct {
- Groups []*WorkspaceGroup `json:"groups"`
-}
-
-type cmd struct {
- fn func()
-}
-
-type Manager struct {
- display wlclient.WaylandDisplay
- ctx *wlclient.Context
- registry *wlclient.Registry
- manager *ext_workspace.ExtWorkspaceManagerV1
-
- outputNames syncmap.Map[uint32, string]
-
- groups syncmap.Map[uint32, *workspaceGroupState]
-
- workspaces syncmap.Map[uint32, *workspaceState]
-
- wlMutex sync.Mutex
- cmdq chan cmd
- stopChan chan struct{}
- wg sync.WaitGroup
-
- subscribers syncmap.Map[string, chan State]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotified *State
-
- stateMutex sync.RWMutex
- state *State
-}
-
-type workspaceGroupState struct {
- id uint32
- handle *ext_workspace.ExtWorkspaceGroupHandleV1
- outputIDs map[uint32]bool
- workspaceIDs []uint32
- removed bool
-}
-
-type workspaceState struct {
- id uint32
- handle *ext_workspace.ExtWorkspaceHandleV1
- workspaceID string
- name string
- coordinates []uint32
- state uint32
- groupID uint32
- removed bool
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{
- Groups: []*WorkspaceGroup{},
- }
- }
- stateCopy := *m.state
- return stateCopy
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
-
- m.subscribers.Store(id, ch)
-
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if ch, ok := m.subscribers.LoadAndDelete(id); ok {
- close(ch)
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func stateChanged(old, new *State) bool {
- if old == nil || new == nil {
- return true
- }
- if len(old.Groups) != len(new.Groups) {
- return true
- }
-
- for i, newGroup := range new.Groups {
- if i >= len(old.Groups) {
- return true
- }
- oldGroup := old.Groups[i]
- if oldGroup.ID != newGroup.ID {
- return true
- }
- if len(oldGroup.Outputs) != len(newGroup.Outputs) {
- return true
- }
- for j, newOutput := range newGroup.Outputs {
- if j >= len(oldGroup.Outputs) {
- return true
- }
- if oldGroup.Outputs[j] != newOutput {
- return true
- }
- }
- if len(oldGroup.Workspaces) != len(newGroup.Workspaces) {
- return true
- }
- for j, newWs := range newGroup.Workspaces {
- if j >= len(oldGroup.Workspaces) {
- return true
- }
- oldWs := oldGroup.Workspaces[j]
- if oldWs.ID != newWs.ID || oldWs.Name != newWs.Name || oldWs.State != newWs.State {
- return true
- }
- if oldWs.Active != newWs.Active || oldWs.Urgent != newWs.Urgent || oldWs.Hidden != newWs.Hidden {
- return true
- }
- if len(oldWs.Coordinates) != len(newWs.Coordinates) {
- return true
- }
- for k, coord := range newWs.Coordinates {
- if k >= len(oldWs.Coordinates) {
- return true
- }
- if oldWs.Coordinates[k] != coord {
- return true
- }
- }
- }
- }
-
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions.go
deleted file mode 100644
index 52515d1..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package freedesktop
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/godbus/dbus/v5"
-)
-
-func (m *Manager) SetIconFile(iconPath string) error {
- if !m.state.Accounts.Available || m.accountsObj == nil {
- return fmt.Errorf("accounts service not available")
- }
-
- err := m.accountsObj.Call(dbusAccountsUserInterface+".SetIconFile", 0, iconPath).Err
- if err != nil {
- return fmt.Errorf("failed to set icon file: %w", err)
- }
-
- m.updateAccountsState()
- return nil
-}
-
-func (m *Manager) SetRealName(name string) error {
- if !m.state.Accounts.Available || m.accountsObj == nil {
- return fmt.Errorf("accounts service not available")
- }
-
- err := m.accountsObj.Call(dbusAccountsUserInterface+".SetRealName", 0, name).Err
- if err != nil {
- return fmt.Errorf("failed to set real name: %w", err)
- }
-
- m.updateAccountsState()
- return nil
-}
-
-func (m *Manager) SetEmail(email string) error {
- if !m.state.Accounts.Available || m.accountsObj == nil {
- return fmt.Errorf("accounts service not available")
- }
-
- err := m.accountsObj.Call(dbusAccountsUserInterface+".SetEmail", 0, email).Err
- if err != nil {
- return fmt.Errorf("failed to set email: %w", err)
- }
-
- m.updateAccountsState()
- return nil
-}
-
-func (m *Manager) SetLanguage(language string) error {
- if !m.state.Accounts.Available || m.accountsObj == nil {
- return fmt.Errorf("accounts service not available")
- }
-
- err := m.accountsObj.Call(dbusAccountsUserInterface+".SetLanguage", 0, language).Err
- if err != nil {
- return fmt.Errorf("failed to set language: %w", err)
- }
-
- m.updateAccountsState()
- return nil
-}
-
-func (m *Manager) SetLocation(location string) error {
- if !m.state.Accounts.Available || m.accountsObj == nil {
- return fmt.Errorf("accounts service not available")
- }
-
- err := m.accountsObj.Call(dbusAccountsUserInterface+".SetLocation", 0, location).Err
- if err != nil {
- return fmt.Errorf("failed to set location: %w", err)
- }
-
- m.updateAccountsState()
- return nil
-}
-
-func (m *Manager) GetUserIconFile(username string) (string, error) {
- if m.systemConn == nil {
- return "", fmt.Errorf("accounts service not available")
- }
-
- accountsManager := m.systemConn.Object(dbusAccountsDest, dbus.ObjectPath(dbusAccountsPath))
-
- var userPath dbus.ObjectPath
- err := accountsManager.Call(dbusAccountsInterface+".FindUserByName", 0, username).Store(&userPath)
- if err != nil {
- return "", fmt.Errorf("user not found: %w", err)
- }
-
- userObj := m.systemConn.Object(dbusAccountsDest, userPath)
- variant, err := userObj.GetProperty(dbusAccountsUserInterface + ".IconFile")
- if err != nil {
- return "", err
- }
-
- var iconFile string
- if err := variant.Store(&iconFile); err != nil {
- return "", err
- }
-
- return iconFile, nil
-}
-
-func (m *Manager) SetIconTheme(iconTheme string) error {
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "icon-theme", iconTheme); err != nil {
- return fmt.Errorf("failed to set icon theme: %w", err)
- }
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions_test.go
deleted file mode 100644
index e2c21e8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/actions_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package freedesktop
-
-import (
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_SetIconFile(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.SetIconFile("/path/to/icon.png")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- })
-}
-
-func TestManager_SetRealName(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.SetRealName("New Name")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- })
-}
-
-func TestManager_SetEmail(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.SetEmail("test@example.com")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- })
-}
-
-func TestManager_SetLanguage(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.SetLanguage("en_US.UTF-8")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- })
-}
-
-func TestManager_SetLocation(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.SetLocation("Test Location")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- })
-}
-
-func TestManager_GetUserIconFile(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- iconFile, err := manager.GetUserIconFile("testuser")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- assert.Empty(t, iconFile)
- })
-}
-
-func TestManager_UpdateAccountsState(t *testing.T) {
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.updateAccountsState()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "accounts service not available")
- })
-}
-
-func TestManager_UpdateSettingsState(t *testing.T) {
- t.Run("settings not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Settings: SettingsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- err := manager.updateSettingsState()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "settings portal not available")
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/constants.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/constants.go
deleted file mode 100644
index 12db87b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/constants.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package freedesktop
-
-const (
- dbusAccountsDest = "org.freedesktop.Accounts"
- dbusAccountsPath = "/org/freedesktop/Accounts"
- dbusAccountsInterface = "org.freedesktop.Accounts"
- dbusAccountsUserInterface = "org.freedesktop.Accounts.User"
-
- dbusPortalDest = "org.freedesktop.portal.Desktop"
- dbusPortalPath = "/org/freedesktop/portal/desktop"
- dbusPortalSettingsInterface = "org.freedesktop.portal.Settings"
-
- dbusPropsInterface = "org.freedesktop.DBus.Properties"
-
- dbusScreensaverName = "org.freedesktop.ScreenSaver"
- dbusScreensaverPath = "/ScreenSaver"
- dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
- dbusScreensaverInterface = "org.freedesktop.ScreenSaver"
-
- dbusGnomeScreensaverName = "org.gnome.ScreenSaver"
- dbusGnomeScreensaverPath = "/org/gnome/ScreenSaver"
- dbusGnomeScreensaverInterface = "org.gnome.ScreenSaver"
-)
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers.go
deleted file mode 100644
index c3090fa..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package freedesktop
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "freedesktop.getState":
- handleGetState(conn, req, manager)
- case "freedesktop.accounts.setIconFile":
- handleSetIconFile(conn, req, manager)
- case "freedesktop.accounts.setRealName":
- handleSetRealName(conn, req, manager)
- case "freedesktop.accounts.setEmail":
- handleSetEmail(conn, req, manager)
- case "freedesktop.accounts.setLanguage":
- handleSetLanguage(conn, req, manager)
- case "freedesktop.accounts.setLocation":
- handleSetLocation(conn, req, manager)
- case "freedesktop.accounts.getUserIconFile":
- handleGetUserIconFile(conn, req, manager)
- case "freedesktop.settings.getColorScheme":
- handleGetColorScheme(conn, req, manager)
- case "freedesktop.settings.setIconTheme":
- handleSetIconTheme(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleSetIconFile(conn net.Conn, req models.Request, manager *Manager) {
- iconPath, err := params.String(req.Params, "path")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetIconFile(iconPath); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "icon file set"})
-}
-
-func handleSetRealName(conn net.Conn, req models.Request, manager *Manager) {
- name, err := params.String(req.Params, "name")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetRealName(name); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "real name set"})
-}
-
-func handleSetEmail(conn net.Conn, req models.Request, manager *Manager) {
- email, err := params.String(req.Params, "email")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetEmail(email); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "email set"})
-}
-
-func handleSetLanguage(conn net.Conn, req models.Request, manager *Manager) {
- language, err := params.String(req.Params, "language")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetLanguage(language); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "language set"})
-}
-
-func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
- location, err := params.String(req.Params, "location")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetLocation(location); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"})
-}
-
-func handleGetUserIconFile(conn net.Conn, req models.Request, manager *Manager) {
- username, err := params.String(req.Params, "username")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- iconFile, err := manager.GetUserIconFile(username)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Value: iconFile})
-}
-
-func handleGetColorScheme(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.updateSettingsState(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- state := manager.GetState()
- models.Respond(conn, req.ID, map[string]uint32{"colorScheme": state.Settings.ColorScheme})
-}
-
-func handleSetIconTheme(conn net.Conn, req models.Request, manager *Manager) {
- iconTheme, err := params.String(req.Params, "iconTheme")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetIconTheme(iconTheme); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "icon theme set"})
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers_test.go
deleted file mode 100644
index f2cb5a2..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/handlers_test.go
+++ /dev/null
@@ -1,581 +0,0 @@
-package freedesktop
-
-import (
- "bytes"
- "encoding/json"
- "net"
- "sync"
- "testing"
-
- mockdbus "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/godbus/dbus/v5"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-)
-
-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 mockGetAllAccountsProperties() *dbus.Call {
- props := map[string]dbus.Variant{
- "IconFile": dbus.MakeVariant("/path/to/icon.png"),
- "RealName": dbus.MakeVariant("Test"),
- "UserName": dbus.MakeVariant("test"),
- "AccountType": dbus.MakeVariant(int32(0)),
- "HomeDirectory": dbus.MakeVariant("/home/test"),
- "Shell": dbus.MakeVariant("/bin/bash"),
- "Email": dbus.MakeVariant(""),
- "Language": dbus.MakeVariant(""),
- "Location": dbus.MakeVariant(""),
- "Locked": dbus.MakeVariant(false),
- "PasswordMode": dbus.MakeVariant(int32(1)),
- }
- return &dbus.Call{Err: nil, Body: []any{props}}
-}
-
-func TestRespondError_Freedesktop(t *testing.T) {
- conn := newMockNetConn()
- models.RespondError(conn, 123, "test error")
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Equal(t, "test error", resp.Error)
- assert.Nil(t, resp.Result)
-}
-
-func TestRespond_Freedesktop(t *testing.T) {
- conn := newMockNetConn()
- result := models.SuccessResult{Success: true, Message: "test"}
- models.Respond(conn, 123, result)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "test", resp.Result.Message)
-}
-
-func TestHandleGetState(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- UserName: "testuser",
- RealName: "Test User",
- UID: 1000,
- },
- Settings: SettingsState{
- Available: true,
- ColorScheme: 1,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "freedesktop.getState"}
-
- handleGetState(conn, req, manager)
-
- var resp models.Response[FreedeskState]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Accounts.Available)
- assert.Equal(t, "testuser", resp.Result.Accounts.UserName)
- assert.True(t, resp.Result.Settings.Available)
- assert.Equal(t, uint32(1), resp.Result.Settings.ColorScheme)
-}
-
-func TestHandleSetIconFile(t *testing.T) {
- t.Run("missing path parameter", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setIconFile",
- Params: map[string]any{},
- }
-
- handleSetIconFile(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'path' parameter")
- })
-
- t.Run("successful set icon file", func(t *testing.T) {
- mockAccountsObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockAccountsObj.EXPECT().Call("org.freedesktop.Accounts.User.SetIconFile", dbus.Flags(0), "/path/to/icon.png").Return(mockCall)
- mockAccountsObj.EXPECT().CallWithContext(mock.Anything, "org.freedesktop.DBus.Properties.GetAll", dbus.Flags(0), "org.freedesktop.Accounts.User").Return(mockGetAllAccountsProperties())
-
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- },
- },
- stateMutex: sync.RWMutex{},
- accountsObj: mockAccountsObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setIconFile",
- Params: map[string]any{
- "path": "/path/to/icon.png",
- },
- }
-
- handleSetIconFile(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "icon file set", resp.Result.Message)
- })
-
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setIconFile",
- Params: map[string]any{
- "path": "/path/to/icon.png",
- },
- }
-
- handleSetIconFile(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "accounts service not available")
- })
-}
-
-func TestHandleSetRealName(t *testing.T) {
- t.Run("missing name parameter", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setRealName",
- Params: map[string]any{},
- }
-
- handleSetRealName(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'name' parameter")
- })
-
- t.Run("successful set real name", func(t *testing.T) {
- mockAccountsObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockAccountsObj.EXPECT().Call("org.freedesktop.Accounts.User.SetRealName", dbus.Flags(0), "New Name").Return(mockCall)
- mockAccountsObj.EXPECT().CallWithContext(mock.Anything, "org.freedesktop.DBus.Properties.GetAll", dbus.Flags(0), "org.freedesktop.Accounts.User").Return(mockGetAllAccountsProperties())
-
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- },
- },
- stateMutex: sync.RWMutex{},
- accountsObj: mockAccountsObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setRealName",
- Params: map[string]any{
- "name": "New Name",
- },
- }
-
- handleSetRealName(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "real name set", resp.Result.Message)
- })
-}
-
-func TestHandleSetEmail(t *testing.T) {
- t.Run("missing email parameter", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setEmail",
- Params: map[string]any{},
- }
-
- handleSetEmail(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'email' parameter")
- })
-
- t.Run("successful set email", func(t *testing.T) {
- mockAccountsObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockAccountsObj.EXPECT().Call("org.freedesktop.Accounts.User.SetEmail", dbus.Flags(0), "test@example.com").Return(mockCall)
- mockAccountsObj.EXPECT().CallWithContext(mock.Anything, "org.freedesktop.DBus.Properties.GetAll", dbus.Flags(0), "org.freedesktop.Accounts.User").Return(mockGetAllAccountsProperties())
-
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- },
- },
- stateMutex: sync.RWMutex{},
- accountsObj: mockAccountsObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setEmail",
- Params: map[string]any{
- "email": "test@example.com",
- },
- }
-
- handleSetEmail(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "email set", resp.Result.Message)
- })
-}
-
-func TestHandleSetLanguage(t *testing.T) {
- t.Run("missing language parameter", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setLanguage",
- Params: map[string]any{},
- }
-
- handleSetLanguage(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'language' parameter")
- })
-}
-
-func TestHandleSetLocation(t *testing.T) {
- t.Run("missing location parameter", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.setLocation",
- Params: map[string]any{},
- }
-
- handleSetLocation(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'location' parameter")
- })
-}
-
-func TestHandleGetUserIconFile(t *testing.T) {
- t.Run("missing username parameter", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.getUserIconFile",
- Params: map[string]any{},
- }
-
- handleGetUserIconFile(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'username' parameter")
- })
-
- t.Run("accounts not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.accounts.getUserIconFile",
- Params: map[string]any{
- "username": "testuser",
- },
- }
-
- handleGetUserIconFile(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "accounts service not available")
- })
-}
-
-func TestHandleGetColorScheme(t *testing.T) {
- t.Run("settings not available", func(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Settings: SettingsState{
- Available: false,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "freedesktop.settings.getColorScheme"}
-
- handleGetColorScheme(conn, req, manager)
-
- var resp models.Response[map[string]uint32]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "settings portal not available")
- })
-
- t.Run("successful get color scheme", func(t *testing.T) {
- mockSettingsObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{
- Err: nil,
- Body: []any{dbus.MakeVariant(uint32(1))},
- }
- mockSettingsObj.EXPECT().Call("org.freedesktop.portal.Settings.ReadOne", dbus.Flags(0), "org.freedesktop.appearance", "color-scheme").Return(mockCall)
-
- manager := &Manager{
- state: &FreedeskState{
- Settings: SettingsState{
- Available: true,
- },
- },
- stateMutex: sync.RWMutex{},
- settingsObj: mockSettingsObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "freedesktop.settings.getColorScheme"}
-
- handleGetColorScheme(conn, req, manager)
-
- var resp models.Response[map[string]uint32]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.Equal(t, uint32(1), (*resp.Result)["colorScheme"])
- })
-}
-
-func TestHandleRequest(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- UserName: "testuser",
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- t.Run("unknown method", func(t *testing.T) {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.unknown",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "unknown method")
- })
-
- t.Run("valid method - getState", func(t *testing.T) {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "freedesktop.getState",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[FreedeskState]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- })
-
- t.Run("all method routes", func(t *testing.T) {
- tests := []string{
- "freedesktop.accounts.setIconFile",
- "freedesktop.accounts.setRealName",
- "freedesktop.accounts.setEmail",
- "freedesktop.accounts.setLanguage",
- "freedesktop.accounts.setLocation",
- "freedesktop.accounts.getUserIconFile",
- "freedesktop.settings.getColorScheme",
- }
-
- for _, method := range tests {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: method,
- Params: map[string]any{},
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- // Will have errors due to missing params or service unavailable
- // but the method routing should work
- }
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager.go
deleted file mode 100644
index 054f859..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package freedesktop
-
-import (
- "context"
- "fmt"
- "os"
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
- "github.com/godbus/dbus/v5"
-)
-
-func NewManager() (*Manager, error) {
- systemConn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to system bus: %w", err)
- }
-
- sessionConn, err := dbus.ConnectSessionBus()
- if err != nil {
- sessionConn = nil
- }
-
- m := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{},
- Settings: SettingsState{},
- Screensaver: ScreensaverState{},
- },
- stateMutex: sync.RWMutex{},
- systemConn: systemConn,
- sessionConn: sessionConn,
- currentUID: uint64(os.Getuid()),
- }
-
- m.initializeAccounts()
- m.initializeSettings()
- m.initializeScreensaver()
-
- return m, nil
-}
-
-func (m *Manager) initializeAccounts() error {
- accountsManager := m.systemConn.Object(dbusAccountsDest, dbus.ObjectPath(dbusAccountsPath))
-
- var userPath dbus.ObjectPath
- err := accountsManager.Call(dbusAccountsInterface+".FindUserById", 0, int64(m.currentUID)).Store(&userPath)
- if err != nil {
- m.stateMutex.Lock()
- m.state.Accounts.Available = false
- m.stateMutex.Unlock()
- return err
- }
-
- m.accountsObj = m.systemConn.Object(dbusAccountsDest, userPath)
-
- m.stateMutex.Lock()
- m.state.Accounts.Available = true
- m.state.Accounts.UserPath = string(userPath)
- m.state.Accounts.UID = m.currentUID
- m.stateMutex.Unlock()
-
- if err := m.updateAccountsState(); err != nil {
- return fmt.Errorf("failed to update accounts state: %w", err)
- }
-
- return nil
-}
-
-func (m *Manager) initializeSettings() error {
- if m.sessionConn == nil {
- m.stateMutex.Lock()
- m.state.Settings.Available = false
- m.stateMutex.Unlock()
- return fmt.Errorf("no session bus connection")
- }
-
- m.settingsObj = m.sessionConn.Object(dbusPortalDest, dbus.ObjectPath(dbusPortalPath))
-
- var variant dbus.Variant
- err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant)
- if err != nil {
- m.stateMutex.Lock()
- m.state.Settings.Available = false
- m.stateMutex.Unlock()
- return err
- }
-
- m.stateMutex.Lock()
- m.state.Settings.Available = true
- m.stateMutex.Unlock()
-
- if err := m.updateSettingsState(); err != nil {
- return fmt.Errorf("failed to update settings state: %w", err)
- }
-
- return nil
-}
-
-func (m *Manager) updateAccountsState() error {
- if !m.state.Accounts.Available || m.accountsObj == nil {
- return fmt.Errorf("accounts service not available")
- }
-
- ctx := context.Background()
- props, err := m.getAccountProperties(ctx)
- if err != nil {
- return err
- }
-
- m.stateMutex.Lock()
- defer m.stateMutex.Unlock()
-
- m.state.Accounts.IconFile = dbusutil.GetOr(props, "IconFile", "")
- m.state.Accounts.RealName = dbusutil.GetOr(props, "RealName", "")
- m.state.Accounts.UserName = dbusutil.GetOr(props, "UserName", "")
- m.state.Accounts.AccountType = dbusutil.GetOr(props, "AccountType", int32(0))
- m.state.Accounts.HomeDirectory = dbusutil.GetOr(props, "HomeDirectory", "")
- m.state.Accounts.Shell = dbusutil.GetOr(props, "Shell", "")
- m.state.Accounts.Email = dbusutil.GetOr(props, "Email", "")
- m.state.Accounts.Language = dbusutil.GetOr(props, "Language", "")
- m.state.Accounts.Location = dbusutil.GetOr(props, "Location", "")
- m.state.Accounts.Locked = dbusutil.GetOr(props, "Locked", false)
- m.state.Accounts.PasswordMode = dbusutil.GetOr(props, "PasswordMode", int32(0))
-
- return nil
-}
-
-func (m *Manager) updateSettingsState() error {
- if !m.state.Settings.Available || m.settingsObj == nil {
- return fmt.Errorf("settings portal not available")
- }
-
- var variant dbus.Variant
- err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant)
- if err != nil {
- return err
- }
-
- if colorScheme, ok := dbusutil.As[uint32](variant); ok {
- m.stateMutex.Lock()
- m.state.Settings.ColorScheme = colorScheme
- m.stateMutex.Unlock()
- }
-
- return nil
-}
-
-func (m *Manager) getAccountProperties(ctx context.Context) (map[string]dbus.Variant, error) {
- var props map[string]dbus.Variant
- err := m.accountsObj.CallWithContext(ctx, dbusPropsInterface+".GetAll", 0, dbusAccountsUserInterface).Store(&props)
- if err != nil {
- return nil, err
- }
- return props, nil
-}
-
-func (m *Manager) GetState() FreedeskState {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- return *m.state
-}
-
-func (m *Manager) Subscribe(id string) chan FreedeskState {
- ch := make(chan FreedeskState, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) NotifySubscribers() {
- state := m.GetState()
- m.subscribers.Range(func(key string, ch chan FreedeskState) bool {
- select {
- case ch <- state:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) Close() {
- m.subscribers.Range(func(key string, ch chan FreedeskState) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.screensaverSubscribers.Range(func(key string, ch chan ScreensaverState) bool {
- close(ch)
- m.screensaverSubscribers.Delete(key)
- return true
- })
-
- if m.systemConn != nil {
- m.systemConn.Close()
- }
- if m.sessionConn != nil {
- m.sessionConn.Close()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager_test.go
deleted file mode 100644
index b3617f5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/manager_test.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package freedesktop
-
-import (
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_GetState(t *testing.T) {
- state := &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- UserName: "testuser",
- RealName: "Test User",
- UID: 1000,
- },
- Settings: SettingsState{
- Available: true,
- ColorScheme: 1,
- },
- }
-
- manager := &Manager{
- state: state,
- stateMutex: sync.RWMutex{},
- }
-
- result := manager.GetState()
- assert.True(t, result.Accounts.Available)
- assert.Equal(t, "testuser", result.Accounts.UserName)
- assert.Equal(t, "Test User", result.Accounts.RealName)
- assert.Equal(t, uint64(1000), result.Accounts.UID)
- assert.True(t, result.Settings.Available)
- assert.Equal(t, uint32(1), result.Settings.ColorScheme)
-}
-
-func TestManager_GetState_ThreadSafe(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- UserName: "testuser",
- },
- Settings: SettingsState{
- Available: true,
- ColorScheme: 1,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- done := make(chan bool)
- for i := 0; i < 10; i++ {
- go func() {
- state := manager.GetState()
- assert.True(t, state.Accounts.Available)
- assert.Equal(t, "testuser", state.Accounts.UserName)
- done <- true
- }()
- }
-
- for i := 0; i < 10; i++ {
- <-done
- }
-}
-
-func TestManager_Close(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- systemConn: nil,
- sessionConn: nil,
- }
-
- assert.NotPanics(t, func() {
- manager.Close()
- })
-}
-
-func TestNewManager(t *testing.T) {
- t.Run("attempts to create manager", func(t *testing.T) {
- manager, err := NewManager()
- if err != nil {
- assert.Nil(t, manager)
- } else {
- assert.NotNil(t, manager)
- assert.NotNil(t, manager.state)
- assert.NotNil(t, manager.systemConn)
-
- manager.Close()
- }
- })
-}
-
-func TestManager_GetState_EmptyState(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{},
- stateMutex: sync.RWMutex{},
- }
-
- result := manager.GetState()
- assert.False(t, result.Accounts.Available)
- assert.Empty(t, result.Accounts.UserName)
- assert.False(t, result.Settings.Available)
- assert.Equal(t, uint32(0), result.Settings.ColorScheme)
-}
-
-func TestManager_AccountsState_Modification(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Accounts: AccountsState{
- Available: true,
- UserName: "testuser",
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- state := manager.GetState()
- state.Accounts.UserName = "modifieduser"
-
- original := manager.GetState()
- assert.Equal(t, "testuser", original.Accounts.UserName)
-}
-
-func TestManager_SettingsState_Modification(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Settings: SettingsState{
- Available: true,
- ColorScheme: 0,
- },
- },
- stateMutex: sync.RWMutex{},
- }
-
- state := manager.GetState()
- state.Settings.ColorScheme = 1
-
- original := manager.GetState()
- assert.Equal(t, uint32(0), original.Settings.ColorScheme)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver.go
deleted file mode 100644
index 095a4c0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver.go
+++ /dev/null
@@ -1,349 +0,0 @@
-package freedesktop
-
-import (
- "fmt"
- "path/filepath"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5/introspect"
-)
-
-type screensaverHandler struct {
- manager *Manager
-}
-
-func screensaverIntrospectIface(ifaceName string) introspect.Interface {
- return introspect.Interface{
- Name: ifaceName,
- Methods: []introspect.Method{
- {
- Name: "Inhibit",
- Args: []introspect.Arg{
- {Name: "application_name", Type: "s", Direction: "in"},
- {Name: "reason_for_inhibit", Type: "s", Direction: "in"},
- {Name: "cookie", Type: "u", Direction: "out"},
- },
- },
- {
- Name: "UnInhibit",
- Args: []introspect.Arg{
- {Name: "cookie", Type: "u", Direction: "in"},
- },
- },
- {
- Name: "GetActive",
- Args: []introspect.Arg{
- {Name: "active", Type: "b", Direction: "out"},
- },
- },
- {
- Name: "SetActive",
- Args: []introspect.Arg{
- {Name: "active", Type: "b", Direction: "in"},
- },
- },
- {
- Name: "Lock",
- },
- },
- Signals: []introspect.Signal{
- {
- Name: "ActiveChanged",
- Args: []introspect.Arg{
- {Name: "new_value", Type: "b"},
- },
- },
- },
- }
-}
-
-func (m *Manager) initializeScreensaver() error {
- if m.sessionConn == nil {
- m.stateMutex.Lock()
- m.state.Screensaver.Available = false
- m.stateMutex.Unlock()
- return nil
- }
-
- handler := &screensaverHandler{manager: m}
-
- m.screensaverFreedesktopClaimed = m.claimScreensaverName(handler,
- dbusScreensaverName, dbusScreensaverInterface, dbusScreensaverPath, dbusScreensaverPath2)
- m.screensaverGnomeClaimed = m.claimScreensaverName(handler,
- dbusGnomeScreensaverName, dbusGnomeScreensaverInterface, dbusGnomeScreensaverPath)
-
- if !m.screensaverFreedesktopClaimed && !m.screensaverGnomeClaimed {
- log.Warn("No screensaver interface could be claimed")
- m.stateMutex.Lock()
- m.state.Screensaver.Available = false
- m.stateMutex.Unlock()
- return nil
- }
-
- go m.watchPeerDisconnects()
-
- m.stateMutex.Lock()
- m.state.Screensaver.Available = true
- m.state.Screensaver.Active = false
- m.state.Screensaver.Inhibited = false
- m.state.Screensaver.Inhibitors = []ScreensaverInhibitor{}
- m.stateMutex.Unlock()
-
- log.Info("Screensaver listener initialized")
- return nil
-}
-
-func (m *Manager) claimScreensaverName(handler *screensaverHandler, name, iface string, paths ...dbus.ObjectPath) bool {
- reply, err := m.sessionConn.RequestName(name, dbus.NameFlagDoNotQueue)
- if err != nil {
- log.Warnf("Failed to request screensaver name %s: %v", name, err)
- return false
- }
- if reply != dbus.RequestNameReplyPrimaryOwner {
- log.Warnf("Screensaver name %s already owned by another process", name)
- return false
- }
- if err := m.exportScreensaverOnPaths(handler, iface, paths...); err != nil {
- log.Warnf("Failed to export screensaver on %s: %v", name, err)
- return false
- }
- log.Infof("Claimed %s on session bus", name)
- return true
-}
-
-// exportScreensaverOnPaths exports the handler and introspection on the given
-// paths under the specified interface name.
-func (m *Manager) exportScreensaverOnPaths(handler *screensaverHandler, ifaceName string, paths ...dbus.ObjectPath) error {
- iface := screensaverIntrospectIface(ifaceName)
- for _, path := range paths {
- if err := m.sessionConn.Export(handler, path, ifaceName); err != nil {
- return fmt.Errorf("export handler on %s: %w", path, err)
- }
- node := &introspect.Node{
- Name: string(path),
- Interfaces: []introspect.Interface{
- introspect.IntrospectData,
- iface,
- },
- }
- if err := m.sessionConn.Export(introspect.NewIntrospectable(node), path, "org.freedesktop.DBus.Introspectable"); err != nil {
- log.Warnf("Failed to export introspectable on %s: %v", path, err)
- }
- }
- return nil
-}
-
-func (h *screensaverHandler) Inhibit(sender dbus.Sender, appName, reason string) (uint32, *dbus.Error) {
- if appName == "" {
- return 0, dbus.NewError("org.freedesktop.DBus.Error.InvalidArgs", []any{"application name required"})
- }
-
- if reason == "" {
- return 0, dbus.NewError("org.freedesktop.DBus.Error.InvalidArgs", []any{"reason required"})
- }
-
- if strings.Contains(strings.ToLower(reason), "audio") && !strings.Contains(strings.ToLower(reason), "video") {
- log.Debugf("Ignoring audio-only inhibit from %s: %s", appName, reason)
- return 0, nil
- }
-
- if idx := strings.LastIndex(appName, "/"); idx != -1 && idx < len(appName)-1 {
- appName = appName[idx+1:]
- }
- appName = filepath.Base(appName)
-
- cookie := atomic.AddUint32(&h.manager.screensaverCookieCounter, 1)
-
- inhibitor := ScreensaverInhibitor{
- Cookie: cookie,
- AppName: appName,
- Reason: reason,
- Peer: string(sender),
- StartTime: time.Now().Unix(),
- }
-
- h.manager.stateMutex.Lock()
- h.manager.state.Screensaver.Inhibitors = append(h.manager.state.Screensaver.Inhibitors, inhibitor)
- h.manager.state.Screensaver.Inhibited = len(h.manager.state.Screensaver.Inhibitors) > 0
- h.manager.stateMutex.Unlock()
-
- log.Infof("Screensaver inhibited by %s (%s): %s -> cookie %08X", appName, sender, reason, cookie)
-
- h.manager.NotifyScreensaverSubscribers()
-
- return cookie, nil
-}
-
-func (h *screensaverHandler) UnInhibit(sender dbus.Sender, cookie uint32) *dbus.Error {
- h.manager.stateMutex.Lock()
- defer h.manager.stateMutex.Unlock()
-
- found := false
- inhibitors := h.manager.state.Screensaver.Inhibitors
- for i, inh := range inhibitors {
- if inh.Cookie != cookie {
- continue
- }
- log.Infof("Screensaver uninhibited by %s (%s) cookie %08X", inh.AppName, sender, cookie)
- h.manager.state.Screensaver.Inhibitors = append(inhibitors[:i], inhibitors[i+1:]...)
- found = true
- break
- }
-
- if !found {
- log.Debugf("UnInhibit: no match for cookie %08X", cookie)
- return nil
- }
-
- h.manager.state.Screensaver.Inhibited = len(h.manager.state.Screensaver.Inhibitors) > 0
-
- go h.manager.NotifyScreensaverSubscribers()
-
- return nil
-}
-
-func (m *Manager) watchPeerDisconnects() {
- if m.sessionConn == nil {
- return
- }
-
- if err := m.sessionConn.AddMatchSignal(
- dbus.WithMatchInterface("org.freedesktop.DBus"),
- dbus.WithMatchMember("NameOwnerChanged"),
- ); err != nil {
- log.Warnf("Failed to watch peer disconnects: %v", err)
- return
- }
-
- signals := make(chan *dbus.Signal, 64)
- m.sessionConn.Signal(signals)
-
- for sig := range signals {
- if sig.Name != "org.freedesktop.DBus.NameOwnerChanged" {
- continue
- }
- if len(sig.Body) < 3 {
- continue
- }
-
- name, ok1 := sig.Body[0].(string)
- newOwner, ok2 := sig.Body[2].(string)
- if !ok1 || !ok2 {
- continue
- }
- if newOwner != "" {
- continue
- }
-
- m.removeInhibitorsByPeer(name)
- }
-}
-
-func (m *Manager) removeInhibitorsByPeer(peer string) {
- m.stateMutex.Lock()
- defer m.stateMutex.Unlock()
-
- var remaining []ScreensaverInhibitor
- var removed []ScreensaverInhibitor
- for _, inh := range m.state.Screensaver.Inhibitors {
- if inh.Peer == peer {
- removed = append(removed, inh)
- continue
- }
- remaining = append(remaining, inh)
- }
-
- if len(removed) == 0 {
- return
- }
-
- for _, inh := range removed {
- log.Infof("Screensaver: peer %s died, removing inhibitor from %s (cookie %08X)", peer, inh.AppName, inh.Cookie)
- }
-
- m.state.Screensaver.Inhibitors = remaining
- m.state.Screensaver.Inhibited = len(remaining) > 0
-
- go m.NotifyScreensaverSubscribers()
-}
-
-func (m *Manager) GetScreensaverState() ScreensaverState {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- return m.state.Screensaver
-}
-
-func (m *Manager) SubscribeScreensaver(id string) chan ScreensaverState {
- ch := make(chan ScreensaverState, 64)
- m.screensaverSubscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) UnsubscribeScreensaver(id string) {
- if val, ok := m.screensaverSubscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) NotifyScreensaverSubscribers() {
- state := m.GetScreensaverState()
- m.screensaverSubscribers.Range(func(key string, ch chan ScreensaverState) bool {
- select {
- case ch <- state:
- default:
- }
- return true
- })
-}
-
-func (h *screensaverHandler) GetActive() (bool, *dbus.Error) {
- h.manager.stateMutex.RLock()
- active := h.manager.state.Screensaver.Active
- h.manager.stateMutex.RUnlock()
- return active, nil
-}
-
-func (h *screensaverHandler) SetActive(active bool) *dbus.Error {
- h.manager.SetScreenLockActive(active)
- return nil
-}
-
-func (h *screensaverHandler) Lock() *dbus.Error {
- h.manager.SetScreenLockActive(true)
- return nil
-}
-
-func (m *Manager) SetScreenLockActive(active bool) {
- m.stateMutex.Lock()
- changed := m.state.Screensaver.Active != active
- m.state.Screensaver.Active = active
- m.stateMutex.Unlock()
-
- if !changed {
- return
- }
-
- log.Infof("Screen lock active changed: %v", active)
- defer m.NotifyScreensaverSubscribers()
-
- if m.sessionConn == nil {
- return
- }
- if m.screensaverFreedesktopClaimed {
- if err := m.sessionConn.Emit(dbusScreensaverPath, dbusScreensaverInterface+".ActiveChanged", active); err != nil {
- log.Warnf("Failed to emit ActiveChanged on %s: %v", dbusScreensaverPath, err)
- }
- if err := m.sessionConn.Emit(dbusScreensaverPath2, dbusScreensaverInterface+".ActiveChanged", active); err != nil {
- log.Warnf("Failed to emit ActiveChanged on %s: %v", dbusScreensaverPath2, err)
- }
- }
- if m.screensaverGnomeClaimed {
- if err := m.sessionConn.Emit(dbusGnomeScreensaverPath, dbusGnomeScreensaverInterface+".ActiveChanged", active); err != nil {
- log.Warnf("Failed to emit ActiveChanged on %s: %v", dbusGnomeScreensaverPath, err)
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver_test.go
deleted file mode 100644
index 324c17e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/screensaver_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package freedesktop
-
-import (
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestSetScreenLockActive_ChangesState(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Screensaver: ScreensaverState{Available: true},
- },
- stateMutex: sync.RWMutex{},
- }
-
- assert.False(t, manager.GetScreensaverState().Active)
-
- manager.SetScreenLockActive(true)
- assert.True(t, manager.GetScreensaverState().Active)
-
- manager.SetScreenLockActive(false)
- assert.False(t, manager.GetScreensaverState().Active)
-}
-
-func TestSetScreenLockActive_NoChangeNoDuplicate(t *testing.T) {
- ch := make(chan ScreensaverState, 64)
- manager := &Manager{
- state: &FreedeskState{
- Screensaver: ScreensaverState{Available: true, Active: false},
- },
- stateMutex: sync.RWMutex{},
- }
- manager.screensaverSubscribers.Store("test", ch)
- defer manager.screensaverSubscribers.Delete("test")
-
- // Setting to same value should not notify
- manager.SetScreenLockActive(false)
-
- select {
- case <-ch:
- t.Fatal("should not have received notification for no-change")
- case <-time.After(50 * time.Millisecond):
- // Expected: no notification
- }
-}
-
-func TestSetScreenLockActive_NotifiesSubscribers(t *testing.T) {
- ch := make(chan ScreensaverState, 64)
- manager := &Manager{
- state: &FreedeskState{
- Screensaver: ScreensaverState{Available: true, Active: false},
- },
- stateMutex: sync.RWMutex{},
- }
- manager.screensaverSubscribers.Store("test", ch)
- defer manager.screensaverSubscribers.Delete("test")
-
- manager.SetScreenLockActive(true)
-
- select {
- case state := <-ch:
- assert.True(t, state.Active)
- case <-time.After(time.Second):
- t.Fatal("timeout waiting for subscriber notification")
- }
-}
-
-func TestSetScreenLockActive_NilSessionConn(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Screensaver: ScreensaverState{Available: true},
- },
- stateMutex: sync.RWMutex{},
- }
-
- assert.NotPanics(t, func() {
- manager.SetScreenLockActive(true)
- })
- assert.True(t, manager.GetScreensaverState().Active)
-}
-
-func TestGetActive_ReturnsCurrentState(t *testing.T) {
- manager := &Manager{
- state: &FreedeskState{
- Screensaver: ScreensaverState{Available: true, Active: true},
- },
- stateMutex: sync.RWMutex{},
- }
-
- handler := &screensaverHandler{manager: manager}
- active, dbusErr := handler.GetActive()
- assert.Nil(t, dbusErr)
- assert.True(t, active)
-}
-
-func TestScreensaverState_ActiveDefaultsFalse(t *testing.T) {
- state := ScreensaverState{}
- assert.False(t, state.Active)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types.go
deleted file mode 100644
index 0a9517a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package freedesktop
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/godbus/dbus/v5"
-)
-
-type AccountsState struct {
- Available bool `json:"available"`
- UserPath string `json:"userPath"`
- IconFile string `json:"iconFile"`
- RealName string `json:"realName"`
- UserName string `json:"userName"`
- AccountType int32 `json:"accountType"`
- HomeDirectory string `json:"homeDirectory"`
- Shell string `json:"shell"`
- Email string `json:"email"`
- Language string `json:"language"`
- Location string `json:"location"`
- Locked bool `json:"locked"`
- PasswordMode int32 `json:"passwordMode"`
- UID uint64 `json:"uid"`
-}
-
-type SettingsState struct {
- Available bool `json:"available"`
- ColorScheme uint32 `json:"colorScheme"`
-}
-
-type ScreensaverInhibitor struct {
- Cookie uint32 `json:"cookie"`
- AppName string `json:"appName"`
- Reason string `json:"reason"`
- Peer string `json:"peer"`
- StartTime int64 `json:"startTime"`
-}
-
-type ScreensaverState struct {
- Available bool `json:"available"`
- Active bool `json:"active"`
- Inhibited bool `json:"inhibited"`
- Inhibitors []ScreensaverInhibitor `json:"inhibitors"`
-}
-
-type FreedeskState struct {
- Accounts AccountsState `json:"accounts"`
- Settings SettingsState `json:"settings"`
- Screensaver ScreensaverState `json:"screensaver"`
-}
-
-type Manager struct {
- state *FreedeskState
- stateMutex sync.RWMutex
- systemConn *dbus.Conn
- sessionConn *dbus.Conn
- accountsObj dbus.BusObject
- settingsObj dbus.BusObject
- currentUID uint64
- subscribers syncmap.Map[string, chan FreedeskState]
- screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
- screensaverCookieCounter uint32
- screensaverFreedesktopClaimed bool
- screensaverGnomeClaimed bool
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types_test.go
deleted file mode 100644
index deb0327..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/freedesktop/types_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package freedesktop
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestAccountsState_Struct(t *testing.T) {
- state := AccountsState{
- Available: true,
- UserPath: "/org/freedesktop/Accounts/User1000",
- RealName: "Test User",
- UserName: "testuser",
- Locked: false,
- UID: 1000,
- }
-
- assert.True(t, state.Available)
- assert.Equal(t, "/org/freedesktop/Accounts/User1000", state.UserPath)
- assert.Equal(t, "Test User", state.RealName)
- assert.Equal(t, "testuser", state.UserName)
- assert.Equal(t, uint64(1000), state.UID)
- assert.False(t, state.Locked)
-}
-
-func TestSettingsState_Struct(t *testing.T) {
- state := SettingsState{
- Available: true,
- ColorScheme: 1, // Dark mode
- }
-
- assert.True(t, state.Available)
- assert.Equal(t, uint32(1), state.ColorScheme)
-}
-
-func TestFreedeskState_Struct(t *testing.T) {
- state := FreedeskState{
- Accounts: AccountsState{
- Available: true,
- UserName: "testuser",
- UID: 1000,
- },
- Settings: SettingsState{
- Available: true,
- ColorScheme: 0, // Light mode
- },
- }
-
- assert.True(t, state.Accounts.Available)
- assert.Equal(t, "testuser", state.Accounts.UserName)
- assert.True(t, state.Settings.Available)
- assert.Equal(t, uint32(0), state.Settings.ColorScheme)
-}
-
-func TestAccountsState_DefaultValues(t *testing.T) {
- state := AccountsState{}
-
- assert.False(t, state.Available)
- assert.Empty(t, state.UserPath)
- assert.Empty(t, state.UserName)
- assert.Equal(t, uint64(0), state.UID)
-}
-
-func TestSettingsState_DefaultValues(t *testing.T) {
- state := SettingsState{}
-
- assert.False(t, state.Available)
- assert.Equal(t, uint32(0), state.ColorScheme)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/handlers.go
deleted file mode 100644
index c9d556c..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/handlers.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package location
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-type LocationEvent struct {
- Type string `json:"type"`
- Data State `json:"data"`
-}
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "location.getState":
- handleGetState(conn, req, manager)
- case "location.subscribe":
- handleSubscribe(conn, req, manager)
-
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- event := LocationEvent{
- Type: "state_changed",
- Data: initialState,
- }
-
- if err := json.NewEncoder(conn).Encode(models.Response[LocationEvent]{
- ID: req.ID,
- Result: &event,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- event := LocationEvent{
- Type: "state_changed",
- Data: state,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[LocationEvent]{
- Result: &event,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/manager.go
deleted file mode 100644
index 24bcd95..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/manager.go
+++ /dev/null
@@ -1,175 +0,0 @@
-package location
-
-import (
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-func NewManager(client geolocation.Client) (*Manager, error) {
- currLocation, err := client.GetLocation()
- if err != nil {
- log.Warnf("Failed to get initial location: %v", err)
- }
-
- m := &Manager{
- client: client,
- dirty: make(chan struct{}),
- stopChan: make(chan struct{}),
-
- state: &State{
- Latitude: currLocation.Latitude,
- Longitude: currLocation.Longitude,
- },
- }
-
- if err := m.startSignalPump(); err != nil {
- return nil, err
- }
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- return m, nil
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.notifierWg.Wait()
-
- m.sigWG.Wait()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if ch, ok := m.subscribers.LoadAndDelete(id); ok {
- close(ch)
- }
-}
-
-func (m *Manager) startSignalPump() error {
- m.sigWG.Add(1)
- go func() {
- defer m.sigWG.Done()
-
- subscription := m.client.Subscribe("locationManager")
- defer m.client.Unsubscribe("locationManager")
-
- for {
- select {
- case <-m.stopChan:
- return
- case location, ok := <-subscription:
- if !ok {
- return
- }
-
- m.handleLocationChange(location)
- }
- }
- }()
-
- return nil
-}
-
-func (m *Manager) handleLocationChange(location geolocation.Location) {
- m.stateMutex.Lock()
- defer m.stateMutex.Unlock()
-
- m.state.Latitude = location.Latitude
- m.state.Longitude = location.Longitude
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{
- Latitude: 0.0,
- Longitude: 0.0,
- }
- }
- stateCopy := *m.state
- return stateCopy
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 200 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.GetState()
-
- if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- currentState:
- default:
- log.Warn("Location: subscriber channel full, dropping update")
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotified = &stateCopy
- pending = false
- }
- }
-}
-
-func stateChanged(old, new *State) bool {
- if old == nil || new == nil {
- return true
- }
- if old.Latitude != new.Latitude {
- return true
- }
- if old.Longitude != new.Longitude {
- return true
- }
-
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/types.go
deleted file mode 100644
index 1f3d518..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/location/types.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package location
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type State struct {
- Latitude float64 `json:"latitude"`
- Longitude float64 `json:"longitude"`
-}
-
-type Manager struct {
- state *State
- stateMutex sync.RWMutex
-
- client geolocation.Client
-
- stopChan chan struct{}
- sigWG sync.WaitGroup
-
- subscribers syncmap.Map[string, chan State]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotified *State
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/actions.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/actions.go
deleted file mode 100644
index 858248e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/actions.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package loginctl
-
-import (
- "fmt"
-)
-
-func (m *Manager) Lock() error {
- if m.sessionObj == nil {
- return fmt.Errorf("session object not available")
- }
- err := m.sessionObj.Call(dbusSessionInterface+".Lock", 0).Err
- if err != nil {
- if refreshErr := m.refreshSessionBinding(); refreshErr == nil {
- err = m.sessionObj.Call(dbusSessionInterface+".Lock", 0).Err
- }
- if err != nil {
- return fmt.Errorf("failed to lock session: %w", err)
- }
- }
- return nil
-}
-
-func (m *Manager) Unlock() error {
- err := m.sessionObj.Call(dbusSessionInterface+".Unlock", 0).Err
- if err != nil {
- if refreshErr := m.refreshSessionBinding(); refreshErr == nil {
- err = m.sessionObj.Call(dbusSessionInterface+".Unlock", 0).Err
- }
- if err != nil {
- return fmt.Errorf("failed to unlock session: %w", err)
- }
- }
- return nil
-}
-
-func (m *Manager) Activate() error {
- err := m.sessionObj.Call(dbusSessionInterface+".Activate", 0).Err
- if err != nil {
- if refreshErr := m.refreshSessionBinding(); refreshErr == nil {
- err = m.sessionObj.Call(dbusSessionInterface+".Activate", 0).Err
- }
- if err != nil {
- return fmt.Errorf("failed to activate session: %w", err)
- }
- }
- return nil
-}
-
-func (m *Manager) SetIdleHint(idle bool) error {
- err := m.sessionObj.Call(dbusSessionInterface+".SetIdleHint", 0, idle).Err
- if err != nil {
- if refreshErr := m.refreshSessionBinding(); refreshErr == nil {
- err = m.sessionObj.Call(dbusSessionInterface+".SetIdleHint", 0, idle).Err
- }
- if err != nil {
- return fmt.Errorf("failed to set idle hint: %w", err)
- }
- }
- return nil
-}
-
-func (m *Manager) Terminate() error {
- err := m.sessionObj.Call(dbusSessionInterface+".Terminate", 0).Err
- if err != nil {
- if refreshErr := m.refreshSessionBinding(); refreshErr == nil {
- err = m.sessionObj.Call(dbusSessionInterface+".Terminate", 0).Err
- }
- if err != nil {
- return fmt.Errorf("failed to terminate session: %w", err)
- }
- }
- return nil
-}
-
-func (m *Manager) SetLockBeforeSuspend(enabled bool) {
- m.lockBeforeSuspend.Store(enabled)
-}
-
-func (m *Manager) SetSleepInhibitorEnabled(enabled bool) {
- m.sleepInhibitorEnabled.Store(enabled)
- if enabled {
- // Re-acquire inhibitor if enabled
- m.acquireSleepInhibitor()
- } else {
- // Release inhibitor if disabled
- m.releaseSleepInhibitor()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/constants.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/constants.go
deleted file mode 100644
index 1a29588..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/constants.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package loginctl
-
-const (
- dbusDest = "org.freedesktop.login1"
- dbusPath = "/org/freedesktop/login1"
- dbusManagerInterface = "org.freedesktop.login1.Manager"
- dbusSessionInterface = "org.freedesktop.login1.Session"
- dbusUserInterface = "org.freedesktop.login1.User"
- dbusPropsInterface = "org.freedesktop.DBus.Properties"
-)
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers.go
deleted file mode 100644
index 564e996..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package loginctl
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "loginctl.getState":
- handleGetState(conn, req, manager)
- case "loginctl.lock":
- handleLock(conn, req, manager)
- case "loginctl.unlock":
- handleUnlock(conn, req, manager)
- case "loginctl.activate":
- handleActivate(conn, req, manager)
- case "loginctl.setIdleHint":
- handleSetIdleHint(conn, req, manager)
- case "loginctl.setLockBeforeSuspend":
- handleSetLockBeforeSuspend(conn, req, manager)
- case "loginctl.setSleepInhibitorEnabled":
- handleSetSleepInhibitorEnabled(conn, req, manager)
- case "loginctl.lockerReady":
- handleLockerReady(conn, req, manager)
- case "loginctl.terminate":
- handleTerminate(conn, req, manager)
- case "loginctl.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleLock(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.Lock(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "locked"})
-}
-
-func handleUnlock(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.Unlock(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "unlocked"})
-}
-
-func handleActivate(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.Activate(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "activated"})
-}
-
-func handleSetIdleHint(conn net.Conn, req models.Request, manager *Manager) {
- idle, err := params.Bool(req.Params, "idle")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetIdleHint(idle); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "idle hint set"})
-}
-
-func handleSetLockBeforeSuspend(conn net.Conn, req models.Request, manager *Manager) {
- enabled, err := params.Bool(req.Params, "enabled")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetLockBeforeSuspend(enabled)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "lock before suspend set"})
-}
-
-func handleSetSleepInhibitorEnabled(conn net.Conn, req models.Request, manager *Manager) {
- enabled, err := params.Bool(req.Params, "enabled")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetSleepInhibitorEnabled(enabled)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sleep inhibitor setting updated"})
-}
-
-func handleLockerReady(conn net.Conn, req models.Request, manager *Manager) {
- manager.lockTimerMu.Lock()
- if manager.lockTimer != nil {
- manager.lockTimer.Stop()
- manager.lockTimer = nil
- }
- manager.lockTimerMu.Unlock()
-
- id := manager.sleepCycleID.Load()
- manager.releaseForCycle(id)
-
- if manager.inSleepCycle.Load() {
- manager.signalLockerReady()
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "ok"})
-}
-
-func handleTerminate(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.Terminate(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "terminated"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- event := SessionEvent{
- Type: EventStateChanged,
- Data: initialState,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[SessionEvent]{
- ID: req.ID,
- Result: &event,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- event := SessionEvent{
- Type: EventStateChanged,
- Data: state,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[SessionEvent]{
- Result: &event,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers_test.go
deleted file mode 100644
index d3169e4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/handlers_test.go
+++ /dev/null
@@ -1,500 +0,0 @@
-package loginctl
-
-import (
- "bytes"
- "encoding/json"
- "net"
- "sync"
- "testing"
- "time"
-
- mockdbus "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/godbus/dbus/v5"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-)
-
-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 TestRespondError_Loginctl(t *testing.T) {
- conn := newMockNetConn()
- models.RespondError(conn, 123, "test error")
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Equal(t, "test error", resp.Error)
- assert.Nil(t, resp.Result)
-}
-
-func TestRespond_Loginctl(t *testing.T) {
- conn := newMockNetConn()
- result := models.SuccessResult{Success: true, Message: "test"}
- models.Respond(conn, 123, result)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "test", resp.Result.Message)
-}
-
-func TestHandleGetState(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- Active: true,
- SessionType: "wayland",
- SessionClass: "user",
- UserName: "testuser",
- },
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.getState"}
-
- handleGetState(conn, req, manager)
-
- var resp models.Response[SessionState]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.Equal(t, "1", resp.Result.SessionID)
- assert.False(t, resp.Result.Locked)
- assert.True(t, resp.Result.Active)
-}
-
-func TestHandleLock(t *testing.T) {
- t.Run("successful lock", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Lock", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.lock"}
- handleLock(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "locked", resp.Result.Message)
- })
-
- t.Run("lock fails", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: assert.AnError}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Lock", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.lock"}
- handleLock(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "failed to lock session")
- })
-}
-
-func TestHandleUnlock(t *testing.T) {
- t.Run("successful unlock", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Unlock", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.unlock"}
- handleUnlock(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "unlocked", resp.Result.Message)
- })
-
- t.Run("unlock fails", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: assert.AnError}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Unlock", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.unlock"}
- handleUnlock(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "failed to unlock session")
- })
-}
-
-func TestHandleActivate(t *testing.T) {
- t.Run("successful activate", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Activate", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.activate"}
- handleActivate(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "activated", resp.Result.Message)
- })
-
- t.Run("activate fails", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: assert.AnError}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Activate", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.activate"}
- handleActivate(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "failed to activate session")
- })
-}
-
-func TestHandleSetIdleHint(t *testing.T) {
- t.Run("missing idle parameter", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "loginctl.setIdleHint",
- Params: map[string]any{},
- }
-
- handleSetIdleHint(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'idle' parameter")
- })
-
- t.Run("successful set idle hint true", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.SetIdleHint", dbus.Flags(0), true).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "loginctl.setIdleHint",
- Params: map[string]any{
- "idle": true,
- },
- }
-
- handleSetIdleHint(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "idle hint set", resp.Result.Message)
- })
-
- t.Run("set idle hint fails", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: assert.AnError}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.SetIdleHint", dbus.Flags(0), false).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "loginctl.setIdleHint",
- Params: map[string]any{
- "idle": false,
- },
- }
-
- handleSetIdleHint(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "failed to set idle hint")
- })
-}
-
-func TestHandleTerminate(t *testing.T) {
- t.Run("successful terminate", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Terminate", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.terminate"}
- handleTerminate(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "terminated", resp.Result.Message)
- })
-
- t.Run("terminate fails", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: assert.AnError}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Terminate", dbus.Flags(0)).Return(mockCall)
-
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- sessionObj: mockSessionObj,
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.terminate"}
- handleTerminate(conn, req, manager)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "failed to terminate session")
- })
-}
-
-func TestHandleRequest(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- },
- stateMutex: sync.RWMutex{},
- }
-
- t.Run("unknown method", func(t *testing.T) {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "loginctl.unknown",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "unknown method")
- })
-
- t.Run("valid method - getState", func(t *testing.T) {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "loginctl.getState",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[SessionState]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- })
-
- t.Run("lock method", func(t *testing.T) {
- mockSessionObj := mockdbus.NewMockBusObject(t)
- mockCall := &dbus.Call{Err: nil}
- mockSessionObj.EXPECT().Call("org.freedesktop.login1.Session.Lock", mock.Anything).Return(mockCall)
-
- manager.sessionObj = mockSessionObj
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "loginctl.lock",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- })
-}
-
-func TestHandleSubscribe(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- },
- stateMutex: sync.RWMutex{},
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "loginctl.subscribe"}
-
- done := make(chan bool)
- go func() {
- handleSubscribe(conn, req, manager)
- done <- true
- }()
-
- time.Sleep(50 * time.Millisecond)
-
- conn.Close()
-
- if conn.writeBuf.Len() > 0 {
- var resp models.Response[SessionEvent]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- if err == nil {
- assert.Equal(t, 123, resp.ID)
- require.NotNil(t, resp.Result)
- assert.Equal(t, EventStateChanged, resp.Result.Type)
- assert.Equal(t, "1", resp.Result.Data.SessionID)
- }
- }
-
- select {
- case <-done:
- case <-time.After(100 * time.Millisecond):
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager.go
deleted file mode 100644
index 0c51e64..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager.go
+++ /dev/null
@@ -1,723 +0,0 @@
-package loginctl
-
-import (
- "context"
- "fmt"
- "os"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
- "github.com/godbus/dbus/v5"
-)
-
-func NewManager() (*Manager, error) {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to system bus: %w", err)
- }
-
- m := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
-
- stopChan: make(chan struct{}),
- conn: conn,
- dirty: make(chan struct{}, 1),
- signals: make(chan *dbus.Signal, 256),
- }
- m.sleepInhibitorEnabled.Store(true)
-
- if err := m.initialize(); err != nil {
- conn.Close()
- return nil, err
- }
-
- if err := m.acquireSleepInhibitor(); err != nil {
- fmt.Fprintf(os.Stderr, "sleep inhibitor unavailable: %v\n", err)
- }
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- if err := m.startSignalPump(); err != nil {
- m.Close()
- return nil, err
- }
-
- return m, nil
-}
-
-func (m *Manager) initialize() error {
- m.managerObj = m.conn.Object(dbusDest, dbus.ObjectPath(dbusPath))
-
- m.initializeFallbackDelay()
-
- sessionID, sessionPath, err := m.discoverSession()
- if err != nil {
- return fmt.Errorf("failed to get session path: %w", err)
- }
-
- m.stateMutex.Lock()
- m.state.SessionID = sessionID
- m.state.SessionPath = string(sessionPath)
- m.sessionPath = sessionPath
- m.stateMutex.Unlock()
-
- m.sessionObj = m.conn.Object(dbusDest, sessionPath)
-
- if err := m.updateSessionState(); err != nil {
- return err
- }
-
- return nil
-}
-
-func (m *Manager) discoverSession() (string, dbus.ObjectPath, error) {
- // 1. Explicit XDG_SESSION_ID
- if id := os.Getenv("XDG_SESSION_ID"); id != "" {
- if path, err := m.getSession(id); err == nil {
- fmt.Fprintf(os.Stderr, "loginctl: using XDG_SESSION_ID=%s\n", id)
- return id, path, nil
- }
- }
-
- // 2. PID-based lookup (works when caller is inside a session cgroup)
- if id, path, err := m.getSessionByPID(uint32(os.Getpid())); err == nil {
- fmt.Fprintf(os.Stderr, "loginctl: found session %s via PID\n", id)
- return id, path, nil
- }
-
- // 3. User's primary display session (handles UWSM and similar)
- if id, path, err := m.getUserDisplaySession(); err == nil {
- fmt.Fprintf(os.Stderr, "loginctl: found session %s via User.Display\n", id)
- return id, path, nil
- }
-
- // 4. Score all sessions for current UID
- if id, path, err := m.findBestSession(); err == nil {
- fmt.Fprintf(os.Stderr, "loginctl: found session %s via ListSessions scoring\n", id)
- return id, path, nil
- }
-
- // 5. Last resort: "self"
- path, err := m.getSession("self")
- if err != nil {
- return "", "", fmt.Errorf("%w", err)
- }
- return "self", path, nil
-}
-
-func (m *Manager) getSession(id string) (dbus.ObjectPath, error) {
- var out dbus.ObjectPath
- err := m.managerObj.Call(dbusManagerInterface+".GetSession", 0, id).Store(&out)
- if err != nil {
- return "", err
- }
- return out, nil
-}
-
-func (m *Manager) getSessionByPID(pid uint32) (string, dbus.ObjectPath, error) {
- var path dbus.ObjectPath
- if err := m.managerObj.Call(dbusManagerInterface+".GetSessionByPID", 0, pid).Store(&path); err != nil {
- return "", "", err
- }
-
- sessionObj := m.conn.Object(dbusDest, path)
- var id dbus.Variant
- if err := sessionObj.Call(dbusPropsInterface+".Get", 0, dbusSessionInterface, "Id").Store(&id); err != nil {
- return "", "", err
- }
- return id.Value().(string), path, nil
-}
-
-func (m *Manager) getUserDisplaySession() (string, dbus.ObjectPath, error) {
- uid := uint32(os.Getuid())
-
- var userPath dbus.ObjectPath
- if err := m.managerObj.Call(dbusManagerInterface+".GetUser", 0, uid).Store(&userPath); err != nil {
- return "", "", err
- }
-
- userObj := m.conn.Object(dbusDest, userPath)
- var display dbus.Variant
- if err := userObj.Call(dbusPropsInterface+".Get", 0, dbusUserInterface, "Display").Store(&display); err != nil {
- return "", "", err
- }
-
- pair, ok := display.Value().([]any)
- if !ok || len(pair) < 2 {
- return "", "", fmt.Errorf("unexpected Display format")
- }
-
- sessionID, _ := pair[0].(string)
- sessionPath, _ := pair[1].(dbus.ObjectPath)
- if sessionID == "" || sessionPath == "" {
- return "", "", fmt.Errorf("empty Display session")
- }
-
- return sessionID, sessionPath, nil
-}
-
-type sessionCandidate struct {
- id string
- path dbus.ObjectPath
-}
-
-func (m *Manager) findBestSession() (string, dbus.ObjectPath, error) {
- // ListSessions returns a(susso): [][]any where each entry is [id, uid, name, seat, path]
- var raw [][]any
- if err := m.managerObj.Call(dbusManagerInterface+".ListSessions", 0).Store(&raw); err != nil {
- return "", "", err
- }
-
- uid := uint32(os.Getuid())
- var candidates []sessionCandidate
- for _, entry := range raw {
- if len(entry) < 5 {
- continue
- }
- entryUID, _ := entry[1].(uint32)
- if entryUID != uid {
- continue
- }
- id, _ := entry[0].(string)
- path, _ := entry[4].(dbus.ObjectPath)
- if id != "" && path != "" {
- candidates = append(candidates, sessionCandidate{id: id, path: path})
- }
- }
- if len(candidates) == 0 {
- return "", "", fmt.Errorf("no sessions for uid %d", uid)
- }
-
- bestScore := -1
- var best sessionCandidate
- for _, c := range candidates {
- score := m.scoreSession(c.path)
- if score > bestScore {
- bestScore = score
- best = c
- }
- }
- if bestScore < 0 {
- return "", "", fmt.Errorf("no viable session found")
- }
- return best.id, best.path, nil
-}
-
-func (m *Manager) scoreSession(path dbus.ObjectPath) int {
- obj := m.conn.Object(dbusDest, path)
- var props map[string]dbus.Variant
- if err := obj.Call(dbusPropsInterface+".GetAll", 0, dbusSessionInterface).Store(&props); err != nil {
- return -1
- }
-
- getStr := func(key string) string {
- if v, ok := props[key]; ok {
- if s, ok := v.Value().(string); ok {
- return s
- }
- }
- return ""
- }
- getBool := func(key string) bool {
- if v, ok := props[key]; ok {
- if b, ok := v.Value().(bool); ok {
- return b
- }
- }
- return false
- }
- getUint32 := func(key string) uint32 {
- if v, ok := props[key]; ok {
- if u, ok := v.Value().(uint32); ok {
- return u
- }
- }
- return 0
- }
-
- class := getStr("Class")
- if class != "user" {
- return -1
- }
- if getBool("Remote") {
- return -1
- }
-
- score := 0
-
- if getBool("Active") {
- score += 100
- }
-
- switch getStr("Type") {
- case "wayland", "x11":
- score += 80
- case "tty":
- score += 10
- }
-
- if v, ok := props["Seat"]; ok {
- if seatArr, ok := v.Value().([]any); ok && len(seatArr) >= 1 {
- if seat, ok := seatArr[0].(string); ok && seat != "" {
- score += 40
- if seat == "seat0" {
- score += 10
- }
- }
- }
- }
-
- if getUint32("VTNr") > 0 {
- score += 20
- }
-
- return score
-}
-
-func (m *Manager) refreshSessionBinding() error {
- if m.managerObj == nil || m.conn == nil {
- return fmt.Errorf("manager not fully initialized")
- }
-
- sessionPath, err := m.getSession(m.state.SessionID)
- if err != nil {
- return fmt.Errorf("failed to get session path: %w", err)
- }
-
- m.stateMutex.RLock()
- currentPath := m.sessionPath
- m.stateMutex.RUnlock()
-
- if sessionPath == currentPath {
- return nil
- }
-
- m.stopSignalPump()
-
- m.stateMutex.Lock()
- m.state.SessionPath = string(sessionPath)
- m.sessionPath = sessionPath
- m.stateMutex.Unlock()
-
- m.sessionObj = m.conn.Object(dbusDest, sessionPath)
-
- if err := m.updateSessionState(); err != nil {
- return err
- }
-
- m.signals = make(chan *dbus.Signal, 256)
- return m.startSignalPump()
-}
-
-func (m *Manager) updateSessionState() error {
- ctx := context.Background()
- props, err := m.getSessionProperties(ctx)
- if err != nil {
- return err
- }
-
- m.stateMutex.Lock()
- defer m.stateMutex.Unlock()
-
- m.state.Active = dbusutil.GetOr(props, "Active", m.state.Active)
- m.state.IdleHint = dbusutil.GetOr(props, "IdleHint", m.state.IdleHint)
- m.state.IdleSinceHint = dbusutil.GetOr(props, "IdleSinceHint", m.state.IdleSinceHint)
- if lockedHint, ok := dbusutil.Get[bool](props, "LockedHint"); ok {
- m.state.LockedHint = lockedHint
- m.state.Locked = lockedHint
- }
- m.state.SessionType = dbusutil.GetOr(props, "Type", m.state.SessionType)
- m.state.SessionClass = dbusutil.GetOr(props, "Class", m.state.SessionClass)
- if v, ok := props["User"]; ok {
- if userArr, ok := v.Value().([]any); ok && len(userArr) >= 1 {
- if uid, ok := userArr[0].(uint32); ok {
- m.state.User = uid
- }
- }
- }
- m.state.UserName = dbusutil.GetOr(props, "Name", m.state.UserName)
- m.state.RemoteHost = dbusutil.GetOr(props, "RemoteHost", m.state.RemoteHost)
- m.state.Service = dbusutil.GetOr(props, "Service", m.state.Service)
- m.state.TTY = dbusutil.GetOr(props, "TTY", m.state.TTY)
- m.state.Display = dbusutil.GetOr(props, "Display", m.state.Display)
- m.state.Remote = dbusutil.GetOr(props, "Remote", m.state.Remote)
- if v, ok := props["Seat"]; ok {
- if seatArr, ok := v.Value().([]any); ok && len(seatArr) >= 1 {
- if seatID, ok := seatArr[0].(string); ok {
- m.state.Seat = seatID
- }
- }
- }
- m.state.VTNr = dbusutil.GetOr(props, "VTNr", m.state.VTNr)
-
- return nil
-}
-
-func (m *Manager) getSessionProperties(ctx context.Context) (map[string]dbus.Variant, error) {
- var props map[string]dbus.Variant
- err := m.sessionObj.CallWithContext(ctx, dbusPropsInterface+".GetAll", 0, dbusSessionInterface).Store(&props)
- if err != nil {
- return nil, err
- }
- return props, nil
-}
-
-func (m *Manager) acquireSleepInhibitor() error {
- if !m.sleepInhibitorEnabled.Load() {
- return nil
- }
-
- m.inhibitMu.Lock()
- defer m.inhibitMu.Unlock()
-
- if m.inhibitFile != nil {
- return nil
- }
-
- if m.managerObj == nil {
- return fmt.Errorf("manager object not available")
- }
-
- file, err := m.inhibit("sleep", "DankMaterialShell", "Lock before suspend", "delay")
- if err != nil {
- return err
- }
-
- m.inhibitFile = file
- return nil
-}
-
-func (m *Manager) inhibit(what, who, why, mode string) (*os.File, error) {
- var fd dbus.UnixFD
- err := m.managerObj.Call(dbusManagerInterface+".Inhibit", 0, what, who, why, mode).Store(&fd)
- if err != nil {
- return nil, err
- }
- return os.NewFile(uintptr(fd), "inhibit"), nil
-}
-
-func (m *Manager) releaseSleepInhibitor() {
- m.inhibitMu.Lock()
- f := m.inhibitFile
- m.inhibitFile = nil
- m.inhibitMu.Unlock()
- if f != nil {
- f.Close()
- }
-}
-
-func (m *Manager) releaseForCycle(id uint64) {
- if !m.inSleepCycle.Load() || m.sleepCycleID.Load() != id {
- return
- }
- m.releaseSleepInhibitor()
-}
-
-func (m *Manager) initializeFallbackDelay() {
- var maxDelayUSec uint64
- err := m.managerObj.Call(
- dbusPropsInterface+".Get",
- 0,
- dbusManagerInterface,
- "InhibitDelayMaxUSec",
- ).Store(&maxDelayUSec)
-
- if err != nil {
- m.fallbackDelay = 2 * time.Second
- return
- }
-
- maxDelay := time.Duration(maxDelayUSec) * time.Microsecond
- computed := (maxDelay * 8) / 10
-
- if computed < 2*time.Second {
- m.fallbackDelay = 2 * time.Second
- } else if computed > 4*time.Second {
- m.fallbackDelay = 4 * time.Second
- } else {
- m.fallbackDelay = computed
- }
-}
-
-func (m *Manager) newLockerReadyCh() chan struct{} {
- m.lockerReadyChMu.Lock()
- defer m.lockerReadyChMu.Unlock()
- m.lockerReadyCh = make(chan struct{})
- return m.lockerReadyCh
-}
-
-func (m *Manager) signalLockerReady() {
- m.lockerReadyChMu.Lock()
- ch := m.lockerReadyCh
- if ch != nil {
- close(ch)
- m.lockerReadyCh = nil
- }
- m.lockerReadyChMu.Unlock()
-}
-
-func (m *Manager) snapshotState() SessionState {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- return *m.state
-}
-
-func stateChangedMeaningfully(old, new *SessionState) bool {
- if old.Locked != new.Locked {
- return true
- }
- if old.LockedHint != new.LockedHint {
- return true
- }
- if old.Active != new.Active {
- return true
- }
- if old.IdleHint != new.IdleHint {
- return true
- }
- if old.PreparingForSleep != new.PreparingForSleep {
- return true
- }
- return false
-}
-
-func (m *Manager) GetState() SessionState {
- return m.snapshotState()
-}
-
-func (m *Manager) Subscribe(id string) chan SessionState {
- ch := make(chan SessionState, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.snapshotState()
-
- if m.lastNotifiedState != nil && !stateChangedMeaningfully(m.lastNotifiedState, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan SessionState) bool {
- select {
- case ch <- currentState:
- default:
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotifiedState = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) startSignalPump() error {
- m.conn.Signal(m.signals)
-
- if err := m.conn.AddMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- ); err != nil {
- m.conn.RemoveSignal(m.signals)
- return err
- }
- if err := m.conn.AddMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Lock"),
- ); err != nil {
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- m.conn.RemoveSignal(m.signals)
- return err
- }
- if err := m.conn.AddMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Unlock"),
- ); err != nil {
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Lock"),
- )
- m.conn.RemoveSignal(m.signals)
- return err
- }
- if err := m.conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusPath)),
- dbus.WithMatchInterface(dbusManagerInterface),
- dbus.WithMatchMember("PrepareForSleep"),
- ); err != nil {
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Lock"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Unlock"),
- )
- m.conn.RemoveSignal(m.signals)
- return err
- }
-
- if err := m.conn.AddMatchSignal(
- dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
- dbus.WithMatchInterface("org.freedesktop.DBus"),
- dbus.WithMatchMember("NameOwnerChanged"),
- ); err != nil {
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Lock"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Unlock"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusPath)),
- dbus.WithMatchInterface(dbusManagerInterface),
- dbus.WithMatchMember("PrepareForSleep"),
- )
- m.conn.RemoveSignal(m.signals)
- return err
- }
-
- m.sigWG.Add(1)
- go func() {
- defer m.sigWG.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case sig, ok := <-m.signals:
- if !ok {
- return
- }
- if sig == nil {
- continue
- }
- m.handleDBusSignal(sig)
- }
- }
- }()
- return nil
-}
-
-func (m *Manager) stopSignalPump() {
- if m.conn == nil {
- return
- }
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Lock"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(m.sessionPath),
- dbus.WithMatchInterface(dbusSessionInterface),
- dbus.WithMatchMember("Unlock"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusPath)),
- dbus.WithMatchInterface(dbusManagerInterface),
- dbus.WithMatchMember("PrepareForSleep"),
- )
- m.conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
- dbus.WithMatchInterface("org.freedesktop.DBus"),
- dbus.WithMatchMember("NameOwnerChanged"),
- )
-
- m.conn.RemoveSignal(m.signals)
- close(m.signals)
-
- m.sigWG.Wait()
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.notifierWg.Wait()
-
- m.stopSignalPump()
-
- m.releaseSleepInhibitor()
-
- m.subscribers.Range(func(key string, ch chan SessionState) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- if m.conn != nil {
- m.conn.Close()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager_test.go
deleted file mode 100644
index 148ee60..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/manager_test.go
+++ /dev/null
@@ -1,296 +0,0 @@
-package loginctl
-
-import (
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_GetState(t *testing.T) {
- state := &SessionState{
- SessionID: "1",
- Locked: false,
- Active: true,
- IdleHint: false,
- SessionType: "wayland",
- SessionClass: "user",
- UserName: "testuser",
- }
-
- manager := &Manager{
- state: state,
- stateMutex: sync.RWMutex{},
- }
-
- result := manager.GetState()
- assert.Equal(t, "1", result.SessionID)
- assert.False(t, result.Locked)
- assert.True(t, result.Active)
- assert.Equal(t, "wayland", result.SessionType)
- assert.Equal(t, "testuser", result.UserName)
-}
-
-func TestManager_Subscribe(t *testing.T) {
- manager := &Manager{
- state: &SessionState{},
- }
-
- ch := manager.Subscribe("test-client")
- assert.NotNil(t, ch)
- assert.Equal(t, 64, cap(ch))
-
- _, exists := manager.subscribers.Load("test-client")
- assert.True(t, exists)
-}
-
-func TestManager_Unsubscribe(t *testing.T) {
- manager := &Manager{
- state: &SessionState{},
- }
-
- ch := manager.Subscribe("test-client")
-
- manager.Unsubscribe("test-client")
-
- _, ok := <-ch
- assert.False(t, ok)
-
- _, exists := manager.subscribers.Load("test-client")
- assert.False(t, exists)
-}
-
-func TestManager_Unsubscribe_NonExistent(t *testing.T) {
- manager := &Manager{
- state: &SessionState{},
- }
-
- // Unsubscribe a non-existent client should not panic
- assert.NotPanics(t, func() {
- manager.Unsubscribe("non-existent")
- })
-}
-
-func TestManager_NotifySubscribers(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- },
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
- manager.notifierWg.Add(1)
- go manager.notifier()
-
- ch := make(chan SessionState, 10)
- manager.subscribers.Store("test-client", ch)
-
- manager.notifySubscribers()
-
- select {
- case state := <-ch:
- assert.Equal(t, "1", state.SessionID)
- assert.False(t, state.Locked)
- case <-time.After(200 * time.Millisecond):
- t.Fatal("did not receive state update")
- }
-
- close(manager.stopChan)
- manager.notifierWg.Wait()
-}
-
-func TestManager_NotifySubscribers_Debounce(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- },
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
- manager.notifierWg.Add(1)
- go manager.notifier()
-
- ch := make(chan SessionState, 10)
- manager.subscribers.Store("test-client", ch)
-
- manager.notifySubscribers()
- manager.notifySubscribers()
- manager.notifySubscribers()
-
- receivedCount := 0
- timeout := time.After(200 * time.Millisecond)
- for {
- select {
- case <-ch:
- receivedCount++
- case <-timeout:
- assert.Equal(t, 1, receivedCount, "should receive exactly one debounced update")
- close(manager.stopChan)
- manager.notifierWg.Wait()
- return
- }
- }
-}
-
-func TestManager_Close(t *testing.T) {
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- }
-
- ch1 := make(chan SessionState, 1)
- ch2 := make(chan SessionState, 1)
- manager.subscribers.Store("client1", ch1)
- manager.subscribers.Store("client2", ch2)
-
- manager.Close()
-
- select {
- case <-manager.stopChan:
- case <-time.After(100 * time.Millisecond):
- t.Fatal("stopChan not closed")
- }
-
- _, ok1 := <-ch1
- _, ok2 := <-ch2
- assert.False(t, ok1, "ch1 should be closed")
- assert.False(t, ok2, "ch2 should be closed")
-
- count := 0
- manager.subscribers.Range(func(key string, ch chan SessionState) bool {
- count++
- return true
- })
- assert.Equal(t, 0, count)
-}
-
-func TestManager_GetState_ThreadSafe(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- Active: true,
- },
- stateMutex: sync.RWMutex{},
- }
-
- done := make(chan bool)
- for i := 0; i < 10; i++ {
- go func() {
- state := manager.GetState()
- assert.Equal(t, "1", state.SessionID)
- assert.True(t, state.Active)
- done <- true
- }()
- }
-
- for i := 0; i < 10; i++ {
- select {
- case <-done:
- case <-time.After(1 * time.Second):
- t.Fatal("timeout waiting for goroutines")
- }
- }
-}
-
-func TestStateChangedMeaningfully(t *testing.T) {
- tests := []struct {
- name string
- old *SessionState
- new *SessionState
- expected bool
- }{
- {
- name: "no change",
- old: &SessionState{Locked: false, Active: true, IdleHint: false},
- new: &SessionState{Locked: false, Active: true, IdleHint: false},
- expected: false,
- },
- {
- name: "locked changed",
- old: &SessionState{Locked: false, Active: true, IdleHint: false},
- new: &SessionState{Locked: true, Active: true, IdleHint: false},
- expected: true,
- },
- {
- name: "active changed",
- old: &SessionState{Locked: false, Active: true, IdleHint: false},
- new: &SessionState{Locked: false, Active: false, IdleHint: false},
- expected: true,
- },
- {
- name: "idle hint changed",
- old: &SessionState{Locked: false, Active: true, IdleHint: false},
- new: &SessionState{Locked: false, Active: true, IdleHint: true},
- expected: true,
- },
- {
- name: "locked hint changed",
- old: &SessionState{Locked: false, Active: true, LockedHint: false},
- new: &SessionState{Locked: false, Active: true, LockedHint: true},
- expected: true,
- },
- {
- name: "preparing for sleep changed",
- old: &SessionState{Locked: false, Active: true, PreparingForSleep: false},
- new: &SessionState{Locked: false, Active: true, PreparingForSleep: true},
- expected: true,
- },
- {
- name: "non-meaningful change (username)",
- old: &SessionState{Locked: false, Active: true, UserName: "user1"},
- new: &SessionState{Locked: false, Active: true, UserName: "user2"},
- expected: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := stateChangedMeaningfully(tt.old, tt.new)
- assert.Equal(t, tt.expected, result)
- })
- }
-}
-
-func TestManager_SnapshotState(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- SessionID: "1",
- Locked: false,
- Active: true,
- UserName: "testuser",
- },
- stateMutex: sync.RWMutex{},
- }
-
- snapshot := manager.snapshotState()
- assert.Equal(t, "1", snapshot.SessionID)
- assert.False(t, snapshot.Locked)
- assert.True(t, snapshot.Active)
- assert.Equal(t, "testuser", snapshot.UserName)
-
- snapshot.Locked = true
- assert.False(t, manager.state.Locked)
-}
-
-func TestNewManager(t *testing.T) {
- t.Run("attempts to create manager", func(t *testing.T) {
- manager, err := NewManager()
- if err != nil {
- assert.Nil(t, manager)
- } else {
- assert.NotNil(t, manager)
- assert.NotNil(t, manager.state)
- assert.NotNil(t, &manager.subscribers)
- assert.NotNil(t, manager.stopChan)
-
- manager.Close()
- }
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor.go
deleted file mode 100644
index 4c0605a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package loginctl
-
-import (
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
- "github.com/godbus/dbus/v5"
-)
-
-func (m *Manager) handleDBusSignal(sig *dbus.Signal) {
- switch sig.Name {
- case dbusSessionInterface + ".Lock":
- m.stateMutex.Lock()
- m.state.Locked = true
- m.state.LockedHint = true
- m.stateMutex.Unlock()
- m.notifySubscribers()
-
- if m.sleepInhibitorEnabled.Load() && m.inSleepCycle.Load() {
- id := m.sleepCycleID.Load()
- m.lockTimerMu.Lock()
- if m.lockTimer != nil {
- m.lockTimer.Stop()
- }
- m.lockTimer = time.AfterFunc(m.fallbackDelay, func() {
- m.releaseForCycle(id)
- })
- m.lockTimerMu.Unlock()
- }
-
- case dbusSessionInterface + ".Unlock":
- m.stateMutex.Lock()
- m.state.Locked = false
- m.state.LockedHint = false
- m.stateMutex.Unlock()
- m.notifySubscribers()
-
- // Cancel the lock timer if it's still running
- m.lockTimerMu.Lock()
- if m.lockTimer != nil {
- m.lockTimer.Stop()
- m.lockTimer = nil
- }
- m.lockTimerMu.Unlock()
-
- // Re-acquire the sleep inhibitor (acquireSleepInhibitor checks the enabled flag)
- m.acquireSleepInhibitor()
-
- case dbusManagerInterface + ".PrepareForSleep":
- if len(sig.Body) == 0 {
- return
- }
- preparing, _ := sig.Body[0].(bool)
-
- if preparing {
- cycleID := m.sleepCycleID.Add(1)
- m.inSleepCycle.Store(true)
-
- if m.lockBeforeSuspend.Load() {
- m.Lock()
- }
-
- readyCh := m.newLockerReadyCh()
- go func(id uint64, ch <-chan struct{}) {
- <-ch
- if m.inSleepCycle.Load() && m.sleepCycleID.Load() == id {
- m.releaseSleepInhibitor()
- }
- }(cycleID, readyCh)
- } else {
- m.inSleepCycle.Store(false)
- m.signalLockerReady()
- m.refreshSessionBinding()
- m.acquireSleepInhibitor()
- }
-
- m.stateMutex.Lock()
- m.state.PreparingForSleep = preparing
- m.stateMutex.Unlock()
- m.notifySubscribers()
-
- case dbusPropsInterface + ".PropertiesChanged":
- m.handlePropertiesChanged(sig)
-
- case "org.freedesktop.DBus.NameOwnerChanged":
- if len(sig.Body) == 3 {
- name, _ := sig.Body[0].(string)
- oldOwner, _ := sig.Body[1].(string)
- newOwner, _ := sig.Body[2].(string)
- if name == dbusDest && oldOwner != "" && newOwner != "" {
- m.updateSessionState()
- if !m.inSleepCycle.Load() {
- m.acquireSleepInhibitor()
- }
- m.notifySubscribers()
- }
- }
- }
-}
-
-func (m *Manager) handlePropertiesChanged(sig *dbus.Signal) {
- if len(sig.Body) < 2 {
- return
- }
-
- iface, ok := sig.Body[0].(string)
- if !ok || iface != dbusSessionInterface {
- return
- }
-
- changes, ok := sig.Body[1].(map[string]dbus.Variant)
- if !ok {
- return
- }
-
- var needsUpdate bool
-
- for key, variant := range changes {
- switch key {
- case "Active":
- if val, ok := dbusutil.As[bool](variant); ok {
- m.stateMutex.Lock()
- m.state.Active = val
- m.stateMutex.Unlock()
- needsUpdate = true
- }
- case "IdleHint":
- if val, ok := dbusutil.As[bool](variant); ok {
- m.stateMutex.Lock()
- m.state.IdleHint = val
- m.stateMutex.Unlock()
- needsUpdate = true
- }
- case "IdleSinceHint":
- if val, ok := dbusutil.As[uint64](variant); ok {
- m.stateMutex.Lock()
- m.state.IdleSinceHint = val
- m.stateMutex.Unlock()
- needsUpdate = true
- }
- case "LockedHint":
- if val, ok := dbusutil.As[bool](variant); ok {
- m.stateMutex.Lock()
- m.state.LockedHint = val
- m.state.Locked = val
- m.stateMutex.Unlock()
- needsUpdate = true
- }
- }
- }
-
- if needsUpdate {
- m.notifySubscribers()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor_test.go
deleted file mode 100644
index ecbb57f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/monitor_test.go
+++ /dev/null
@@ -1,298 +0,0 @@
-package loginctl
-
-import (
- "sync"
- "testing"
-
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_HandleDBusSignal_Lock(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- Locked: false,
- LockedHint: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.login1.Session.Lock",
- }
-
- manager.handleDBusSignal(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.True(t, manager.state.Locked)
- assert.True(t, manager.state.LockedHint)
-}
-
-func TestManager_HandleDBusSignal_Unlock(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- Locked: true,
- LockedHint: true,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.login1.Session.Unlock",
- }
-
- manager.handleDBusSignal(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.False(t, manager.state.Locked)
- assert.False(t, manager.state.LockedHint)
-}
-
-func TestManager_HandleDBusSignal_PrepareForSleep(t *testing.T) {
- t.Run("preparing for sleep - true", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- PreparingForSleep: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.login1.Manager.PrepareForSleep",
- Body: []any{true},
- }
-
- manager.handleDBusSignal(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.True(t, manager.state.PreparingForSleep)
- })
-
- t.Run("preparing for sleep - false", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- PreparingForSleep: true,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.login1.Manager.PrepareForSleep",
- Body: []any{false},
- }
-
- manager.handleDBusSignal(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.False(t, manager.state.PreparingForSleep)
- })
-
- t.Run("empty body", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- PreparingForSleep: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.login1.Manager.PrepareForSleep",
- Body: []any{},
- }
-
- manager.handleDBusSignal(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.False(t, manager.state.PreparingForSleep)
- })
-}
-
-func TestManager_HandlePropertiesChanged(t *testing.T) {
- t.Run("active property changed", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- Active: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{
- "org.freedesktop.login1.Session",
- map[string]dbus.Variant{
- "Active": dbus.MakeVariant(true),
- },
- },
- }
-
- manager.handlePropertiesChanged(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.True(t, manager.state.Active)
- })
-
- t.Run("idle hint property changed", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- IdleHint: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{
- "org.freedesktop.login1.Session",
- map[string]dbus.Variant{
- "IdleHint": dbus.MakeVariant(true),
- },
- },
- }
-
- manager.handlePropertiesChanged(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.True(t, manager.state.IdleHint)
- })
-
- t.Run("idle since hint property changed", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- IdleSinceHint: 0,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{
- "org.freedesktop.login1.Session",
- map[string]dbus.Variant{
- "IdleSinceHint": dbus.MakeVariant(uint64(123456789)),
- },
- },
- }
-
- manager.handlePropertiesChanged(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.Equal(t, uint64(123456789), manager.state.IdleSinceHint)
- })
-
- t.Run("locked hint property changed", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- LockedHint: false,
- Locked: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{
- "org.freedesktop.login1.Session",
- map[string]dbus.Variant{
- "LockedHint": dbus.MakeVariant(true),
- },
- },
- }
-
- manager.handlePropertiesChanged(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.True(t, manager.state.LockedHint)
- assert.True(t, manager.state.Locked)
- })
-
- t.Run("wrong interface", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- Active: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{
- "org.freedesktop.SomeOtherInterface",
- map[string]dbus.Variant{
- "Active": dbus.MakeVariant(true),
- },
- },
- }
-
- manager.handlePropertiesChanged(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.False(t, manager.state.Active)
- })
-
- t.Run("empty body", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{},
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{},
- }
-
- assert.NotPanics(t, func() {
- manager.handlePropertiesChanged(sig)
- })
- })
-
- t.Run("multiple properties changed", func(t *testing.T) {
- manager := &Manager{
- state: &SessionState{
- Active: false,
- IdleHint: false,
- },
- stateMutex: sync.RWMutex{},
- dirty: make(chan struct{}, 1),
- }
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{
- "org.freedesktop.login1.Session",
- map[string]dbus.Variant{
- "Active": dbus.MakeVariant(true),
- "IdleHint": dbus.MakeVariant(true),
- },
- },
- }
-
- manager.handlePropertiesChanged(sig)
-
- manager.stateMutex.RLock()
- defer manager.stateMutex.RUnlock()
- assert.True(t, manager.state.Active)
- assert.True(t, manager.state.IdleHint)
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types.go
deleted file mode 100644
index 76c7414..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package loginctl
-
-import (
- "os"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/godbus/dbus/v5"
-)
-
-type SessionState struct {
- SessionID string `json:"sessionId"`
- SessionPath string `json:"sessionPath"`
- Locked bool `json:"locked"`
- Active bool `json:"active"`
- IdleHint bool `json:"idleHint"`
- IdleSinceHint uint64 `json:"idleSinceHint"`
- LockedHint bool `json:"lockedHint"`
- SessionType string `json:"sessionType"`
- SessionClass string `json:"sessionClass"`
- User uint32 `json:"user"`
- UserName string `json:"userName"`
- RemoteHost string `json:"remoteHost"`
- Service string `json:"service"`
- TTY string `json:"tty"`
- Display string `json:"display"`
- Remote bool `json:"remote"`
- Seat string `json:"seat"`
- VTNr uint32 `json:"vtnr"`
- PreparingForSleep bool `json:"preparingForSleep"`
-}
-
-type EventType string
-
-const (
- EventStateChanged EventType = "state_changed"
- EventLock EventType = "lock"
- EventUnlock EventType = "unlock"
- EventPrepareForSleep EventType = "prepare_for_sleep"
- EventIdleHintChanged EventType = "idle_hint_changed"
- EventLockedHintChanged EventType = "locked_hint_changed"
-)
-
-type SessionEvent struct {
- Type EventType `json:"type"`
- Data SessionState `json:"data"`
-}
-
-type Manager struct {
- state *SessionState
- stateMutex sync.RWMutex
- subscribers syncmap.Map[string, chan SessionState]
- stopChan chan struct{}
- conn *dbus.Conn
- sessionPath dbus.ObjectPath
- managerObj dbus.BusObject
- sessionObj dbus.BusObject
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotifiedState *SessionState
- signals chan *dbus.Signal
- sigWG sync.WaitGroup
- inhibitMu sync.Mutex
- inhibitFile *os.File
- lockBeforeSuspend atomic.Bool
- inSleepCycle atomic.Bool
- sleepCycleID atomic.Uint64
- lockerReadyChMu sync.Mutex
- lockerReadyCh chan struct{}
- lockTimerMu sync.Mutex
- lockTimer *time.Timer
- sleepInhibitorEnabled atomic.Bool
- fallbackDelay time.Duration
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types_test.go
deleted file mode 100644
index 383300f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/loginctl/types_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package loginctl
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestEventType_Constants(t *testing.T) {
- assert.Equal(t, EventType("state_changed"), EventStateChanged)
- assert.Equal(t, EventType("lock"), EventLock)
- assert.Equal(t, EventType("unlock"), EventUnlock)
- assert.Equal(t, EventType("prepare_for_sleep"), EventPrepareForSleep)
- assert.Equal(t, EventType("idle_hint_changed"), EventIdleHintChanged)
- assert.Equal(t, EventType("locked_hint_changed"), EventLockedHintChanged)
-}
-
-func TestSessionState_Struct(t *testing.T) {
- state := SessionState{
- SessionID: "1",
- Locked: false,
- Active: true,
- SessionType: "wayland",
- User: 1000,
- UserName: "testuser",
- }
-
- assert.Equal(t, "1", state.SessionID)
- assert.True(t, state.Active)
- assert.False(t, state.Locked)
- assert.Equal(t, "wayland", state.SessionType)
- assert.Equal(t, uint32(1000), state.User)
- assert.Equal(t, "testuser", state.UserName)
-}
-
-func TestSessionEvent_Struct(t *testing.T) {
- state := SessionState{
- SessionID: "1",
- Locked: true,
- }
-
- event := SessionEvent{
- Type: EventLock,
- Data: state,
- }
-
- assert.Equal(t, EventLock, event.Type)
- assert.Equal(t, "1", event.Data.SessionID)
- assert.True(t, event.Data.Locked)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/matugen_handler.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/matugen_handler.go
deleted file mode 100644
index 73c6ad6..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/matugen_handler.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package server
-
-import (
- "context"
- "net"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-type MatugenQueueResult struct {
- Success bool `json:"success"`
- Message string `json:"message,omitempty"`
-}
-
-func handleMatugenQueue(conn net.Conn, req models.Request) {
- opts := matugen.Options{
- StateDir: models.GetOr(req, "stateDir", ""),
- ShellDir: models.GetOr(req, "shellDir", ""),
- ConfigDir: models.GetOr(req, "configDir", ""),
- Kind: models.GetOr(req, "kind", ""),
- Value: models.GetOr(req, "value", ""),
- Mode: matugen.ColorMode(models.GetOr(req, "mode", "")),
- IconTheme: models.GetOr(req, "iconTheme", ""),
- MatugenType: models.GetOr(req, "matugenType", ""),
- RunUserTemplates: models.GetOr(req, "runUserTemplates", true),
- StockColors: models.GetOr(req, "stockColors", ""),
- SyncModeWithPortal: models.GetOr(req, "syncModeWithPortal", false),
- TerminalsAlwaysDark: models.GetOr(req, "terminalsAlwaysDark", false),
- SkipTemplates: models.GetOr(req, "skipTemplates", ""),
- Contrast: models.GetOr(req, "contrast", 0.0),
- }
-
- wait := models.GetOr(req, "wait", true)
-
- queue := matugen.GetQueue()
- resultCh := queue.Submit(opts)
-
- if !wait {
- models.Respond(conn, req.ID, MatugenQueueResult{
- Success: true,
- Message: "queued",
- })
- return
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
- defer cancel()
-
- select {
- case result := <-resultCh:
- if result.Error != nil {
- if result.Error == context.Canceled {
- models.Respond(conn, req.ID, MatugenQueueResult{
- Success: false,
- Message: "cancelled",
- })
- return
- }
- models.RespondError(conn, req.ID, result.Error.Error())
- return
- }
- models.Respond(conn, req.ID, MatugenQueueResult{
- Success: true,
- Message: "completed",
- })
- case <-ctx.Done():
- models.RespondError(conn, req.ID, "timeout waiting for theme generation")
- }
-}
-
-func handleMatugenStatus(conn net.Conn, req models.Request) {
- queue := matugen.GetQueue()
- models.Respond(conn, req.ID, map[string]bool{
- "running": queue.IsRunning(),
- "pending": queue.HasPending(),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types.go
deleted file mode 100644
index 2933fb5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package models
-
-import (
- "encoding/json"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-type Request struct {
- ID int `json:"id,omitempty"`
- Method string `json:"method"`
- Params map[string]any `json:"params,omitempty"`
-}
-
-func Get[T any](r Request, key string) (T, bool) {
- v, err := params.Get[T](r.Params, key)
- return v, err == nil
-}
-
-func GetOr[T any](r Request, key string, def T) T {
- return params.GetOpt(r.Params, key, def)
-}
-
-type Response[T any] struct {
- ID int `json:"id,omitempty"`
- Result *T `json:"result,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-func RespondError(conn net.Conn, id int, errMsg string) {
- log.Errorf("DMS API Error: id=%d error=%s", id, errMsg)
- resp := Response[any]{ID: id, Error: errMsg}
- json.NewEncoder(conn).Encode(resp)
-}
-
-func Respond[T any](conn net.Conn, id int, result T) {
- resp := Response[T]{ID: id, Result: &result}
- json.NewEncoder(conn).Encode(resp)
-}
-
-type SuccessResult struct {
- Success bool `json:"success"`
- Message string `json:"message,omitempty"`
- Value string `json:"value,omitempty"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types_test.go
deleted file mode 100644
index 98f9528..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/models/types_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package models
-
-import "testing"
-
-func TestGet(t *testing.T) {
- req := Request{Params: map[string]any{"name": "test", "count": 42, "enabled": true}}
-
- name, ok := Get[string](req, "name")
- if !ok || name != "test" {
- t.Errorf("Get[string] = %q, %v; want 'test', true", name, ok)
- }
-
- count, ok := Get[int](req, "count")
- if !ok || count != 42 {
- t.Errorf("Get[int] = %d, %v; want 42, true", count, ok)
- }
-
- enabled, ok := Get[bool](req, "enabled")
- if !ok || !enabled {
- t.Errorf("Get[bool] = %v, %v; want true, true", enabled, ok)
- }
-
- _, ok = Get[string](req, "missing")
- if ok {
- t.Error("Get missing key should return false")
- }
-
- _, ok = Get[int](req, "name")
- if ok {
- t.Error("Get wrong type should return false")
- }
-}
-
-func TestGetOr(t *testing.T) {
- req := Request{Params: map[string]any{"name": "test", "enabled": true}}
-
- if v := GetOr(req, "name", "default"); v != "test" {
- t.Errorf("GetOr existing = %q; want 'test'", v)
- }
-
- if v := GetOr(req, "missing", "default"); v != "default" {
- t.Errorf("GetOr missing = %q; want 'default'", v)
- }
-
- if v := GetOr(req, "enabled", false); !v {
- t.Errorf("GetOr bool = %v; want true", v)
- }
-
- if v := GetOr(req, "name", 0); v != 0 {
- t.Errorf("GetOr wrong type = %d; want 0 (default)", v)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/API.md b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/API.md
deleted file mode 100644
index 361ebac..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/API.md
+++ /dev/null
@@ -1,552 +0,0 @@
-# NetworkManager API Documentation
-
-## Overview
-
-The network manager API provides methods for managing WiFi connections, monitoring network state, and handling credential prompts through NetworkManager or iwd (and systemd-networkd for ethernet only). Communication occurs over a message-based protocol (websocket, IPC, etc.) with event subscriptions for state updates.
-
-## API Methods
-
-### network.wifi.connect
-
-Initiate a WiFi connection.
-
-**Request:**
-```json
-{
- "method": "network.wifi.connect",
- "params": {
- "ssid": "NetworkName",
- "password": "optional-password",
- "interactive": true
- }
-}
-```
-
-**Parameters:**
-- `ssid` (string, required): Network SSID
-- `password` (string, optional): Pre-shared key for WPA/WPA2/WPA3 networks
-- `interactive` (boolean, optional): Enable credential prompting if authentication fails or password is missing. Automatically set to `true` when connecting to secured networks without providing a password.
-
-**Response:**
-```json
-{
- "success": true,
- "message": "connecting"
-}
-```
-
-**Behavior:**
-- Returns immediately; connection happens asynchronously
-- State updates delivered via `network` service subscription
-- Credential prompts delivered via `network.credentials` service subscription
-
-### network.credentials.submit
-
-Submit credentials in response to a prompt.
-
-**Request:**
-```json
-{
- "method": "network.credentials.submit",
- "params": {
- "token": "correlation-token",
- "secrets": {
- "psk": "password"
- },
- "save": true
- }
-}
-```
-
-**Parameters:**
-- `token` (string, required): Token from credential prompt
-- `secrets` (object, required): Key-value map of credential fields
-- `save` (boolean, optional): Whether to persist credentials (default: false)
-
-**Common secret fields:**
-- `psk`: Pre-shared key for WPA2/WPA3 personal networks
-- `identity`: Username for 802.1X enterprise networks
-- `password`: Password for 802.1X enterprise networks
-
-### network.credentials.cancel
-
-Cancel a credential prompt.
-
-**Request:**
-```json
-{
- "method": "network.credentials.cancel",
- "params": {
- "token": "correlation-token"
- }
-}
-```
-
-## Event Subscriptions
-
-### Subscribing to Events
-
-Subscribe to receive network state updates and credential prompts:
-
-```json
-{
- "method": "subscribe",
- "params": {
- "services": ["network", "network.credentials"]
- }
-}
-```
-
-Both services are required for full connection handling. Missing `network.credentials` means credential prompts won't be received.
-
-### network Service Events
-
-State updates are sent whenever network configuration changes:
-
-```json
-{
- "service": "network",
- "data": {
- "networkStatus": "wifi",
- "isConnecting": false,
- "connectingSSID": "",
- "wifiConnected": true,
- "wifiSSID": "MyNetwork",
- "wifiIP": "192.168.1.100",
- "lastError": ""
- }
-}
-```
-
-**State fields:**
-- `networkStatus`: Current connection type (`wifi`, `ethernet`, `disconnected`)
-- `isConnecting`: Whether a connection attempt is in progress
-- `connectingSSID`: SSID being connected to (empty when idle)
-- `wifiConnected`: Whether associated with an access point
-- `wifiSSID`: Currently connected network name
-- `wifiIP`: Assigned IP address (empty until DHCP completes)
-- `lastError`: Error message from last failed connection attempt
-
-### network.credentials Service Events
-
-Credential prompts are sent when authentication is required:
-
-```json
-{
- "service": "network.credentials",
- "data": {
- "token": "unique-prompt-id",
- "ssid": "NetworkName",
- "setting": "802-11-wireless-security",
- "fields": ["psk"],
- "hints": ["wpa3", "sae"],
- "reason": "Credentials required"
- }
-}
-```
-
-**Prompt fields:**
-- `token`: Unique identifier for this prompt (use in submit/cancel)
-- `ssid`: Network requesting credentials
-- `setting`: Authentication type (`802-11-wireless-security` for personal WiFi, `802-1x` for enterprise)
-- `fields`: Array of required credential field names
-- `hints`: Additional context about the network type
-- `reason`: Human-readable explanation (e.g., "Previous password was incorrect")
-
-## Connection Flow
-
-### Typical Timeline
-
-```
-T+0ms Call network.wifi.connect
-T+10ms Receive {"success": true, "message": "connecting"}
-T+100ms State update: isConnecting=true, connectingSSID="Network"
-T+500ms Credential prompt (if needed)
-T+1000ms Submit credentials
-T+3000ms State update: wifiConnected=true, wifiIP="192.168.x.x"
-```
-
-### State Machine
-
-```
-IDLE
- |
- | network.wifi.connect
- v
-CONNECTING (isConnecting=true, connectingSSID set)
- |
- +-- Needs credentials
- | |
- | v
- | PROMPTING (credential prompt event)
- | |
- | | network.credentials.submit
- | v
- | back to CONNECTING
- |
- +-- Success
- | |
- | v
- | CONNECTED (wifiConnected=true, wifiIP set, isConnecting=false)
- |
- +-- Failure
- |
- v
- ERROR (isConnecting=false, !wifiConnected, lastError set)
-```
-
-## Connection Success Detection
-
-A connection is successful when all of the following are true:
-
-1. `wifiConnected` is `true`
-2. `wifiIP` is set and non-empty
-3. `wifiSSID` matches the target network
-4. `isConnecting` is `false`
-
-Do not rely on `wifiConnected` alone - the device may be associated with an access point but not have an IP address yet.
-
-**Example:**
-```javascript
-function isConnectionComplete(state, targetSSID) {
- return state.wifiConnected &&
- state.wifiIP &&
- state.wifiIP !== "" &&
- state.wifiSSID === targetSSID &&
- !state.isConnecting;
-}
-```
-
-## Error Handling
-
-### Error Detection
-
-Errors occur when a connection attempt stops without success:
-
-```javascript
-function checkForFailure(state, wasConnecting, targetSSID) {
- // Was connecting, now idle, but not connected
- if (wasConnecting &&
- !state.isConnecting &&
- state.connectingSSID === "" &&
- !state.wifiConnected) {
- return state.lastError || "Connection failed";
- }
- return null;
-}
-```
-
-### Common Error Scenarios
-
-#### Wrong Password
-
-**Detection methods:**
-
-1. Quick failure (< 3 seconds from start)
-2. `lastError` contains "password", "auth", or "secrets"
-3. Second credential prompt with `reason: "Previous password was incorrect"`
-
-**Handling:**
-```javascript
-if (prompt.reason === "Previous password was incorrect") {
- // Show error, clear password field, re-focus input
-}
-```
-
-#### Network Out of Range
-
-**Detection:**
-- `lastError` contains "not-found" or "connection-attempt-failed"
-
-#### Connection Timeout
-
-**Detection:**
-- `isConnecting` remains true for > 30 seconds
-
-**Implementation:**
-```javascript
-let timeout = setTimeout(() => {
- if (currentState.isConnecting) {
- handleTimeout();
- }
-}, 30000);
-```
-
-#### DHCP Failure
-
-**Detection:**
-- `wifiConnected` is true
-- `wifiIP` is empty after 15+ seconds
-
-### Error Message Translation
-
-Map technical errors to user-friendly messages:
-
-| lastError value | Meaning | User message |
-|----------------|---------|--------------|
-| `secrets-required` | Password needed | "Please enter password" |
-| `authentication-failed` | Wrong password | "Incorrect password" |
-| `connection-removed` | Profile deleted | "Network configuration removed" |
-| `connection-attempt-failed` | Generic failure | "Failed to connect" |
-| `network-not-found` | Out of range | "Network not found" |
-| `(timeout)` | Timeout | "Connection timed out" |
-
-## Credential Handling
-
-### Secret Agent Architecture
-
-The credential system uses a broker pattern:
-
-```
-NetworkManager -> SecretAgent -> PromptBroker -> UI -> User
- ^
- |
- User Response
- |
-NetworkManager <- SecretAgent <- PromptBroker <- UI
-```
-
-### Implementing a Broker
-
-```go
-type CustomBroker struct {
- ui UIInterface
- pending map[string]chan network.PromptReply
-}
-
-func (b *CustomBroker) Ask(ctx context.Context, req network.PromptRequest) (string, error) {
- token := generateToken()
- b.pending[token] = make(chan network.PromptReply, 1)
-
- // Send to UI
- b.ui.ShowCredentialPrompt(token, req)
-
- return token, nil
-}
-
-func (b *CustomBroker) Wait(ctx context.Context, token string) (network.PromptReply, error) {
- select {
- case <-ctx.Done():
- return network.PromptReply{}, errors.New("timeout")
- case reply := <-b.pending[token]:
- return reply, nil
- }
-}
-
-func (b *CustomBroker) Resolve(token string, reply network.PromptReply) error {
- if ch, ok := b.pending[token]; ok {
- ch <- reply
- close(ch)
- delete(b.pending, token)
- }
- return nil
-}
-```
-
-### Credential Field Types
-
-**Personal WiFi (802-11-wireless-security):**
-- Fields: `["psk"]`
-- UI: Single password input
-
-**Enterprise WiFi (802-1x):**
-- Fields: `["identity", "password"]`
-- UI: Username and password inputs
-
-### Building Secrets Object
-
-```javascript
-function buildSecrets(setting, fields, formData) {
- let secrets = {};
-
- if (setting === "802-11-wireless-security") {
- secrets.psk = formData.password;
- } else if (setting === "802-1x") {
- secrets.identity = formData.username;
- secrets.password = formData.password;
- }
-
- return secrets;
-}
-```
-
-## Best Practices
-
-### Track Target Network
-
-Always store which network you're connecting to:
-
-```javascript
-let targetSSID = null;
-
-function connect(ssid) {
- targetSSID = ssid;
- // send request
-}
-
-function onStateUpdate(state) {
- if (!targetSSID) return;
-
- if (state.wifiSSID === targetSSID && state.wifiConnected && state.wifiIP) {
- // Success for the network we care about
- targetSSID = null;
- }
-}
-```
-
-### Implement Timeouts
-
-Never wait indefinitely for a connection:
-
-```javascript
-const CONNECTION_TIMEOUT = 30000; // 30 seconds
-const DHCP_TIMEOUT = 15000; // 15 seconds
-
-let timer = setTimeout(() => {
- if (stillConnecting) {
- handleTimeout();
- }
-}, CONNECTION_TIMEOUT);
-```
-
-### Handle Credential Re-prompts
-
-Wrong passwords trigger a second prompt:
-
-```javascript
-function onCredentialPrompt(prompt) {
- if (prompt.reason.includes("incorrect")) {
- // Show error, but keep dialog open
- showError("Wrong password");
- clearPasswordField();
- } else {
- // First time prompt
- showDialog(prompt);
- }
-}
-```
-
-### Clean Up State
-
-Reset tracking variables on success, failure, or cancellation:
-
-```javascript
-function cleanup() {
- clearTimeout(timer);
- targetSSID = null;
- closeDialogs();
-}
-```
-
-### Subscribe to Both Services
-
-Missing `network.credentials` means prompts won't arrive:
-
-```javascript
-// Correct
-services: ["network", "network.credentials"]
-
-// Wrong - will miss credential prompts
-services: ["network"]
-```
-
-## Testing
-
-### Connection Test Checklist
-
-- [ ] Connect to open network
-- [ ] Connect to WPA2 network with password provided
-- [ ] Connect to WPA2 network without password (triggers prompt)
-- [ ] Enter wrong password (verify error and re-prompt)
-- [ ] Cancel credential prompt
-- [ ] Connection timeout after 30 seconds
-- [ ] DHCP timeout detection
-- [ ] Network out of range
-- [ ] Reconnect to already-configured network
-
-### Verifying Secret Agent Setup
-
-Check connection profile flags:
-```bash
-nmcli connection show "NetworkName" | grep flags
-# Should show: 802-11-wireless-security.psk-flags: 1 (agent-owned)
-```
-
-Check agent registration in logs:
-```
-INFO: Registered with NetworkManager as secret agent
-```
-
-## Security
-
-- Never log credential values (passwords, PSKs)
-- Clear password fields when dialogs close
-- Implement prompt timeouts (default: 2 minutes)
-- Validate user input before submission
-- Use secure channels for credential transmission
-
-## Troubleshooting
-
-### Credential prompt doesn't appear
-
-**Check:**
-- Subscribed to both `network` and `network.credentials`
-- Connection has `interactive: true`
-- Secret flags set to AGENT_OWNED (value: 1)
-- Broker registered successfully
-
-### Connection succeeds without prompting
-
-**Cause:** NetworkManager found saved credentials
-
-**Solution:** Delete existing connection first, or use different credentials
-
-### State updates seem delayed
-
-**Expected behavior:** State changes occur in rapid succession during connection
-
-**Solution:** Debounce UI updates; only act on final state
-
-### Multiple rapid credential prompts
-
-**Cause:** Connection profile has incorrect flags or conflicting agents
-
-**Solution:**
-- Check only one agent is running
-- Verify psk-flags value
-- Check NetworkManager logs for agent conflicts
-
-## Data Structures Reference
-
-### PromptRequest
-```go
-type PromptRequest struct {
- SSID string `json:"ssid"`
- SettingName string `json:"setting"`
- Fields []string `json:"fields"`
- Hints []string `json:"hints"`
- Reason string `json:"reason"`
-}
-```
-
-### PromptReply
-```go
-type PromptReply struct {
- Secrets map[string]string `json:"secrets"`
- Save bool `json:"save"`
- Cancel bool `json:"cancel"`
-}
-```
-
-### NetworkState
-```go
-type NetworkState struct {
- NetworkStatus string `json:"networkStatus"`
- IsConnecting bool `json:"isConnecting"`
- ConnectingSSID string `json:"connectingSSID"`
- WifiConnected bool `json:"wifiConnected"`
- WifiSSID string `json:"wifiSSID"`
- WifiIP string `json:"wifiIP"`
- LastError string `json:"lastError"`
-}
-```
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_iwd.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_iwd.go
deleted file mode 100644
index 5d49f04..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_iwd.go
+++ /dev/null
@@ -1,306 +0,0 @@
-package network
-
-import (
- "context"
- "errors"
- "fmt"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- iwdAgentManagerPath = "/net/connman/iwd"
- iwdAgentManagerIface = "net.connman.iwd.AgentManager"
- iwdAgentInterface = "net.connman.iwd.Agent"
- iwdAgentObjectPath = "/com/danklinux/iwdagent"
-)
-
-type ConnectionStateChecker interface {
- IsConnectingTo(ssid string) bool
-}
-
-type IWDAgent struct {
- conn *dbus.Conn
- objPath dbus.ObjectPath
- prompts PromptBroker
- onUserCanceled func()
- onPromptRetry func(ssid string)
- lastRequestSSID string
- stateChecker ConnectionStateChecker
-}
-
-const iwdAgentIntrospectXML = `
-<node>
- <interface name="net.connman.iwd.Agent">
- <method name="Release">
- <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
- </method>
- <method name="RequestPassphrase">
- <arg type="o" name="network" direction="in"/>
- <arg type="s" name="passphrase" direction="out"/>
- </method>
- <method name="RequestPrivateKeyPassphrase">
- <arg type="o" name="network" direction="in"/>
- <arg type="s" name="passphrase" direction="out"/>
- </method>
- <method name="RequestUserNameAndPassword">
- <arg type="o" name="network" direction="in"/>
- <arg type="s" name="username" direction="out"/>
- <arg type="s" name="password" direction="out"/>
- </method>
- <method name="RequestUserPassword">
- <arg type="o" name="network" direction="in"/>
- <arg type="s" name="user" direction="in"/>
- <arg type="s" name="password" direction="out"/>
- </method>
- <method name="Cancel">
- <arg type="s" name="reason" direction="in"/>
- <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
- </method>
- </interface>
-</node>`
-
-func NewIWDAgent(conn *dbus.Conn, prompts PromptBroker) (*IWDAgent, error) {
- if conn == nil {
- return nil, fmt.Errorf("dbus connection is nil")
- }
-
- agent := &IWDAgent{
- conn: conn,
- objPath: dbus.ObjectPath(iwdAgentObjectPath),
- prompts: prompts,
- }
-
- if err := conn.Export(agent, agent.objPath, iwdAgentInterface); err != nil {
- return nil, fmt.Errorf("failed to export IWD agent: %w", err)
- }
-
- if err := conn.Export(agent, agent.objPath, "org.freedesktop.DBus.Introspectable"); err != nil {
- return nil, fmt.Errorf("failed to export introspection: %w", err)
- }
-
- mgr := conn.Object("net.connman.iwd", dbus.ObjectPath(iwdAgentManagerPath))
- call := mgr.Call(iwdAgentManagerIface+".RegisterAgent", 0, agent.objPath)
- if call.Err != nil {
- return nil, fmt.Errorf("failed to register agent with iwd: %w", call.Err)
- }
-
- return agent, nil
-}
-
-func (a *IWDAgent) Close() {
- if a.conn != nil {
- mgr := a.conn.Object("net.connman.iwd", dbus.ObjectPath(iwdAgentManagerPath))
- mgr.Call(iwdAgentManagerIface+".UnregisterAgent", 0, a.objPath)
- }
-}
-
-func (a *IWDAgent) SetStateChecker(checker ConnectionStateChecker) {
- a.stateChecker = checker
-}
-
-func (a *IWDAgent) getNetworkName(networkPath dbus.ObjectPath) string {
- netObj := a.conn.Object("net.connman.iwd", networkPath)
- nameVar, err := netObj.GetProperty("net.connman.iwd.Network.Name")
- if err == nil {
- if name, ok := nameVar.Value().(string); ok {
- return name
- }
- }
- return string(networkPath)
-}
-
-func (a *IWDAgent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Error) {
- ssid := a.getNetworkName(network)
-
- if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.prompts == nil {
- if a.onUserCanceled != nil {
- a.onUserCanceled()
- }
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.lastRequestSSID == ssid {
- if a.onPromptRetry != nil {
- a.onPromptRetry(ssid)
- }
- }
- a.lastRequestSSID = ssid
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
- defer cancel()
-
- token, err := a.prompts.Ask(ctx, PromptRequest{
- SSID: ssid,
- Fields: []string{"psk"},
- })
- if err != nil {
- if a.onUserCanceled != nil {
- a.onUserCanceled()
- }
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- reply, err := a.prompts.Wait(ctx, token)
- if err != nil {
- if reply.Cancel || errors.Is(err, errdefs.ErrSecretPromptCancelled) {
- if a.onUserCanceled != nil {
- a.onUserCanceled()
- }
- }
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if passphrase, ok := reply.Secrets["psk"]; ok {
- return passphrase, nil
- }
-
- if a.onUserCanceled != nil {
- a.onUserCanceled()
- }
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
-}
-
-func (a *IWDAgent) RequestPrivateKeyPassphrase(network dbus.ObjectPath) (string, *dbus.Error) {
- ssid := a.getNetworkName(network)
-
- if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.prompts == nil {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.lastRequestSSID == ssid {
- if a.onPromptRetry != nil {
- a.onPromptRetry(ssid)
- }
- }
- a.lastRequestSSID = ssid
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
- defer cancel()
-
- token, err := a.prompts.Ask(ctx, PromptRequest{
- SSID: ssid,
- Fields: []string{"private-key-password"},
- })
- if err != nil {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- reply, err := a.prompts.Wait(ctx, token)
- if err != nil || reply.Cancel {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if passphrase, ok := reply.Secrets["private-key-password"]; ok {
- return passphrase, nil
- }
-
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
-}
-
-func (a *IWDAgent) RequestUserNameAndPassword(network dbus.ObjectPath) (string, string, *dbus.Error) {
- ssid := a.getNetworkName(network)
-
- if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
- return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.prompts == nil {
- return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.lastRequestSSID == ssid {
- if a.onPromptRetry != nil {
- a.onPromptRetry(ssid)
- }
- }
- a.lastRequestSSID = ssid
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
- defer cancel()
-
- token, err := a.prompts.Ask(ctx, PromptRequest{
- SSID: ssid,
- Fields: []string{"identity", "password"},
- })
- if err != nil {
- return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- reply, err := a.prompts.Wait(ctx, token)
- if err != nil || reply.Cancel {
- return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- username, hasUser := reply.Secrets["identity"]
- password, hasPass := reply.Secrets["password"]
-
- if hasUser && hasPass {
- return username, password, nil
- }
-
- return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
-}
-
-func (a *IWDAgent) RequestUserPassword(network dbus.ObjectPath, user string) (string, *dbus.Error) {
- ssid := a.getNetworkName(network)
-
- if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.prompts == nil {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if a.lastRequestSSID == ssid {
- if a.onPromptRetry != nil {
- a.onPromptRetry(ssid)
- }
- }
- a.lastRequestSSID = ssid
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
- defer cancel()
-
- token, err := a.prompts.Ask(ctx, PromptRequest{
- SSID: ssid,
- Fields: []string{"password"},
- })
- if err != nil {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- reply, err := a.prompts.Wait(ctx, token)
- if err != nil || reply.Cancel {
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
- }
-
- if password, ok := reply.Secrets["password"]; ok {
- return password, nil
- }
-
- return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
-}
-
-func (a *IWDAgent) Cancel(reason string) *dbus.Error {
- return nil
-}
-
-func (a *IWDAgent) Release() *dbus.Error {
- return nil
-}
-
-func (a *IWDAgent) Introspect() (string, *dbus.Error) {
- return iwdAgentIntrospectXML, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager.go
deleted file mode 100644
index 088b7fa..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager.go
+++ /dev/null
@@ -1,897 +0,0 @@
-package network
-
-import (
- "context"
- "errors"
- "fmt"
- "strconv"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
-)
-
-const (
- nmAgentManagerPath = "/org/freedesktop/NetworkManager/AgentManager"
- nmAgentManagerIface = "org.freedesktop.NetworkManager.AgentManager"
- nmSecretAgentIface = "org.freedesktop.NetworkManager.SecretAgent"
- agentObjectPath = "/org/freedesktop/NetworkManager/SecretAgent"
- agentIdentifier = "com.danklinux.NMAgent"
-)
-
-type SecretAgent struct {
- conn *dbus.Conn
- objPath dbus.ObjectPath
- id string
- prompts PromptBroker
- manager *Manager
- backend *NetworkManagerBackend
-}
-
-type (
- nmVariantMap map[string]dbus.Variant
- nmSettingMap map[string]nmVariantMap
-)
-
-const introspectXML = `
-<node>
- <interface name="org.freedesktop.NetworkManager.SecretAgent">
- <method name="GetSecrets">
- <arg type="a{sa{sv}}" name="connection" direction="in"/>
- <arg type="o" name="connection_path" direction="in"/>
- <arg type="s" name="setting_name" direction="in"/>
- <arg type="as" name="hints" direction="in"/>
- <arg type="u" name="flags" direction="in"/>
- <arg type="a{sa{sv}}" name="secrets" direction="out"/>
- </method>
- <method name="DeleteSecrets">
- <arg type="a{sa{sv}}" name="connection" direction="in"/>
- <arg type="o" name="connection_path" direction="in"/>
- </method>
- <method name="DeleteSecrets2">
- <arg type="o" name="connection_path" direction="in"/>
- <arg type="s" name="setting" direction="in"/>
- </method>
- <method name="CancelGetSecrets">
- <arg type="o" name="connection_path" direction="in"/>
- <arg type="s" name="setting_name" direction="in"/>
- </method>
- </interface>
- <interface name="org.freedesktop.DBus.Introspectable">
- <method name="Introspect">
- <arg name="data" type="s" direction="out"/>
- </method>
- </interface>
-</node>`
-
-func NewSecretAgent(prompts PromptBroker, manager *Manager, backend *NetworkManagerBackend) (*SecretAgent, error) {
- c, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to system bus: %w", err)
- }
-
- sa := &SecretAgent{
- conn: c,
- objPath: dbus.ObjectPath(agentObjectPath),
- id: agentIdentifier,
- prompts: prompts,
- manager: manager,
- backend: backend,
- }
-
- if err := c.Export(sa, sa.objPath, nmSecretAgentIface); err != nil {
- c.Close()
- return nil, fmt.Errorf("failed to export secret agent: %w", err)
- }
-
- if err := c.Export(sa, sa.objPath, "org.freedesktop.DBus.Introspectable"); err != nil {
- c.Close()
- return nil, fmt.Errorf("failed to export introspection: %w", err)
- }
-
- mgr := c.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(nmAgentManagerPath))
- call := mgr.Call(nmAgentManagerIface+".Register", 0, sa.id)
- if call.Err != nil {
- c.Close()
- return nil, fmt.Errorf("failed to register agent with NetworkManager: %w", call.Err)
- }
-
- log.Infof("[SecretAgent] Registered with NetworkManager (id=%s, unique name=%s, fixed path=%s)", sa.id, c.Names()[0], sa.objPath)
- return sa, nil
-}
-
-func (a *SecretAgent) Close() {
- if a.conn != nil {
- mgr := a.conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(nmAgentManagerPath))
- mgr.Call(nmAgentManagerIface+".Unregister", 0, a.id)
- a.conn.Close()
- }
-}
-
-func (a *SecretAgent) GetSecrets(
- conn map[string]nmVariantMap,
- path dbus.ObjectPath,
- settingName string,
- hints []string,
- flags uint32,
-) (nmSettingMap, *dbus.Error) {
- log.Infof("[SecretAgent] GetSecrets called: path=%s, setting=%s, hints=%v, flags=%d",
- path, settingName, hints, flags)
-
- connType, displayName, vpnSvc := readConnTypeAndName(conn)
- ssid := readSSID(conn)
- fields := fieldsNeeded(settingName, hints, conn)
- vpnPasswordFlags := readVPNPasswordFlags(conn, settingName)
-
- log.Infof("[SecretAgent] connType=%s, name=%s, vpnSvc=%s, fields=%v, flags=%d, vpnPasswordFlags=%d", connType, displayName, vpnSvc, fields, flags, vpnPasswordFlags)
-
- if a.backend != nil {
- a.backend.stateMutex.RLock()
- isConnecting := a.backend.state.IsConnecting
- connectingSSID := a.backend.state.ConnectingSSID
- isConnectingVPN := a.backend.state.IsConnectingVPN
- connectingVPNUUID := a.backend.state.ConnectingVPNUUID
- a.backend.stateMutex.RUnlock()
-
- switch connType {
- case "802-11-wireless":
- // If we're connecting to a WiFi network, only respond if it's the one we're connecting to
- if isConnecting && connectingSSID != ssid {
- log.Infof("[SecretAgent] Ignoring WiFi request for SSID '%s' - we're connecting to '%s'", ssid, connectingSSID)
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
- }
- case "vpn", "wireguard":
- var connUuid string
- if c, ok := conn["connection"]; ok {
- if v, ok := c["uuid"]; ok {
- if s, ok2 := v.Value().(string); ok2 {
- connUuid = s
- }
- }
- }
-
- // If we're connecting to a VPN, only respond if it's the one we're connecting to
- // This prevents interfering with nmcli/other tools when our app isn't connecting
- if isConnectingVPN && connUuid != connectingVPNUUID {
- log.Infof("[SecretAgent] Ignoring VPN request for UUID '%s' - we're connecting to '%s'", connUuid, connectingVPNUUID)
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
- }
- }
- }
-
- if len(fields) == 0 {
- if settingName == "vpn" {
- if a.backend != nil {
- a.backend.stateMutex.RLock()
- isConnectingVPN := a.backend.state.IsConnectingVPN
- a.backend.stateMutex.RUnlock()
-
- if !isConnectingVPN {
- log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
- }
-
- fields = inferVPNFields(conn, vpnSvc)
- log.Infof("[SecretAgent] VPN with empty hints but we're connecting - inferred fields: %v", fields)
- } else {
- log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
- }
- }
-
- if len(fields) == 0 {
- const (
- NM_SETTING_SECRET_FLAG_NONE = 0
- NM_SETTING_SECRET_FLAG_AGENT_OWNED = 1
- NM_SETTING_SECRET_FLAG_NOT_SAVED = 2
- NM_SETTING_SECRET_FLAG_NOT_REQUIRED = 4
- )
-
- var passwordFlags uint32 = 0xFFFF
- switch settingName {
- case "802-11-wireless-security":
- if wifiSecSettings, ok := conn["802-11-wireless-security"]; ok {
- if flagsVariant, ok := wifiSecSettings["psk-flags"]; ok {
- if pwdFlags, ok := flagsVariant.Value().(uint32); ok {
- passwordFlags = pwdFlags
- }
- }
- }
- case "802-1x":
- if dot1xSettings, ok := conn["802-1x"]; ok {
- if flagsVariant, ok := dot1xSettings["password-flags"]; ok {
- if pwdFlags, ok := flagsVariant.Value().(uint32); ok {
- passwordFlags = pwdFlags
- }
- }
- }
- }
-
- if passwordFlags == 0xFFFF {
- log.Warnf("[SecretAgent] Could not determine password-flags for empty hints - returning NoSecrets error")
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
- } else if passwordFlags&NM_SETTING_SECRET_FLAG_NOT_REQUIRED != 0 {
- log.Infof("[SecretAgent] Secrets not required (flags=%d)", passwordFlags)
- out := nmSettingMap{}
- out[settingName] = nmVariantMap{}
- return out, nil
- } else if passwordFlags&NM_SETTING_SECRET_FLAG_AGENT_OWNED != 0 {
- switch settingName {
- case "802-11-wireless-security":
- fields = []string{"psk"}
- case "802-1x":
- fields = infer8021xFields(conn)
- default:
- log.Warnf("[SecretAgent] Agent-owned secrets for unhandled setting %s (flags=%d)", settingName, passwordFlags)
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
- }
- log.Infof("[SecretAgent] Agent-owned secrets, inferred fields: %v", fields)
- } else {
- log.Infof("[SecretAgent] No secrets needed, using system stored secrets (flags=%d)", passwordFlags)
- out := nmSettingMap{}
- out[settingName] = nmVariantMap{}
- return out, nil
- }
- }
- }
-
- reason := reasonFromFlags(flags)
- if a.manager != nil && connType == "802-11-wireless" && a.manager.WasRecentlyFailed(ssid) {
- reason = "wrong-password"
- }
- if settingName == "vpn" && isPKCS11Auth(conn, vpnSvc) {
- reason = "pkcs11"
- }
-
- var connId, connUuid string
- if c, ok := conn["connection"]; ok {
- if v, ok := c["id"]; ok {
- if s, ok2 := v.Value().(string); ok2 {
- connId = s
- }
- }
- if v, ok := c["uuid"]; ok {
- if s, ok2 := v.Value().(string); ok2 {
- connUuid = s
- }
- }
- }
-
- if settingName == "vpn" && a.backend != nil {
- // Check for cached PKCS11 PIN first
- isPKCS11Request := len(fields) == 1 && fields[0] == "key_pass"
- if isPKCS11Request {
- a.backend.cachedPKCS11Mu.Lock()
- cached := a.backend.cachedPKCS11PIN
- if cached != nil && cached.ConnectionUUID == connUuid {
- a.backend.cachedPKCS11PIN = nil
- a.backend.cachedPKCS11Mu.Unlock()
-
- log.Infof("[SecretAgent] Using cached PKCS11 PIN")
-
- out := nmSettingMap{}
- vpnSec := nmVariantMap{}
- vpnSec["secrets"] = dbus.MakeVariant(map[string]string{"key_pass": cached.PIN})
- out[settingName] = vpnSec
-
- return out, nil
- }
- a.backend.cachedPKCS11Mu.Unlock()
- }
-
- // Check for cached VPN password
- a.backend.cachedVPNCredsMu.Lock()
- cached := a.backend.cachedVPNCreds
- if cached != nil && cached.ConnectionUUID == connUuid {
- a.backend.cachedVPNCreds = nil
- a.backend.cachedVPNCredsMu.Unlock()
-
- log.Infof("[SecretAgent] Using cached password from pre-activation prompt")
-
- out := nmSettingMap{}
- vpnSec := nmVariantMap{}
- vpnSec["secrets"] = dbus.MakeVariant(map[string]string{"password": cached.Password})
- out[settingName] = vpnSec
-
- if cached.SavePassword {
- a.backend.pendingVPNSaveMu.Lock()
- a.backend.pendingVPNSave = &pendingVPNCredentials{
- ConnectionPath: string(path),
- Password: cached.Password,
- SavePassword: true,
- }
- a.backend.pendingVPNSaveMu.Unlock()
- }
-
- return out, nil
- }
- a.backend.cachedVPNCredsMu.Unlock()
-
- a.backend.cachedGPSamlMu.Lock()
- cachedGPSaml := a.backend.cachedGPSamlCookie
- if cachedGPSaml != nil && cachedGPSaml.ConnectionUUID == connUuid {
- a.backend.cachedGPSamlMu.Unlock()
-
- log.Infof("[SecretAgent] Using cached GlobalProtect SAML cookie for %s", connUuid)
-
- return buildGPSamlSecretsResponse(settingName, cachedGPSaml.Cookie, cachedGPSaml.Host, cachedGPSaml.Fingerprint), nil
- }
- a.backend.cachedGPSamlMu.Unlock()
-
- if len(fields) == 1 && fields[0] == "gp-saml" {
- gateway := ""
- protocol := ""
- if vpnSettings, ok := conn["vpn"]; ok {
- if dataVariant, ok := vpnSettings["data"]; ok {
- if dataMap, ok := dataVariant.Value().(map[string]string); ok {
- if gw, ok := dataMap["gateway"]; ok {
- gateway = gw
- }
- if proto, ok := dataMap["protocol"]; ok && proto != "" {
- protocol = proto
- }
- }
- }
- }
-
- if protocol != "gp" {
- return nil, dbus.MakeFailedError(fmt.Errorf("gp-saml auth only supported for GlobalProtect (protocol=gp), got: %s", protocol))
- }
-
- log.Infof("[SecretAgent] Starting GlobalProtect SAML authentication for gateway=%s", gateway)
-
- samlCtx, samlCancel := context.WithTimeout(context.Background(), 5*time.Minute)
- defer samlCancel()
-
- authResult, err := a.backend.runGlobalProtectSAMLAuth(samlCtx, gateway, protocol)
- if err != nil {
- log.Warnf("[SecretAgent] GlobalProtect SAML authentication failed: %v", err)
- return nil, dbus.MakeFailedError(fmt.Errorf("GlobalProtect SAML authentication failed: %w", err))
- }
-
- log.Infof("[SecretAgent] GlobalProtect SAML authentication successful, returning cookie to NetworkManager")
-
- a.backend.cachedGPSamlMu.Lock()
- a.backend.cachedGPSamlCookie = &cachedGPSamlCookie{
- ConnectionUUID: connUuid,
- Cookie: authResult.Cookie,
- Host: authResult.Host,
- User: authResult.User,
- Fingerprint: authResult.Fingerprint,
- }
- a.backend.cachedGPSamlMu.Unlock()
-
- return buildGPSamlSecretsResponse(settingName, authResult.Cookie, authResult.Host, authResult.Fingerprint), nil
- }
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
- defer cancel()
-
- token, err := a.prompts.Ask(ctx, PromptRequest{
- Name: displayName,
- SSID: ssid,
- ConnType: connType,
- VpnService: vpnSvc,
- SettingName: settingName,
- Fields: fields,
- FieldsInfo: buildFieldsInfo(settingName, fields, vpnSvc),
- Hints: hints,
- Reason: reason,
- ConnectionId: connId,
- ConnectionUuid: connUuid,
- ConnectionPath: string(path),
- })
- if err != nil {
- log.Warnf("[SecretAgent] Failed to create prompt: %v", err)
- return nil, dbus.MakeFailedError(err)
- }
-
- log.Infof("[SecretAgent] Waiting for user input (token=%s)", token)
- reply, err := a.prompts.Wait(ctx, token)
- if err != nil {
- log.Warnf("[SecretAgent] Prompt failed or cancelled: %v", err)
-
- // Clear connecting state immediately on cancellation
- if a.backend != nil {
- a.backend.stateMutex.Lock()
- wasConnecting := a.backend.state.IsConnecting
- wasConnectingVPN := a.backend.state.IsConnectingVPN
- cancelledSSID := a.backend.state.ConnectingSSID
- cancelledVPNUUID := a.backend.state.ConnectingVPNUUID
- if wasConnecting || wasConnectingVPN {
- log.Infof("[SecretAgent] Clearing connecting state due to cancelled prompt")
- a.backend.state.IsConnecting = false
- a.backend.state.ConnectingSSID = ""
- a.backend.state.IsConnectingVPN = false
- a.backend.state.ConnectingVPNUUID = ""
- }
- a.backend.stateMutex.Unlock()
-
- // If this was a WiFi connection that was just cancelled, remove the connection profile
- // (it was created with AddConnection but activation was cancelled)
- if wasConnecting && cancelledSSID != "" && connType == "802-11-wireless" {
- log.Infof("[SecretAgent] Removing connection profile for cancelled WiFi connection: %s", cancelledSSID)
- if err := a.backend.ForgetWiFiNetwork(cancelledSSID); err != nil {
- log.Warnf("[SecretAgent] Failed to remove cancelled connection profile: %v", err)
- }
- }
-
- // If this was a VPN connection that was cancelled, deactivate it
- if wasConnectingVPN && cancelledVPNUUID != "" {
- log.Infof("[SecretAgent] Deactivating cancelled VPN connection: %s", cancelledVPNUUID)
- if err := a.backend.DisconnectVPN(cancelledVPNUUID); err != nil {
- log.Warnf("[SecretAgent] Failed to deactivate cancelled VPN: %v", err)
- }
- }
-
- if (wasConnecting || wasConnectingVPN) && a.backend.onStateChange != nil {
- a.backend.onStateChange()
- }
- }
-
- if reply.Cancel || errors.Is(err, errdefs.ErrSecretPromptCancelled) {
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.UserCanceled", nil)
- }
-
- if errors.Is(err, errdefs.ErrSecretPromptTimeout) {
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.Failed", nil)
- }
- return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.Failed", nil)
- }
-
- log.Infof("[SecretAgent] User provided secrets, save=%v", reply.Save)
-
- out := nmSettingMap{}
- sec := nmVariantMap{}
-
- var vpnUsername string
- for k, v := range reply.Secrets {
- if settingName == "vpn" && k == "username" {
- vpnUsername = v
- }
- sec[k] = dbus.MakeVariant(v)
- }
-
- // Check if this is PKCS11 auth (key_pass)
- pin, isPKCS11 := reply.Secrets["key_pass"]
-
- switch settingName {
- case "vpn":
- // VPN secrets must be wrapped in a "secrets" key per NM spec
- secretsDict := make(map[string]string)
- for k, v := range reply.Secrets {
- if k != "username" {
- secretsDict[k] = v
- }
- }
- vpnSec := nmVariantMap{}
- vpnSec["secrets"] = dbus.MakeVariant(secretsDict)
- out[settingName] = vpnSec
- log.Infof("[SecretAgent] Returning VPN secrets with %d fields for %s", len(secretsDict), vpnSvc)
-
- // Cache PKCS11 PIN in case GetSecrets is called again during activation
- if isPKCS11 && a.backend != nil {
- a.backend.cachedPKCS11Mu.Lock()
- a.backend.cachedPKCS11PIN = &cachedPKCS11PIN{
- ConnectionUUID: connUuid,
- PIN: pin,
- }
- a.backend.cachedPKCS11Mu.Unlock()
- log.Infof("[SecretAgent] Cached PKCS11 PIN for potential re-request")
- }
- case "802-1x":
- secretsOnly := nmVariantMap{}
- for k, v := range reply.Secrets {
- switch k {
- case "password", "private-key-password", "phase2-private-key-password", "pin":
- secretsOnly[k] = dbus.MakeVariant(v)
- }
- }
- out[settingName] = secretsOnly
-
- if identity, ok := reply.Secrets["identity"]; ok && identity != "" {
- a.save8021xIdentity(path, identity)
- }
- log.Infof("[SecretAgent] Returning 802-1x enterprise secrets with %d fields", len(secretsOnly))
- default:
- out[settingName] = sec
- }
- if settingName == "vpn" && a.backend != nil && !isPKCS11 && (vpnUsername != "" || reply.Save) {
- pw := reply.Secrets["password"]
- a.backend.pendingVPNSaveMu.Lock()
- a.backend.pendingVPNSave = &pendingVPNCredentials{
- ConnectionPath: string(path),
- Username: vpnUsername,
- Password: pw,
- SavePassword: reply.Save,
- }
- a.backend.pendingVPNSaveMu.Unlock()
- log.Infof("[SecretAgent] Queued credentials persist for after connection succeeds")
- }
-
- return out, nil
-}
-
-func (a *SecretAgent) DeleteSecrets(conn map[string]nmVariantMap, path dbus.ObjectPath) *dbus.Error {
- ssid := readSSID(conn)
- log.Infof("[SecretAgent] DeleteSecrets called: path=%s, SSID=%s", path, ssid)
- return nil
-}
-
-func (a *SecretAgent) DeleteSecrets2(path dbus.ObjectPath, setting string) *dbus.Error {
- log.Infof("[SecretAgent] DeleteSecrets2 (alternate) called: path=%s, setting=%s", path, setting)
- return nil
-}
-
-func (a *SecretAgent) CancelGetSecrets(path dbus.ObjectPath, settingName string) *dbus.Error {
- log.Infof("[SecretAgent] CancelGetSecrets called: path=%s, setting=%s", path, settingName)
-
- if a.prompts != nil {
- if err := a.prompts.Cancel(string(path), settingName); err != nil {
- log.Warnf("[SecretAgent] Failed to cancel prompt: %v", err)
- }
- }
-
- return nil
-}
-
-func (a *SecretAgent) Introspect() (string, *dbus.Error) {
- return introspectXML, nil
-}
-
-func (a *SecretAgent) save8021xIdentity(path dbus.ObjectPath, identity string) {
- connObj := a.conn.Object("org.freedesktop.NetworkManager", path)
- var existing map[string]map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existing); err != nil {
- log.Warnf("[SecretAgent] Failed to get settings for identity save: %v", err)
- return
- }
-
- settings := make(map[string]map[string]dbus.Variant)
- if connSection, ok := existing["connection"]; ok {
- settings["connection"] = connSection
- }
-
- dot1x, ok := existing["802-1x"]
- if !ok {
- dot1x = make(map[string]dbus.Variant)
- }
- dot1x["identity"] = dbus.MakeVariant(identity)
- settings["802-1x"] = dot1x
-
- var result map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
- settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
- log.Warnf("[SecretAgent] Failed to save 802.1x identity: %v", err)
- return
- }
- log.Infof("[SecretAgent] Saved 802.1x identity to connection profile")
-}
-
-func readSSID(conn map[string]nmVariantMap) string {
- if w, ok := conn["802-11-wireless"]; ok {
- if v, ok := w["ssid"]; ok {
- if b, ok := v.Value().([]byte); ok {
- return string(b)
- }
- if s, ok := v.Value().(string); ok {
- return s
- }
- }
- }
- return ""
-}
-
-func readConnTypeAndName(conn map[string]nmVariantMap) (string, string, string) {
- var connType, name, svc string
- if c, ok := conn["connection"]; ok {
- if v, ok := c["type"]; ok {
- if s, ok2 := v.Value().(string); ok2 {
- connType = s
- }
- }
- if v, ok := c["id"]; ok {
- if s, ok2 := v.Value().(string); ok2 {
- name = s
- }
- }
- }
- if vpn, ok := conn["vpn"]; ok {
- if v, ok := vpn["service-type"]; ok {
- if s, ok2 := v.Value().(string); ok2 {
- svc = s
- }
- }
- }
- if name == "" && connType == "802-11-wireless" {
- name = readSSID(conn)
- }
- return connType, name, svc
-}
-
-func fieldsNeeded(setting string, hints []string, conn map[string]nmVariantMap) []string {
- switch setting {
- case "802-11-wireless-security":
- return []string{"psk"}
- case "802-1x":
- if len(hints) > 0 {
- return hints
- }
- return infer8021xFields(conn)
- case "vpn":
- return hints
- default:
- return []string{}
- }
-}
-
-func infer8021xFields(conn map[string]nmVariantMap) []string {
- dot1x, ok := conn["802-1x"]
- if !ok {
- return []string{"identity", "password"}
- }
-
- var fields []string
-
- if v, ok := dot1x["identity"]; ok {
- if id, ok := v.Value().(string); ok && id != "" {
- // identity already stored, don't ask again
- } else {
- fields = append(fields, "identity")
- }
- } else {
- fields = append(fields, "identity")
- }
-
- var eapMethods []string
- if v, ok := dot1x["eap"]; ok {
- if methods, ok := v.Value().([]string); ok {
- eapMethods = methods
- }
- }
-
- switch {
- case len(eapMethods) > 0 && eapMethods[0] == "tls":
- fields = append(fields, "private-key-password")
- default:
- fields = append(fields, "password")
- }
-
- return fields
-}
-
-func buildFieldsInfo(setting string, fields []string, vpnService string) []FieldInfo {
- result := make([]FieldInfo, 0, len(fields))
- for _, f := range fields {
- info := FieldInfo{Name: f}
- switch setting {
- case "802-11-wireless-security":
- info.Label = "Password"
- info.IsSecret = true
- case "802-1x":
- switch f {
- case "identity":
- info.Label = "Username"
- info.IsSecret = false
- case "password":
- info.Label = "Password"
- info.IsSecret = true
- default:
- info.Label = f
- info.IsSecret = true
- }
- case "vpn":
- info.Label, info.IsSecret = vpnFieldMeta(f, vpnService)
- default:
- info.Label = f
- info.IsSecret = true
- }
- result = append(result, info)
- }
- return result
-}
-
-func inferVPNFields(conn map[string]nmVariantMap, vpnService string) []string {
- fields := []string{"password"}
-
- vpnSettings, ok := conn["vpn"]
- if !ok {
- return fields
- }
-
- dataVariant, ok := vpnSettings["data"]
- if !ok {
- return fields
- }
-
- dataMap, ok := dataVariant.Value().(map[string]string)
- if !ok {
- return fields
- }
-
- connType := dataMap["connection-type"]
-
- switch {
- case strings.Contains(vpnService, "openconnect"):
- protocol := dataMap["protocol"]
- authType := dataMap["authtype"]
- username := dataMap["username"]
-
- if authType == "cert" && strings.HasPrefix(dataMap["usercert"], "pkcs11:") {
- return []string{"key_pass"}
- }
-
- if needsExternalBrowserAuth(protocol, authType, username, dataMap) {
- switch protocol {
- case "gp":
- log.Infof("[SecretAgent] GlobalProtect SAML auth detected")
- return []string{"gp-saml"}
- default:
- log.Infof("[SecretAgent] External browser auth detected for protocol '%s' but only GlobalProtect (gp) SAML is currently supported, falling back to credentials", protocol)
- }
- }
-
- if username == "" {
- fields = []string{"username", "password"}
- }
- case strings.Contains(vpnService, "openvpn"):
- if connType == "password" || connType == "password-tls" {
- if dataMap["username"] == "" {
- fields = []string{"username", "password"}
- }
- }
- case strings.Contains(vpnService, "vpnc"), strings.Contains(vpnService, "l2tp"),
- strings.Contains(vpnService, "pptp"):
- if dataMap["username"] == "" {
- fields = []string{"username", "password"}
- }
- }
-
- return fields
-}
-
-func needsExternalBrowserAuth(protocol, authType, username string, data map[string]string) bool {
- if method, ok := data["saml-auth-method"]; ok {
- if method == "REDIRECT" || method == "POST" {
- return true
- }
- }
-
- if authType != "" && authType != "password" && authType != "cert" {
- return true
- }
-
- switch protocol {
- case "gp":
- if authType == "" && username == "" {
- return true
- }
- }
-
- return false
-}
-
-func vpnFieldMeta(field, vpnService string) (label string, isSecret bool) {
- switch field {
- case "gp-saml":
- return "GlobalProtect SAML/SSO", false
- case "key_pass":
- return "PIN", true
- case "password":
- return "Password", true
- case "Xauth password":
- return "IPSec Password", true
- case "IPSec secret":
- return "IPSec Pre-Shared Key", true
- case "cert-pass":
- return "Certificate Password", true
- case "http-proxy-password":
- return "HTTP Proxy Password", true
- case "username":
- return "Username", false
- case "Xauth username":
- return "IPSec Username", false
- case "proxy-password":
- return "Proxy Password", true
- case "private-key-password":
- return "Private Key Password", true
- }
- titleCaser := cases.Title(language.English)
- if strings.HasSuffix(field, "password") || strings.HasSuffix(field, "secret") ||
- strings.HasSuffix(field, "pass") || strings.HasSuffix(field, "psk") {
- return titleCaser.String(strings.ReplaceAll(field, "-", " ")), true
- }
- return titleCaser.String(strings.ReplaceAll(field, "-", " ")), false
-}
-
-func isPKCS11Auth(conn map[string]nmVariantMap, vpnService string) bool {
- if !strings.Contains(vpnService, "openconnect") {
- return false
- }
- vpnSettings, ok := conn["vpn"]
- if !ok {
- return false
- }
- dataVariant, ok := vpnSettings["data"]
- if !ok {
- return false
- }
- dataMap, ok := dataVariant.Value().(map[string]string)
- if !ok {
- return false
- }
- return dataMap["authtype"] == "cert" && strings.HasPrefix(dataMap["usercert"], "pkcs11:")
-}
-
-func readVPNPasswordFlags(conn map[string]nmVariantMap, settingName string) uint32 {
- if settingName != "vpn" {
- return 0xFFFF
- }
-
- vpnSettings, ok := conn["vpn"]
- if !ok {
- return 0xFFFF
- }
-
- dataVariant, ok := vpnSettings["data"]
- if !ok {
- return 0xFFFF
- }
-
- dataMap, ok := dataVariant.Value().(map[string]string)
- if !ok {
- return 0xFFFF
- }
-
- flagsStr, ok := dataMap["password-flags"]
- if !ok {
- return 0xFFFF
- }
-
- flags64, err := strconv.ParseUint(flagsStr, 10, 32)
- if err != nil {
- return 0xFFFF
- }
-
- return uint32(flags64)
-}
-
-func reasonFromFlags(flags uint32) string {
- const (
- NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE = 0x0
- NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION = 0x1
- NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW = 0x2
- NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED = 0x4
- NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE = 0x8
- NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM = 0x80000000
- NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS = 0x40000000
- )
-
- if flags&NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW != 0 {
- return "wrong-password"
- }
- if flags&NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED != 0 {
- return "user-requested"
- }
- return "required"
-}
-
-func buildGPSamlSecretsResponse(settingName, cookie, host, fingerprint string) nmSettingMap {
- out := nmSettingMap{}
- vpnSec := nmVariantMap{}
-
- secrets := map[string]string{
- "cookie": cookie,
- "gateway": host,
- "gwcert": fingerprint,
- }
- vpnSec["secrets"] = dbus.MakeVariant(secrets)
-
- out[settingName] = vpnSec
- return out
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager_test.go
deleted file mode 100644
index 2040986..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/agent_networkmanager_test.go
+++ /dev/null
@@ -1,355 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNeedsExternalBrowserAuth(t *testing.T) {
- tests := []struct {
- name string
- protocol string
- authType string
- username string
- data map[string]string
- expected bool
- }{
- {
- name: "GP with saml-auth-method REDIRECT",
- protocol: "gp",
- authType: "password",
- username: "user",
- data: map[string]string{"saml-auth-method": "REDIRECT"},
- expected: true,
- },
- {
- name: "GP with saml-auth-method POST",
- protocol: "gp",
- authType: "password",
- username: "user",
- data: map[string]string{"saml-auth-method": "POST"},
- expected: true,
- },
- {
- name: "GP with no authtype and no username",
- protocol: "gp",
- authType: "",
- username: "",
- data: map[string]string{},
- expected: true,
- },
- {
- name: "GP with username and password authtype",
- protocol: "gp",
- authType: "password",
- username: "john",
- data: map[string]string{},
- expected: false,
- },
- {
- name: "GP with username but no authtype",
- protocol: "gp",
- authType: "",
- username: "john",
- data: map[string]string{},
- expected: false,
- },
- {
- name: "GP with authtype but no username - should detect SAML",
- protocol: "gp",
- authType: "",
- username: "",
- data: map[string]string{},
- expected: true,
- },
- {
- name: "pulse with SAML",
- protocol: "pulse",
- authType: "",
- username: "",
- data: map[string]string{"saml-auth-method": "REDIRECT"},
- expected: true,
- },
- {
- name: "fortinet with non-password authtype",
- protocol: "fortinet",
- authType: "saml",
- username: "",
- data: map[string]string{},
- expected: true,
- },
- {
- name: "anyconnect with cert",
- protocol: "anyconnect",
- authType: "cert",
- username: "",
- data: map[string]string{},
- expected: false,
- },
- {
- name: "anyconnect with password",
- protocol: "anyconnect",
- authType: "password",
- username: "user",
- data: map[string]string{},
- expected: false,
- },
- {
- name: "empty protocol",
- protocol: "",
- authType: "",
- username: "",
- data: map[string]string{},
- expected: false,
- },
- {
- name: "GP with cert authtype",
- protocol: "gp",
- authType: "cert",
- username: "",
- data: map[string]string{},
- expected: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := needsExternalBrowserAuth(tt.protocol, tt.authType, tt.username, tt.data)
- assert.Equal(t, tt.expected, result)
- })
- }
-}
-
-func TestBuildGPSamlSecretsResponse(t *testing.T) {
- tests := []struct {
- name string
- settingName string
- cookie string
- host string
- fingerprint string
- }{
- {
- name: "all fields populated",
- settingName: "vpn",
- cookie: "authcookie=abc123&portal=GATE",
- host: "vpn.example.com",
- fingerprint: "pin-sha256:ABCD1234",
- },
- {
- name: "empty fingerprint",
- settingName: "vpn",
- cookie: "authcookie=xyz",
- host: "10.0.0.1",
- fingerprint: "",
- },
- {
- name: "complex cookie with special chars",
- settingName: "vpn",
- cookie: "authcookie=077058d3bc81&portal=PANGP_GW_01-N&user=john.doe@example.com&domain=Default&preferred-ip=192.168.1.100",
- host: "connect.seclore.com",
- fingerprint: "pin-sha256:xp3scfzy3rOgQEXnfPiYKrUk7D66a8b8O+gEXaMPleE=",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := buildGPSamlSecretsResponse(tt.settingName, tt.cookie, tt.host, tt.fingerprint)
-
- assert.NotNil(t, result)
- assert.Contains(t, result, tt.settingName)
-
- vpnSec := result[tt.settingName]
- assert.NotNil(t, vpnSec)
-
- secretsVariant, ok := vpnSec["secrets"]
- assert.True(t, ok, "secrets key should exist")
-
- secrets, ok := secretsVariant.Value().(map[string]string)
- assert.True(t, ok, "secrets should be map[string]string")
-
- assert.Equal(t, tt.cookie, secrets["cookie"])
- assert.Equal(t, tt.host, secrets["gateway"])
- assert.Equal(t, tt.fingerprint, secrets["gwcert"])
- })
- }
-}
-
-func TestVpnFieldMeta_GPSaml(t *testing.T) {
- label, isSecret := vpnFieldMeta("gp-saml", "org.freedesktop.NetworkManager.openconnect")
-
- assert.Equal(t, "GlobalProtect SAML/SSO", label)
- assert.False(t, isSecret, "gp-saml should not be marked as secret")
-}
-
-func TestVpnFieldMeta_StandardFields(t *testing.T) {
- tests := []struct {
- field string
- vpnService string
- expectedLabel string
- expectedSecret bool
- }{
- {
- field: "username",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- expectedLabel: "Username",
- expectedSecret: false,
- },
- {
- field: "password",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- expectedLabel: "Password",
- expectedSecret: true,
- },
- {
- field: "key_pass",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- expectedLabel: "PIN",
- expectedSecret: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.field, func(t *testing.T) {
- label, isSecret := vpnFieldMeta(tt.field, tt.vpnService)
- assert.Equal(t, tt.expectedLabel, label)
- assert.Equal(t, tt.expectedSecret, isSecret)
- })
- }
-}
-
-func TestInferVPNFields_GPSaml(t *testing.T) {
- tests := []struct {
- name string
- vpnService string
- dataMap map[string]string
- expectedLen int
- shouldHave []string
- }{
- {
- name: "GP with no authtype and no username - should require SAML",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "gp",
- "gateway": "vpn.example.com",
- },
- expectedLen: 1,
- shouldHave: []string{"gp-saml"},
- },
- {
- name: "GP with saml-auth-method REDIRECT",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "gp",
- "gateway": "vpn.example.com",
- "saml-auth-method": "REDIRECT",
- "username": "john",
- },
- expectedLen: 1,
- shouldHave: []string{"gp-saml"},
- },
- {
- name: "GP with saml-auth-method POST",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "gp",
- "gateway": "vpn.example.com",
- "saml-auth-method": "POST",
- },
- expectedLen: 1,
- shouldHave: []string{"gp-saml"},
- },
- {
- name: "GP with username and password authtype - should use credentials",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "gp",
- "gateway": "vpn.example.com",
- "authtype": "password",
- "username": "john",
- },
- expectedLen: 1,
- shouldHave: []string{"password"},
- },
- {
- name: "GP with username but no authtype - password only",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "gp",
- "gateway": "vpn.example.com",
- "username": "john",
- },
- expectedLen: 1,
- shouldHave: []string{"password"},
- },
- {
- name: "GP with PKCS11 cert",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "gp",
- "gateway": "vpn.example.com",
- "authtype": "cert",
- "usercert": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II",
- },
- expectedLen: 1,
- shouldHave: []string{"key_pass"},
- },
- {
- name: "non-GP protocol (anyconnect)",
- vpnService: "org.freedesktop.NetworkManager.openconnect",
- dataMap: map[string]string{
- "protocol": "anyconnect",
- "gateway": "vpn.example.com",
- },
- expectedLen: 2,
- shouldHave: []string{"username", "password"},
- },
- {
- name: "OpenVPN with username",
- vpnService: "org.freedesktop.NetworkManager.openvpn",
- dataMap: map[string]string{
- "connection-type": "password",
- "username": "john",
- },
- expectedLen: 1,
- shouldHave: []string{"password"},
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Convert dataMap to nmVariantMap
- vpnSettings := make(nmVariantMap)
- vpnSettings["data"] = dbus.MakeVariant(tt.dataMap)
- vpnSettings["service-type"] = dbus.MakeVariant(tt.vpnService)
-
- conn := make(map[string]nmVariantMap)
- conn["vpn"] = vpnSettings
-
- fields := inferVPNFields(conn, tt.vpnService)
-
- assert.Len(t, fields, tt.expectedLen, "unexpected number of fields")
- if len(tt.shouldHave) > 0 {
- for _, expected := range tt.shouldHave {
- assert.Contains(t, fields, expected, "should contain field: %s", expected)
- }
- }
- })
- }
-}
-
-func TestNmVariantMap(t *testing.T) {
- // Test that nmVariantMap and nmSettingMap work correctly
- settingMap := make(nmSettingMap)
- variantMap := make(nmVariantMap)
-
- variantMap["test-key"] = dbus.MakeVariant("test-value")
- settingMap["test-setting"] = variantMap
-
- assert.Contains(t, settingMap, "test-setting")
- assert.Contains(t, settingMap["test-setting"], "test-key")
-
- value := settingMap["test-setting"]["test-key"].Value()
- assert.Equal(t, "test-value", value)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend.go
deleted file mode 100644
index f1bb04d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package network
-
-type Backend interface {
- Initialize() error
- Close()
-
- GetWiFiEnabled() (bool, error)
- SetWiFiEnabled(enabled bool) error
-
- ScanWiFi() error
- ScanWiFiDevice(device string) error
- GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
- GetWiFiQRCodeContent(ssid string) (string, error)
- GetWiFiDevices() []WiFiDevice
-
- ConnectWiFi(req ConnectionRequest) error
- DisconnectWiFi() error
- DisconnectWiFiDevice(device string) error
- ForgetWiFiNetwork(ssid string) error
- SetWiFiAutoconnect(ssid string, autoconnect bool) error
-
- GetEthernetDevices() []EthernetDevice
- GetWiredConnections() ([]WiredConnection, error)
- GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error)
- ConnectEthernet() error
- DisconnectEthernet() error
- DisconnectEthernetDevice(device string) error
- ActivateWiredConnection(uuid string) error
-
- ListVPNProfiles() ([]VPNProfile, error)
- ListActiveVPN() ([]VPNActive, error)
- ConnectVPN(uuidOrName string, singleActive bool) error
- DisconnectVPN(uuidOrName string) error
- DisconnectAllVPN() error
- ClearVPNCredentials(uuidOrName string) error
- ListVPNPlugins() ([]VPNPlugin, error)
- ImportVPN(filePath string, name string) (*VPNImportResult, error)
- GetVPNConfig(uuidOrName string) (*VPNConfig, error)
- UpdateVPNConfig(uuid string, updates map[string]any) error
- SetVPNCredentials(uuid string, username string, password string, save bool) error
- DeleteVPN(uuidOrName string) error
-
- GetCurrentState() (*BackendState, error)
-
- StartMonitoring(onStateChange func()) error
- StopMonitoring()
-
- GetPromptBroker() PromptBroker
- SetPromptBroker(broker PromptBroker) error
- SubmitCredentials(token string, secrets map[string]string, save bool) error
- CancelCredentials(token string) error
-}
-
-type BackendState struct {
- Backend string
- NetworkStatus NetworkStatus
- EthernetIP string
- EthernetDevice string
- EthernetConnected bool
- EthernetConnectionUuid string
- EthernetDevices []EthernetDevice
- WiFiIP string
- WiFiDevice string
- WiFiConnected bool
- WiFiEnabled bool
- WiFiSSID string
- WiFiBSSID string
- WiFiSignal uint8
- WiFiNetworks []WiFiNetwork
- WiFiDevices []WiFiDevice
- WiredConnections []WiredConnection
- VPNProfiles []VPNProfile
- VPNActive []VPNActive
- IsConnecting bool
- ConnectingSSID string
- ConnectingDevice string
- IsConnectingVPN bool
- ConnectingVPNUUID string
- LastError string
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_iwd_networkd.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_iwd_networkd.go
deleted file mode 100644
index ce883ba..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_iwd_networkd.go
+++ /dev/null
@@ -1,245 +0,0 @@
-package network
-
-import (
- "fmt"
-)
-
-type HybridIwdNetworkdBackend struct {
- wifi *IWDBackend
- l3 *SystemdNetworkdBackend
- onStateChange func()
-}
-
-func NewHybridIwdNetworkdBackend(w *IWDBackend, n *SystemdNetworkdBackend) (*HybridIwdNetworkdBackend, error) {
- return &HybridIwdNetworkdBackend{
- wifi: w,
- l3: n,
- }, nil
-}
-
-func (b *HybridIwdNetworkdBackend) Initialize() error {
- if err := b.wifi.Initialize(); err != nil {
- return fmt.Errorf("iwd init: %w", err)
- }
- if err := b.l3.Initialize(); err != nil {
- return fmt.Errorf("networkd init: %w", err)
- }
- return nil
-}
-
-func (b *HybridIwdNetworkdBackend) Close() {
- b.wifi.Close()
- b.l3.Close()
-}
-
-func (b *HybridIwdNetworkdBackend) StartMonitoring(onStateChange func()) error {
- b.onStateChange = onStateChange
-
- mergedCallback := func() {
- ws, _ := b.wifi.GetCurrentState()
- ls, _ := b.l3.GetCurrentState()
-
- if ws != nil && ls != nil && ws.WiFiDevice != "" && ls.WiFiIP != "" {
- b.wifi.MarkIPConfigSeen()
- }
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
- }
-
- if err := b.wifi.StartMonitoring(mergedCallback); err != nil {
- return fmt.Errorf("wifi monitoring: %w", err)
- }
- if err := b.l3.StartMonitoring(mergedCallback); err != nil {
- return fmt.Errorf("l3 monitoring: %w", err)
- }
-
- return nil
-}
-
-func (b *HybridIwdNetworkdBackend) StopMonitoring() {
- b.wifi.StopMonitoring()
- b.l3.StopMonitoring()
-}
-
-func (b *HybridIwdNetworkdBackend) GetCurrentState() (*BackendState, error) {
- ws, err := b.wifi.GetCurrentState()
- if err != nil {
- return nil, err
- }
- ls, err := b.l3.GetCurrentState()
- if err != nil {
- return nil, err
- }
-
- merged := *ws
- merged.Backend = "iwd+networkd"
-
- merged.WiFiIP = ls.WiFiIP
- merged.EthernetConnected = ls.EthernetConnected
- merged.EthernetIP = ls.EthernetIP
- merged.EthernetDevice = ls.EthernetDevice
- merged.EthernetConnectionUuid = ls.EthernetConnectionUuid
- merged.WiredConnections = ls.WiredConnections
- merged.EthernetDevices = ls.EthernetDevices
-
- if ls.EthernetConnected && ls.EthernetIP != "" {
- merged.NetworkStatus = StatusEthernet
- } else if ws.WiFiConnected && ls.WiFiIP != "" {
- merged.NetworkStatus = StatusWiFi
- } else {
- merged.NetworkStatus = StatusDisconnected
- }
-
- return &merged, nil
-}
-
-func (b *HybridIwdNetworkdBackend) GetWiFiEnabled() (bool, error) {
- return b.wifi.GetWiFiEnabled()
-}
-
-func (b *HybridIwdNetworkdBackend) SetWiFiEnabled(enabled bool) error {
- return b.wifi.SetWiFiEnabled(enabled)
-}
-
-func (b *HybridIwdNetworkdBackend) ScanWiFi() error {
- return b.wifi.ScanWiFi()
-}
-
-func (b *HybridIwdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) {
- return b.wifi.GetWiFiNetworkDetails(ssid)
-}
-
-func (b *HybridIwdNetworkdBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
- return b.wifi.GetWiFiQRCodeContent(ssid)
-}
-
-func (b *HybridIwdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
- if err := b.wifi.ConnectWiFi(req); err != nil {
- return err
- }
-
- ws, err := b.wifi.GetCurrentState()
- if err == nil && ws.WiFiDevice != "" {
- b.l3.EnsureDhcpUp(ws.WiFiDevice) //nolint:errcheck
- }
-
- return nil
-}
-
-func (b *HybridIwdNetworkdBackend) DisconnectWiFi() error {
- return b.wifi.DisconnectWiFi()
-}
-
-func (b *HybridIwdNetworkdBackend) ForgetWiFiNetwork(ssid string) error {
- return b.wifi.ForgetWiFiNetwork(ssid)
-}
-
-func (b *HybridIwdNetworkdBackend) GetWiredConnections() ([]WiredConnection, error) {
- return b.l3.GetWiredConnections()
-}
-
-func (b *HybridIwdNetworkdBackend) GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error) {
- return b.l3.GetWiredNetworkDetails(uuid)
-}
-
-func (b *HybridIwdNetworkdBackend) ConnectEthernet() error {
- return b.l3.ConnectEthernet()
-}
-
-func (b *HybridIwdNetworkdBackend) DisconnectEthernet() error {
- return b.l3.DisconnectEthernet()
-}
-
-func (b *HybridIwdNetworkdBackend) DisconnectEthernetDevice(device string) error {
- return b.l3.DisconnectEthernetDevice(device)
-}
-
-func (b *HybridIwdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
- return b.l3.GetEthernetDevices()
-}
-
-func (b *HybridIwdNetworkdBackend) ActivateWiredConnection(uuid string) error {
- return b.l3.ActivateWiredConnection(uuid)
-}
-
-func (b *HybridIwdNetworkdBackend) ListVPNProfiles() ([]VPNProfile, error) {
- return []VPNProfile{}, nil
-}
-
-func (b *HybridIwdNetworkdBackend) ListActiveVPN() ([]VPNActive, error) {
- return []VPNActive{}, nil
-}
-
-func (b *HybridIwdNetworkdBackend) ConnectVPN(uuidOrName string, singleActive bool) error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) DisconnectVPN(uuidOrName string) error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) DisconnectAllVPN() error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
- return []VPNPlugin{}, nil
-}
-
-func (b *HybridIwdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
- return nil, fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
- return nil, fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]any) error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) DeleteVPN(uuidOrName string) error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
-
-func (b *HybridIwdNetworkdBackend) GetPromptBroker() PromptBroker {
- return b.wifi.GetPromptBroker()
-}
-
-func (b *HybridIwdNetworkdBackend) SetPromptBroker(broker PromptBroker) error {
- return b.wifi.SetPromptBroker(broker)
-}
-
-func (b *HybridIwdNetworkdBackend) SubmitCredentials(token string, secrets map[string]string, save bool) error {
- return b.wifi.SubmitCredentials(token, secrets, save)
-}
-
-func (b *HybridIwdNetworkdBackend) CancelCredentials(token string) error {
- return b.wifi.CancelCredentials(token)
-}
-
-func (b *HybridIwdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
- return b.wifi.SetWiFiAutoconnect(ssid, autoconnect)
-}
-
-func (b *HybridIwdNetworkdBackend) ScanWiFiDevice(device string) error {
- return b.wifi.ScanWiFiDevice(device)
-}
-
-func (b *HybridIwdNetworkdBackend) DisconnectWiFiDevice(device string) error {
- return b.wifi.DisconnectWiFiDevice(device)
-}
-
-func (b *HybridIwdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
- return b.wifi.GetWiFiDevices()
-}
-
-func (b *HybridIwdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
- return fmt.Errorf("VPN not supported in hybrid mode")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_test.go
deleted file mode 100644
index cd061ed..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_hybrid_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestHybridIwdNetworkdBackend_New(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
-
- hybrid, err := NewHybridIwdNetworkdBackend(wifi, l3)
- assert.NoError(t, err)
- assert.NotNil(t, hybrid)
- assert.NotNil(t, hybrid.wifi)
- assert.NotNil(t, hybrid.l3)
-}
-
-func TestHybridIwdNetworkdBackend_GetCurrentState_MergesState(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- wifi.state.WiFiConnected = true
- wifi.state.WiFiSSID = "TestNetwork"
- wifi.state.WiFiBSSID = "00:11:22:33:44:55"
- wifi.state.WiFiSignal = 75
- wifi.state.WiFiDevice = "wlan0"
-
- l3.state.WiFiIP = "192.168.1.100"
- l3.state.EthernetConnected = false
-
- state, err := hybrid.GetCurrentState()
- assert.NoError(t, err)
- assert.NotNil(t, state)
- assert.Equal(t, "iwd+networkd", state.Backend)
- assert.Equal(t, "TestNetwork", state.WiFiSSID)
- assert.Equal(t, "00:11:22:33:44:55", state.WiFiBSSID)
- assert.Equal(t, uint8(75), state.WiFiSignal)
- assert.Equal(t, "192.168.1.100", state.WiFiIP)
- assert.True(t, state.WiFiConnected)
- assert.False(t, state.EthernetConnected)
- assert.Equal(t, StatusWiFi, state.NetworkStatus)
-}
-
-func TestHybridIwdNetworkdBackend_GetCurrentState_EthernetPriority(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- wifi.state.WiFiConnected = true
- wifi.state.WiFiSSID = "TestNetwork"
-
- l3.state.WiFiIP = "192.168.1.100"
- l3.state.EthernetConnected = true
- l3.state.EthernetIP = "192.168.1.50"
- l3.state.EthernetDevice = "eth0"
-
- state, err := hybrid.GetCurrentState()
- assert.NoError(t, err)
- assert.Equal(t, StatusEthernet, state.NetworkStatus)
- assert.Equal(t, "192.168.1.50", state.EthernetIP)
- assert.Equal(t, "eth0", state.EthernetDevice)
-}
-
-func TestHybridIwdNetworkdBackend_GetCurrentState_WiFiNoIP(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- wifi.state.WiFiConnected = true
- wifi.state.WiFiSSID = "TestNetwork"
-
- l3.state.WiFiIP = ""
- l3.state.EthernetConnected = false
-
- state, err := hybrid.GetCurrentState()
- assert.NoError(t, err)
- assert.Equal(t, StatusDisconnected, state.NetworkStatus)
- assert.True(t, state.WiFiConnected)
- assert.Empty(t, state.WiFiIP)
-}
-
-func TestHybridIwdNetworkdBackend_WiFiDelegation(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- enabled, err := hybrid.GetWiFiEnabled()
- assert.NoError(t, err)
- assert.True(t, enabled)
-
- state, err := hybrid.GetCurrentState()
- assert.NoError(t, err)
- assert.NotNil(t, state)
- assert.Equal(t, "iwd+networkd", state.Backend)
-}
-
-func TestHybridIwdNetworkdBackend_WiredDelegation(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- conns, err := hybrid.GetWiredConnections()
- assert.NoError(t, err)
- assert.Empty(t, conns)
-}
-
-func TestHybridIwdNetworkdBackend_VPNNotSupported(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- profiles, err := hybrid.ListVPNProfiles()
- assert.NoError(t, err)
- assert.Empty(t, profiles)
-
- active, err := hybrid.ListActiveVPN()
- assert.NoError(t, err)
- assert.Empty(t, active)
-
- err = hybrid.ConnectVPN("test", false)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-}
-
-func TestHybridIwdNetworkdBackend_PromptBrokerDelegation(t *testing.T) {
- wifi, _ := NewIWDBackend()
- l3, _ := NewSystemdNetworkdBackend()
- hybrid, _ := NewHybridIwdNetworkdBackend(wifi, l3)
-
- broker := hybrid.GetPromptBroker()
- assert.Nil(t, broker)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd.go
deleted file mode 100644
index f73aaf4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd.go
+++ /dev/null
@@ -1,238 +0,0 @@
-package network
-
-import (
- "fmt"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- iwdBusName = "net.connman.iwd"
- iwdObjectPath = "/"
- iwdAdapterInterface = "net.connman.iwd.Adapter"
- iwdDeviceInterface = "net.connman.iwd.Device"
- iwdStationInterface = "net.connman.iwd.Station"
- iwdNetworkInterface = "net.connman.iwd.Network"
- iwdKnownNetworkInterface = "net.connman.iwd.KnownNetwork"
- dbusObjectManager = "org.freedesktop.DBus.ObjectManager"
- dbusPropertiesInterface = "org.freedesktop.DBus.Properties"
-)
-
-type connectAttempt struct {
- ssid string
- netPath dbus.ObjectPath
- start time.Time
- deadline time.Time
- sawAuthish bool
- connectedAt time.Time
- sawIPConfig bool
- sawPromptRetry bool
- finalized bool
- mu sync.Mutex
-}
-
-type IWDBackend struct {
- conn *dbus.Conn
- state *BackendState
- stateMutex sync.RWMutex
- promptBroker PromptBroker
- onStateChange func()
-
- devicePath dbus.ObjectPath
- stationPath dbus.ObjectPath
- adapterPath dbus.ObjectPath
-
- iwdAgent *IWDAgent
-
- stopChan chan struct{}
- sigWG sync.WaitGroup
- curAttempt *connectAttempt
- attemptMutex sync.RWMutex
- recentScans map[string]time.Time
- recentScansMu sync.Mutex
-}
-
-func NewIWDBackend() (*IWDBackend, error) {
- backend := &IWDBackend{
- state: &BackendState{
- Backend: "iwd",
- WiFiEnabled: true,
- },
- stopChan: make(chan struct{}),
- recentScans: make(map[string]time.Time),
- }
-
- return backend, nil
-}
-
-func (b *IWDBackend) Initialize() error {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("failed to connect to system bus: %w", err)
- }
- b.conn = conn
-
- if err := b.discoverDevices(); err != nil {
- conn.Close()
- return fmt.Errorf("failed to discover iwd devices: %w", err)
- }
-
- if err := b.updateState(); err != nil {
- conn.Close()
- return fmt.Errorf("failed to get initial state: %w", err)
- }
-
- return nil
-}
-
-func (b *IWDBackend) Close() {
- close(b.stopChan)
- b.sigWG.Wait()
-
- if b.iwdAgent != nil {
- b.iwdAgent.Close()
- }
-
- if b.conn != nil {
- b.conn.Close()
- }
-}
-
-func (b *IWDBackend) discoverDevices() error {
- obj := b.conn.Object(iwdBusName, iwdObjectPath)
-
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
- err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
- if err != nil {
- return fmt.Errorf("failed to get managed objects: %w", err)
- }
-
- for path, interfaces := range objects {
- if _, hasStation := interfaces[iwdStationInterface]; hasStation {
- b.stationPath = path
- }
- if _, hasDevice := interfaces[iwdDeviceInterface]; hasDevice {
- b.devicePath = path
-
- if devProps, ok := interfaces[iwdDeviceInterface]; ok {
- if nameVar, ok := devProps["Name"]; ok {
- if name, ok := nameVar.Value().(string); ok {
- b.stateMutex.Lock()
- b.state.WiFiDevice = name
- b.stateMutex.Unlock()
- }
- }
- }
- }
- if _, hasAdapter := interfaces[iwdAdapterInterface]; hasAdapter {
- b.adapterPath = path
- }
- }
-
- if b.stationPath == "" || b.devicePath == "" {
- return fmt.Errorf("no WiFi device found")
- }
-
- return nil
-}
-
-func (b *IWDBackend) GetCurrentState() (*BackendState, error) {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
-
- state := *b.state
- state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
- state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
- state.WiFiDevices = b.getWiFiDevicesLocked()
-
- return &state, nil
-}
-
-func (b *IWDBackend) OnUserCanceledPrompt() {
- b.stateMutex.RLock()
- cancelledSSID := b.state.ConnectingSSID
- b.stateMutex.RUnlock()
-
- b.setConnectError("user-canceled")
-
- if cancelledSSID != "" {
- if err := b.ForgetWiFiNetwork(cancelledSSID); err != nil {
- log.Warnf("failed to forget cancelled WiFi network %s: %v", cancelledSSID, err)
- }
- }
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-}
-
-func (b *IWDBackend) OnPromptRetry(ssid string) {
- b.attemptMutex.RLock()
- att := b.curAttempt
- b.attemptMutex.RUnlock()
-
- if att != nil && att.ssid == ssid {
- att.mu.Lock()
- att.sawPromptRetry = true
- att.mu.Unlock()
- }
-}
-
-func (b *IWDBackend) MarkIPConfigSeen() {
- b.attemptMutex.RLock()
- att := b.curAttempt
- b.attemptMutex.RUnlock()
- if att != nil {
- att.mu.Lock()
- att.sawIPConfig = true
- att.mu.Unlock()
- }
-}
-
-func (b *IWDBackend) GetPromptBroker() PromptBroker {
- return b.promptBroker
-}
-
-func (b *IWDBackend) SetPromptBroker(broker PromptBroker) error {
- if broker == nil {
- return fmt.Errorf("broker cannot be nil")
- }
-
- b.promptBroker = broker
- return nil
-}
-
-func (b *IWDBackend) SubmitCredentials(token string, secrets map[string]string, save bool) error {
- if b.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- return b.promptBroker.Resolve(token, PromptReply{
- Secrets: secrets,
- Save: save,
- Cancel: false,
- })
-}
-
-func (b *IWDBackend) CancelCredentials(token string) error {
- if b.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- return b.promptBroker.Resolve(token, PromptReply{
- Cancel: true,
- })
-}
-
-func (b *IWDBackend) StopMonitoring() {
- select {
- case <-b.stopChan:
- return
- default:
- close(b.stopChan)
- }
- b.sigWG.Wait()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_signals.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_signals.go
deleted file mode 100644
index bde4753..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_signals.go
+++ /dev/null
@@ -1,355 +0,0 @@
-package network
-
-import (
- "fmt"
- "time"
-
- "github.com/godbus/dbus/v5"
-)
-
-func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
- b.onStateChange = onStateChange
-
- if b.promptBroker != nil {
- agent, err := NewIWDAgent(b.conn, b.promptBroker)
- if err != nil {
- return fmt.Errorf("failed to start IWD agent: %w", err)
- }
- agent.onUserCanceled = b.OnUserCanceledPrompt
- agent.onPromptRetry = b.OnPromptRetry
- b.iwdAgent = agent
- }
-
- sigChan := make(chan *dbus.Signal, 100)
- b.conn.Signal(sigChan)
-
- if b.devicePath != "" {
- err := b.conn.AddMatchSignal(
- dbus.WithMatchObjectPath(b.devicePath),
- dbus.WithMatchInterface(dbusPropertiesInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- if err != nil {
- return fmt.Errorf("failed to add device signal match: %w", err)
- }
- }
-
- if b.stationPath != "" {
- err := b.conn.AddMatchSignal(
- dbus.WithMatchObjectPath(b.stationPath),
- dbus.WithMatchInterface(dbusPropertiesInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- if err != nil {
- return fmt.Errorf("failed to add station signal match: %w", err)
- }
- }
-
- b.sigWG.Add(1)
- go b.signalHandler(sigChan)
-
- return nil
-}
-
-func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
- defer b.sigWG.Done()
-
- for {
- select {
- case <-b.stopChan:
- b.conn.RemoveSignal(sigChan)
- close(sigChan)
- return
-
- case sig := <-sigChan:
- if sig == nil {
- return
- }
-
- if sig.Name != dbusPropertiesInterface+".PropertiesChanged" {
- continue
- }
-
- if len(sig.Body) < 2 {
- continue
- }
-
- iface, ok := sig.Body[0].(string)
- if !ok {
- continue
- }
-
- changed, ok := sig.Body[1].(map[string]dbus.Variant)
- if !ok {
- continue
- }
-
- stateChanged := false
-
- switch iface {
- case iwdDeviceInterface:
- if sig.Path == b.devicePath {
- if poweredVar, ok := changed["Powered"]; ok {
- if powered, ok := poweredVar.Value().(bool); ok {
- b.stateMutex.Lock()
- if b.state.WiFiEnabled != powered {
- b.state.WiFiEnabled = powered
- stateChanged = true
- }
- b.stateMutex.Unlock()
- }
- }
- }
-
- case iwdStationInterface:
- if sig.Path == b.stationPath {
- if scanningVar, ok := changed["Scanning"]; ok {
- if scanning, ok := scanningVar.Value().(bool); ok && !scanning {
- networks, err := b.updateWiFiNetworks()
- if err == nil {
- b.stateMutex.Lock()
- b.state.WiFiNetworks = networks
- b.stateMutex.Unlock()
- stateChanged = true
- }
-
- b.stateMutex.RLock()
- wifiConnected := b.state.WiFiConnected
- b.stateMutex.RUnlock()
-
- if wifiConnected {
- stationObj := b.conn.Object(iwdBusName, b.stationPath)
- connNetVar, err := stationObj.GetProperty(iwdStationInterface + ".ConnectedNetwork")
- if err == nil && connNetVar.Value() != nil {
- if netPath, ok := connNetVar.Value().(dbus.ObjectPath); ok && netPath != "/" {
- var orderedNetworks [][]dbus.Variant
- err = stationObj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
- if err == nil {
- for _, netData := range orderedNetworks {
- if len(netData) < 2 {
- continue
- }
- currentNetPath, ok := netData[0].Value().(dbus.ObjectPath)
- if !ok || currentNetPath != netPath {
- continue
- }
- signalStrength, ok := netData[1].Value().(int16)
- if !ok {
- continue
- }
- signalDbm := signalStrength / 100
- signal := uint8(signalDbm + 100)
- if signalDbm > 0 {
- signal = 100
- } else if signalDbm < -100 {
- signal = 0
- }
- b.stateMutex.Lock()
- if b.state.WiFiSignal != signal {
- b.state.WiFiSignal = signal
- stateChanged = true
- }
- b.stateMutex.Unlock()
- break
- }
- }
- }
- }
- }
- }
- }
-
- if stateVar, ok := changed["State"]; ok {
- if state, ok := stateVar.Value().(string); ok {
- b.attemptMutex.RLock()
- att := b.curAttempt
- b.attemptMutex.RUnlock()
-
- var connPath dbus.ObjectPath
- if v, ok := changed["ConnectedNetwork"]; ok {
- if v.Value() != nil {
- if p, ok := v.Value().(dbus.ObjectPath); ok {
- connPath = p
- }
- }
- }
- if connPath == "" {
- station := b.conn.Object(iwdBusName, b.stationPath)
- if cnVar, err := station.GetProperty(iwdStationInterface + ".ConnectedNetwork"); err == nil && cnVar.Value() != nil {
- cnVar.Store(&connPath)
- }
- }
-
- b.stateMutex.RLock()
- prevConnected := b.state.WiFiConnected
- prevSSID := b.state.WiFiSSID
- b.stateMutex.RUnlock()
-
- targetPath := dbus.ObjectPath("")
- if att != nil {
- targetPath = att.netPath
- }
-
- isTarget := att != nil && targetPath != "" && connPath == targetPath
-
- if att != nil {
- switch state {
- case "authenticating", "associating", "associated", "roaming":
- att.mu.Lock()
- att.sawAuthish = true
- att.mu.Unlock()
- }
- }
-
- if att != nil && state == "connected" && isTarget {
- att.mu.Lock()
- if att.connectedAt.IsZero() {
- att.connectedAt = time.Now()
- }
- att.mu.Unlock()
- }
-
- if att != nil && state == "configuring" {
- att.mu.Lock()
- att.sawIPConfig = true
- att.mu.Unlock()
- }
-
- switch state {
- case "connected":
- b.stateMutex.Lock()
- b.state.WiFiConnected = true
- b.state.NetworkStatus = StatusWiFi
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.LastError = ""
- b.stateMutex.Unlock()
-
- if connPath != "" && connPath != "/" {
- netObj := b.conn.Object(iwdBusName, connPath)
- if nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name"); err == nil {
- if name, ok := nameVar.Value().(string); ok {
- b.stateMutex.Lock()
- b.state.WiFiSSID = name
- b.stateMutex.Unlock()
- }
- }
- }
-
- stateChanged = true
-
- if att != nil && isTarget {
- go func(attLocal *connectAttempt, tgt dbus.ObjectPath) {
- time.Sleep(3 * time.Second)
- station := b.conn.Object(iwdBusName, b.stationPath)
- var nowState string
- if stVar, err := station.GetProperty(iwdStationInterface + ".State"); err == nil {
- stVar.Store(&nowState)
- }
- var nowConn dbus.ObjectPath
- if cnVar, err := station.GetProperty(iwdStationInterface + ".ConnectedNetwork"); err == nil && cnVar.Value() != nil {
- cnVar.Store(&nowConn)
- }
-
- if nowState == "connected" && nowConn == tgt {
- b.finalizeAttempt(attLocal, "")
- b.attemptMutex.Lock()
- if b.curAttempt == attLocal {
- b.curAttempt = nil
- }
- b.attemptMutex.Unlock()
- }
- }(att, targetPath)
- }
-
- case "disconnecting", "disconnected":
- if att != nil {
- wasConnectedToTarget := prevConnected && prevSSID == att.ssid
- if wasConnectedToTarget || isTarget {
- code := b.classifyAttempt(att)
- b.finalizeAttempt(att, code)
- b.attemptMutex.Lock()
- if b.curAttempt == att {
- b.curAttempt = nil
- }
- b.attemptMutex.Unlock()
- }
- }
-
- b.stateMutex.Lock()
- b.state.WiFiConnected = false
- if state == "disconnected" {
- b.state.NetworkStatus = StatusDisconnected
- }
- b.stateMutex.Unlock()
- stateChanged = true
- }
- }
- }
-
- if connNetVar, ok := changed["ConnectedNetwork"]; ok {
- if netPath, ok := connNetVar.Value().(dbus.ObjectPath); ok && netPath != "/" {
- netObj := b.conn.Object(iwdBusName, netPath)
- nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name")
- if err == nil {
- if name, ok := nameVar.Value().(string); ok {
- b.stateMutex.Lock()
- if b.state.WiFiSSID != name {
- b.state.WiFiSSID = name
- stateChanged = true
- }
- b.stateMutex.Unlock()
- }
- }
-
- stationObj := b.conn.Object(iwdBusName, b.stationPath)
- var orderedNetworks [][]dbus.Variant
- err = stationObj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
- if err == nil {
- for _, netData := range orderedNetworks {
- if len(netData) < 2 {
- continue
- }
- currentNetPath, ok := netData[0].Value().(dbus.ObjectPath)
- if !ok || currentNetPath != netPath {
- continue
- }
- signalStrength, ok := netData[1].Value().(int16)
- if !ok {
- continue
- }
- signalDbm := signalStrength / 100
- signal := uint8(signalDbm + 100)
- if signalDbm > 0 {
- signal = 100
- } else if signalDbm < -100 {
- signal = 0
- }
- b.stateMutex.Lock()
- if b.state.WiFiSignal != signal {
- b.state.WiFiSignal = signal
- stateChanged = true
- }
- b.stateMutex.Unlock()
- break
- }
- }
- } else {
- b.stateMutex.Lock()
- if b.state.WiFiSSID != "" {
- b.state.WiFiSSID = ""
- b.state.WiFiSignal = 0
- stateChanged = true
- }
- b.stateMutex.Unlock()
- }
- }
- }
- }
-
- if stateChanged && b.onStateChange != nil {
- b.onStateChange()
- }
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_test.go
deleted file mode 100644
index 829e413..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_test.go
+++ /dev/null
@@ -1,212 +0,0 @@
-package network
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestIWDBackend_MarkIPConfigSeen(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/net/connman/iwd/0/1/test",
- start: time.Now(),
- deadline: time.Now().Add(15 * time.Second),
- }
-
- backend.attemptMutex.Lock()
- backend.curAttempt = att
- backend.attemptMutex.Unlock()
-
- backend.MarkIPConfigSeen()
-
- att.mu.Lock()
- assert.True(t, att.sawIPConfig, "sawIPConfig should be true after MarkIPConfigSeen")
- att.mu.Unlock()
-}
-
-func TestIWDBackend_MarkIPConfigSeen_NoAttempt(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- backend.attemptMutex.Lock()
- backend.curAttempt = nil
- backend.attemptMutex.Unlock()
-
- backend.MarkIPConfigSeen()
-}
-
-func TestIWDBackend_OnPromptRetry(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/net/connman/iwd/0/1/test",
- start: time.Now(),
- deadline: time.Now().Add(15 * time.Second),
- }
-
- backend.attemptMutex.Lock()
- backend.curAttempt = att
- backend.attemptMutex.Unlock()
-
- backend.OnPromptRetry("TestNetwork")
-
- att.mu.Lock()
- assert.True(t, att.sawPromptRetry, "sawPromptRetry should be true after OnPromptRetry")
- att.mu.Unlock()
-}
-
-func TestIWDBackend_OnPromptRetry_WrongSSID(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/net/connman/iwd/0/1/test",
- start: time.Now(),
- deadline: time.Now().Add(15 * time.Second),
- }
-
- backend.attemptMutex.Lock()
- backend.curAttempt = att
- backend.attemptMutex.Unlock()
-
- backend.OnPromptRetry("DifferentNetwork")
-
- att.mu.Lock()
- assert.False(t, att.sawPromptRetry, "sawPromptRetry should remain false for different SSID")
- att.mu.Unlock()
-}
-
-func TestIWDBackend_ClassifyAttempt_BadCredentials_PromptRetry(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/test",
- start: time.Now().Add(-5 * time.Second),
- deadline: time.Now().Add(10 * time.Second),
- sawPromptRetry: true,
- }
-
- code := backend.classifyAttempt(att)
- assert.Equal(t, "bad-credentials", code)
-}
-
-func TestIWDBackend_ClassifyAttempt_DhcpTimeout(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/test",
- start: time.Now().Add(-13 * time.Second),
- deadline: time.Now().Add(2 * time.Second),
- sawAuthish: true,
- sawIPConfig: false,
- }
-
- code := backend.classifyAttempt(att)
- assert.Equal(t, "dhcp-timeout", code)
-}
-
-func TestIWDBackend_ClassifyAttempt_AssocTimeout(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/test",
- start: time.Now().Add(-5 * time.Second),
- deadline: time.Now().Add(10 * time.Second),
- }
-
- backend.recentScansMu.Lock()
- backend.recentScans["TestNetwork"] = time.Now()
- backend.recentScansMu.Unlock()
-
- code := backend.classifyAttempt(att)
- assert.Equal(t, "assoc-timeout", code)
-}
-
-func TestIWDBackend_ClassifyAttempt_NoSuchSSID(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/test",
- start: time.Now().Add(-5 * time.Second),
- deadline: time.Now().Add(10 * time.Second),
- }
-
- code := backend.classifyAttempt(att)
- assert.Equal(t, "no-such-ssid", code)
-}
-
-func TestIWDBackend_MapIwdDBusError(t *testing.T) {
- backend, _ := NewIWDBackend()
-
- testCases := []struct {
- name string
- expected string
- }{
- {"net.connman.iwd.Error.AlreadyConnected", "already-connected"},
- {"net.connman.iwd.Error.AuthenticationFailed", "bad-credentials"},
- {"net.connman.iwd.Error.InvalidKey", "bad-credentials"},
- {"net.connman.iwd.Error.IncorrectPassphrase", "bad-credentials"},
- {"net.connman.iwd.Error.NotFound", "no-such-ssid"},
- {"net.connman.iwd.Error.NotSupported", "connection-failed"},
- {"net.connman.iwd.Agent.Error.Canceled", "user-canceled"},
- {"net.connman.iwd.Error.Unknown", "connection-failed"},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- code := backend.mapIwdDBusError(tc.name)
- assert.Equal(t, tc.expected, code)
- })
- }
-}
-
-func TestConnectAttempt_Finalization(t *testing.T) {
- backend, _ := NewIWDBackend()
- backend.state = &BackendState{}
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/test",
- start: time.Now(),
- deadline: time.Now().Add(15 * time.Second),
- }
-
- backend.finalizeAttempt(att, "bad-credentials")
-
- att.mu.Lock()
- assert.True(t, att.finalized)
- att.mu.Unlock()
-
- backend.stateMutex.RLock()
- assert.False(t, backend.state.IsConnecting)
- assert.Empty(t, backend.state.ConnectingSSID)
- assert.Equal(t, "bad-credentials", backend.state.LastError)
- backend.stateMutex.RUnlock()
-}
-
-func TestConnectAttempt_DoubleFinalization(t *testing.T) {
- backend, _ := NewIWDBackend()
- backend.state = &BackendState{}
-
- att := &connectAttempt{
- ssid: "TestNetwork",
- netPath: "/test",
- start: time.Now(),
- deadline: time.Now().Add(15 * time.Second),
- }
-
- backend.finalizeAttempt(att, "bad-credentials")
- backend.finalizeAttempt(att, "dhcp-timeout")
-
- backend.stateMutex.RLock()
- assert.Equal(t, "bad-credentials", backend.state.LastError)
- backend.stateMutex.RUnlock()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_unimplemented.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_unimplemented.go
deleted file mode 100644
index f5a8b03..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_unimplemented.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package network
-
-import (
- "fmt"
- "os"
-)
-
-func (b *IWDBackend) GetWiredConnections() ([]WiredConnection, error) {
- return nil, fmt.Errorf("wired connections not supported by iwd")
-}
-
-func (b *IWDBackend) GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error) {
- return nil, fmt.Errorf("wired connections not supported by iwd")
-}
-
-func (b *IWDBackend) ConnectEthernet() error {
- return fmt.Errorf("wired connections not supported by iwd")
-}
-
-func (b *IWDBackend) DisconnectEthernet() error {
- return fmt.Errorf("wired connections not supported by iwd")
-}
-
-func (b *IWDBackend) DisconnectEthernetDevice(device string) error {
- return fmt.Errorf("wired connections not supported by iwd")
-}
-
-func (b *IWDBackend) GetEthernetDevices() []EthernetDevice {
- return []EthernetDevice{}
-}
-
-func (b *IWDBackend) ActivateWiredConnection(uuid string) error {
- return fmt.Errorf("wired connections not supported by iwd")
-}
-
-func (b *IWDBackend) ListVPNProfiles() ([]VPNProfile, error) {
- return nil, fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) ListActiveVPN() ([]VPNActive, error) {
- return nil, fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) ConnectVPN(uuidOrName string, singleActive bool) error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) DisconnectVPN(uuidOrName string) error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) DisconnectAllVPN() error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) ListVPNPlugins() ([]VPNPlugin, error) {
- return nil, fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
- return nil, fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
- return nil, fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) UpdateVPNConfig(uuid string, updates map[string]any) error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) DeleteVPN(uuidOrName string) error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
- return fmt.Errorf("VPN not supported by iwd backend")
-}
-
-func (b *IWDBackend) ScanWiFiDevice(device string) error {
- return b.ScanWiFi()
-}
-
-func (b *IWDBackend) DisconnectWiFiDevice(device string) error {
- return b.DisconnectWiFi()
-}
-
-func (b *IWDBackend) GetWiFiDevices() []WiFiDevice {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- return b.getWiFiDevicesLocked()
-}
-
-func (b *IWDBackend) getWiFiDevicesLocked() []WiFiDevice {
- if b.state.WiFiDevice == "" {
- return nil
- }
-
- stateStr := "disconnected"
- if b.state.WiFiConnected {
- stateStr = "connected"
- }
-
- return []WiFiDevice{{
- Name: b.state.WiFiDevice,
- State: stateStr,
- Connected: b.state.WiFiConnected,
- SSID: b.state.WiFiSSID,
- Signal: b.state.WiFiSignal,
- IP: b.state.WiFiIP,
- Networks: b.state.WiFiNetworks,
- }}
-}
-
-func (b *IWDBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
- path := iwdConfigPath(ssid)
-
- data, err := os.ReadFile(path)
- if err != nil {
- return "", fmt.Errorf("no saved iwd config for `%s`: %w", ssid, err)
- }
-
- passphrase, err := parseIWDPassphrase(string(data))
- if err != nil {
- return "", fmt.Errorf("failed to read passphrase for `%s`: %w", ssid, err)
- }
-
- return FormatWiFiQRString("WPA", ssid, passphrase), nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_wifi.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_wifi.go
deleted file mode 100644
index b3bea13..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_iwd_wifi.go
+++ /dev/null
@@ -1,662 +0,0 @@
-package network
-
-import (
- "fmt"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/godbus/dbus/v5"
-)
-
-func (b *IWDBackend) updateState() error {
- if b.devicePath == "" {
- return nil
- }
-
- obj := b.conn.Object(iwdBusName, b.devicePath)
-
- poweredVar, err := obj.GetProperty(iwdDeviceInterface + ".Powered")
- if err == nil {
- if powered, ok := poweredVar.Value().(bool); ok {
- b.stateMutex.Lock()
- b.state.WiFiEnabled = powered
- b.stateMutex.Unlock()
- }
- }
-
- if b.stationPath == "" {
- return nil
- }
-
- stationObj := b.conn.Object(iwdBusName, b.stationPath)
-
- stateVar, err := stationObj.GetProperty(iwdStationInterface + ".State")
- if err == nil {
- if state, ok := stateVar.Value().(string); ok {
- b.stateMutex.Lock()
- b.state.WiFiConnected = (state == "connected")
- if state == "connected" {
- b.state.NetworkStatus = StatusWiFi
- } else {
- b.state.NetworkStatus = StatusDisconnected
- }
- b.stateMutex.Unlock()
- }
- }
-
- connNetVar, err := stationObj.GetProperty(iwdStationInterface + ".ConnectedNetwork")
- if err == nil && connNetVar.Value() != nil {
- if netPath, ok := connNetVar.Value().(dbus.ObjectPath); ok && netPath != "/" {
- netObj := b.conn.Object(iwdBusName, netPath)
-
- nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name")
- if err == nil {
- if name, ok := nameVar.Value().(string); ok {
- b.stateMutex.Lock()
- b.state.WiFiSSID = name
- b.stateMutex.Unlock()
- }
- }
-
- var orderedNetworks [][]dbus.Variant
- err = stationObj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
- if err == nil {
- for _, netData := range orderedNetworks {
- if len(netData) < 2 {
- continue
- }
- currentNetPath, ok := netData[0].Value().(dbus.ObjectPath)
- if !ok || currentNetPath != netPath {
- continue
- }
- signalStrength, ok := netData[1].Value().(int16)
- if !ok {
- continue
- }
- signalDbm := signalStrength / 100
- signal := uint8(signalDbm + 100)
- if signalDbm > 0 {
- signal = 100
- } else if signalDbm < -100 {
- signal = 0
- }
- b.stateMutex.Lock()
- b.state.WiFiSignal = signal
- b.stateMutex.Unlock()
- break
- }
- }
- }
- }
-
- networks, err := b.updateWiFiNetworks()
- if err == nil {
- b.stateMutex.Lock()
- b.state.WiFiNetworks = networks
- b.stateMutex.Unlock()
- }
-
- return nil
-}
-
-func (b *IWDBackend) GetWiFiEnabled() (bool, error) {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- return b.state.WiFiEnabled, nil
-}
-
-func (b *IWDBackend) SetWiFiEnabled(enabled bool) error {
- if b.devicePath == "" {
- return fmt.Errorf("no WiFi device available")
- }
-
- obj := b.conn.Object(iwdBusName, b.devicePath)
- call := obj.Call(dbusPropertiesInterface+".Set", 0, iwdDeviceInterface, "Powered", dbus.MakeVariant(enabled))
- if call.Err != nil {
- return fmt.Errorf("failed to set WiFi enabled: %w", call.Err)
- }
-
- b.stateMutex.Lock()
- b.state.WiFiEnabled = enabled
- b.stateMutex.Unlock()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *IWDBackend) ScanWiFi() error {
- if b.stationPath == "" {
- return fmt.Errorf("no WiFi device available")
- }
-
- obj := b.conn.Object(iwdBusName, b.stationPath)
-
- scanningVar, err := obj.GetProperty(iwdStationInterface + ".Scanning")
- if err != nil {
- return fmt.Errorf("failed to check scanning state: %w", err)
- }
-
- if scanning, ok := scanningVar.Value().(bool); ok && scanning {
- return fmt.Errorf("scan already in progress")
- }
-
- call := obj.Call(iwdStationInterface+".Scan", 0)
- if call.Err != nil {
- return fmt.Errorf("scan request failed: %w", call.Err)
- }
-
- return nil
-}
-
-func (b *IWDBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
- if b.stationPath == "" {
- return nil, fmt.Errorf("no WiFi device available")
- }
-
- obj := b.conn.Object(iwdBusName, b.stationPath)
-
- var orderedNetworks [][]dbus.Variant
- err := obj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
- if err != nil {
- return nil, fmt.Errorf("failed to get networks: %w", err)
- }
-
- knownNetworks, err := b.getKnownNetworks()
- if err != nil {
- knownNetworks = make(map[string]bool)
- }
-
- autoconnectMap, err := b.getAutoconnectSettings()
- if err != nil {
- autoconnectMap = make(map[string]bool)
- }
-
- b.stateMutex.RLock()
- currentSSID := b.state.WiFiSSID
- wifiConnected := b.state.WiFiConnected
- b.stateMutex.RUnlock()
-
- networks := make([]WiFiNetwork, 0, len(orderedNetworks))
- for _, netData := range orderedNetworks {
- if len(netData) < 2 {
- continue
- }
-
- networkPath, ok := netData[0].Value().(dbus.ObjectPath)
- if !ok {
- continue
- }
-
- signalStrength, ok := netData[1].Value().(int16)
- if !ok {
- continue
- }
-
- netObj := b.conn.Object(iwdBusName, networkPath)
-
- nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name")
- if err != nil {
- continue
- }
- name, ok := nameVar.Value().(string)
- if !ok {
- continue
- }
-
- typeVar, err := netObj.GetProperty(iwdNetworkInterface + ".Type")
- if err != nil {
- continue
- }
- netType, ok := typeVar.Value().(string)
- if !ok {
- continue
- }
-
- signalDbm := signalStrength / 100
- signal := uint8(signalDbm + 100)
- if signalDbm > 0 {
- signal = 100
- } else if signalDbm < -100 {
- signal = 0
- }
-
- secured := netType != "open"
-
- network := WiFiNetwork{
- SSID: name,
- Signal: signal,
- Secured: secured,
- Connected: wifiConnected && name == currentSSID,
- Saved: knownNetworks[name],
- Autoconnect: autoconnectMap[name],
- Enterprise: netType == "8021x",
- }
-
- networks = append(networks, network)
- }
-
- sortWiFiNetworks(networks)
-
- b.stateMutex.Lock()
- b.state.WiFiNetworks = networks
- b.stateMutex.Unlock()
-
- now := time.Now()
- b.recentScansMu.Lock()
- for _, net := range networks {
- b.recentScans[net.SSID] = now
- }
- b.recentScansMu.Unlock()
-
- return networks, nil
-}
-
-func (b *IWDBackend) getKnownNetworks() (map[string]bool, error) {
- obj := b.conn.Object(iwdBusName, iwdObjectPath)
-
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
- err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
- if err != nil {
- return nil, err
- }
-
- known := make(map[string]bool)
- for _, interfaces := range objects {
- if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
- if nameVar, ok := knownProps["Name"]; ok {
- if name, ok := nameVar.Value().(string); ok {
- known[name] = true
- }
- }
- }
- }
-
- return known, nil
-}
-
-func (b *IWDBackend) getAutoconnectSettings() (map[string]bool, error) {
- obj := b.conn.Object(iwdBusName, iwdObjectPath)
-
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
- err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
- if err != nil {
- return nil, err
- }
-
- autoconnectMap := make(map[string]bool)
- for _, interfaces := range objects {
- if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
- if nameVar, ok := knownProps["Name"]; ok {
- if name, ok := nameVar.Value().(string); ok {
- autoconnect := true
- if acVar, ok := knownProps["AutoConnect"]; ok {
- if ac, ok := acVar.Value().(bool); ok {
- autoconnect = ac
- }
- }
- autoconnectMap[name] = autoconnect
- }
- }
- }
- }
-
- return autoconnectMap, nil
-}
-
-func (b *IWDBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) {
- b.stateMutex.RLock()
- networks := b.state.WiFiNetworks
- b.stateMutex.RUnlock()
-
- var found *WiFiNetwork
- for i := range networks {
- if networks[i].SSID == ssid {
- found = &networks[i]
- break
- }
- }
-
- if found == nil {
- return nil, fmt.Errorf("network not found: %s", ssid)
- }
-
- return &NetworkInfoResponse{
- SSID: ssid,
- Bands: []WiFiNetwork{*found},
- }, nil
-}
-
-func (b *IWDBackend) setConnectError(code string) {
- b.stateMutex.Lock()
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.LastError = code
- b.stateMutex.Unlock()
-}
-
-func (b *IWDBackend) seenInRecentScan(ssid string) bool {
- b.recentScansMu.Lock()
- defer b.recentScansMu.Unlock()
- lastSeen, ok := b.recentScans[ssid]
- return ok && time.Since(lastSeen) < 30*time.Second
-}
-
-func (b *IWDBackend) classifyAttempt(att *connectAttempt) string {
- att.mu.Lock()
- defer att.mu.Unlock()
-
- if att.sawPromptRetry {
- return errdefs.ErrBadCredentials
- }
-
- if !att.connectedAt.IsZero() && !att.sawIPConfig {
- connDuration := time.Since(att.connectedAt)
- if connDuration > 500*time.Millisecond && connDuration < 3*time.Second {
- return errdefs.ErrBadCredentials
- }
- }
-
- if (att.sawAuthish || !att.connectedAt.IsZero()) && !att.sawIPConfig {
- if time.Since(att.start) > 12*time.Second {
- return errdefs.ErrDhcpTimeout
- }
- }
-
- if !att.sawAuthish && att.connectedAt.IsZero() {
- if !b.seenInRecentScan(att.ssid) {
- return errdefs.ErrNoSuchSSID
- }
- return errdefs.ErrAssocTimeout
- }
-
- return errdefs.ErrAssocTimeout
-}
-
-func (b *IWDBackend) finalizeAttempt(att *connectAttempt, code string) {
- att.mu.Lock()
- if att.finalized {
- att.mu.Unlock()
- return
- }
- att.finalized = true
- att.mu.Unlock()
-
- b.stateMutex.Lock()
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.LastError = code
- b.stateMutex.Unlock()
-
- b.updateState()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-}
-
-func (b *IWDBackend) startAttemptWatchdog(att *connectAttempt) {
- b.sigWG.Add(1)
- go func() {
- defer b.sigWG.Done()
-
- ticker := time.NewTicker(250 * time.Millisecond)
- defer ticker.Stop()
-
- for {
- select {
- case <-ticker.C:
- att.mu.Lock()
- finalized := att.finalized
- att.mu.Unlock()
-
- if finalized || time.Now().After(att.deadline) {
- if !finalized {
- b.finalizeAttempt(att, b.classifyAttempt(att))
- }
- return
- }
-
- station := b.conn.Object(iwdBusName, b.stationPath)
- stVar, err := station.GetProperty(iwdStationInterface + ".State")
- if err != nil {
- continue
- }
- state, _ := stVar.Value().(string)
-
- cnVar, err := station.GetProperty(iwdStationInterface + ".ConnectedNetwork")
- if err != nil {
- continue
- }
- var connPath dbus.ObjectPath
- if cnVar.Value() != nil {
- connPath, _ = cnVar.Value().(dbus.ObjectPath)
- }
-
- att.mu.Lock()
- if connPath == att.netPath && state == "connected" && att.connectedAt.IsZero() {
- att.connectedAt = time.Now()
- }
- if state == "configuring" {
- att.sawIPConfig = true
- }
- att.mu.Unlock()
-
- case <-b.stopChan:
- return
- }
- }
- }()
-}
-
-func (b *IWDBackend) mapIwdDBusError(name string) string {
- switch name {
- case "net.connman.iwd.Error.AlreadyConnected":
- return errdefs.ErrAlreadyConnected
- case "net.connman.iwd.Error.AuthenticationFailed",
- "net.connman.iwd.Error.InvalidKey",
- "net.connman.iwd.Error.IncorrectPassphrase":
- return errdefs.ErrBadCredentials
- case "net.connman.iwd.Error.NotFound":
- return errdefs.ErrNoSuchSSID
- case "net.connman.iwd.Error.NotSupported":
- return errdefs.ErrConnectionFailed
- case "net.connman.iwd.Agent.Error.Canceled":
- return errdefs.ErrUserCanceled
- default:
- return errdefs.ErrConnectionFailed
- }
-}
-
-func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
- if b.stationPath == "" {
- b.setConnectError(errdefs.ErrWifiDisabled)
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return fmt.Errorf("no WiFi device available")
- }
-
- networkPath, err := b.findNetworkPath(req.SSID)
- if err != nil {
- b.setConnectError(errdefs.ErrNoSuchSSID)
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return fmt.Errorf("network not found: %w", err)
- }
-
- att := &connectAttempt{
- ssid: req.SSID,
- netPath: networkPath,
- start: time.Now(),
- deadline: time.Now().Add(15 * time.Second),
- }
-
- b.attemptMutex.Lock()
- b.curAttempt = att
- b.attemptMutex.Unlock()
-
- b.stateMutex.Lock()
- b.state.IsConnecting = true
- b.state.ConnectingSSID = req.SSID
- b.state.LastError = ""
- b.stateMutex.Unlock()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- netObj := b.conn.Object(iwdBusName, networkPath)
- go func() {
- call := netObj.Call(iwdNetworkInterface+".Connect", 0)
- if call.Err != nil {
- var code string
- if dbusErr, ok := call.Err.(dbus.Error); ok {
- code = b.mapIwdDBusError(dbusErr.Name)
- } else if dbusErrPtr, ok := call.Err.(*dbus.Error); ok {
- code = b.mapIwdDBusError(dbusErrPtr.Name)
- } else {
- code = errdefs.ErrConnectionFailed
- }
-
- att.mu.Lock()
- if att.sawPromptRetry {
- code = errdefs.ErrBadCredentials
- }
- att.mu.Unlock()
-
- b.finalizeAttempt(att, code)
- return
- }
-
- b.startAttemptWatchdog(att)
- }()
-
- return nil
-}
-
-func (b *IWDBackend) findNetworkPath(ssid string) (dbus.ObjectPath, error) {
- obj := b.conn.Object(iwdBusName, iwdObjectPath)
-
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
- err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
- if err != nil {
- return "", err
- }
-
- for path, interfaces := range objects {
- if netProps, ok := interfaces[iwdNetworkInterface]; ok {
- if nameVar, ok := netProps["Name"]; ok {
- if name, ok := nameVar.Value().(string); ok && name == ssid {
- return path, nil
- }
- }
- }
- }
-
- return "", fmt.Errorf("network not found")
-}
-
-func (b *IWDBackend) DisconnectWiFi() error {
- if b.stationPath == "" {
- return fmt.Errorf("no WiFi device available")
- }
-
- obj := b.conn.Object(iwdBusName, b.stationPath)
- call := obj.Call(iwdStationInterface+".Disconnect", 0)
- if call.Err != nil {
- return fmt.Errorf("failed to disconnect: %w", call.Err)
- }
-
- b.updateState()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *IWDBackend) ForgetWiFiNetwork(ssid string) error {
- b.stateMutex.RLock()
- currentSSID := b.state.WiFiSSID
- isConnected := b.state.WiFiConnected
- b.stateMutex.RUnlock()
-
- obj := b.conn.Object(iwdBusName, iwdObjectPath)
-
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
- err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
- if err != nil {
- return err
- }
-
- for path, interfaces := range objects {
- if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
- if nameVar, ok := knownProps["Name"]; ok {
- if name, ok := nameVar.Value().(string); ok && name == ssid {
- knownObj := b.conn.Object(iwdBusName, path)
- call := knownObj.Call(iwdKnownNetworkInterface+".Forget", 0)
- if call.Err != nil {
- return fmt.Errorf("failed to forget network: %w", call.Err)
- }
-
- if isConnected && currentSSID == ssid {
- b.stateMutex.Lock()
- b.state.WiFiConnected = false
- b.state.WiFiSSID = ""
- b.state.WiFiSignal = 0
- b.state.WiFiIP = ""
- b.state.NetworkStatus = StatusDisconnected
- b.stateMutex.Unlock()
- }
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
- }
- }
- }
- }
-
- return fmt.Errorf("network not found")
-}
-
-func (b *IWDBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
- obj := b.conn.Object(iwdBusName, iwdObjectPath)
-
- var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
- err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
- if err != nil {
- return err
- }
-
- for path, interfaces := range objects {
- if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
- if nameVar, ok := knownProps["Name"]; ok {
- if name, ok := nameVar.Value().(string); ok && name == ssid {
- knownObj := b.conn.Object(iwdBusName, path)
- call := knownObj.Call(dbusPropertiesInterface+".Set", 0, iwdKnownNetworkInterface, "AutoConnect", dbus.MakeVariant(autoconnect))
- if call.Err != nil {
- return fmt.Errorf("failed to set autoconnect: %w", call.Err)
- }
-
- b.updateState()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
- }
- }
- }
- }
-
- return fmt.Errorf("network not found")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd.go
deleted file mode 100644
index 802cbf0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd.go
+++ /dev/null
@@ -1,301 +0,0 @@
-package network
-
-import (
- "fmt"
- "net"
- "strings"
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- networkdBusName = "org.freedesktop.network1"
- networkdManagerPath = "/org/freedesktop/network1"
- networkdManagerIface = "org.freedesktop.network1.Manager"
- networkdLinkIface = "org.freedesktop.network1.Link"
-)
-
-type linkInfo struct {
- ifindex int32
- name string
- path dbus.ObjectPath
- opState string
-}
-
-type SystemdNetworkdBackend struct {
- conn *dbus.Conn
- managerPath dbus.ObjectPath
- links map[string]*linkInfo
- linksMutex sync.RWMutex
- state *BackendState
- stateMutex sync.RWMutex
- onStateChange func()
- stopChan chan struct{}
- signals chan *dbus.Signal
- sigWG sync.WaitGroup
-}
-
-func NewSystemdNetworkdBackend() (*SystemdNetworkdBackend, error) {
- return &SystemdNetworkdBackend{
- managerPath: networkdManagerPath,
- links: make(map[string]*linkInfo),
- state: &BackendState{
- Backend: "networkd",
- WiFiNetworks: []WiFiNetwork{},
- },
- stopChan: make(chan struct{}),
- }, nil
-}
-
-func (b *SystemdNetworkdBackend) Initialize() error {
- c, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("connect bus: %w", err)
- }
- b.conn = c
-
- if err := b.enumerateLinks(); err != nil {
- c.Close()
- return fmt.Errorf("enumerate links: %w", err)
- }
-
- if err := b.updateState(); err != nil {
- c.Close()
- return fmt.Errorf("update initial state: %w", err)
- }
-
- return nil
-}
-
-func (b *SystemdNetworkdBackend) Close() {
- close(b.stopChan)
- b.StopMonitoring()
-
- if b.conn != nil {
- b.conn.Close()
- }
-}
-
-func (b *SystemdNetworkdBackend) enumerateLinks() error {
- obj := b.conn.Object(networkdBusName, b.managerPath)
-
- var links []struct {
- Ifindex int32
- Name string
- Path dbus.ObjectPath
- }
- err := obj.Call(networkdManagerIface+".ListLinks", 0).Store(&links)
- if err != nil {
- return fmt.Errorf("ListLinks: %w", err)
- }
-
- b.linksMutex.Lock()
- defer b.linksMutex.Unlock()
-
- for _, l := range links {
- b.links[l.Name] = &linkInfo{
- ifindex: l.Ifindex,
- name: l.Name,
- path: l.Path,
- }
- log.Debugf("networkd: enumerated link %s (ifindex=%d, path=%s)", l.Name, l.Ifindex, l.Path)
- }
-
- return nil
-}
-
-func (b *SystemdNetworkdBackend) updateState() error {
- b.linksMutex.RLock()
- defer b.linksMutex.RUnlock()
-
- var wiredIface *linkInfo
- var wifiIface *linkInfo
-
- for name, link := range b.links {
- if b.isVirtualInterface(name) {
- continue
- }
-
- linkObj := b.conn.Object(networkdBusName, link.path)
- opStateVar, err := linkObj.GetProperty(networkdLinkIface + ".OperationalState")
- if err == nil {
- if opState, ok := opStateVar.Value().(string); ok {
- link.opState = opState
- }
- }
-
- if strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
- if wifiIface == nil || link.opState == "routable" || link.opState == "carrier" {
- wifiIface = link
- }
- } else if !b.isVirtualInterface(name) {
- if wiredIface == nil || link.opState == "routable" || link.opState == "carrier" {
- wiredIface = link
- }
- }
- }
-
- var wiredConns []WiredConnection
- var ethernetDevices []EthernetDevice
- for name, link := range b.links {
- if b.isVirtualInterface(name) || strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
- continue
- }
-
- active := link.opState == "routable" || link.opState == "carrier"
- wiredConns = append(wiredConns, WiredConnection{
- Path: link.path,
- ID: name,
- UUID: "wired:" + name,
- Type: "ethernet",
- IsActive: active,
- })
-
- var ip string
- var hwAddr string
- if iface, err := net.InterfaceByName(name); err == nil {
- hwAddr = iface.HardwareAddr.String()
- if addrs := b.getAddresses(name); len(addrs) > 0 {
- ip = addrs[0]
- }
- }
-
- stateStr := "disconnected"
- switch link.opState {
- case "routable":
- stateStr = "routable"
- case "carrier":
- stateStr = "carrier"
- case "degraded":
- stateStr = "degraded"
- case "no-carrier":
- stateStr = "no-carrier"
- case "off":
- stateStr = "off"
- }
-
- ethernetDevices = append(ethernetDevices, EthernetDevice{
- Name: name,
- HwAddress: hwAddr,
- State: stateStr,
- Connected: active,
- IP: ip,
- })
- }
-
- b.stateMutex.Lock()
- defer b.stateMutex.Unlock()
-
- b.state.NetworkStatus = StatusDisconnected
- b.state.EthernetConnected = false
- b.state.EthernetIP = ""
- b.state.WiFiConnected = false
- b.state.WiFiIP = ""
- b.state.WiredConnections = wiredConns
- b.state.EthernetDevices = ethernetDevices
-
- if wiredIface != nil {
- b.state.EthernetDevice = wiredIface.name
- log.Debugf("networkd: wired interface %s opState=%s", wiredIface.name, wiredIface.opState)
- if wiredIface.opState == "routable" || wiredIface.opState == "carrier" {
- b.state.EthernetConnected = true
- b.state.NetworkStatus = StatusEthernet
-
- if addrs := b.getAddresses(wiredIface.name); len(addrs) > 0 {
- b.state.EthernetIP = addrs[0]
- log.Debugf("networkd: ethernet IP %s on %s", addrs[0], wiredIface.name)
- }
- }
- }
-
- if wifiIface != nil {
- b.state.WiFiDevice = wifiIface.name
- log.Debugf("networkd: wifi interface %s opState=%s", wifiIface.name, wifiIface.opState)
- if wifiIface.opState == "routable" || wifiIface.opState == "carrier" {
- b.state.WiFiConnected = true
-
- if addrs := b.getAddresses(wifiIface.name); len(addrs) > 0 {
- b.state.WiFiIP = addrs[0]
- log.Debugf("networkd: wifi IP %s on %s", addrs[0], wifiIface.name)
- if b.state.NetworkStatus == StatusDisconnected {
- b.state.NetworkStatus = StatusWiFi
- }
- }
- }
- }
-
- return nil
-}
-
-func (b *SystemdNetworkdBackend) isVirtualInterface(name string) bool {
- virtualPrefixes := []string{
- "lo", "docker", "veth", "virbr", "br-", "vnet", "tun", "tap",
- "vboxnet", "vmnet", "kube", "cni", "flannel", "cali",
- }
- for _, prefix := range virtualPrefixes {
- if strings.HasPrefix(name, prefix) {
- return true
- }
- }
- return false
-}
-
-func (b *SystemdNetworkdBackend) getAddresses(ifname string) []string {
- iface, err := net.InterfaceByName(ifname)
- if err != nil {
- return nil
- }
-
- addrs, err := iface.Addrs()
- if err != nil {
- return nil
- }
-
- var result []string
- for _, addr := range addrs {
- if ipnet, ok := addr.(*net.IPNet); ok {
- if ipv4 := ipnet.IP.To4(); ipv4 != nil {
- result = append(result, ipv4.String())
- }
- }
- }
- return result
-}
-
-func (b *SystemdNetworkdBackend) GetCurrentState() (*BackendState, error) {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- s := *b.state
- return &s, nil
-}
-
-func (b *SystemdNetworkdBackend) GetPromptBroker() PromptBroker {
- return nil
-}
-
-func (b *SystemdNetworkdBackend) SetPromptBroker(broker PromptBroker) error {
- return nil
-}
-
-func (b *SystemdNetworkdBackend) SubmitCredentials(token string, secrets map[string]string, save bool) error {
- return fmt.Errorf("credentials not needed by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) CancelCredentials(token string) error {
- return fmt.Errorf("credentials not needed by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) EnsureDhcpUp(ifname string) error {
- b.linksMutex.RLock()
- link, exists := b.links[ifname]
- b.linksMutex.RUnlock()
-
- if !exists {
- return fmt.Errorf("interface %s not found", ifname)
- }
-
- linkObj := b.conn.Object(networkdBusName, link.path)
- return linkObj.Call(networkdLinkIface+".Reconfigure", 0).Err
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_ethernet.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_ethernet.go
deleted file mode 100644
index 8f9b225..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_ethernet.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package network
-
-import (
- "fmt"
- "net"
- "strings"
-)
-
-func (b *SystemdNetworkdBackend) GetWiredConnections() ([]WiredConnection, error) {
- b.linksMutex.RLock()
- defer b.linksMutex.RUnlock()
-
- var conns []WiredConnection
- for name, link := range b.links {
- if b.isVirtualInterface(name) || strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
- continue
- }
-
- active := link.opState == "routable" || link.opState == "carrier"
- conns = append(conns, WiredConnection{
- Path: link.path,
- ID: name,
- UUID: "wired:" + name,
- Type: "ethernet",
- IsActive: active,
- })
- }
-
- return conns, nil
-}
-
-func (b *SystemdNetworkdBackend) GetWiredNetworkDetails(id string) (*WiredNetworkInfoResponse, error) {
- ifname := strings.TrimPrefix(id, "wired:")
-
- b.linksMutex.RLock()
- _, exists := b.links[ifname]
- b.linksMutex.RUnlock()
-
- if !exists {
- return nil, fmt.Errorf("interface %s not found", ifname)
- }
-
- iface, err := net.InterfaceByName(ifname)
- if err != nil {
- return nil, fmt.Errorf("get interface: %w", err)
- }
-
- addrs, _ := iface.Addrs()
- var ipv4s, ipv6s []string
- for _, addr := range addrs {
- if ipnet, ok := addr.(*net.IPNet); ok {
- if ipv4 := ipnet.IP.To4(); ipv4 != nil {
- ipv4s = append(ipv4s, ipnet.String())
- } else if ipv6 := ipnet.IP.To16(); ipv6 != nil {
- ipv6s = append(ipv6s, ipnet.String())
- }
- }
- }
-
- return &WiredNetworkInfoResponse{
- UUID: id,
- IFace: ifname,
- HwAddr: iface.HardwareAddr.String(),
- IPv4: WiredIPConfig{
- IPs: ipv4s,
- },
- IPv6: WiredIPConfig{
- IPs: ipv6s,
- },
- }, nil
-}
-
-func (b *SystemdNetworkdBackend) ConnectEthernet() error {
- b.linksMutex.RLock()
- var primaryWired *linkInfo
- for name, l := range b.links {
- if strings.HasPrefix(name, "lo") || strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
- continue
- }
- primaryWired = l
- break
- }
- b.linksMutex.RUnlock()
-
- if primaryWired == nil {
- return fmt.Errorf("no wired interface found")
- }
-
- linkObj := b.conn.Object(networkdBusName, primaryWired.path)
- return linkObj.Call(networkdLinkIface+".Reconfigure", 0).Err
-}
-
-func (b *SystemdNetworkdBackend) DisconnectEthernet() error {
- return fmt.Errorf("not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ActivateWiredConnection(id string) error {
- ifname := strings.TrimPrefix(id, "wired:")
-
- b.linksMutex.RLock()
- link, exists := b.links[ifname]
- b.linksMutex.RUnlock()
-
- if !exists {
- return fmt.Errorf("interface %s not found", ifname)
- }
-
- linkObj := b.conn.Object(networkdBusName, link.path)
- return linkObj.Call(networkdLinkIface+".Reconfigure", 0).Err
-}
-
-func (b *SystemdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- return append([]EthernetDevice(nil), b.state.EthernetDevices...)
-}
-
-func (b *SystemdNetworkdBackend) DisconnectEthernetDevice(device string) error {
- return fmt.Errorf("not supported by networkd backend")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_signals.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_signals.go
deleted file mode 100644
index dc642ed..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_signals.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package network
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-func (b *SystemdNetworkdBackend) StartMonitoring(onStateChange func()) error {
- b.onStateChange = onStateChange
-
- b.signals = make(chan *dbus.Signal, 64)
- b.conn.Signal(b.signals)
-
- matchRules := []string{
- "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path_namespace='/org/freedesktop/network1'",
- "type='signal',interface='org.freedesktop.network1.Manager'",
- }
-
- for _, rule := range matchRules {
- if err := b.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule).Err; err != nil {
- return fmt.Errorf("add match %q: %w", rule, err)
- }
- }
-
- b.sigWG.Add(1)
- go b.signalLoop()
-
- return nil
-}
-
-func (b *SystemdNetworkdBackend) StopMonitoring() {
- b.sigWG.Wait()
-}
-
-func (b *SystemdNetworkdBackend) signalLoop() {
- defer b.sigWG.Done()
-
- for {
- select {
- case <-b.stopChan:
- return
- case sig := <-b.signals:
- if sig == nil {
- continue
- }
-
- if sig.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
- if len(sig.Body) < 2 {
- continue
- }
- iface, ok := sig.Body[0].(string)
- if !ok || iface != networkdLinkIface {
- continue
- }
-
- b.enumerateLinks()
- if err := b.updateState(); err != nil {
- log.Warnf("networkd state update failed: %v", err)
- }
- if b.onStateChange != nil {
- b.onStateChange()
- }
- }
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_test.go
deleted file mode 100644
index 18c5281..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestSystemdNetworkdBackend_New(t *testing.T) {
- backend, err := NewSystemdNetworkdBackend()
- assert.NoError(t, err)
- assert.NotNil(t, backend)
- assert.Equal(t, "networkd", backend.state.Backend)
- assert.NotNil(t, backend.links)
- assert.NotNil(t, backend.stopChan)
-}
-
-func TestSystemdNetworkdBackend_GetCurrentState(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
- backend.state.NetworkStatus = StatusEthernet
- backend.state.EthernetConnected = true
- backend.state.EthernetIP = "192.168.1.100"
-
- state, err := backend.GetCurrentState()
- assert.NoError(t, err)
- assert.NotNil(t, state)
- assert.Equal(t, StatusEthernet, state.NetworkStatus)
- assert.True(t, state.EthernetConnected)
- assert.Equal(t, "192.168.1.100", state.EthernetIP)
-}
-
-func TestSystemdNetworkdBackend_WiFiNotSupported(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- err := backend.ScanWiFi()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- req := ConnectionRequest{SSID: "test"}
- err = backend.ConnectWiFi(req)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- err = backend.DisconnectWiFi()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- err = backend.ForgetWiFiNetwork("test")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- _, err = backend.GetWiFiNetworkDetails("test")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-}
-
-func TestSystemdNetworkdBackend_VPNNotSupported(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- profiles, err := backend.ListVPNProfiles()
- assert.NoError(t, err)
- assert.Empty(t, profiles)
-
- active, err := backend.ListActiveVPN()
- assert.NoError(t, err)
- assert.Empty(t, active)
-
- err = backend.ConnectVPN("test", false)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- err = backend.DisconnectVPN("test")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- err = backend.DisconnectAllVPN()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-
- err = backend.ClearVPNCredentials("test")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-}
-
-func TestSystemdNetworkdBackend_PromptBroker(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- broker := backend.GetPromptBroker()
- assert.Nil(t, broker)
-
- err := backend.SetPromptBroker(nil)
- assert.NoError(t, err)
-
- err = backend.SubmitCredentials("token", nil, false)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not needed")
-
- err = backend.CancelCredentials("token")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not needed")
-}
-
-func TestSystemdNetworkdBackend_GetWiFiEnabled(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- enabled, err := backend.GetWiFiEnabled()
- assert.NoError(t, err)
- assert.True(t, enabled)
-}
-
-func TestSystemdNetworkdBackend_SetWiFiEnabled(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- err := backend.SetWiFiEnabled(false)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-}
-
-func TestSystemdNetworkdBackend_DisconnectEthernet(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- err := backend.DisconnectEthernet()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-}
-
-func TestSystemdNetworkdBackend_GetEthernetDevices(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- backend.state.EthernetDevices = []EthernetDevice{
- {Name: "enp0s3", State: "routable", Connected: true},
- {Name: "enp0s8", State: "no-carrier", Connected: false},
- }
-
- devices := backend.GetEthernetDevices()
- assert.Len(t, devices, 2)
- assert.Equal(t, "enp0s3", devices[0].Name)
- assert.True(t, devices[0].Connected)
-}
-
-func TestSystemdNetworkdBackend_DisconnectEthernetDevice(t *testing.T) {
- backend, _ := NewSystemdNetworkdBackend()
-
- err := backend.DisconnectEthernetDevice("enp0s3")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not supported")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_unimplemented.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_unimplemented.go
deleted file mode 100644
index 695c3c5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkd_unimplemented.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package network
-
-import "fmt"
-
-func (b *SystemdNetworkdBackend) GetWiFiEnabled() (bool, error) {
- return true, nil
-}
-
-func (b *SystemdNetworkdBackend) SetWiFiEnabled(enabled bool) error {
- return fmt.Errorf("WiFi control not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ScanWiFi() error {
- return fmt.Errorf("WiFi scan not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) {
- return nil, fmt.Errorf("WiFi details not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
- return "", fmt.Errorf("WiFi QR Code not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
- return fmt.Errorf("WiFi connect not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) DisconnectWiFi() error {
- return fmt.Errorf("WiFi disconnect not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ForgetWiFiNetwork(ssid string) error {
- return fmt.Errorf("WiFi forget not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ListVPNProfiles() ([]VPNProfile, error) {
- return []VPNProfile{}, nil
-}
-
-func (b *SystemdNetworkdBackend) ListActiveVPN() ([]VPNActive, error) {
- return []VPNActive{}, nil
-}
-
-func (b *SystemdNetworkdBackend) ConnectVPN(uuidOrName string, singleActive bool) error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) DisconnectVPN(uuidOrName string) error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) DisconnectAllVPN() error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
- return []VPNPlugin{}, nil
-}
-
-func (b *SystemdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
- return nil, fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
- return nil, fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]any) error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) DeleteVPN(uuidOrName string) error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
- return fmt.Errorf("VPN not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
- return fmt.Errorf("WiFi autoconnect not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) ScanWiFiDevice(device string) error {
- return fmt.Errorf("WiFi scan not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) DisconnectWiFiDevice(device string) error {
- return fmt.Errorf("WiFi disconnect not supported by networkd backend")
-}
-
-func (b *SystemdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager.go
deleted file mode 100644
index 2233975..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager.go
+++ /dev/null
@@ -1,405 +0,0 @@
-package network
-
-import (
- "fmt"
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/Wifx/gonetworkmanager/v2"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- dbusNMPath = "/org/freedesktop/NetworkManager"
- dbusNMInterface = "org.freedesktop.NetworkManager"
- dbusNMDeviceInterface = "org.freedesktop.NetworkManager.Device"
- dbusNMWiredInterface = "org.freedesktop.NetworkManager.Device.Wired"
- dbusNMWirelessInterface = "org.freedesktop.NetworkManager.Device.Wireless"
- dbusNMAccessPointInterface = "org.freedesktop.NetworkManager.AccessPoint"
- dbusPropsInterface = "org.freedesktop.DBus.Properties"
-
- NmDeviceStateReasonWrongPassword = 8
- NmDeviceStateReasonSupplicantTimeout = 24
- NmDeviceStateReasonSupplicantFailed = 25
- NmDeviceStateReasonSecretsRequired = 7
- NmDeviceStateReasonNoSecrets = 6
- NmDeviceStateReasonNoSsid = 10
- NmDeviceStateReasonDhcpClientFailed = 14
- NmDeviceStateReasonIpConfigUnavailable = 18
- NmDeviceStateReasonSupplicantDisconnect = 23
- NmDeviceStateReasonCarrier = 40
- NmDeviceStateReasonNewActivation = 60
-)
-
-type wifiDeviceInfo struct {
- device gonetworkmanager.Device
- wireless gonetworkmanager.DeviceWireless
- name string
- hwAddress string
-}
-
-type ethernetDeviceInfo struct {
- device gonetworkmanager.Device
- wired gonetworkmanager.DeviceWired
- name string
- hwAddress string
-}
-
-type NetworkManagerBackend struct {
- nmConn any
- ethernetDevice any
- ethernetDevices map[string]*ethernetDeviceInfo
- wifiDevice any
- settings any
- wifiDev any
- wifiDevices map[string]*wifiDeviceInfo
-
- dbusConn *dbus.Conn
- signals chan *dbus.Signal
- sigWG sync.WaitGroup
- stopChan chan struct{}
-
- secretAgent *SecretAgent
- promptBroker PromptBroker
-
- state *BackendState
- stateMutex sync.RWMutex
-
- lastFailedSSID string
- lastFailedTime int64
- failedMutex sync.RWMutex
-
- pendingVPNSave *pendingVPNCredentials
- pendingVPNSaveMu sync.Mutex
- cachedVPNCreds *cachedVPNCredentials
- cachedVPNCredsMu sync.Mutex
- cachedPKCS11PIN *cachedPKCS11PIN
- cachedPKCS11Mu sync.Mutex
- cachedGPSamlCookie *cachedGPSamlCookie
- cachedGPSamlMu sync.Mutex
-
- onStateChange func()
-}
-
-type pendingVPNCredentials struct {
- ConnectionPath string
- Username string
- Password string
- SavePassword bool
-}
-
-type cachedVPNCredentials struct {
- ConnectionUUID string
- Password string
- SavePassword bool
-}
-
-type cachedPKCS11PIN struct {
- ConnectionUUID string
- PIN string
-}
-
-type cachedGPSamlCookie struct {
- ConnectionUUID string
- Cookie string
- Host string
- User string
- Fingerprint string
-}
-
-func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*NetworkManagerBackend, error) {
- var nm gonetworkmanager.NetworkManager
- var err error
-
- if len(nmConn) > 0 && nmConn[0] != nil {
- // Use injected connection (for testing)
- nm = nmConn[0]
- } else {
- // Create real connection
- nm, err = gonetworkmanager.NewNetworkManager()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to NetworkManager: %w", err)
- }
- }
-
- backend := &NetworkManagerBackend{
- nmConn: nm,
- stopChan: make(chan struct{}),
- ethernetDevices: make(map[string]*ethernetDeviceInfo),
- wifiDevices: make(map[string]*wifiDeviceInfo),
- state: &BackendState{
- Backend: "networkmanager",
- },
- }
-
- return backend, nil
-}
-
-func (b *NetworkManagerBackend) Initialize() error {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- if s, err := gonetworkmanager.NewSettings(); err == nil {
- b.settings = s
- }
-
- devices, err := nm.GetDevices()
- if err != nil {
- return fmt.Errorf("failed to get devices: %w", err)
- }
-
- for _, dev := range devices {
- devType, err := dev.GetPropertyDeviceType()
- if err != nil {
- continue
- }
-
- switch devType {
- case gonetworkmanager.NmDeviceTypeEthernet:
- if managed, _ := dev.GetPropertyManaged(); !managed {
- continue
- }
- iface, err := dev.GetPropertyInterface()
- if err != nil {
- continue
- }
- w, err := gonetworkmanager.NewDeviceWired(dev.GetPath())
- if err != nil {
- continue
- }
- hwAddr, _ := w.GetPropertyHwAddress()
-
- b.ethernetDevices[iface] = &ethernetDeviceInfo{
- device: dev,
- wired: w,
- name: iface,
- hwAddress: hwAddr,
- }
-
- if b.ethernetDevice == nil {
- b.ethernetDevice = dev
- }
- if err := b.updateEthernetState(); err != nil {
- continue
- }
- _, err = b.listEthernetConnections()
- if err != nil {
- return fmt.Errorf("failed to get wired configurations: %w", err)
- }
-
- case gonetworkmanager.NmDeviceTypeWifi:
- iface, err := dev.GetPropertyInterface()
- if err != nil {
- continue
- }
- w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath())
- if err != nil {
- continue
- }
- hwAddr, _ := w.GetPropertyHwAddress()
-
- b.wifiDevices[iface] = &wifiDeviceInfo{
- device: dev,
- wireless: w,
- name: iface,
- hwAddress: hwAddr,
- }
-
- if b.wifiDevice == nil {
- b.wifiDevice = dev
- b.wifiDev = w
- }
- }
- }
-
- wifiEnabled, err := nm.GetPropertyWirelessEnabled()
- if err == nil {
- b.stateMutex.Lock()
- b.state.WiFiEnabled = wifiEnabled
- b.stateMutex.Unlock()
- }
-
- if err := b.updateWiFiState(); err != nil {
- log.Warnf("Failed to update WiFi state: %v", err)
- }
-
- if wifiEnabled {
- if _, err := b.updateWiFiNetworks(); err != nil {
- log.Warnf("Failed to get initial networks: %v", err)
- }
- b.updateAllWiFiDevices()
- }
-
- b.updateAllEthernetDevices()
-
- if err := b.updatePrimaryConnection(); err != nil {
- return err
- }
-
- if _, err := b.ListVPNProfiles(); err != nil {
- log.Warnf("Failed to get initial VPN profiles: %v", err)
- }
-
- if _, err := b.ListActiveVPN(); err != nil {
- log.Warnf("Failed to get initial active VPNs: %v", err)
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) Close() {
- close(b.stopChan)
- b.StopMonitoring()
-
- if b.secretAgent != nil {
- b.secretAgent.Close()
- }
-}
-
-func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
-
- state := *b.state
- state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
- state.WiFiDevices = append([]WiFiDevice(nil), b.state.WiFiDevices...)
- state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
- state.EthernetDevices = append([]EthernetDevice(nil), b.state.EthernetDevices...)
- state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
- state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
-
- return &state, nil
-}
-
-func (b *NetworkManagerBackend) StartMonitoring(onStateChange func()) error {
- b.onStateChange = onStateChange
-
- if err := b.startSecretAgent(); err != nil {
- return fmt.Errorf("failed to start secret agent: %w", err)
- }
-
- if err := b.startSignalPump(); err != nil {
- return err
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) StopMonitoring() {
- b.stopSignalPump()
-}
-
-func (b *NetworkManagerBackend) GetPromptBroker() PromptBroker {
- return b.promptBroker
-}
-
-func (b *NetworkManagerBackend) SetPromptBroker(broker PromptBroker) error {
- if broker == nil {
- return fmt.Errorf("broker cannot be nil")
- }
-
- hadAgent := b.secretAgent != nil
-
- b.promptBroker = broker
-
- if b.secretAgent != nil {
- b.secretAgent.Close()
- b.secretAgent = nil
- }
-
- if hadAgent {
- return b.startSecretAgent()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) SubmitCredentials(token string, secrets map[string]string, save bool) error {
- if b.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- return b.promptBroker.Resolve(token, PromptReply{
- Secrets: secrets,
- Save: save,
- Cancel: false,
- })
-}
-
-func (b *NetworkManagerBackend) CancelCredentials(token string) error {
- if b.promptBroker == nil {
- return fmt.Errorf("prompt broker not initialized")
- }
-
- return b.promptBroker.Resolve(token, PromptReply{
- Cancel: true,
- })
-}
-
-func (b *NetworkManagerBackend) ensureWiFiDevice() error {
- if b.wifiDev != nil {
- return nil
- }
-
- if b.wifiDevice == nil {
- return fmt.Errorf("no WiFi device available")
- }
-
- dev := b.wifiDevice.(gonetworkmanager.Device)
- wifiDev, err := gonetworkmanager.NewDeviceWireless(dev.GetPath())
- if err != nil {
- return fmt.Errorf("failed to get wireless device: %w", err)
- }
- b.wifiDev = wifiDev
- return nil
-}
-
-func (b *NetworkManagerBackend) startSecretAgent() error {
- if b.promptBroker == nil {
- return fmt.Errorf("prompt broker not set")
- }
-
- agent, err := NewSecretAgent(b.promptBroker, nil, b)
- if err != nil {
- return err
- }
-
- b.secretAgent = agent
- return nil
-}
-
-func (b *NetworkManagerBackend) getActiveConnections() (map[string]bool, error) {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- activeUUIDs := make(map[string]bool)
-
- activeConns, err := nm.GetPropertyActiveConnections()
- if err != nil {
- return activeUUIDs, fmt.Errorf("failed to get active connections: %w", err)
- }
-
- for _, activeConn := range activeConns {
- connType, err := activeConn.GetPropertyType()
- if err != nil {
- continue
- }
-
- if connType != "802-3-ethernet" {
- continue
- }
-
- state, err := activeConn.GetPropertyState()
- if err != nil {
- continue
- }
- if state < 1 || state > 2 {
- continue
- }
-
- uuid, err := activeConn.GetPropertyUUID()
- if err != nil {
- continue
- }
- activeUUIDs[uuid] = true
- }
- return activeUUIDs, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet.go
deleted file mode 100644
index 47f785b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet.go
+++ /dev/null
@@ -1,402 +0,0 @@
-package network
-
-import (
- "fmt"
- "net"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/Wifx/gonetworkmanager/v2"
-)
-
-func (b *NetworkManagerBackend) GetWiredConnections() ([]WiredConnection, error) {
- return b.listEthernetConnections()
-}
-
-func (b *NetworkManagerBackend) GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error) {
- if b.ethernetDevice == nil {
- return nil, fmt.Errorf("no ethernet device available")
- }
-
- dev := b.ethernetDevice.(gonetworkmanager.Device)
-
- iface, _ := dev.GetPropertyInterface()
- driver, _ := dev.GetPropertyDriver()
-
- hwAddr := "Not available"
- var speed uint32 = 0
- wiredDevice, err := gonetworkmanager.NewDeviceWired(dev.GetPath())
- if err == nil {
- hwAddr, _ = wiredDevice.GetPropertyHwAddress()
- speed, _ = wiredDevice.GetPropertySpeed()
- }
- var ipv4Config WiredIPConfig
- var ipv6Config WiredIPConfig
-
- activeConn, err := dev.GetPropertyActiveConnection()
- if err == nil && activeConn != nil {
- ip4Config, err := activeConn.GetPropertyIP4Config()
- if err == nil && ip4Config != nil {
- var ips []string
- addresses, err := ip4Config.GetPropertyAddressData()
- if err == nil && len(addresses) > 0 {
- for _, addr := range addresses {
- ips = append(ips, fmt.Sprintf("%s/%s", addr.Address, strconv.Itoa(int(addr.Prefix))))
- }
- }
-
- gateway, _ := ip4Config.GetPropertyGateway()
- dnsAddrs := ""
- dns, err := ip4Config.GetPropertyNameserverData()
- if err == nil && len(dns) > 0 {
- for _, d := range dns {
- if len(dnsAddrs) > 0 {
- dnsAddrs = strings.Join([]string{dnsAddrs, d.Address}, "; ")
- } else {
- dnsAddrs = d.Address
- }
- }
- }
-
- ipv4Config = WiredIPConfig{
- IPs: ips,
- Gateway: gateway,
- DNS: dnsAddrs,
- }
- }
-
- ip6Config, err := activeConn.GetPropertyIP6Config()
- if err == nil && ip6Config != nil {
- var ips []string
- addresses, err := ip6Config.GetPropertyAddressData()
- if err == nil && len(addresses) > 0 {
- for _, addr := range addresses {
- ips = append(ips, fmt.Sprintf("%s/%s", addr.Address, strconv.Itoa(int(addr.Prefix))))
- }
- }
-
- gateway, _ := ip6Config.GetPropertyGateway()
- dnsAddrs := ""
- dns, err := ip6Config.GetPropertyNameservers()
- if err == nil && len(dns) > 0 {
- for _, d := range dns {
- if len(d) == 16 {
- ip := net.IP(d)
- if len(dnsAddrs) > 0 {
- dnsAddrs = strings.Join([]string{dnsAddrs, ip.String()}, "; ")
- } else {
- dnsAddrs = ip.String()
- }
- }
- }
- }
-
- ipv6Config = WiredIPConfig{
- IPs: ips,
- Gateway: gateway,
- DNS: dnsAddrs,
- }
- }
- }
-
- return &WiredNetworkInfoResponse{
- UUID: uuid,
- IFace: iface,
- Driver: driver,
- HwAddr: hwAddr,
- Speed: strconv.Itoa(int(speed)),
- IPv4: ipv4Config,
- IPv6: ipv6Config,
- }, nil
-}
-
-func (b *NetworkManagerBackend) ConnectEthernet() error {
- if b.ethernetDevice == nil {
- return fmt.Errorf("no ethernet device available")
- }
-
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- dev := b.ethernetDevice.(gonetworkmanager.Device)
-
- settingsMgr, err := gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
-
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- for _, conn := range connections {
- connSettings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- if connMeta, ok := connSettings["connection"]; ok {
- if connType, ok := connMeta["type"].(string); ok && connType == "802-3-ethernet" {
- _, err := nm.ActivateConnection(conn, dev, nil)
- if err != nil {
- return fmt.Errorf("failed to activate ethernet: %w", err)
- }
-
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
- }
- }
- }
-
- settings := make(map[string]map[string]any)
- settings["connection"] = map[string]any{
- "id": "Wired connection",
- "type": "802-3-ethernet",
- }
-
- _, err = nm.AddAndActivateConnection(settings, dev)
- if err != nil {
- return fmt.Errorf("failed to create and activate ethernet: %w", err)
- }
-
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) DisconnectEthernet() error {
- if b.ethernetDevice == nil {
- return fmt.Errorf("no ethernet device available")
- }
-
- dev := b.ethernetDevice.(gonetworkmanager.Device)
-
- err := dev.Disconnect()
- if err != nil {
- return fmt.Errorf("failed to disconnect: %w", err)
- }
-
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) ActivateWiredConnection(uuid string) error {
- if b.ethernetDevice == nil {
- return fmt.Errorf("no ethernet device available")
- }
-
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- dev := b.ethernetDevice.(gonetworkmanager.Device)
-
- settingsMgr, err := gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
-
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- var targetConnection gonetworkmanager.Connection
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- if connectionSettings, ok := settings["connection"]; ok {
- if connUUID, ok := connectionSettings["uuid"].(string); ok && connUUID == uuid {
- targetConnection = conn
- break
- }
- }
- }
-
- if targetConnection == nil {
- return fmt.Errorf("connection with UUID %s not found", uuid)
- }
-
- _, err = nm.ActivateConnection(targetConnection, dev, nil)
- if err != nil {
- return fmt.Errorf("error activation connection: %w", err)
- }
-
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) listEthernetConnections() ([]WiredConnection, error) {
- if b.ethernetDevice == nil {
- return nil, fmt.Errorf("no ethernet device available")
- }
-
- s := b.settings
- if s == nil {
- s, err := gonetworkmanager.NewSettings()
- if err != nil {
- return nil, fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return nil, fmt.Errorf("failed to get connections: %w", err)
- }
-
- wiredConfigs := make([]WiredConnection, 0)
- activeUUIDs, err := b.getActiveConnections()
-
- if err != nil {
- return nil, fmt.Errorf("failed to get active wired connections: %w", err)
- }
-
- currentUuid := ""
- for _, connection := range connections {
- path := connection.GetPath()
- settings, err := connection.GetSettings()
- if err != nil {
- log.Errorf("unable to get settings for %s: %v", path, err)
- continue
- }
-
- connectionSettings := settings["connection"]
- connType, _ := connectionSettings["type"].(string)
- connID, _ := connectionSettings["id"].(string)
- connUUID, _ := connectionSettings["uuid"].(string)
-
- if connType == "802-3-ethernet" {
- wiredConfigs = append(wiredConfigs, WiredConnection{
- Path: path,
- ID: connID,
- UUID: connUUID,
- Type: connType,
- IsActive: activeUUIDs[connUUID],
- })
- if activeUUIDs[connUUID] {
- currentUuid = connUUID
- }
- }
- }
-
- b.stateMutex.Lock()
- b.state.EthernetConnectionUuid = currentUuid
- b.state.WiredConnections = wiredConfigs
- b.stateMutex.Unlock()
-
- return wiredConfigs, nil
-}
-
-func (b *NetworkManagerBackend) GetEthernetDevices() []EthernetDevice {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- return append([]EthernetDevice(nil), b.state.EthernetDevices...)
-}
-
-func (b *NetworkManagerBackend) DisconnectEthernetDevice(device string) error {
- info, ok := b.ethernetDevices[device]
- if !ok {
- return fmt.Errorf("ethernet device %s not found", device)
- }
-
- if err := info.device.Disconnect(); err != nil {
- return fmt.Errorf("failed to disconnect %s: %w", device, err)
- }
-
- b.updateAllEthernetDevices()
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) updateAllEthernetDevices() {
- devices := make([]EthernetDevice, 0, len(b.ethernetDevices))
-
- for name, info := range b.ethernetDevices {
- state, _ := info.device.GetPropertyState()
- connected := state == gonetworkmanager.NmDeviceStateActivated
- driver, _ := info.device.GetPropertyDriver()
-
- var ip string
- var speed uint32 = 0
- if connected {
- ip = b.getDeviceIP(info.device)
- }
- if info.wired != nil {
- speed, _ = info.wired.GetPropertySpeed()
- }
-
- stateStr := "disconnected"
- switch state {
- case gonetworkmanager.NmDeviceStateActivated:
- stateStr = "activated"
- case gonetworkmanager.NmDeviceStatePrepare:
- stateStr = "preparing"
- case gonetworkmanager.NmDeviceStateConfig:
- stateStr = "configuring"
- case gonetworkmanager.NmDeviceStateIpConfig:
- stateStr = "ip-config"
- case gonetworkmanager.NmDeviceStateIpCheck:
- stateStr = "ip-check"
- case gonetworkmanager.NmDeviceStateSecondaries:
- stateStr = "secondaries"
- case gonetworkmanager.NmDeviceStateDeactivating:
- stateStr = "deactivating"
- case gonetworkmanager.NmDeviceStateFailed:
- stateStr = "failed"
- case gonetworkmanager.NmDeviceStateUnavailable:
- stateStr = "unavailable"
- case gonetworkmanager.NmDeviceStateUnmanaged:
- stateStr = "unmanaged"
- }
-
- devices = append(devices, EthernetDevice{
- Name: name,
- HwAddress: info.hwAddress,
- State: stateStr,
- Connected: connected,
- IP: ip,
- Speed: speed,
- Driver: driver,
- })
- }
-
- b.stateMutex.Lock()
- b.state.EthernetDevices = devices
- b.stateMutex.Unlock()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet_test.go
deleted file mode 100644
index 0901913..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_ethernet_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package network
-
-import (
- "testing"
-
- mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNetworkManagerBackend_GetWiredConnections_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- _, err = backend.GetWiredConnections()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestNetworkManagerBackend_GetWiredNetworkDetails_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- _, err = backend.GetWiredNetworkDetails("test-uuid")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestNetworkManagerBackend_ConnectEthernet_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- err = backend.ConnectEthernet()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestNetworkManagerBackend_DisconnectEthernet_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- err = backend.DisconnectEthernet()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestNetworkManagerBackend_ActivateWiredConnection_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- err = backend.ActivateWiredConnection("test-uuid")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestNetworkManagerBackend_ActivateWiredConnection_NotFound(t *testing.T) {
- t.Skip("ActivateWiredConnection creates a new Settings instance internally, cannot be fully mocked")
-}
-
-func TestNetworkManagerBackend_ListEthernetConnections_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- _, err = backend.listEthernetConnections()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestNetworkManagerBackend_GetEthernetDevices_Empty(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- devices := backend.GetEthernetDevices()
- assert.Empty(t, devices)
-}
-
-func TestNetworkManagerBackend_GetEthernetDevices_WithState(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.state.EthernetDevices = []EthernetDevice{
- {Name: "enp0s3", HwAddress: "00:11:22:33:44:55", State: "activated", Connected: true, IP: "192.168.1.100"},
- {Name: "enp0s8", HwAddress: "00:11:22:33:44:66", State: "disconnected", Connected: false},
- }
-
- devices := backend.GetEthernetDevices()
- assert.Len(t, devices, 2)
- assert.Equal(t, "enp0s3", devices[0].Name)
- assert.True(t, devices[0].Connected)
- assert.Equal(t, "enp0s8", devices[1].Name)
- assert.False(t, devices[1].Connected)
-}
-
-func TestNetworkManagerBackend_DisconnectEthernetDevice_NotFound(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- err = backend.DisconnectEthernetDevice("nonexistent")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not found")
-}
-
-func TestNetworkManagerBackend_UpdateAllEthernetDevices_Empty(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.updateAllEthernetDevices()
- assert.Empty(t, backend.state.EthernetDevices)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml.go
deleted file mode 100644
index b1fa7fb..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml.go
+++ /dev/null
@@ -1,203 +0,0 @@
-package network
-
-import (
- "bufio"
- "context"
- "fmt"
- "os/exec"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-type gpSamlAuthResult struct {
- Cookie string
- Host string
- User string
- Fingerprint string
-}
-
-// runGlobalProtectSAMLAuth handles GlobalProtect SAML/SSO authentication using gp-saml-gui.
-// Only supports protocol=gp. Other protocols need their own implementations.
-func (b *NetworkManagerBackend) runGlobalProtectSAMLAuth(ctx context.Context, gateway, protocol string) (*gpSamlAuthResult, error) {
- if gateway == "" {
- return nil, fmt.Errorf("GP SAML auth: gateway is empty")
- }
- if protocol != "gp" {
- return nil, fmt.Errorf("only GlobalProtect (protocol=gp) SAML is supported, got: %s", protocol)
- }
-
- log.Infof("[GP-SAML] Starting GlobalProtect SAML authentication with gp-saml-gui for gateway=%s", gateway)
-
- gpSamlPath, err := exec.LookPath("gp-saml-gui")
- if err != nil {
- return nil, fmt.Errorf("GlobalProtect SAML requires gp-saml-gui (install: pip install gp-saml-gui): %w", err)
- }
-
- args := []string{
- "--gateway",
- "--allow-insecure-crypto",
- gateway,
- }
-
- cmd := exec.CommandContext(ctx, gpSamlPath, args...)
-
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return nil, fmt.Errorf("GP SAML auth: failed to create stdout pipe: %w", err)
- }
-
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return nil, fmt.Errorf("GP SAML auth: failed to create stderr pipe: %w", err)
- }
-
- if err := cmd.Start(); err != nil {
- return nil, fmt.Errorf("GP SAML auth: failed to start gp-saml-gui: %w", err)
- }
-
- go func() {
- scanner := bufio.NewScanner(stderr)
- for scanner.Scan() {
- log.Debugf("[GP-SAML] gp-saml-gui: %s", scanner.Text())
- }
- }()
-
- result := &gpSamlAuthResult{Host: gateway}
- var allOutput []string
-
- scanner := bufio.NewScanner(stdout)
- for scanner.Scan() {
- line := scanner.Text()
- allOutput = append(allOutput, line)
- log.Infof("[GP-SAML] stdout: %s", line)
-
- switch {
- case strings.HasPrefix(line, "COOKIE="):
- result.Cookie = unshellQuote(strings.TrimPrefix(line, "COOKIE="))
- case strings.HasPrefix(line, "HOST="):
- result.Host = unshellQuote(strings.TrimPrefix(line, "HOST="))
- case strings.HasPrefix(line, "USER="):
- result.User = unshellQuote(strings.TrimPrefix(line, "USER="))
- case strings.HasPrefix(line, "FINGERPRINT="):
- result.Fingerprint = unshellQuote(strings.TrimPrefix(line, "FINGERPRINT="))
- default:
- parseGPSamlFromCommandLine(line, result)
- }
- }
-
- if err := cmd.Wait(); err != nil {
- if ctx.Err() != nil {
- return nil, fmt.Errorf("GP SAML auth timed out or was cancelled: %w", ctx.Err())
- }
- if result.Cookie == "" {
- return nil, fmt.Errorf("GP SAML auth failed: %w (output: %s)", err, strings.Join(allOutput, "\n"))
- }
- log.Warnf("[GP-SAML] gp-saml-gui exited with error but cookie was captured: %v", err)
- }
-
- if result.Cookie == "" {
- return nil, fmt.Errorf("GP SAML auth: no cookie in gp-saml-gui output")
- }
-
- log.Infof("[GP-SAML] Got prelogin-cookie from gp-saml-gui, converting to openconnect cookie via --authenticate")
-
- // Convert prelogin-cookie to full openconnect cookie format
- ocResult, err := convertGPPreloginCookie(ctx, gateway, result.Cookie, result.User)
- if err != nil {
- return nil, fmt.Errorf("GP SAML auth: failed to convert prelogin-cookie: %w", err)
- }
-
- result.Cookie = ocResult.Cookie
- result.Host = ocResult.Host
- result.Fingerprint = ocResult.Fingerprint
-
- log.Infof("[GP-SAML] Authentication successful: user=%s, host=%s, cookie_len=%d, has_fingerprint=%v",
- result.User, result.Host, len(result.Cookie), result.Fingerprint != "")
- return result, nil
-}
-
-func convertGPPreloginCookie(ctx context.Context, gateway, preloginCookie, user string) (*gpSamlAuthResult, error) {
- ocPath, err := exec.LookPath("openconnect")
- if err != nil {
- return nil, fmt.Errorf("openconnect not found: %w", err)
- }
-
- args := []string{
- "--protocol=gp",
- "--usergroup=gateway:prelogin-cookie",
- "--user=" + user,
- "--passwd-on-stdin",
- "--allow-insecure-crypto",
- "--authenticate",
- gateway,
- }
-
- cmd := exec.CommandContext(ctx, ocPath, args...)
- cmd.Stdin = strings.NewReader(preloginCookie)
-
- output, err := cmd.CombinedOutput()
- if err != nil {
- return nil, fmt.Errorf("openconnect --authenticate failed: %w\noutput: %s", err, string(output))
- }
-
- result := &gpSamlAuthResult{}
- for _, line := range strings.Split(string(output), "\n") {
- line = strings.TrimSpace(line)
- switch {
- case strings.HasPrefix(line, "COOKIE="):
- result.Cookie = unshellQuote(strings.TrimPrefix(line, "COOKIE="))
- case strings.HasPrefix(line, "HOST="):
- result.Host = unshellQuote(strings.TrimPrefix(line, "HOST="))
- case strings.HasPrefix(line, "FINGERPRINT="):
- result.Fingerprint = unshellQuote(strings.TrimPrefix(line, "FINGERPRINT="))
- case strings.HasPrefix(line, "CONNECT_URL="):
- connectURL := unshellQuote(strings.TrimPrefix(line, "CONNECT_URL="))
- if connectURL != "" && result.Host == "" {
- result.Host = connectURL
- }
- }
- }
-
- if result.Cookie == "" {
- return nil, fmt.Errorf("no COOKIE in openconnect --authenticate output: %s", string(output))
- }
-
- log.Infof("[GP-SAML] openconnect --authenticate: cookie_len=%d, host=%s, fingerprint=%s",
- len(result.Cookie), result.Host, result.Fingerprint)
-
- return result, nil
-}
-
-func unshellQuote(s string) string {
- if len(s) >= 2 {
- if (s[0] == '\'' && s[len(s)-1] == '\'') ||
- (s[0] == '"' && s[len(s)-1] == '"') {
- return s[1 : len(s)-1]
- }
- }
- return s
-}
-
-func parseGPSamlFromCommandLine(line string, result *gpSamlAuthResult) {
- if !strings.Contains(line, "openconnect") {
- return
- }
-
- for _, part := range strings.Fields(line) {
- switch {
- case strings.HasPrefix(part, "--cookie="):
- if result.Cookie == "" {
- result.Cookie = strings.TrimPrefix(part, "--cookie=")
- }
- case strings.HasPrefix(part, "--servercert="):
- if result.Fingerprint == "" {
- result.Fingerprint = strings.TrimPrefix(part, "--servercert=")
- }
- case strings.HasPrefix(part, "--user="):
- if result.User == "" {
- result.User = strings.TrimPrefix(part, "--user=")
- }
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml_test.go
deleted file mode 100644
index 5655815..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_gp_saml_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestUnshellQuote(t *testing.T) {
- tests := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "single quoted",
- input: "'hello world'",
- expected: "hello world",
- },
- {
- name: "double quoted",
- input: `"hello world"`,
- expected: "hello world",
- },
- {
- name: "unquoted",
- input: "hello",
- expected: "hello",
- },
- {
- name: "empty single quotes",
- input: "''",
- expected: "",
- },
- {
- name: "empty double quotes",
- input: `""`,
- expected: "",
- },
- {
- name: "single quote only",
- input: "'",
- expected: "'",
- },
- {
- name: "mismatched quotes",
- input: "'hello\"",
- expected: "'hello\"",
- },
- {
- name: "with special chars",
- input: "'cookie=abc123&user=john'",
- expected: "cookie=abc123&user=john",
- },
- {
- name: "complex cookie",
- input: `'authcookie=077058d3bc81&portal=PANGP_GW_01-N&user=john.doe@example.com&domain=Default&preferred-ip=192.168.1.100'`,
- expected: "authcookie=077058d3bc81&portal=PANGP_GW_01-N&user=john.doe@example.com&domain=Default&preferred-ip=192.168.1.100",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := unshellQuote(tt.input)
- assert.Equal(t, tt.expected, result)
- })
- }
-}
-
-func TestParseGPSamlFromCommandLine(t *testing.T) {
- tests := []struct {
- name string
- line string
- initialResult *gpSamlAuthResult
- expectedCookie string
- expectedUser string
- expectedFP string
- }{
- {
- name: "full openconnect command",
- line: "openconnect --protocol=gp --cookie=AUTH123 --servercert=pin-sha256:ABC --user=john",
- initialResult: &gpSamlAuthResult{},
- expectedCookie: "AUTH123",
- expectedUser: "john",
- expectedFP: "pin-sha256:ABC",
- },
- {
- name: "with equals signs in cookie",
- line: "openconnect --cookie=authcookie=xyz123&portal=GATE --user=jane",
- initialResult: &gpSamlAuthResult{},
- expectedCookie: "authcookie=xyz123&portal=GATE",
- expectedUser: "jane",
- expectedFP: "",
- },
- {
- name: "non-openconnect line",
- line: "some other output",
- initialResult: &gpSamlAuthResult{},
- expectedCookie: "",
- expectedUser: "",
- expectedFP: "",
- },
- {
- name: "preserves existing values",
- line: "openconnect --user=newuser",
- initialResult: &gpSamlAuthResult{Cookie: "existing", Fingerprint: "existing-fp"},
- expectedCookie: "existing",
- expectedUser: "newuser",
- expectedFP: "existing-fp",
- },
- {
- name: "only updates empty fields",
- line: "openconnect --cookie=NEW --user=NEW",
- initialResult: &gpSamlAuthResult{Cookie: "OLD"},
- expectedCookie: "OLD",
- expectedUser: "NEW",
- expectedFP: "",
- },
- {
- name: "real gp-saml-gui output",
- line: "openconnect --protocol=gp --user=john.doe@example.com --os=linux-64 --usergroup=gateway:prelogin-cookie --passwd-on-stdin",
- initialResult: &gpSamlAuthResult{},
- expectedCookie: "",
- expectedUser: "john.doe@example.com",
- expectedFP: "",
- },
- {
- name: "with server cert flag",
- line: "openconnect --servercert=pin-sha256:xp3scfzy3rOgQEXnfPiYKrUk7D66a8b8O+gEXaMPleE= vpn.example.com",
- initialResult: &gpSamlAuthResult{},
- expectedCookie: "",
- expectedUser: "",
- expectedFP: "pin-sha256:xp3scfzy3rOgQEXnfPiYKrUk7D66a8b8O+gEXaMPleE=",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := tt.initialResult
- parseGPSamlFromCommandLine(tt.line, result)
-
- assert.Equal(t, tt.expectedCookie, result.Cookie, "cookie mismatch")
- assert.Equal(t, tt.expectedUser, result.User, "user mismatch")
- assert.Equal(t, tt.expectedFP, result.Fingerprint, "fingerprint mismatch")
- })
- }
-}
-
-func TestParseGPSamlFromCommandLine_MultipleLines(t *testing.T) {
- // Simulate gp-saml-gui output with command line suggestion
- lines := []string{
- "",
- "SAML REDIRECT",
- "Got SAML Login URL",
- "POST to ACS endpoint...",
- "Got 'prelogin-cookie': 'FAKE_cookie_12345'",
- "openconnect --protocol=gp --user=john.doe@example.com --usergroup=gateway:prelogin-cookie --passwd-on-stdin vpn.example.com",
- "",
- }
-
- result := &gpSamlAuthResult{}
- for _, line := range lines {
- parseGPSamlFromCommandLine(line, result)
- }
-
- assert.Equal(t, "john.doe@example.com", result.User)
- assert.Empty(t, result.Cookie, "cookie should not be parsed from command line")
- assert.Empty(t, result.Fingerprint)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals.go
deleted file mode 100644
index 5274ed0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals.go
+++ /dev/null
@@ -1,530 +0,0 @@
-package network
-
-import (
- "github.com/Wifx/gonetworkmanager/v2"
- "github.com/godbus/dbus/v5"
-)
-
-func (b *NetworkManagerBackend) startSignalPump() error {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return err
- }
- b.dbusConn = conn
-
- signals := make(chan *dbus.Signal, 256)
- b.signals = signals
- conn.Signal(signals)
-
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- ); err != nil {
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
-
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings")),
- dbus.WithMatchInterface("org.freedesktop.NetworkManager.Settings"),
- dbus.WithMatchMember("NewConnection"),
- ); err != nil {
- conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
-
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings")),
- dbus.WithMatchInterface("org.freedesktop.NetworkManager.Settings"),
- dbus.WithMatchMember("ConnectionRemoved"),
- ); err != nil {
- conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- conn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings")),
- dbus.WithMatchInterface("org.freedesktop.NetworkManager.Settings"),
- dbus.WithMatchMember("NewConnection"),
- )
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
-
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
- dbus.WithMatchInterface(dbusNMInterface),
- dbus.WithMatchMember("DeviceAdded"),
- ); err != nil {
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
-
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
- dbus.WithMatchInterface(dbusNMInterface),
- dbus.WithMatchMember("DeviceRemoved"),
- ); err != nil {
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
-
- for _, info := range b.wifiDevices {
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(info.device.GetPath())),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- ); err != nil {
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
- }
-
- for _, info := range b.ethernetDevices {
- if err := conn.AddMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(info.device.GetPath())),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- ); err != nil {
- conn.RemoveSignal(signals)
- conn.Close()
- return err
- }
- }
-
- b.sigWG.Add(1)
- go func() {
- defer b.sigWG.Done()
- for {
- select {
- case <-b.stopChan:
- return
- case sig, ok := <-signals:
- if !ok {
- return
- }
- if sig == nil {
- continue
- }
- b.handleDBusSignal(sig)
- }
- }
- }()
- return nil
-}
-
-func (b *NetworkManagerBackend) stopSignalPump() {
- if b.dbusConn == nil {
- return
- }
-
- b.dbusConn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
-
- for _, info := range b.wifiDevices {
- b.dbusConn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(info.device.GetPath())),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- }
-
- for _, info := range b.ethernetDevices {
- b.dbusConn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(dbus.ObjectPath(info.device.GetPath())),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- }
-
- if b.signals != nil {
- b.dbusConn.RemoveSignal(b.signals)
- close(b.signals)
- }
-
- b.sigWG.Wait()
-
- b.dbusConn.Close()
-}
-
-func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) {
- if sig.Name == "org.freedesktop.NetworkManager.Settings.NewConnection" ||
- sig.Name == "org.freedesktop.NetworkManager.Settings.ConnectionRemoved" {
- b.ListVPNProfiles()
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return
- }
-
- if sig.Name == "org.freedesktop.NetworkManager.DeviceAdded" {
- if len(sig.Body) >= 1 {
- if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
- b.handleDeviceAdded(devicePath)
- }
- }
- return
- }
-
- if sig.Name == "org.freedesktop.NetworkManager.DeviceRemoved" {
- if len(sig.Body) >= 1 {
- if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
- b.handleDeviceRemoved(devicePath)
- }
- }
- return
- }
-
- if len(sig.Body) < 2 {
- return
- }
-
- iface, ok := sig.Body[0].(string)
- if !ok {
- return
- }
-
- changes, ok := sig.Body[1].(map[string]dbus.Variant)
- if !ok {
- return
- }
-
- switch iface {
- case dbusNMInterface:
- b.handleNetworkManagerChange(changes)
-
- case dbusNMDeviceInterface:
- b.handleDeviceChange(sig.Path, changes)
-
- case dbusNMWiredInterface:
- b.handleWiredChange(changes)
-
- case dbusNMWirelessInterface:
- b.handleWiFiChange(changes)
-
- case dbusNMAccessPointInterface:
- b.handleAccessPointChange(changes)
- }
-}
-
-func (b *NetworkManagerBackend) handleNetworkManagerChange(changes map[string]dbus.Variant) {
- var needsUpdate bool
-
- for key := range changes {
- switch key {
- case "PrimaryConnection", "State", "ActiveConnections":
- needsUpdate = true
- case "WirelessEnabled":
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- if enabled, err := nm.GetPropertyWirelessEnabled(); err == nil {
- b.stateMutex.Lock()
- b.state.WiFiEnabled = enabled
- b.stateMutex.Unlock()
- needsUpdate = true
- }
- default:
- continue
- }
- }
-
- if needsUpdate {
- b.updatePrimaryConnection()
- if _, exists := changes["State"]; exists {
- b.updateEthernetState()
- b.updateWiFiState()
- }
- if _, exists := changes["ActiveConnections"]; exists {
- b.updateVPNConnectionState()
- b.ListActiveVPN()
- }
- if b.onStateChange != nil {
- b.onStateChange()
- }
- }
-}
-
-func (b *NetworkManagerBackend) handleDeviceChange(devicePath dbus.ObjectPath, changes map[string]dbus.Variant) {
- var needsUpdate bool
- var stateChanged bool
- var managedChanged bool
-
- for key := range changes {
- switch key {
- case "State":
- stateChanged = true
- needsUpdate = true
- case "Ip4Config":
- needsUpdate = true
- case "Managed":
- managedChanged = true
- default:
- continue
- }
- }
-
- if managedChanged {
- if managedVariant, ok := changes["Managed"]; ok {
- if managed, ok := managedVariant.Value().(bool); ok && managed {
- b.handleDeviceAdded(devicePath)
- return
- }
- }
- }
-
- if !needsUpdate {
- return
- }
-
- b.updateAllEthernetDevices()
- b.updateEthernetState()
- b.updateAllWiFiDevices()
- b.updateWiFiState()
- if stateChanged {
- b.listEthernetConnections()
- b.updatePrimaryConnection()
- }
- if b.onStateChange != nil {
- b.onStateChange()
- }
-}
-
-func (b *NetworkManagerBackend) handleWiredChange(changes map[string]dbus.Variant) {
- var needsUpdate bool
-
- for key := range changes {
- switch key {
- case "Carrier", "Speed", "HwAddress":
- needsUpdate = true
- default:
- continue
- }
- }
-
- if !needsUpdate {
- return
- }
-
- b.updateAllEthernetDevices()
- b.updateEthernetState()
- b.updatePrimaryConnection()
- if b.onStateChange != nil {
- b.onStateChange()
- }
-}
-
-func (b *NetworkManagerBackend) handleWiFiChange(changes map[string]dbus.Variant) {
- var needsStateUpdate bool
- var needsNetworkUpdate bool
-
- for key := range changes {
- switch key {
- case "ActiveAccessPoint":
- needsStateUpdate = true
- needsNetworkUpdate = true
- case "AccessPoints":
- needsNetworkUpdate = true
- default:
- continue
- }
- }
-
- if needsStateUpdate {
- b.updateWiFiState()
- }
- if needsNetworkUpdate {
- b.updateWiFiNetworks()
- }
- if needsStateUpdate || needsNetworkUpdate {
- if b.onStateChange != nil {
- b.onStateChange()
- }
- }
-}
-
-func (b *NetworkManagerBackend) handleAccessPointChange(changes map[string]dbus.Variant) {
- _, hasStrength := changes["Strength"]
- if !hasStrength {
- return
- }
-
- b.stateMutex.RLock()
- oldSignal := b.state.WiFiSignal
- b.stateMutex.RUnlock()
-
- b.updateWiFiState()
-
- b.stateMutex.RLock()
- newSignal := b.state.WiFiSignal
- b.stateMutex.RUnlock()
-
- if signalChangeSignificant(oldSignal, newSignal) {
- if b.onStateChange != nil {
- b.onStateChange()
- }
- }
-}
-
-func (b *NetworkManagerBackend) handleDeviceAdded(devicePath dbus.ObjectPath) {
- dev, err := gonetworkmanager.NewDevice(devicePath)
- if err != nil {
- return
- }
-
- devType, err := dev.GetPropertyDeviceType()
- if err != nil {
- return
- }
-
- if devType != gonetworkmanager.NmDeviceTypeEthernet && devType != gonetworkmanager.NmDeviceTypeWifi {
- return
- }
-
- if b.dbusConn != nil {
- b.dbusConn.AddMatchSignal(
- dbus.WithMatchObjectPath(devicePath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- }
-
- managed, _ := dev.GetPropertyManaged()
- if !managed {
- return
- }
-
- iface, err := dev.GetPropertyInterface()
- if err != nil {
- return
- }
-
- switch devType {
- case gonetworkmanager.NmDeviceTypeEthernet:
- w, err := gonetworkmanager.NewDeviceWired(devicePath)
- if err != nil {
- return
- }
- hwAddr, _ := w.GetPropertyHwAddress()
-
- b.ethernetDevices[iface] = &ethernetDeviceInfo{
- device: dev,
- wired: w,
- name: iface,
- hwAddress: hwAddr,
- }
-
- if b.ethernetDevice == nil {
- b.ethernetDevice = dev
- }
-
- b.updateAllEthernetDevices()
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- case gonetworkmanager.NmDeviceTypeWifi:
- w, err := gonetworkmanager.NewDeviceWireless(devicePath)
- if err != nil {
- return
- }
- hwAddr, _ := w.GetPropertyHwAddress()
-
- b.wifiDevices[iface] = &wifiDeviceInfo{
- device: dev,
- wireless: w,
- name: iface,
- hwAddress: hwAddr,
- }
-
- if b.wifiDevice == nil {
- b.wifiDevice = dev
- b.wifiDev = w
- }
-
- b.updateAllWiFiDevices()
- b.updateWiFiState()
- }
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-}
-
-func (b *NetworkManagerBackend) handleDeviceRemoved(devicePath dbus.ObjectPath) {
- if b.dbusConn != nil {
- b.dbusConn.RemoveMatchSignal(
- dbus.WithMatchObjectPath(devicePath),
- dbus.WithMatchInterface(dbusPropsInterface),
- dbus.WithMatchMember("PropertiesChanged"),
- )
- }
-
- for iface, info := range b.ethernetDevices {
- if info.device.GetPath() == devicePath {
- delete(b.ethernetDevices, iface)
-
- if b.ethernetDevice != nil {
- dev := b.ethernetDevice.(gonetworkmanager.Device)
- if dev.GetPath() == devicePath {
- b.ethernetDevice = nil
- for _, remaining := range b.ethernetDevices {
- b.ethernetDevice = remaining.device
- break
- }
- }
- }
-
- b.updateAllEthernetDevices()
- b.updateEthernetState()
- b.listEthernetConnections()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return
- }
- }
-
- for iface, info := range b.wifiDevices {
- if info.device.GetPath() == devicePath {
- delete(b.wifiDevices, iface)
-
- if b.wifiDevice != nil {
- dev := b.wifiDevice.(gonetworkmanager.Device)
- if dev.GetPath() == devicePath {
- b.wifiDevice = nil
- b.wifiDev = nil
- for _, remaining := range b.wifiDevices {
- b.wifiDevice = remaining.device
- b.wifiDev = remaining.wireless
- break
- }
- }
- }
-
- b.updateAllWiFiDevices()
- b.updateWiFiState()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals_test.go
deleted file mode 100644
index 6b58684..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_signals_test.go
+++ /dev/null
@@ -1,255 +0,0 @@
-package network
-
-import (
- "testing"
-
- mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
- "github.com/Wifx/gonetworkmanager/v2"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNetworkManagerBackend_HandleDBusSignal_NewConnection(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.NetworkManager.Settings.NewConnection",
- Body: []any{"/org/freedesktop/NetworkManager/Settings/1"},
- }
-
- assert.NotPanics(t, func() {
- backend.handleDBusSignal(sig)
- })
-}
-
-func TestNetworkManagerBackend_HandleDBusSignal_ConnectionRemoved(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.NetworkManager.Settings.ConnectionRemoved",
- Body: []any{"/org/freedesktop/NetworkManager/Settings/1"},
- }
-
- assert.NotPanics(t, func() {
- backend.handleDBusSignal(sig)
- })
-}
-
-func TestNetworkManagerBackend_HandleDBusSignal_InvalidBody(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{"only-one-element"},
- }
-
- assert.NotPanics(t, func() {
- backend.handleDBusSignal(sig)
- })
-}
-
-func TestNetworkManagerBackend_HandleDBusSignal_InvalidInterface(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{123, map[string]dbus.Variant{}},
- }
-
- assert.NotPanics(t, func() {
- backend.handleDBusSignal(sig)
- })
-}
-
-func TestNetworkManagerBackend_HandleDBusSignal_InvalidChanges(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- sig := &dbus.Signal{
- Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
- Body: []any{dbusNMInterface, "not-a-map"},
- }
-
- assert.NotPanics(t, func() {
- backend.handleDBusSignal(sig)
- })
-}
-
-func TestNetworkManagerBackend_HandleNetworkManagerChange(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil).Maybe()
- mockNM.EXPECT().GetPropertyPrimaryConnection().Return(nil, nil).Maybe()
-
- changes := map[string]dbus.Variant{
- "PrimaryConnection": dbus.MakeVariant("/"),
- "State": dbus.MakeVariant(uint32(70)),
- }
-
- assert.NotPanics(t, func() {
- backend.handleNetworkManagerChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleNetworkManagerChange_WirelessEnabled(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyWirelessEnabled().Return(true, nil)
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil).Maybe()
- mockNM.EXPECT().GetPropertyPrimaryConnection().Return(nil, nil).Maybe()
-
- changes := map[string]dbus.Variant{
- "WirelessEnabled": dbus.MakeVariant(true),
- }
-
- assert.NotPanics(t, func() {
- backend.handleNetworkManagerChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleNetworkManagerChange_ActiveConnections(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil)
- mockNM.EXPECT().GetPropertyPrimaryConnection().Return(nil, nil).Maybe()
-
- changes := map[string]dbus.Variant{
- "ActiveConnections": dbus.MakeVariant([]any{}),
- }
-
- assert.NotPanics(t, func() {
- backend.handleNetworkManagerChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleDeviceChange(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil).Maybe()
- mockNM.EXPECT().GetPropertyPrimaryConnection().Return(nil, nil).Maybe()
-
- changes := map[string]dbus.Variant{
- "State": dbus.MakeVariant(uint32(100)),
- }
-
- assert.NotPanics(t, func() {
- backend.handleDeviceChange("/org/freedesktop/NetworkManager/Devices/1", changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleDeviceChange_Ip4Config(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- changes := map[string]dbus.Variant{
- "Ip4Config": dbus.MakeVariant("/"),
- }
-
- assert.NotPanics(t, func() {
- backend.handleDeviceChange("/org/freedesktop/NetworkManager/Devices/1", changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleWiFiChange_ActiveAccessPoint(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- changes := map[string]dbus.Variant{
- "ActiveAccessPoint": dbus.MakeVariant("/"),
- }
-
- assert.NotPanics(t, func() {
- backend.handleWiFiChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleWiFiChange_AccessPoints(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- changes := map[string]dbus.Variant{
- "AccessPoints": dbus.MakeVariant([]any{}),
- }
-
- assert.NotPanics(t, func() {
- backend.handleWiFiChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleAccessPointChange_NoStrength(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- changes := map[string]dbus.Variant{
- "SomeOtherProperty": dbus.MakeVariant("value"),
- }
-
- assert.NotPanics(t, func() {
- backend.handleAccessPointChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_HandleAccessPointChange_WithStrength(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.stateMutex.Lock()
- backend.state.WiFiSignal = 50
- backend.stateMutex.Unlock()
-
- changes := map[string]dbus.Variant{
- "Strength": dbus.MakeVariant(uint8(80)),
- }
-
- assert.NotPanics(t, func() {
- backend.handleAccessPointChange(changes)
- })
-}
-
-func TestNetworkManagerBackend_StopSignalPump_NoConnection(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.dbusConn = nil
- assert.NotPanics(t, func() {
- backend.stopSignalPump()
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state.go
deleted file mode 100644
index ba77f6d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state.go
+++ /dev/null
@@ -1,277 +0,0 @@
-package network
-
-import (
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/Wifx/gonetworkmanager/v2"
-)
-
-func (b *NetworkManagerBackend) updatePrimaryConnection() error {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- activeConns, err := nm.GetPropertyActiveConnections()
- if err != nil {
- return err
- }
-
- hasActiveVPN := false
- for _, activeConn := range activeConns {
- connType, err := activeConn.GetPropertyType()
- if err != nil {
- continue
- }
- if connType == "vpn" || connType == "wireguard" {
- state, _ := activeConn.GetPropertyState()
- if state == 2 {
- hasActiveVPN = true
- break
- }
- }
- }
-
- if hasActiveVPN {
- b.stateMutex.Lock()
- b.state.NetworkStatus = StatusVPN
- b.stateMutex.Unlock()
- return nil
- }
-
- primaryConn, err := nm.GetPropertyPrimaryConnection()
- if err != nil {
- return err
- }
-
- if primaryConn == nil || primaryConn.GetPath() == "/" {
- b.stateMutex.Lock()
- b.state.NetworkStatus = StatusDisconnected
- b.stateMutex.Unlock()
- return nil
- }
-
- connType, err := primaryConn.GetPropertyType()
- if err != nil {
- return err
- }
-
- b.stateMutex.Lock()
- switch connType {
- case "802-3-ethernet":
- b.state.NetworkStatus = StatusEthernet
- case "802-11-wireless":
- b.state.NetworkStatus = StatusWiFi
- case "vpn", "wireguard":
- b.state.NetworkStatus = StatusVPN
- default:
- b.state.NetworkStatus = StatusDisconnected
- }
- b.stateMutex.Unlock()
-
- return nil
-}
-
-func (b *NetworkManagerBackend) updateEthernetState() error {
- var connectedDevice string
- var connectedIP string
- var anyConnected bool
-
- for name, info := range b.ethernetDevices {
- state, err := info.device.GetPropertyState()
- if err != nil {
- continue
- }
-
- if state == gonetworkmanager.NmDeviceStateActivated {
- anyConnected = true
- connectedDevice = name
- connectedIP = b.getDeviceIP(info.device)
- break
- }
- }
-
- if !anyConnected && b.ethernetDevice != nil {
- dev := b.ethernetDevice.(gonetworkmanager.Device)
- iface, _ := dev.GetPropertyInterface()
- connectedDevice = iface
- }
-
- b.stateMutex.Lock()
- b.state.EthernetDevice = connectedDevice
- b.state.EthernetConnected = anyConnected
- b.state.EthernetIP = connectedIP
- b.stateMutex.Unlock()
-
- return nil
-}
-
-func (b *NetworkManagerBackend) getDeviceStateReason(dev gonetworkmanager.Device) uint32 {
- path := dev.GetPath()
- obj := b.dbusConn.Object("org.freedesktop.NetworkManager", path)
-
- variant, err := obj.GetProperty(dbusNMDeviceInterface + ".StateReason")
- if err != nil {
- return 0
- }
-
- if stateReasonStruct, ok := variant.Value().([]any); ok && len(stateReasonStruct) >= 2 {
- if reason, ok := stateReasonStruct[1].(uint32); ok {
- return reason
- }
- }
-
- return 0
-}
-
-func (b *NetworkManagerBackend) classifyNMStateReason(reason uint32) string {
- switch reason {
- case NmDeviceStateReasonWrongPassword,
- NmDeviceStateReasonSupplicantTimeout,
- NmDeviceStateReasonSupplicantFailed,
- NmDeviceStateReasonSecretsRequired:
- return errdefs.ErrBadCredentials
- case NmDeviceStateReasonNoSecrets:
- return errdefs.ErrUserCanceled
- case NmDeviceStateReasonNoSsid:
- return errdefs.ErrNoSuchSSID
- case NmDeviceStateReasonDhcpClientFailed,
- NmDeviceStateReasonIpConfigUnavailable:
- return errdefs.ErrDhcpTimeout
- case NmDeviceStateReasonSupplicantDisconnect,
- NmDeviceStateReasonCarrier:
- return errdefs.ErrAssocTimeout
- default:
- return errdefs.ErrConnectionFailed
- }
-}
-
-func (b *NetworkManagerBackend) updateWiFiState() error {
- if b.wifiDevice == nil {
- return nil
- }
-
- dev := b.wifiDevice.(gonetworkmanager.Device)
-
- iface, err := dev.GetPropertyInterface()
- if err != nil {
- return err
- }
-
- state, err := dev.GetPropertyState()
- if err != nil {
- return err
- }
-
- connected := state == gonetworkmanager.NmDeviceStateActivated
- failed := state == gonetworkmanager.NmDeviceStateFailed
- disconnected := state == gonetworkmanager.NmDeviceStateDisconnected
-
- var ip, ssid, bssid string
- var signal uint8
-
- if connected {
- if err := b.ensureWiFiDevice(); err == nil && b.wifiDev != nil {
- w := b.wifiDev.(gonetworkmanager.DeviceWireless)
- activeAP, err := w.GetPropertyActiveAccessPoint()
- if err == nil && activeAP != nil && activeAP.GetPath() != "/" {
- ssid, _ = activeAP.GetPropertySSID()
- signal, _ = activeAP.GetPropertyStrength()
- bssid, _ = activeAP.GetPropertyHWAddress()
- }
- }
-
- ip = b.getDeviceIP(dev)
- }
-
- b.stateMutex.RLock()
- wasConnecting := b.state.IsConnecting
- connectingSSID := b.state.ConnectingSSID
- b.stateMutex.RUnlock()
-
- var reasonCode string
- if wasConnecting && connectingSSID != "" && (failed || (disconnected && !connected)) {
- reason := b.getDeviceStateReason(dev)
-
- if reason == NmDeviceStateReasonNewActivation || reason == 0 {
- return nil
- }
-
- log.Warnf("[updateWiFiState] Connection failed: SSID=%s, state=%d, reason=%d", connectingSSID, state, reason)
-
- reasonCode = b.classifyNMStateReason(reason)
-
- if reasonCode == errdefs.ErrConnectionFailed {
- b.failedMutex.RLock()
- if b.lastFailedSSID == connectingSSID {
- elapsed := time.Now().Unix() - b.lastFailedTime
- if elapsed < 5 {
- reasonCode = errdefs.ErrBadCredentials
- }
- }
- b.failedMutex.RUnlock()
- }
- }
-
- var forgetSSID string
-
- b.stateMutex.Lock()
-
- wasConnecting = b.state.IsConnecting
- connectingSSID = b.state.ConnectingSSID
-
- if wasConnecting && connectingSSID != "" {
- switch {
- case connected && ssid == connectingSSID:
- log.Infof("[updateWiFiState] Connection successful: %s", ssid)
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.LastError = ""
- case failed || (disconnected && !connected):
- log.Warnf("[updateWiFiState] Connection failed: SSID=%s, state=%d", connectingSSID, state)
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.LastError = reasonCode
-
- if reasonCode == errdefs.ErrUserCanceled {
- forgetSSID = connectingSSID
- }
-
- b.failedMutex.Lock()
- b.lastFailedSSID = connectingSSID
- b.lastFailedTime = time.Now().Unix()
- b.failedMutex.Unlock()
- }
- }
-
- b.state.WiFiDevice = iface
- b.state.WiFiConnected = connected
- b.state.WiFiIP = ip
- b.state.WiFiSSID = ssid
- b.state.WiFiBSSID = bssid
- b.state.WiFiSignal = signal
-
- b.stateMutex.Unlock()
-
- if forgetSSID != "" {
- log.Infof("[updateWiFiState] User cancelled authentication, removing connection profile for %s", forgetSSID)
- if err := b.ForgetWiFiNetwork(forgetSSID); err != nil {
- log.Warnf("[updateWiFiState] Failed to remove cancelled connection: %v", err)
- }
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) getDeviceIP(dev gonetworkmanager.Device) string {
- ip4Config, err := dev.GetPropertyIP4Config()
- if err != nil || ip4Config == nil {
- return ""
- }
-
- addresses, err := ip4Config.GetPropertyAddressData()
- if err != nil || len(addresses) == 0 {
- return ""
- }
-
- return addresses[0].Address
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state_test.go
deleted file mode 100644
index 86aa48f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_state_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
- "github.com/Wifx/gonetworkmanager/v2"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNetworkManagerBackend_UpdatePrimaryConnection(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil)
- mockNM.EXPECT().GetPropertyPrimaryConnection().Return(nil, nil)
-
- err = backend.updatePrimaryConnection()
- assert.NoError(t, err)
-}
-
-func TestNetworkManagerBackend_UpdateEthernetState_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.ethernetDevice = nil
- err = backend.updateEthernetState()
- assert.NoError(t, err)
-}
-
-func TestNetworkManagerBackend_UpdateWiFiState_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- err = backend.updateWiFiState()
- assert.NoError(t, err)
-}
-
-func TestNetworkManagerBackend_ClassifyNMStateReason(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- testCases := []struct {
- reason uint32
- expected string
- }{
- {NmDeviceStateReasonWrongPassword, errdefs.ErrBadCredentials},
- {NmDeviceStateReasonNoSecrets, errdefs.ErrUserCanceled},
- {NmDeviceStateReasonSupplicantTimeout, errdefs.ErrBadCredentials},
- {NmDeviceStateReasonDhcpClientFailed, errdefs.ErrDhcpTimeout},
- {NmDeviceStateReasonNoSsid, errdefs.ErrNoSuchSSID},
- {999, errdefs.ErrConnectionFailed},
- }
-
- for _, tc := range testCases {
- result := backend.classifyNMStateReason(tc.reason)
- assert.Equal(t, tc.expected, result)
- }
-}
-
-func TestNetworkManagerBackend_GetDeviceIP_NoConfig(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockDevice := mock_gonetworkmanager.NewMockDevice(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockDevice.EXPECT().GetPropertyIP4Config().Return(nil, nil)
-
- ip := backend.getDeviceIP(mockDevice)
- assert.Empty(t, ip)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_test.go
deleted file mode 100644
index 1391e4a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package network
-
-import (
- "testing"
-
- mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNetworkManagerBackend_New(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
- assert.NotNil(t, backend)
- assert.Equal(t, "networkmanager", backend.state.Backend)
- assert.NotNil(t, backend.stopChan)
- assert.NotNil(t, backend.state)
-}
-
-func TestNetworkManagerBackend_GetCurrentState(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.state.NetworkStatus = StatusWiFi
- backend.state.WiFiConnected = true
- backend.state.WiFiSSID = "TestNetwork"
- backend.state.WiFiIP = "192.168.1.100"
- backend.state.WiFiNetworks = []WiFiNetwork{
- {SSID: "TestNetwork", Signal: 80, Connected: true},
- }
- backend.state.WiredConnections = []WiredConnection{
- {ID: "Wired connection 1", UUID: "test-uuid"},
- }
-
- state, err := backend.GetCurrentState()
- assert.NoError(t, err)
- assert.NotNil(t, state)
- assert.Equal(t, StatusWiFi, state.NetworkStatus)
- assert.True(t, state.WiFiConnected)
- assert.Equal(t, "TestNetwork", state.WiFiSSID)
- assert.Len(t, state.WiFiNetworks, 1)
- assert.Len(t, state.WiredConnections, 1)
-
- assert.NotSame(t, &backend.state.WiFiNetworks, &state.WiFiNetworks)
- assert.NotSame(t, &backend.state.WiredConnections, &state.WiredConnections)
-}
-
-func TestNetworkManagerBackend_SetPromptBroker_Nil(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- err = backend.SetPromptBroker(nil)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "cannot be nil")
-}
-
-func TestNetworkManagerBackend_SubmitCredentials_NoBroker(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.promptBroker = nil
- err = backend.SubmitCredentials("token", map[string]string{"password": "test"}, false)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not initialized")
-}
-
-func TestNetworkManagerBackend_CancelCredentials_NoBroker(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.promptBroker = nil
- err = backend.CancelCredentials("token")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not initialized")
-}
-
-func TestNetworkManagerBackend_EnsureWiFiDevice_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- backend.wifiDev = nil
-
- err = backend.ensureWiFiDevice()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestNetworkManagerBackend_EnsureWiFiDevice_AlreadySet(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDev = "dummy-device"
-
- err = backend.ensureWiFiDevice()
- assert.NoError(t, err)
-}
-
-func TestNetworkManagerBackend_StartSecretAgent_NoBroker(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.promptBroker = nil
- err = backend.startSecretAgent()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "prompt broker not set")
-}
-
-func TestNetworkManagerBackend_Close(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- assert.NotPanics(t, func() {
- backend.Close()
- })
-}
-
-func TestNetworkManagerBackend_GetPromptBroker(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- broker := backend.GetPromptBroker()
- assert.Nil(t, broker)
-}
-
-func TestNetworkManagerBackend_StopMonitoring_NoSignals(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- assert.NotPanics(t, func() {
- backend.StopMonitoring()
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn.go
deleted file mode 100644
index 261fcef..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn.go
+++ /dev/null
@@ -1,1394 +0,0 @@
-package network
-
-import (
- "bufio"
- "context"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/Wifx/gonetworkmanager/v2"
- "github.com/godbus/dbus/v5"
-)
-
-func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return nil, fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return nil, fmt.Errorf("failed to get connections: %w", err)
- }
-
- profiles := []VPNProfile{}
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connID, _ := connMeta["id"].(string)
- connUUID, _ := connMeta["uuid"].(string)
- autoconnect, _ := connMeta["autoconnect"].(bool)
-
- profile := VPNProfile{
- Name: connID,
- UUID: connUUID,
- Type: connType,
- Autoconnect: autoconnect,
- }
-
- if connType == "vpn" {
- if vpnSettings, ok := settings["vpn"]; ok {
- if svcType, ok := vpnSettings["service-type"].(string); ok {
- profile.ServiceType = svcType
- }
- // Get full data map
- if data, ok := vpnSettings["data"].(map[string]string); ok {
- profile.Data = data
- if remote, ok := data["remote"]; ok {
- profile.RemoteHost = remote
- }
- if username, ok := data["username"]; ok {
- profile.Username = username
- }
- }
- }
- }
-
- profiles = append(profiles, profile)
- }
-
- sort.Slice(profiles, func(i, j int) bool {
- return strings.ToLower(profiles[i].Name) < strings.ToLower(profiles[j].Name)
- })
-
- b.stateMutex.Lock()
- b.state.VPNProfiles = profiles
- b.stateMutex.Unlock()
-
- return profiles, nil
-}
-
-func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- activeConns, err := nm.GetPropertyActiveConnections()
- if err != nil {
- return nil, fmt.Errorf("failed to get active connections: %w", err)
- }
-
- active := []VPNActive{}
- for _, activeConn := range activeConns {
- connType, err := activeConn.GetPropertyType()
- if err != nil {
- continue
- }
-
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- uuid, _ := activeConn.GetPropertyUUID()
- id, _ := activeConn.GetPropertyID()
- state, _ := activeConn.GetPropertyState()
-
- var stateStr string
- switch state {
- case 0:
- stateStr = "unknown"
- case 1:
- stateStr = "activating"
- case 2:
- stateStr = "activated"
- case 3:
- stateStr = "deactivating"
- case 4:
- stateStr = "deactivated"
- }
-
- vpnActive := VPNActive{
- Name: id,
- UUID: uuid,
- State: stateStr,
- Type: connType,
- Plugin: "",
- }
-
- // Get VPN device
- devices, _ := activeConn.GetPropertyDevices()
- if len(devices) > 0 {
- if iface, err := devices[0].GetPropertyInterface(); err == nil {
- vpnActive.Device = iface
- }
- }
-
- // Get VPN IP from IP4Config
- if ip4Config, err := activeConn.GetPropertyIP4Config(); err == nil && ip4Config != nil {
- if addrData, err := ip4Config.GetPropertyAddressData(); err == nil && len(addrData) > 0 {
- vpnActive.IP = addrData[0].Address
- }
- if gw, err := ip4Config.GetPropertyGateway(); err == nil {
- vpnActive.Gateway = gw
- }
- }
-
- // Get MTU from device
- if len(devices) > 0 {
- if mtu, err := devices[0].GetPropertyMtu(); err == nil {
- vpnActive.MTU = mtu
- }
- }
-
- if connType == "vpn" {
- conn, _ := activeConn.GetPropertyConnection()
- if conn != nil {
- connSettings, err := conn.GetSettings()
- if err == nil {
- if vpnSettings, ok := connSettings["vpn"]; ok {
- if svcType, ok := vpnSettings["service-type"].(string); ok {
- vpnActive.Plugin = svcType
- }
- // Get full data map
- if data, ok := vpnSettings["data"].(map[string]string); ok {
- vpnActive.Data = data
- if remote, ok := data["remote"]; ok {
- vpnActive.RemoteHost = remote
- }
- if username, ok := data["username"]; ok {
- vpnActive.Username = username
- }
- }
- }
- }
- }
- }
-
- active = append(active, vpnActive)
- }
-
- b.stateMutex.Lock()
- b.state.VPNActive = active
- b.stateMutex.Unlock()
-
- return active, nil
-}
-
-func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool) error {
- if singleActive {
- active, err := b.ListActiveVPN()
- if err == nil && len(active) > 0 {
- alreadyConnected := false
- for _, vpn := range active {
- if vpn.UUID == uuidOrName || vpn.Name == uuidOrName {
- alreadyConnected = true
- break
- }
- }
-
- if !alreadyConnected {
- if err := b.DisconnectAllVPN(); err != nil {
- log.Warnf("Failed to disconnect existing VPNs: %v", err)
- }
- time.Sleep(500 * time.Millisecond)
- } else {
- return nil
- }
- }
- }
-
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- var targetConn gonetworkmanager.Connection
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connID, _ := connMeta["id"].(string)
- connUUID, _ := connMeta["uuid"].(string)
-
- if connUUID == uuidOrName || connID == uuidOrName {
- targetConn = conn
- break
- }
- }
-
- if targetConn == nil {
- return fmt.Errorf("VPN connection not found: %s", uuidOrName)
- }
-
- targetSettings, err := targetConn.GetSettings()
- if err != nil {
- return fmt.Errorf("failed to get connection settings: %w", err)
- }
-
- var targetUUID string
- var connName string
- if connMeta, ok := targetSettings["connection"]; ok {
- if uuid, ok := connMeta["uuid"].(string); ok {
- targetUUID = uuid
- }
- if id, ok := connMeta["id"].(string); ok {
- connName = id
- }
- }
-
- var vpnServiceType string
- var vpnData map[string]string
- if vpnSettings, ok := targetSettings["vpn"]; ok {
- if svc, ok := vpnSettings["service-type"].(string); ok {
- vpnServiceType = svc
- }
- if data, ok := vpnSettings["data"].(map[string]string); ok {
- vpnData = data
- }
- }
-
- authAction := detectVPNAuthAction(vpnServiceType, vpnData)
-
- switch authAction {
- case "openvpn_username":
- if b.promptBroker == nil {
- return fmt.Errorf("OpenVPN password authentication requires interactive prompt")
- }
- if err := b.handleOpenVPNUsernameAuth(targetConn, connName, targetUUID, vpnServiceType); err != nil {
- return err
- }
- case "gp_saml":
- gateway := vpnData["gateway"]
- protocol := vpnData["protocol"]
- if protocol != "gp" {
- return fmt.Errorf("GlobalProtect SAML authentication only supported for protocol=gp, got: %s", protocol)
- }
-
- log.Infof("[ConnectVPN] GlobalProtect SAML/SSO authentication required for %s (gateway=%s)", connName, gateway)
-
- samlCtx, samlCancel := context.WithTimeout(context.Background(), 5*time.Minute)
- authResult, err := b.runGlobalProtectSAMLAuth(samlCtx, gateway, protocol)
- samlCancel()
- if err != nil {
- errMsg := err.Error()
- switch {
- case strings.Contains(errMsg, "not installed"):
- return fmt.Errorf("gp-saml-gui is not installed (required for GlobalProtect SAML/SSO VPN)")
- case strings.Contains(errMsg, "timed out") || strings.Contains(errMsg, "cancelled"):
- return fmt.Errorf("GlobalProtect SAML authentication timed out — please try again")
- case strings.Contains(errMsg, "no cookie"):
- return fmt.Errorf("GlobalProtect SAML login did not complete — browser was closed before authentication finished")
- case strings.Contains(errMsg, "convert prelogin-cookie"):
- return fmt.Errorf("GlobalProtect VPN authentication succeeded but cookie exchange failed: %w", err)
- default:
- return fmt.Errorf("GlobalProtect SAML authentication failed: %w", err)
- }
- }
-
- b.cachedGPSamlMu.Lock()
- b.cachedGPSamlCookie = &cachedGPSamlCookie{
- ConnectionUUID: targetUUID,
- Cookie: authResult.Cookie,
- Host: authResult.Host,
- User: authResult.User,
- Fingerprint: authResult.Fingerprint,
- }
- b.cachedGPSamlMu.Unlock()
-
- if err := targetConn.ClearSecrets(); err != nil {
- log.Warnf("[ConnectVPN] ClearSecrets failed (non-fatal): %v", err)
- } else {
- log.Infof("[ConnectVPN] Cleared stale stored secrets for %s", connName)
- }
-
- log.Infof("[ConnectVPN] GlobalProtect SAML cookie cached for %s, proceeding with activation", connName)
- }
-
- b.stateMutex.Lock()
- b.state.IsConnectingVPN = true
- b.state.ConnectingVPNUUID = targetUUID
- b.stateMutex.Unlock()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- _, err = nm.ActivateConnection(targetConn, nil, nil)
- if err != nil {
- b.stateMutex.Lock()
- b.state.IsConnectingVPN = false
- b.state.ConnectingVPNUUID = ""
- b.stateMutex.Unlock()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return fmt.Errorf("failed to activate VPN: %w", err)
- }
-
- return nil
-}
-
-func detectVPNAuthAction(serviceType string, data map[string]string) string {
- if data == nil {
- return ""
- }
-
- switch {
- case strings.Contains(serviceType, "openconnect"):
- protocol := data["protocol"]
- if needsExternalBrowserAuth(protocol, data["authtype"], data["username"], data) {
- switch protocol {
- case "gp":
- return "gp_saml"
- default:
- log.Infof("[VPN] External browser auth detected for protocol '%s' but only GlobalProtect (gp) is currently supported", protocol)
- }
- }
- case strings.Contains(serviceType, "openvpn"):
- connType := data["connection-type"]
- username := data["username"]
- if (connType == "password" || connType == "password-tls") && username == "" {
- return "openvpn_username"
- }
- }
- return ""
-}
-
-func (b *NetworkManagerBackend) handleOpenVPNUsernameAuth(targetConn gonetworkmanager.Connection, connName, targetUUID, vpnServiceType string) error {
- log.Infof("[ConnectVPN] OpenVPN requires username in vpn.data - prompting before activation")
-
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
- defer cancel()
-
- token, err := b.promptBroker.Ask(ctx, PromptRequest{
- Name: connName,
- ConnType: "vpn",
- VpnService: vpnServiceType,
- SettingName: "vpn",
- Fields: []string{"username", "password"},
- FieldsInfo: []FieldInfo{{Name: "username", Label: "Username", IsSecret: false}, {Name: "password", Label: "Password", IsSecret: true}},
- Reason: "required",
- ConnectionId: connName,
- ConnectionUuid: targetUUID,
- ConnectionPath: string(targetConn.GetPath()),
- })
- if err != nil {
- return fmt.Errorf("failed to request credentials: %w", err)
- }
-
- reply, err := b.promptBroker.Wait(ctx, token)
- if err != nil {
- return fmt.Errorf("credentials prompt failed: %w", err)
- }
-
- if reply.Cancel {
- return fmt.Errorf("user cancelled authentication")
- }
-
- username := reply.Secrets["username"]
- password := reply.Secrets["password"]
- if username == "" {
- return nil
- }
-
- connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", targetConn.GetPath())
- var existingSettings map[string]map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
- return fmt.Errorf("failed to get settings for username save: %w", err)
- }
-
- settings := make(map[string]map[string]dbus.Variant)
- if connSection, ok := existingSettings["connection"]; ok {
- settings["connection"] = connSection
- }
- vpn := existingSettings["vpn"]
- var data map[string]string
- if dataVariant, ok := vpn["data"]; ok {
- if dm, ok := dataVariant.Value().(map[string]string); ok {
- data = make(map[string]string)
- for k, v := range dm {
- data[k] = v
- }
- } else {
- data = make(map[string]string)
- }
- } else {
- data = make(map[string]string)
- }
- data["username"] = username
-
- vpn["data"] = dbus.MakeVariant(data)
- settings["vpn"] = vpn
-
- var result map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
- settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
- return fmt.Errorf("failed to save username: %w", err)
- }
- log.Infof("[ConnectVPN] Username saved to connection")
-
- if password != "" {
- b.cachedVPNCredsMu.Lock()
- b.cachedVPNCreds = &cachedVPNCredentials{
- ConnectionUUID: targetUUID,
- Password: password,
- SavePassword: reply.Save,
- }
- b.cachedVPNCredsMu.Unlock()
- log.Infof("[ConnectVPN] Cached password for GetSecrets")
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) DisconnectVPN(uuidOrName string) error {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- activeConns, err := nm.GetPropertyActiveConnections()
- if err != nil {
- return fmt.Errorf("failed to get active connections: %w", err)
- }
-
- log.Debugf("[DisconnectVPN] Looking for VPN: %s", uuidOrName)
-
- for _, activeConn := range activeConns {
- connType, err := activeConn.GetPropertyType()
- if err != nil {
- continue
- }
-
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- uuid, _ := activeConn.GetPropertyUUID()
- id, _ := activeConn.GetPropertyID()
- state, _ := activeConn.GetPropertyState()
-
- log.Debugf("[DisconnectVPN] Found active VPN: uuid=%s id=%s state=%d", uuid, id, state)
-
- if uuid == uuidOrName || id == uuidOrName {
- log.Infof("[DisconnectVPN] Deactivating VPN: %s (state=%d)", id, state)
- if err := nm.DeactivateConnection(activeConn); err != nil {
- return fmt.Errorf("failed to deactivate VPN: %w", err)
- }
- b.ListActiveVPN()
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return nil
- }
- }
-
- log.Warnf("[DisconnectVPN] VPN not found in active connections: %s", uuidOrName)
-
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("VPN connection not active and cannot access settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("VPN connection not active: %s", uuidOrName)
- }
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connID, _ := connMeta["id"].(string)
- connUUID, _ := connMeta["uuid"].(string)
-
- if connUUID == uuidOrName || connID == uuidOrName {
- log.Infof("[DisconnectVPN] VPN connection exists but not active: %s", connID)
- return nil
- }
- }
-
- return fmt.Errorf("VPN connection not found: %s", uuidOrName)
-}
-
-func (b *NetworkManagerBackend) DisconnectAllVPN() error {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- activeConns, err := nm.GetPropertyActiveConnections()
- if err != nil {
- return fmt.Errorf("failed to get active connections: %w", err)
- }
-
- var lastErr error
- var disconnected bool
- for _, activeConn := range activeConns {
- connType, err := activeConn.GetPropertyType()
- if err != nil {
- continue
- }
-
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- if err := nm.DeactivateConnection(activeConn); err != nil {
- lastErr = err
- log.Warnf("Failed to deactivate VPN connection: %v", err)
- } else {
- disconnected = true
- }
- }
-
- if disconnected {
- b.ListActiveVPN()
- if b.onStateChange != nil {
- b.onStateChange()
- }
- }
-
- return lastErr
-}
-
-func (b *NetworkManagerBackend) ClearVPNCredentials(uuidOrName string) error {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connID, _ := connMeta["id"].(string)
- connUUID, _ := connMeta["uuid"].(string)
-
- if connUUID == uuidOrName || connID == uuidOrName {
- if connType == "vpn" {
- if vpnSettings, ok := settings["vpn"]; ok {
- delete(vpnSettings, "secrets")
-
- if dataMap, ok := vpnSettings["data"].(map[string]string); ok {
- dataMap["password-flags"] = "1"
- vpnSettings["data"] = dataMap
- }
- }
- }
-
- if err := conn.Update(settings); err != nil {
- return fmt.Errorf("failed to update connection: %w", err)
- }
-
- if err := conn.ClearSecrets(); err != nil {
- log.Warnf("ClearSecrets call failed (may not be critical): %v", err)
- }
-
- log.Infof("Cleared credentials for VPN: %s", connID)
- return nil
- }
- }
-
- return fmt.Errorf("VPN connection not found: %s", uuidOrName)
-}
-
-func (b *NetworkManagerBackend) updateVPNConnectionState() {
- b.stateMutex.RLock()
- isConnectingVPN := b.state.IsConnectingVPN
- connectingVPNUUID := b.state.ConnectingVPNUUID
- b.stateMutex.RUnlock()
-
- if !isConnectingVPN || connectingVPNUUID == "" {
- return
- }
-
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- activeConns, err := nm.GetPropertyActiveConnections()
- if err != nil {
- return
- }
-
- foundConnection := false
- for _, activeConn := range activeConns {
- connType, err := activeConn.GetPropertyType()
- if err != nil {
- continue
- }
-
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connUUID, err := activeConn.GetPropertyUUID()
- if err != nil {
- continue
- }
-
- state, _ := activeConn.GetPropertyState()
- stateReason, _ := activeConn.GetPropertyStateFlags()
-
- if connUUID == connectingVPNUUID {
- foundConnection = true
-
- switch state {
- case 2:
- log.Infof("[updateVPNConnectionState] VPN connection successful: %s", connUUID)
- b.stateMutex.Lock()
- b.state.IsConnectingVPN = false
- b.state.ConnectingVPNUUID = ""
- b.state.LastError = ""
- b.stateMutex.Unlock()
-
- // Clear cached PKCS11 PIN and SAML cookie on success
- b.cachedPKCS11Mu.Lock()
- b.cachedPKCS11PIN = nil
- b.cachedPKCS11Mu.Unlock()
- b.cachedGPSamlMu.Lock()
- b.cachedGPSamlCookie = nil
- b.cachedGPSamlMu.Unlock()
-
- b.pendingVPNSaveMu.Lock()
- pending := b.pendingVPNSave
- b.pendingVPNSave = nil
- b.pendingVPNSaveMu.Unlock()
-
- if pending != nil {
- go b.saveVPNCredentials(pending)
- }
- return
- case 4:
- log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", connUUID, state, stateReason)
- b.stateMutex.Lock()
- b.state.IsConnectingVPN = false
- b.state.ConnectingVPNUUID = ""
- b.state.LastError = "VPN connection failed"
- b.stateMutex.Unlock()
-
- // Clear cached PKCS11 PIN and SAML cookie on failure
- b.cachedPKCS11Mu.Lock()
- b.cachedPKCS11PIN = nil
- b.cachedPKCS11Mu.Unlock()
- b.cachedGPSamlMu.Lock()
- b.cachedGPSamlCookie = nil
- b.cachedGPSamlMu.Unlock()
- return
- }
- }
- }
-
- if !foundConnection {
- log.Warnf("[updateVPNConnectionState] VPN connection no longer exists: %s", connectingVPNUUID)
- b.stateMutex.Lock()
- b.state.IsConnectingVPN = false
- b.state.ConnectingVPNUUID = ""
- b.state.LastError = "VPN connection failed"
- b.stateMutex.Unlock()
-
- // Clear cached PKCS11 PIN and SAML cookie
- b.cachedPKCS11Mu.Lock()
- b.cachedPKCS11PIN = nil
- b.cachedPKCS11Mu.Unlock()
- b.cachedGPSamlMu.Lock()
- b.cachedGPSamlCookie = nil
- b.cachedGPSamlMu.Unlock()
- }
-}
-
-func (b *NetworkManagerBackend) saveVPNCredentials(creds *pendingVPNCredentials) {
- log.Infof("[saveVPNCredentials] Saving credentials for %s (username=%v, savePassword=%v)",
- creds.ConnectionPath, creds.Username != "", creds.SavePassword)
-
- connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(creds.ConnectionPath))
- var existingSettings map[string]map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
- log.Warnf("[saveVPNCredentials] GetSettings failed: %v", err)
- return
- }
-
- settings := make(map[string]map[string]dbus.Variant)
- if connSection, ok := existingSettings["connection"]; ok {
- settings["connection"] = connSection
- }
-
- vpn, ok := existingSettings["vpn"]
- if !ok {
- vpn = make(map[string]dbus.Variant)
- }
-
- // Get existing data map
- var data map[string]string
- if dataVariant, ok := vpn["data"]; ok {
- if dm, ok := dataVariant.Value().(map[string]string); ok {
- data = make(map[string]string)
- for k, v := range dm {
- data[k] = v
- }
- } else {
- data = make(map[string]string)
- }
- } else {
- data = make(map[string]string)
- }
-
- // Always save username if provided
- if creds.Username != "" {
- data["username"] = creds.Username
- log.Infof("[saveVPNCredentials] Saving username")
- }
-
- // Save password if requested
- if creds.SavePassword {
- data["password-flags"] = "0"
- secs := make(map[string]string)
- secs["password"] = creds.Password
- vpn["secrets"] = dbus.MakeVariant(secs)
- log.Infof("[saveVPNCredentials] Saving password with password-flags=0")
- }
-
- vpn["data"] = dbus.MakeVariant(data)
- settings["vpn"] = vpn
-
- var result map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
- settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
- log.Warnf("[saveVPNCredentials] Update2 failed: %v", err)
- } else {
- log.Infof("[saveVPNCredentials] Successfully saved credentials")
- }
-}
-
-func (b *NetworkManagerBackend) ListVPNPlugins() ([]VPNPlugin, error) {
- plugins := []VPNPlugin{}
- pluginDirs := []string{
- "/usr/lib/NetworkManager/VPN",
- "/usr/lib64/NetworkManager/VPN",
- "/etc/NetworkManager/VPN",
- }
-
- seen := make(map[string]bool)
-
- for _, dir := range pluginDirs {
- entries, err := os.ReadDir(dir)
- if err != nil {
- continue
- }
-
- for _, entry := range entries {
- if !strings.HasSuffix(entry.Name(), ".name") {
- continue
- }
-
- filePath := filepath.Join(dir, entry.Name())
- plugin, err := parseVPNPluginFile(filePath)
- if err != nil {
- log.Debugf("Failed to parse VPN plugin file %s: %v", filePath, err)
- continue
- }
-
- if seen[plugin.ServiceType] {
- continue
- }
- seen[plugin.ServiceType] = true
- plugins = append(plugins, *plugin)
- }
- }
-
- sort.Slice(plugins, func(i, j int) bool {
- return strings.ToLower(plugins[i].Name) < strings.ToLower(plugins[j].Name)
- })
-
- return plugins, nil
-}
-
-func parseVPNPluginFile(path string) (*VPNPlugin, error) {
- file, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer file.Close()
-
- plugin := &VPNPlugin{}
- scanner := bufio.NewScanner(file)
-
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") {
- continue
- }
-
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 {
- continue
- }
-
- key := strings.TrimSpace(parts[0])
- value := strings.TrimSpace(parts[1])
-
- switch key {
- case "name":
- plugin.Name = value
- case "service":
- plugin.ServiceType = value
- case "program":
- plugin.Program = value
- case "supports":
- plugin.Supports = strings.Split(value, ",")
- for i := range plugin.Supports {
- plugin.Supports[i] = strings.TrimSpace(plugin.Supports[i])
- }
- }
- }
-
- if plugin.ServiceType == "" {
- return nil, fmt.Errorf("plugin file missing service type")
- }
-
- plugin.FileExtensions = getVPNFileExtensions(plugin.ServiceType)
-
- return plugin, nil
-}
-
-func getVPNFileExtensions(serviceType string) []string {
- switch {
- case strings.Contains(serviceType, "openvpn"):
- return []string{".ovpn", ".conf"}
- case strings.Contains(serviceType, "wireguard"):
- return []string{".conf"}
- case strings.Contains(serviceType, "vpnc"), strings.Contains(serviceType, "cisco"):
- return []string{".pcf", ".conf"}
- case strings.Contains(serviceType, "openconnect"):
- return []string{".conf"}
- case strings.Contains(serviceType, "pptp"):
- return []string{".conf"}
- case strings.Contains(serviceType, "l2tp"):
- return []string{".conf"}
- case strings.Contains(serviceType, "strongswan"), strings.Contains(serviceType, "ipsec"):
- return []string{".conf", ".sswan"}
- default:
- return []string{".conf"}
- }
-}
-
-func (b *NetworkManagerBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
- if _, err := os.Stat(filePath); os.IsNotExist(err) {
- return &VPNImportResult{
- Success: false,
- Error: fmt.Sprintf("file not found: %s", filePath),
- }, nil
- }
-
- ext := strings.ToLower(filepath.Ext(filePath))
-
- switch ext {
- case ".ovpn", ".conf":
- return b.importVPNWithNmcli(filePath, name)
- default:
- return b.importVPNWithNmcli(filePath, name)
- }
-}
-
-func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
- vpnTypes := []string{"openvpn", "wireguard", "vpnc", "pptp", "l2tp", "openconnect", "strongswan"}
-
- var allErrors []error
- var outputStr string
- for _, vpnType := range vpnTypes {
- cmd := exec.Command("nmcli", "connection", "import", "type", vpnType, "file", filePath)
- output, err := cmd.CombinedOutput()
- if err == nil {
- outputStr = string(output)
- break
- }
- allErrors = append(allErrors, fmt.Errorf("%s: %s", vpnType, strings.TrimSpace(string(output))))
- }
-
- if len(allErrors) == len(vpnTypes) {
- return &VPNImportResult{
- Success: false,
- Error: errors.Join(allErrors...).Error(),
- }, nil
- }
- var connUUID, connName string
-
- lines := strings.Split(outputStr, "\n")
- for _, line := range lines {
- if strings.Contains(line, "successfully added") {
- parts := strings.Fields(line)
- for i, part := range parts {
- if part == "(" && i+1 < len(parts) {
- connUUID = strings.TrimSuffix(parts[i+1], ")")
- break
- }
- }
- }
- }
-
- if name != "" && connUUID != "" {
- renameCmd := exec.Command("nmcli", "connection", "modify", connUUID, "connection.id", name)
- if err := renameCmd.Run(); err != nil {
- log.Warnf("Failed to rename imported VPN: %v", err)
- } else {
- connName = name
- }
- }
-
- if connUUID == "" {
- s := b.settings
- if s == nil {
- var settingsErr error
- s, settingsErr = gonetworkmanager.NewSettings()
- if settingsErr == nil {
- b.settings = s
- }
- }
-
- if s != nil {
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, _ := settingsMgr.ListConnections()
- baseName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
- connID, _ := connMeta["id"].(string)
- if strings.Contains(connID, baseName) || (name != "" && connID == name) {
- connUUID, _ = connMeta["uuid"].(string)
- connName = connID
- break
- }
- }
- }
- }
-
- b.ListVPNProfiles()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return &VPNImportResult{
- Success: true,
- UUID: connUUID,
- Name: connName,
- }, nil
-}
-
-func (b *NetworkManagerBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return nil, fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return nil, fmt.Errorf("failed to get connections: %w", err)
- }
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connID, _ := connMeta["id"].(string)
- connUUID, _ := connMeta["uuid"].(string)
-
- if connUUID != uuidOrName && connID != uuidOrName {
- continue
- }
-
- autoconnect := true
- if ac, ok := connMeta["autoconnect"].(bool); ok {
- autoconnect = ac
- }
-
- config := &VPNConfig{
- UUID: connUUID,
- Name: connID,
- Type: connType,
- Autoconnect: autoconnect,
- Data: make(map[string]string),
- }
-
- if connType == "vpn" {
- if vpnSettings, ok := settings["vpn"]; ok {
- if svcType, ok := vpnSettings["service-type"].(string); ok {
- config.ServiceType = svcType
- }
- if dataMap, ok := vpnSettings["data"].(map[string]string); ok {
- for k, v := range dataMap {
- if !strings.Contains(strings.ToLower(k), "password") &&
- !strings.Contains(strings.ToLower(k), "secret") &&
- !strings.Contains(strings.ToLower(k), "key") {
- config.Data[k] = v
- }
- }
- }
- }
- }
-
- return config, nil
- }
-
- return nil, fmt.Errorf("VPN connection not found: %s", uuidOrName)
-}
-
-func (b *NetworkManagerBackend) UpdateVPNConfig(connUUID string, updates map[string]any) error {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- existingUUID, _ := connMeta["uuid"].(string)
- if existingUUID != connUUID {
- continue
- }
-
- if name, ok := updates["name"].(string); ok && name != "" {
- connMeta["id"] = name
- }
-
- if autoconnect, ok := updates["autoconnect"].(bool); ok {
- connMeta["autoconnect"] = autoconnect
- }
-
- if data, ok := updates["data"].(map[string]any); ok {
- if vpnSettings, ok := settings["vpn"]; ok {
- existingData, _ := vpnSettings["data"].(map[string]string)
- if existingData == nil {
- existingData = make(map[string]string)
- }
- for k, v := range data {
- if strVal, ok := v.(string); ok {
- existingData[k] = strVal
- }
- }
- vpnSettings["data"] = existingData
- }
- }
-
- if ipv4, ok := settings["ipv4"]; ok {
- delete(ipv4, "addresses")
- delete(ipv4, "routes")
- delete(ipv4, "dns")
- }
- if ipv6, ok := settings["ipv6"]; ok {
- delete(ipv6, "addresses")
- delete(ipv6, "routes")
- delete(ipv6, "dns")
- }
-
- if err := conn.Update(settings); err != nil {
- return fmt.Errorf("failed to update connection: %w", err)
- }
-
- b.ListVPNProfiles()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
- }
-
- return fmt.Errorf("VPN connection not found: %s", connUUID)
-}
-
-func (b *NetworkManagerBackend) SetVPNCredentials(connUUID string, username string, password string, saveToKeyring bool) error {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- existingUUID, _ := connMeta["uuid"].(string)
- if existingUUID != connUUID {
- continue
- }
-
- vpnSettings, ok := settings["vpn"]
- if !ok {
- vpnSettings = make(map[string]any)
- settings["vpn"] = vpnSettings
- }
-
- existingData, _ := vpnSettings["data"].(map[string]string)
- if existingData == nil {
- existingData = make(map[string]string)
- }
-
- if username != "" {
- existingData["username"] = username
- }
-
- if saveToKeyring {
- existingData["password-flags"] = "0"
- } else {
- existingData["password-flags"] = "2"
- }
-
- vpnSettings["data"] = existingData
-
- if password != "" {
- secrets := make(map[string]string)
- secrets["password"] = password
- vpnSettings["secrets"] = secrets
- }
-
- if ipv4, ok := settings["ipv4"]; ok {
- delete(ipv4, "addresses")
- delete(ipv4, "routes")
- delete(ipv4, "dns")
- }
- if ipv6, ok := settings["ipv6"]; ok {
- delete(ipv6, "addresses")
- delete(ipv6, "routes")
- delete(ipv6, "dns")
- }
-
- if err := conn.Update(settings); err != nil {
- return fmt.Errorf("failed to update connection: %w", err)
- }
-
- log.Infof("Updated VPN credentials for %s (save=%v)", connUUID, saveToKeyring)
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
- }
-
- return fmt.Errorf("VPN connection not found: %s", connUUID)
-}
-
-func (b *NetworkManagerBackend) DeleteVPN(uuidOrName string) error {
- active, _ := b.ListActiveVPN()
- for _, vpn := range active {
- if vpn.UUID == uuidOrName || vpn.Name == uuidOrName {
- if err := b.DisconnectVPN(uuidOrName); err != nil {
- log.Warnf("Failed to disconnect VPN before deletion: %v", err)
- }
- time.Sleep(200 * time.Millisecond)
- break
- }
- }
-
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return fmt.Errorf("failed to get connections: %w", err)
- }
-
- for _, conn := range connections {
- settings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := settings["connection"]
- if !ok {
- continue
- }
-
- connType, _ := connMeta["type"].(string)
- if connType != "vpn" && connType != "wireguard" {
- continue
- }
-
- connID, _ := connMeta["id"].(string)
- connUUID, _ := connMeta["uuid"].(string)
-
- if connUUID == uuidOrName || connID == uuidOrName {
- if err := conn.Delete(); err != nil {
- return fmt.Errorf("failed to delete VPN: %w", err)
- }
-
- b.ListVPNProfiles()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- log.Infof("Deleted VPN connection: %s (%s)", connID, connUUID)
- return nil
- }
- }
-
- return fmt.Errorf("VPN connection not found: %s", uuidOrName)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn_test.go
deleted file mode 100644
index eebd433..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_vpn_test.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package network
-
-import (
- "testing"
-
- mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
- "github.com/Wifx/gonetworkmanager/v2"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNetworkManagerBackend_ListVPNProfiles(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockSettings := mock_gonetworkmanager.NewMockSettings(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
- backend.settings = mockSettings
-
- mockSettings.EXPECT().ListConnections().Return([]gonetworkmanager.Connection{}, nil)
-
- profiles, err := backend.ListVPNProfiles()
- assert.NoError(t, err)
- assert.Empty(t, profiles)
-}
-
-func TestNetworkManagerBackend_ListActiveVPN(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil)
-
- active, err := backend.ListActiveVPN()
- assert.NoError(t, err)
- assert.Empty(t, active)
-}
-
-func TestNetworkManagerBackend_ConnectVPN_NotFound(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockSettings := mock_gonetworkmanager.NewMockSettings(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
- backend.settings = mockSettings
-
- mockSettings.EXPECT().ListConnections().Return([]gonetworkmanager.Connection{}, nil)
-
- err = backend.ConnectVPN("non-existent-vpn-12345", false)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not found")
-}
-
-func TestNetworkManagerBackend_ConnectVPN_SingleActive_NoActiveVPN(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockSettings := mock_gonetworkmanager.NewMockSettings(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
- backend.settings = mockSettings
-
- mockSettings.EXPECT().ListConnections().Return([]gonetworkmanager.Connection{}, nil)
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil)
-
- err = backend.ConnectVPN("non-existent-vpn-12345", true)
- assert.Error(t, err)
-}
-
-func TestNetworkManagerBackend_DisconnectVPN_NotActive(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil)
-
- err = backend.DisconnectVPN("non-existent-vpn-12345")
- assert.Error(t, err)
-}
-
-func TestNetworkManagerBackend_DisconnectAllVPN(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyActiveConnections().Return([]gonetworkmanager.ActiveConnection{}, nil)
-
- err = backend.DisconnectAllVPN()
- assert.NoError(t, err)
-}
-
-func TestNetworkManagerBackend_ClearVPNCredentials_NotFound(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockSettings := mock_gonetworkmanager.NewMockSettings(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
- backend.settings = mockSettings
-
- mockSettings.EXPECT().ListConnections().Return([]gonetworkmanager.Connection{}, nil)
-
- err = backend.ClearVPNCredentials("non-existent-vpn-12345")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "not found")
-}
-
-func TestNetworkManagerBackend_UpdateVPNConnectionState_NotConnecting(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.stateMutex.Lock()
- backend.state.IsConnectingVPN = false
- backend.state.ConnectingVPNUUID = ""
- backend.stateMutex.Unlock()
-
- assert.NotPanics(t, func() {
- backend.updateVPNConnectionState()
- })
-}
-
-func TestNetworkManagerBackend_UpdateVPNConnectionState_EmptyUUID(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.stateMutex.Lock()
- backend.state.IsConnectingVPN = true
- backend.state.ConnectingVPNUUID = ""
- backend.stateMutex.Unlock()
-
- assert.NotPanics(t, func() {
- backend.updateVPNConnectionState()
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi.go
deleted file mode 100644
index 6f593b1..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi.go
+++ /dev/null
@@ -1,1173 +0,0 @@
-package network
-
-import (
- "bytes"
- "fmt"
- "sort"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/Wifx/gonetworkmanager/v2"
-)
-
-func (b *NetworkManagerBackend) GetWiFiEnabled() (bool, error) {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- return nm.GetPropertyWirelessEnabled()
-}
-
-func (b *NetworkManagerBackend) SetWiFiEnabled(enabled bool) error {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- err := nm.SetPropertyWirelessEnabled(enabled)
- if err != nil {
- return fmt.Errorf("failed to set WiFi enabled: %w", err)
- }
-
- b.stateMutex.Lock()
- b.state.WiFiEnabled = enabled
- b.stateMutex.Unlock()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) ScanWiFi() error {
- if b.wifiDevice == nil {
- return fmt.Errorf("no WiFi device available")
- }
-
- b.stateMutex.RLock()
- enabled := b.state.WiFiEnabled
- b.stateMutex.RUnlock()
-
- if !enabled {
- return fmt.Errorf("WiFi is disabled")
- }
-
- if err := b.ensureWiFiDevice(); err != nil {
- return err
- }
-
- w := b.wifiDev.(gonetworkmanager.DeviceWireless)
- err := w.RequestScan()
- if err != nil {
- return fmt.Errorf("scan request failed: %w", err)
- }
-
- _, err = b.updateWiFiNetworks()
- return err
-}
-
-func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) {
- if b.wifiDevice == nil {
- return nil, fmt.Errorf("no WiFi device available")
- }
-
- if err := b.ensureWiFiDevice(); err != nil {
- return nil, err
- }
- wifiDev := b.wifiDev
-
- w := wifiDev.(gonetworkmanager.DeviceWireless)
- apPaths, err := w.GetAccessPoints()
- if err != nil {
- return nil, fmt.Errorf("failed to get access points: %w", err)
- }
-
- s := b.settings
- if s == nil {
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return nil, fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return nil, fmt.Errorf("failed to get connections: %w", err)
- }
-
- savedSSIDs := make(map[string]bool)
- autoconnectMap := make(map[string]bool)
- for _, conn := range connections {
- connSettings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- if connMeta, ok := connSettings["connection"]; ok {
- if connType, ok := connMeta["type"].(string); ok && connType == "802-11-wireless" {
- if wifiSettings, ok := connSettings["802-11-wireless"]; ok {
- if ssidBytes, ok := wifiSettings["ssid"].([]byte); ok {
- savedSSID := string(ssidBytes)
- savedSSIDs[savedSSID] = true
- autoconnect := true
- if ac, ok := connMeta["autoconnect"].(bool); ok {
- autoconnect = ac
- }
- autoconnectMap[savedSSID] = autoconnect
- }
- }
- }
- }
- }
-
- b.stateMutex.RLock()
- currentSSID := b.state.WiFiSSID
- currentBSSID := b.state.WiFiBSSID
- b.stateMutex.RUnlock()
-
- var bands []WiFiNetwork
-
- for _, ap := range apPaths {
- apSSID, err := ap.GetPropertySSID()
- if err != nil || apSSID != ssid {
- continue
- }
-
- strength, _ := ap.GetPropertyStrength()
- flags, _ := ap.GetPropertyFlags()
- wpaFlags, _ := ap.GetPropertyWPAFlags()
- rsnFlags, _ := ap.GetPropertyRSNFlags()
- freq, _ := ap.GetPropertyFrequency()
- maxBitrate, _ := ap.GetPropertyMaxBitrate()
- bssid, _ := ap.GetPropertyHWAddress()
- mode, _ := ap.GetPropertyMode()
-
- secured := flags != uint32(gonetworkmanager.Nm80211APFlagsNone) ||
- wpaFlags != uint32(gonetworkmanager.Nm80211APSecNone) ||
- rsnFlags != uint32(gonetworkmanager.Nm80211APSecNone)
-
- enterprise := (rsnFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0) ||
- (wpaFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0)
-
- var modeStr string
- switch mode {
- case gonetworkmanager.Nm80211ModeAdhoc:
- modeStr = "adhoc"
- case gonetworkmanager.Nm80211ModeInfra:
- modeStr = "infrastructure"
- case gonetworkmanager.Nm80211ModeAp:
- modeStr = "ap"
- default:
- modeStr = "unknown"
- }
-
- channel := frequencyToChannel(freq)
-
- isConnected := ssid == currentSSID && bssid == currentBSSID
- rate := maxBitrate / 1000
- if isConnected {
- if devBitrate, err := w.GetPropertyBitrate(); err == nil && devBitrate > 0 {
- rate = devBitrate / 1000
- }
- }
-
- network := WiFiNetwork{
- SSID: ssid,
- BSSID: bssid,
- Signal: strength,
- Secured: secured,
- Enterprise: enterprise,
- Connected: isConnected,
- Saved: savedSSIDs[ssid],
- Autoconnect: autoconnectMap[ssid],
- Frequency: freq,
- Mode: modeStr,
- Rate: rate,
- Channel: channel,
- }
-
- bands = append(bands, network)
- }
-
- if len(bands) == 0 {
- return nil, fmt.Errorf("network not found: %s", ssid)
- }
-
- sort.Slice(bands, func(i, j int) bool {
- if bands[i].Connected && !bands[j].Connected {
- return true
- }
- if !bands[i].Connected && bands[j].Connected {
- return false
- }
- return bands[i].Signal > bands[j].Signal
- })
-
- return &NetworkInfoResponse{
- SSID: ssid,
- Bands: bands,
- }, nil
-}
-
-func (b *NetworkManagerBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
- conn, err := b.findConnection(ssid)
- if err != nil {
- return "", fmt.Errorf("no saved connection for `%s`: %w", ssid, err)
- }
-
- connSettings, err := conn.GetSettings()
- if err != nil {
- return "", fmt.Errorf("failed to get settings for `%s`: %w", ssid, err)
- }
-
- secSettings, ok := connSettings["802-11-wireless-security"]
- if !ok {
- return "", fmt.Errorf("network `%s` has no security settings", ssid)
- }
-
- keyMgmt, ok := secSettings["key-mgmt"].(string)
- if !ok {
- return "", fmt.Errorf("failed to identify security type of network `%s`", ssid)
- }
-
- var securityType string
- switch keyMgmt {
- case "none":
- authAlg, _ := secSettings["auth-alg"].(string)
- switch authAlg {
- case "open":
- securityType = "nopass"
- default:
- securityType = "WEP"
- }
- case "ieee8021x":
- securityType = "WEP"
- default:
- securityType = "WPA"
- }
-
- if securityType != "WPA" {
- return "", fmt.Errorf("QR code generation only supports WPA connections, `%s` uses %s", ssid, securityType)
- }
-
- secrets, err := conn.GetSecrets("802-11-wireless-security")
- if err != nil {
- return "", fmt.Errorf("failed to retrieve connection secrets for `%s`: %w", ssid, err)
- }
-
- secSecrets, ok := secrets["802-11-wireless-security"]
- if !ok {
- return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
- }
-
- psk, ok := secSecrets["psk"].(string)
- if !ok {
- return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
- }
-
- return FormatWiFiQRString(securityType, ssid, psk), nil
-}
-
-func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
- devInfo, err := b.getWifiDeviceForConnection(req.Device)
- if err != nil {
- return err
- }
-
- b.stateMutex.RLock()
- alreadyConnected := b.state.WiFiConnected && b.state.WiFiSSID == req.SSID
- b.stateMutex.RUnlock()
-
- if alreadyConnected && !req.Interactive && req.Device == "" {
- return nil
- }
-
- b.stateMutex.Lock()
- b.state.IsConnecting = true
- b.state.ConnectingSSID = req.SSID
- b.state.ConnectingDevice = req.Device
- b.state.LastError = ""
- b.stateMutex.Unlock()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
-
- existingConn, err := b.findConnection(req.SSID)
- if err == nil && existingConn != nil {
- _, err := nm.ActivateConnection(existingConn, devInfo.device, nil)
- if err != nil {
- log.Warnf("[ConnectWiFi] Failed to activate existing connection: %v", err)
- b.stateMutex.Lock()
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.ConnectingDevice = ""
- b.state.LastError = fmt.Sprintf("failed to activate connection: %v", err)
- b.stateMutex.Unlock()
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return fmt.Errorf("failed to activate connection: %w", err)
- }
-
- return nil
- }
-
- if err := b.createAndConnectWiFiOnDevice(req, devInfo); err != nil {
- log.Warnf("[ConnectWiFi] Failed to create and connect: %v", err)
- b.stateMutex.Lock()
- b.state.IsConnecting = false
- b.state.ConnectingSSID = ""
- b.state.ConnectingDevice = ""
- b.state.LastError = err.Error()
- b.stateMutex.Unlock()
- if b.onStateChange != nil {
- b.onStateChange()
- }
- return err
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) DisconnectWiFi() error {
- if b.wifiDevice == nil {
- return fmt.Errorf("no WiFi device available")
- }
-
- dev := b.wifiDevice.(gonetworkmanager.Device)
-
- err := dev.Disconnect()
- if err != nil {
- return fmt.Errorf("failed to disconnect: %w", err)
- }
-
- b.updateWiFiState()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) ForgetWiFiNetwork(ssid string) error {
- conn, err := b.findConnection(ssid)
- if err != nil {
- return fmt.Errorf("connection not found: %w", err)
- }
-
- b.stateMutex.RLock()
- currentSSID := b.state.WiFiSSID
- isConnected := b.state.WiFiConnected
- b.stateMutex.RUnlock()
-
- err = conn.Delete()
- if err != nil {
- return fmt.Errorf("failed to delete connection: %w", err)
- }
-
- if isConnected && currentSSID == ssid {
- b.stateMutex.Lock()
- b.state.WiFiConnected = false
- b.state.WiFiSSID = ""
- b.state.WiFiBSSID = ""
- b.state.WiFiSignal = 0
- b.state.WiFiIP = ""
- b.state.NetworkStatus = StatusDisconnected
- b.stateMutex.Unlock()
- }
-
- b.updateWiFiNetworks()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) IsConnectingTo(ssid string) bool {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- return b.state.IsConnecting && b.state.ConnectingSSID == ssid
-}
-
-func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
- if b.wifiDevice == nil {
- return nil, fmt.Errorf("no WiFi device available")
- }
-
- if err := b.ensureWiFiDevice(); err != nil {
- return nil, err
- }
- wifiDev := b.wifiDev
-
- w := wifiDev.(gonetworkmanager.DeviceWireless)
- apPaths, err := w.GetAccessPoints()
- if err != nil {
- return nil, fmt.Errorf("failed to get access points: %w", err)
- }
-
- s := b.settings
- if s == nil {
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return nil, fmt.Errorf("failed to get settings: %w", err)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return nil, fmt.Errorf("failed to get connections: %w", err)
- }
-
- savedSSIDs := make(map[string]bool)
- autoconnectMap := make(map[string]bool)
- hiddenSSIDs := make(map[string]bool)
- for _, conn := range connections {
- connSettings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := connSettings["connection"]
- if !ok {
- continue
- }
-
- connType, ok := connMeta["type"].(string)
- if !ok || connType != "802-11-wireless" {
- continue
- }
-
- wifiSettings, ok := connSettings["802-11-wireless"]
- if !ok {
- continue
- }
-
- ssidBytes, ok := wifiSettings["ssid"].([]byte)
- if !ok {
- continue
- }
-
- ssid := string(ssidBytes)
- savedSSIDs[ssid] = true
- autoconnect := true
- if ac, ok := connMeta["autoconnect"].(bool); ok {
- autoconnect = ac
- }
- autoconnectMap[ssid] = autoconnect
-
- if hidden, ok := wifiSettings["hidden"].(bool); ok && hidden {
- hiddenSSIDs[ssid] = true
- }
- }
-
- b.stateMutex.RLock()
- currentSSID := b.state.WiFiSSID
- wifiConnected := b.state.WiFiConnected
- wifiSignal := b.state.WiFiSignal
- wifiBSSID := b.state.WiFiBSSID
- b.stateMutex.RUnlock()
-
- seenSSIDs := make(map[string]*WiFiNetwork)
- networks := []WiFiNetwork{}
-
- for _, ap := range apPaths {
- ssid, err := ap.GetPropertySSID()
- if err != nil || ssid == "" {
- continue
- }
-
- if existing, exists := seenSSIDs[ssid]; exists {
- strength, _ := ap.GetPropertyStrength()
- if strength > existing.Signal {
- existing.Signal = strength
- freq, _ := ap.GetPropertyFrequency()
- existing.Frequency = freq
- bssid, _ := ap.GetPropertyHWAddress()
- existing.BSSID = bssid
- }
- continue
- }
-
- strength, _ := ap.GetPropertyStrength()
- flags, _ := ap.GetPropertyFlags()
- wpaFlags, _ := ap.GetPropertyWPAFlags()
- rsnFlags, _ := ap.GetPropertyRSNFlags()
- freq, _ := ap.GetPropertyFrequency()
- maxBitrate, _ := ap.GetPropertyMaxBitrate()
- bssid, _ := ap.GetPropertyHWAddress()
- mode, _ := ap.GetPropertyMode()
-
- secured := flags != uint32(gonetworkmanager.Nm80211APFlagsNone) ||
- wpaFlags != uint32(gonetworkmanager.Nm80211APSecNone) ||
- rsnFlags != uint32(gonetworkmanager.Nm80211APSecNone)
-
- enterprise := (rsnFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0) ||
- (wpaFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0)
-
- var modeStr string
- switch mode {
- case gonetworkmanager.Nm80211ModeAdhoc:
- modeStr = "adhoc"
- case gonetworkmanager.Nm80211ModeInfra:
- modeStr = "infrastructure"
- case gonetworkmanager.Nm80211ModeAp:
- modeStr = "ap"
- default:
- modeStr = "unknown"
- }
-
- channel := frequencyToChannel(freq)
-
- isConnected := ssid == currentSSID
- rate := maxBitrate / 1000
- if isConnected {
- if devBitrate, err := w.GetPropertyBitrate(); err == nil && devBitrate > 0 {
- rate = devBitrate / 1000
- }
- }
-
- network := WiFiNetwork{
- SSID: ssid,
- BSSID: bssid,
- Signal: strength,
- Secured: secured,
- Enterprise: enterprise,
- Connected: isConnected,
- Saved: savedSSIDs[ssid],
- Autoconnect: autoconnectMap[ssid],
- Hidden: hiddenSSIDs[ssid],
- Frequency: freq,
- Mode: modeStr,
- Rate: rate,
- Channel: channel,
- }
-
- seenSSIDs[ssid] = &network
- networks = append(networks, network)
- }
-
- if wifiConnected && currentSSID != "" {
- if _, exists := seenSSIDs[currentSSID]; !exists {
- hiddenNetwork := WiFiNetwork{
- SSID: currentSSID,
- BSSID: wifiBSSID,
- Signal: wifiSignal,
- Secured: true,
- Connected: true,
- Saved: savedSSIDs[currentSSID],
- Autoconnect: autoconnectMap[currentSSID],
- Hidden: true,
- Mode: "infrastructure",
- }
- networks = append(networks, hiddenNetwork)
- }
- }
-
- sortWiFiNetworks(networks)
-
- b.stateMutex.Lock()
- b.state.WiFiNetworks = networks
- b.stateMutex.Unlock()
-
- return networks, nil
-}
-
-func (b *NetworkManagerBackend) findConnection(ssid string) (gonetworkmanager.Connection, error) {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return nil, err
- }
- b.settings = s
- }
-
- settings := s.(gonetworkmanager.Settings)
- connections, err := settings.ListConnections()
- if err != nil {
- return nil, err
- }
-
- ssidBytes := []byte(ssid)
- for _, conn := range connections {
- connSettings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- if connMeta, ok := connSettings["connection"]; ok {
- if connType, ok := connMeta["type"].(string); ok && connType == "802-11-wireless" {
- if wifiSettings, ok := connSettings["802-11-wireless"]; ok {
- if candidateSSID, ok := wifiSettings["ssid"].([]byte); ok {
- if bytes.Equal(candidateSSID, ssidBytes) {
- return conn, nil
- }
- }
- }
- }
- }
- }
-
- return nil, fmt.Errorf("connection not found")
-}
-
-func (b *NetworkManagerBackend) createAndConnectWiFi(req ConnectionRequest) error {
- devInfo, err := b.getWifiDeviceForConnection(req.Device)
- if err != nil {
- return err
- }
- return b.createAndConnectWiFiOnDevice(req, devInfo)
-}
-
-func (b *NetworkManagerBackend) createAndConnectWiFiOnDevice(req ConnectionRequest, devInfo *wifiDeviceInfo) error {
- nm := b.nmConn.(gonetworkmanager.NetworkManager)
- dev := devInfo.device
- w := devInfo.wireless
-
- var targetAP gonetworkmanager.AccessPoint
- var flags, wpaFlags, rsnFlags uint32
-
- if !req.Hidden {
- apPaths, err := w.GetAccessPoints()
- if err != nil {
- return fmt.Errorf("failed to get access points: %w", err)
- }
-
- for _, ap := range apPaths {
- ssid, err := ap.GetPropertySSID()
- if err != nil || ssid != req.SSID {
- continue
- }
- targetAP = ap
- break
- }
-
- if targetAP == nil {
- return fmt.Errorf("access point not found: %s", req.SSID)
- }
-
- flags, _ = targetAP.GetPropertyFlags()
- wpaFlags, _ = targetAP.GetPropertyWPAFlags()
- rsnFlags, _ = targetAP.GetPropertyRSNFlags()
- }
-
- const KeyMgmt8021x = uint32(512)
- const KeyMgmtPsk = uint32(256)
- const KeyMgmtSae = uint32(1024)
-
- var isEnterprise, isPsk, isSae, secured bool
-
- switch {
- case req.Hidden:
- secured = req.Password != "" || req.Username != ""
- isEnterprise = req.Username != ""
- isPsk = req.Password != "" && !isEnterprise
- default:
- isEnterprise = (wpaFlags&KeyMgmt8021x) != 0 || (rsnFlags&KeyMgmt8021x) != 0
- isPsk = (wpaFlags&KeyMgmtPsk) != 0 || (rsnFlags&KeyMgmtPsk) != 0
- isSae = (wpaFlags&KeyMgmtSae) != 0 || (rsnFlags&KeyMgmtSae) != 0
- secured = flags != uint32(gonetworkmanager.Nm80211APFlagsNone) ||
- wpaFlags != uint32(gonetworkmanager.Nm80211APSecNone) ||
- rsnFlags != uint32(gonetworkmanager.Nm80211APSecNone)
- }
-
- if isEnterprise {
- log.Infof("[createAndConnectWiFi] Enterprise network detected (802.1x) - SSID: %s, interactive: %v",
- req.SSID, req.Interactive)
- }
-
- settings := make(map[string]map[string]any)
-
- settings["connection"] = map[string]any{
- "id": req.SSID,
- "type": "802-11-wireless",
- "autoconnect": true,
- }
-
- settings["ipv4"] = map[string]any{"method": "auto"}
- settings["ipv6"] = map[string]any{"method": "auto"}
-
- if secured {
- wifiSettings := map[string]any{
- "ssid": []byte(req.SSID),
- "mode": "infrastructure",
- "security": "802-11-wireless-security",
- }
- if req.Hidden {
- wifiSettings["hidden"] = true
- }
- settings["802-11-wireless"] = wifiSettings
-
- switch {
- case isEnterprise || req.Username != "":
- settings["802-11-wireless-security"] = map[string]any{
- "key-mgmt": "wpa-eap",
- }
-
- eapMethod := "peap"
- if req.EAPMethod != "" {
- eapMethod = req.EAPMethod
- }
-
- phase2Auth := "mschapv2"
- if req.Phase2Auth != "" {
- phase2Auth = req.Phase2Auth
- }
-
- useSystemCACerts := false
- if req.UseSystemCACerts != nil {
- useSystemCACerts = *req.UseSystemCACerts
- }
-
- x := map[string]any{
- "eap": []string{eapMethod},
- "system-ca-certs": useSystemCACerts,
- "password-flags": uint32(0),
- }
-
- switch eapMethod {
- case "peap", "ttls":
- x["phase2-auth"] = phase2Auth
- case "tls":
- if req.ClientCertPath != "" {
- x["client-cert"] = []byte("file://" + req.ClientCertPath)
- }
- if req.PrivateKeyPath != "" {
- x["private-key"] = []byte("file://" + req.PrivateKeyPath)
- }
- }
-
- if req.Username != "" {
- x["identity"] = req.Username
- }
- if req.Password != "" {
- x["password"] = req.Password
- }
- if req.AnonymousIdentity != "" {
- x["anonymous-identity"] = req.AnonymousIdentity
- }
- if req.DomainSuffixMatch != "" {
- x["domain-suffix-match"] = req.DomainSuffixMatch
- }
- if req.CACertPath != "" {
- x["ca-cert"] = []byte("file://" + req.CACertPath)
- }
-
- settings["802-1x"] = x
-
- log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=%s, phase2-auth=%s, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
- eapMethod, phase2Auth, req.Username, req.Interactive, useSystemCACerts, req.DomainSuffixMatch)
-
- case isPsk:
- sec := map[string]any{
- "key-mgmt": "wpa-psk",
- "psk-flags": uint32(0),
- }
- if !req.Interactive {
- sec["psk"] = req.Password
- }
- settings["802-11-wireless-security"] = sec
-
- case isSae:
- sec := map[string]any{
- "key-mgmt": "sae",
- "pmf": int32(3),
- "psk-flags": uint32(0),
- }
- if !req.Interactive {
- sec["psk"] = req.Password
- }
- settings["802-11-wireless-security"] = sec
-
- default:
- return fmt.Errorf("secured network but not SAE/PSK/802.1X (rsn=0x%x wpa=0x%x)", rsnFlags, wpaFlags)
- }
- } else {
- wifiSettings := map[string]any{
- "ssid": []byte(req.SSID),
- "mode": "infrastructure",
- }
- if req.Hidden {
- wifiSettings["hidden"] = true
- }
- settings["802-11-wireless"] = wifiSettings
- }
-
- if req.Interactive {
- s := b.settings
- if s == nil {
- var settingsErr error
- s, settingsErr = gonetworkmanager.NewSettings()
- if settingsErr != nil {
- return fmt.Errorf("failed to get settings manager: %w", settingsErr)
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- conn, err := settingsMgr.AddConnection(settings)
- if err != nil {
- return fmt.Errorf("failed to add connection: %w", err)
- }
-
- if isEnterprise {
- log.Infof("[createAndConnectWiFi] Enterprise connection added, activating (secret agent will be called)")
- }
-
- if req.Hidden {
- _, err = nm.ActivateConnection(conn, dev, nil)
- } else {
- _, err = nm.ActivateWirelessConnection(conn, dev, targetAP)
- }
- if err != nil {
- return fmt.Errorf("failed to activate connection: %w", err)
- }
-
- log.Infof("[createAndConnectWiFi] Connection activation initiated, waiting for NetworkManager state changes...")
- } else {
- var err error
- if req.Hidden {
- _, err = nm.AddAndActivateConnection(settings, dev)
- } else {
- _, err = nm.AddAndActivateWirelessConnection(settings, dev, targetAP)
- }
- if err != nil {
- return fmt.Errorf("failed to connect: %w", err)
- }
- log.Infof("[createAndConnectWiFi] Connection activation initiated, waiting for NetworkManager state changes...")
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
- conn, err := b.findConnection(ssid)
- if err != nil {
- return fmt.Errorf("connection not found: %w", err)
- }
-
- settings, err := conn.GetSettings()
- if err != nil {
- return fmt.Errorf("failed to get connection settings: %w", err)
- }
-
- if connMeta, ok := settings["connection"]; ok {
- connMeta["autoconnect"] = autoconnect
- } else {
- return fmt.Errorf("connection metadata not found")
- }
-
- if ipv4, ok := settings["ipv4"]; ok {
- delete(ipv4, "addresses")
- delete(ipv4, "routes")
- delete(ipv4, "dns")
- }
-
- if ipv6, ok := settings["ipv6"]; ok {
- delete(ipv6, "addresses")
- delete(ipv6, "routes")
- delete(ipv6, "dns")
- }
-
- err = conn.Update(settings)
- if err != nil {
- return fmt.Errorf("failed to update connection: %w", err)
- }
-
- b.updateWiFiNetworks()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) ScanWiFiDevice(device string) error {
- devInfo, ok := b.wifiDevices[device]
- if !ok {
- return fmt.Errorf("WiFi device not found: %s", device)
- }
-
- b.stateMutex.RLock()
- enabled := b.state.WiFiEnabled
- b.stateMutex.RUnlock()
-
- if !enabled {
- return fmt.Errorf("WiFi is disabled")
- }
-
- if err := devInfo.wireless.RequestScan(); err != nil {
- return fmt.Errorf("scan request failed: %w", err)
- }
-
- b.updateAllWiFiDevices()
- return nil
-}
-
-func (b *NetworkManagerBackend) DisconnectWiFiDevice(device string) error {
- devInfo, ok := b.wifiDevices[device]
- if !ok {
- return fmt.Errorf("WiFi device not found: %s", device)
- }
-
- if err := devInfo.device.Disconnect(); err != nil {
- return fmt.Errorf("failed to disconnect: %w", err)
- }
-
- b.updateWiFiState()
- b.updateAllWiFiDevices()
- b.updatePrimaryConnection()
-
- if b.onStateChange != nil {
- b.onStateChange()
- }
-
- return nil
-}
-
-func (b *NetworkManagerBackend) GetWiFiDevices() []WiFiDevice {
- b.stateMutex.RLock()
- defer b.stateMutex.RUnlock()
- return append([]WiFiDevice(nil), b.state.WiFiDevices...)
-}
-
-func (b *NetworkManagerBackend) updateAllWiFiDevices() {
- s := b.settings
- if s == nil {
- var err error
- s, err = gonetworkmanager.NewSettings()
- if err != nil {
- return
- }
- b.settings = s
- }
-
- settingsMgr := s.(gonetworkmanager.Settings)
- connections, err := settingsMgr.ListConnections()
- if err != nil {
- return
- }
-
- savedSSIDs := make(map[string]bool)
- autoconnectMap := make(map[string]bool)
- hiddenSSIDs := make(map[string]bool)
- for _, conn := range connections {
- connSettings, err := conn.GetSettings()
- if err != nil {
- continue
- }
-
- connMeta, ok := connSettings["connection"]
- if !ok {
- continue
- }
-
- connType, ok := connMeta["type"].(string)
- if !ok || connType != "802-11-wireless" {
- continue
- }
-
- wifiSettings, ok := connSettings["802-11-wireless"]
- if !ok {
- continue
- }
-
- ssidBytes, ok := wifiSettings["ssid"].([]byte)
- if !ok {
- continue
- }
-
- ssid := string(ssidBytes)
- savedSSIDs[ssid] = true
- autoconnect := true
- if ac, ok := connMeta["autoconnect"].(bool); ok {
- autoconnect = ac
- }
- autoconnectMap[ssid] = autoconnect
-
- if hidden, ok := wifiSettings["hidden"].(bool); ok && hidden {
- hiddenSSIDs[ssid] = true
- }
- }
-
- var devices []WiFiDevice
-
- for name, devInfo := range b.wifiDevices {
- state, _ := devInfo.device.GetPropertyState()
- connected := state == gonetworkmanager.NmDeviceStateActivated
-
- var ssid, bssid, ip string
- var signal uint8
-
- if connected {
- if activeAP, err := devInfo.wireless.GetPropertyActiveAccessPoint(); err == nil && activeAP != nil && activeAP.GetPath() != "/" {
- ssid, _ = activeAP.GetPropertySSID()
- signal, _ = activeAP.GetPropertyStrength()
- bssid, _ = activeAP.GetPropertyHWAddress()
- }
- ip = b.getDeviceIP(devInfo.device)
- }
-
- stateStr := "disconnected"
- switch state {
- case gonetworkmanager.NmDeviceStateActivated:
- stateStr = "connected"
- case gonetworkmanager.NmDeviceStateConfig, gonetworkmanager.NmDeviceStateIpConfig:
- stateStr = "connecting"
- case gonetworkmanager.NmDeviceStatePrepare:
- stateStr = "preparing"
- case gonetworkmanager.NmDeviceStateDeactivating:
- stateStr = "disconnecting"
- }
-
- apPaths, err := devInfo.wireless.GetAccessPoints()
- var networks []WiFiNetwork
- if err == nil {
- seenSSIDs := make(map[string]*WiFiNetwork)
- for _, ap := range apPaths {
- apSSID, err := ap.GetPropertySSID()
- if err != nil || apSSID == "" {
- continue
- }
-
- if existing, exists := seenSSIDs[apSSID]; exists {
- strength, _ := ap.GetPropertyStrength()
- if strength > existing.Signal {
- existing.Signal = strength
- freq, _ := ap.GetPropertyFrequency()
- existing.Frequency = freq
- apBSSID, _ := ap.GetPropertyHWAddress()
- existing.BSSID = apBSSID
- }
- continue
- }
-
- strength, _ := ap.GetPropertyStrength()
- flags, _ := ap.GetPropertyFlags()
- wpaFlags, _ := ap.GetPropertyWPAFlags()
- rsnFlags, _ := ap.GetPropertyRSNFlags()
- freq, _ := ap.GetPropertyFrequency()
- maxBitrate, _ := ap.GetPropertyMaxBitrate()
- apBSSID, _ := ap.GetPropertyHWAddress()
- mode, _ := ap.GetPropertyMode()
-
- secured := flags != uint32(gonetworkmanager.Nm80211APFlagsNone) ||
- wpaFlags != uint32(gonetworkmanager.Nm80211APSecNone) ||
- rsnFlags != uint32(gonetworkmanager.Nm80211APSecNone)
-
- enterprise := (rsnFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0) ||
- (wpaFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0)
-
- var modeStr string
- switch mode {
- case gonetworkmanager.Nm80211ModeAdhoc:
- modeStr = "adhoc"
- case gonetworkmanager.Nm80211ModeInfra:
- modeStr = "infrastructure"
- case gonetworkmanager.Nm80211ModeAp:
- modeStr = "ap"
- default:
- modeStr = "unknown"
- }
-
- channel := frequencyToChannel(freq)
-
- isConnected := connected && apSSID == ssid
- rate := maxBitrate / 1000
- if isConnected {
- if devBitrate, err := devInfo.wireless.GetPropertyBitrate(); err == nil && devBitrate > 0 {
- rate = devBitrate / 1000
- }
- }
-
- network := WiFiNetwork{
- SSID: apSSID,
- BSSID: apBSSID,
- Signal: strength,
- Secured: secured,
- Enterprise: enterprise,
- Connected: isConnected,
- Saved: savedSSIDs[apSSID],
- Autoconnect: autoconnectMap[apSSID],
- Hidden: hiddenSSIDs[apSSID],
- Frequency: freq,
- Mode: modeStr,
- Rate: rate,
- Channel: channel,
- Device: name,
- }
-
- seenSSIDs[apSSID] = &network
- networks = append(networks, network)
- }
-
- if connected && ssid != "" {
- if _, exists := seenSSIDs[ssid]; !exists {
- hiddenNetwork := WiFiNetwork{
- SSID: ssid,
- BSSID: bssid,
- Signal: signal,
- Secured: true,
- Connected: true,
- Saved: savedSSIDs[ssid],
- Autoconnect: autoconnectMap[ssid],
- Hidden: true,
- Mode: "infrastructure",
- Device: name,
- }
- networks = append(networks, hiddenNetwork)
- }
- }
-
- sortWiFiNetworks(networks)
- }
-
- devices = append(devices, WiFiDevice{
- Name: name,
- HwAddress: devInfo.hwAddress,
- State: stateStr,
- Connected: connected,
- SSID: ssid,
- BSSID: bssid,
- Signal: signal,
- IP: ip,
- Networks: networks,
- })
- }
-
- sort.Slice(devices, func(i, j int) bool {
- return devices[i].Name < devices[j].Name
- })
-
- b.stateMutex.Lock()
- b.state.WiFiDevices = devices
- b.stateMutex.Unlock()
-}
-
-func (b *NetworkManagerBackend) getWifiDeviceForConnection(deviceName string) (*wifiDeviceInfo, error) {
- if deviceName != "" {
- devInfo, ok := b.wifiDevices[deviceName]
- if !ok {
- return nil, fmt.Errorf("WiFi device not found: %s", deviceName)
- }
- return devInfo, nil
- }
-
- if b.wifiDevice == nil {
- return nil, fmt.Errorf("no WiFi device available")
- }
-
- dev := b.wifiDevice.(gonetworkmanager.Device)
- iface, _ := dev.GetPropertyInterface()
- if devInfo, ok := b.wifiDevices[iface]; ok {
- return devInfo, nil
- }
-
- return nil, fmt.Errorf("no WiFi device available")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi_test.go
deleted file mode 100644
index 79c2c5b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/backend_networkmanager_wifi_test.go
+++ /dev/null
@@ -1,202 +0,0 @@
-package network
-
-import (
- "testing"
-
- mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNetworkManagerBackend_GetWiFiEnabled(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().GetPropertyWirelessEnabled().Return(true, nil)
-
- enabled, err := backend.GetWiFiEnabled()
- assert.NoError(t, err)
- assert.True(t, enabled)
-}
-
-func TestNetworkManagerBackend_SetWiFiEnabled(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- mockNM.EXPECT().SetPropertyWirelessEnabled(true).Return(nil)
-
- err = backend.SetWiFiEnabled(true)
- assert.NoError(t, err)
-
- backend.stateMutex.RLock()
- assert.True(t, backend.state.WiFiEnabled)
- backend.stateMutex.RUnlock()
-}
-
-func TestNetworkManagerBackend_ScanWiFi_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- err = backend.ScanWiFi()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestNetworkManagerBackend_ScanWiFi_Disabled(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockDeviceWireless := mock_gonetworkmanager.NewMockDeviceWireless(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = mockDeviceWireless
- backend.wifiDev = mockDeviceWireless
-
- backend.stateMutex.Lock()
- backend.state.WiFiEnabled = false
- backend.stateMutex.Unlock()
-
- err = backend.ScanWiFi()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "WiFi is disabled")
-}
-
-func TestNetworkManagerBackend_GetWiFiNetworkDetails_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- _, err = backend.GetWiFiNetworkDetails("TestNetwork")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestNetworkManagerBackend_ConnectWiFi_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- req := ConnectionRequest{SSID: "TestNetwork", Password: "password"}
- err = backend.ConnectWiFi(req)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestNetworkManagerBackend_ConnectWiFi_AlreadyConnected(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
- mockDeviceWireless := mock_gonetworkmanager.NewMockDeviceWireless(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = mockDeviceWireless
- backend.wifiDev = mockDeviceWireless
- backend.wifiDevices = map[string]*wifiDeviceInfo{
- "wlan0": {
- device: nil,
- wireless: mockDeviceWireless,
- name: "wlan0",
- hwAddress: "00:11:22:33:44:55",
- },
- }
-
- mockDeviceWireless.EXPECT().GetPropertyInterface().Return("wlan0", nil)
-
- backend.stateMutex.Lock()
- backend.state.WiFiConnected = true
- backend.state.WiFiSSID = "TestNetwork"
- backend.state.WiFiDevice = "wlan0"
- backend.stateMutex.Unlock()
-
- req := ConnectionRequest{SSID: "TestNetwork", Password: "password"}
- err = backend.ConnectWiFi(req)
- assert.NoError(t, err)
-}
-
-func TestNetworkManagerBackend_DisconnectWiFi_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- err = backend.DisconnectWiFi()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestNetworkManagerBackend_IsConnectingTo(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.stateMutex.Lock()
- backend.state.IsConnecting = true
- backend.state.ConnectingSSID = "TestNetwork"
- backend.stateMutex.Unlock()
-
- assert.True(t, backend.IsConnectingTo("TestNetwork"))
- assert.False(t, backend.IsConnectingTo("OtherNetwork"))
-}
-
-func TestNetworkManagerBackend_IsConnectingTo_NotConnecting(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.stateMutex.Lock()
- backend.state.IsConnecting = false
- backend.state.ConnectingSSID = ""
- backend.stateMutex.Unlock()
-
- assert.False(t, backend.IsConnectingTo("TestNetwork"))
-}
-
-func TestNetworkManagerBackend_UpdateWiFiNetworks_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- _, err = backend.updateWiFiNetworks()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestNetworkManagerBackend_FindConnection_NoSettings(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.settings = nil
- _, err = backend.findConnection("NonExistentNetwork")
- assert.Error(t, err)
-}
-
-func TestNetworkManagerBackend_CreateAndConnectWiFi_NoDevice(t *testing.T) {
- mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
-
- backend, err := NewNetworkManagerBackend(mockNM)
- assert.NoError(t, err)
-
- backend.wifiDevice = nil
- backend.wifiDev = nil
- req := ConnectionRequest{SSID: "TestNetwork", Password: "password"}
- err = backend.createAndConnectWiFi(req)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/broker.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/broker.go
deleted file mode 100644
index 5290fb5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/broker.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package network
-
-import (
- "context"
- "crypto/rand"
- "encoding/hex"
-)
-
-type PromptBroker interface {
- Ask(ctx context.Context, req PromptRequest) (token string, err error)
- Wait(ctx context.Context, token string) (PromptReply, error)
- Resolve(token string, reply PromptReply) error
- Cancel(path string, setting string) error
-}
-
-func generateToken() (string, error) {
- bytes := make([]byte, 16)
- if _, err := rand.Read(bytes); err != nil {
- return "", err
- }
- return hex.EncodeToString(bytes), nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/connection_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/connection_test.go
deleted file mode 100644
index 7b37731..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/connection_test.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package network_test
-
-import (
- "errors"
- "testing"
-
- mocks_network "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/network"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
- "github.com/stretchr/testify/assert"
-)
-
-func TestConnectionRequest_Validation(t *testing.T) {
- t.Run("basic WiFi connection", func(t *testing.T) {
- req := network.ConnectionRequest{
- SSID: "TestNetwork",
- Password: "testpass123",
- }
-
- assert.NotEmpty(t, req.SSID)
- assert.NotEmpty(t, req.Password)
- assert.Empty(t, req.Username)
- })
-
- t.Run("enterprise WiFi connection", func(t *testing.T) {
- req := network.ConnectionRequest{
- SSID: "EnterpriseNetwork",
- Password: "testpass123",
- Username: "testuser",
- }
-
- assert.NotEmpty(t, req.SSID)
- assert.NotEmpty(t, req.Password)
- assert.NotEmpty(t, req.Username)
- })
-
- t.Run("open WiFi connection", func(t *testing.T) {
- req := network.ConnectionRequest{
- SSID: "OpenNetwork",
- }
-
- assert.NotEmpty(t, req.SSID)
- assert.Empty(t, req.Password)
- assert.Empty(t, req.Username)
- })
-}
-
-func TestManager_ConnectWiFi_NoDevice(t *testing.T) {
- backend := mocks_network.NewMockBackend(t)
- req := network.ConnectionRequest{
- SSID: "TestNetwork",
- Password: "testpass123",
- }
- backend.EXPECT().ConnectWiFi(req).Return(errors.New("no WiFi device available"))
-
- manager := network.NewTestManager(backend, &network.NetworkState{})
-
- err := manager.ConnectWiFi(req)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestManager_DisconnectWiFi_NoDevice(t *testing.T) {
- backend := mocks_network.NewMockBackend(t)
- backend.EXPECT().DisconnectWiFi().Return(errors.New("no WiFi device available"))
-
- manager := network.NewTestManager(backend, &network.NetworkState{})
-
- err := manager.DisconnectWiFi()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no WiFi device available")
-}
-
-func TestManager_ForgetWiFiNetwork_NotFound(t *testing.T) {
- backend := mocks_network.NewMockBackend(t)
- backend.EXPECT().ForgetWiFiNetwork("NonExistentNetwork").Return(errors.New("connection not found"))
-
- manager := network.NewTestManager(backend, &network.NetworkState{})
-
- err := manager.ForgetWiFiNetwork("NonExistentNetwork")
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "connection not found")
-}
-
-func TestManager_ConnectEthernet_NoDevice(t *testing.T) {
- backend := mocks_network.NewMockBackend(t)
- backend.EXPECT().ConnectEthernet().Return(errors.New("no ethernet device available"))
-
- manager := network.NewTestManager(backend, &network.NetworkState{})
-
- err := manager.ConnectEthernet()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-func TestManager_DisconnectEthernet_NoDevice(t *testing.T) {
- backend := mocks_network.NewMockBackend(t)
- backend.EXPECT().DisconnectEthernet().Return(errors.New("no ethernet device available"))
-
- manager := network.NewTestManager(backend, &network.NetworkState{})
-
- err := manager.DisconnectEthernet()
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "no ethernet device available")
-}
-
-// Note: More comprehensive tests for connection operations would require
-// mocking the NetworkManager D-Bus interfaces, which is beyond the scope
-// of these unit tests. The tests above cover the basic error cases and
-// validation logic. Integration tests would be needed for full coverage.
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect.go
deleted file mode 100644
index 6031e42..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package network
-
-import (
- "fmt"
-
- "github.com/godbus/dbus/v5"
-)
-
-type BackendType int
-
-const (
- BackendNone BackendType = iota
- BackendNetworkManager
- BackendIwd
- BackendConnMan
- BackendNetworkd
-)
-
-func nameHasOwner(bus *dbus.Conn, name string) (bool, error) {
- obj := bus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
- var owned bool
- if err := obj.Call("org.freedesktop.DBus.NameHasOwner", 0, name).Store(&owned); err != nil {
- return false, err
- }
- return owned, nil
-}
-
-type DetectResult struct {
- Backend BackendType
- HasNM bool
- HasIwd bool
- HasConnMan bool
- HasWpaSupp bool
- HasNetworkd bool
- ChosenReason string
-}
-
-func DetectNetworkStack() (*DetectResult, error) {
- bus, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("connect system bus: %w", err)
- }
- defer bus.Close()
-
- hasNM, _ := nameHasOwner(bus, "org.freedesktop.NetworkManager")
- hasIwd, _ := nameHasOwner(bus, "net.connman.iwd")
- hasConn, _ := nameHasOwner(bus, "net.connman")
- hasWpa, _ := nameHasOwner(bus, "fi.w1.wpa_supplicant1")
- hasNetworkd, _ := nameHasOwner(bus, "org.freedesktop.network1")
-
- res := &DetectResult{
- HasNM: hasNM,
- HasIwd: hasIwd,
- HasConnMan: hasConn,
- HasWpaSupp: hasWpa,
- HasNetworkd: hasNetworkd,
- }
-
- switch {
- case hasNM:
- res.Backend = BackendNetworkManager
- if hasIwd {
- res.ChosenReason = "NetworkManager present; iwd also running (likely NM's Wi-Fi backend). Using NM API."
- } else {
- res.ChosenReason = "NetworkManager present. Using NM API."
- }
- case hasConn && hasIwd:
- res.Backend = BackendConnMan
- res.ChosenReason = "ConnMan + iwd detected. Use ConnMan API (iwd is its Wi-Fi daemon)."
- case hasIwd && hasNetworkd:
- res.Backend = BackendNetworkd
- res.ChosenReason = "iwd + systemd-networkd detected. Using iwd for Wi-Fi association and networkd for IP/DHCP."
- case hasIwd:
- res.Backend = BackendIwd
- res.ChosenReason = "iwd detected without NM/ConnMan. Using iwd API."
- case hasNetworkd:
- res.Backend = BackendNetworkd
- res.ChosenReason = "systemd-networkd detected (no NM/ConnMan). Using networkd for L3 and wired."
- default:
- res.Backend = BackendNone
- if hasWpa {
- res.ChosenReason = "No NM/ConnMan/iwd; wpa_supplicant present. Consider a wpa_supplicant path."
- } else {
- res.ChosenReason = "No known network manager bus names found."
- }
- }
-
- return res, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect_test.go
deleted file mode 100644
index 87e7635..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/detect_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package network
-
-import (
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestBackendType_Constants(t *testing.T) {
- assert.Equal(t, BackendType(0), BackendNone)
- assert.Equal(t, BackendType(1), BackendNetworkManager)
- assert.Equal(t, BackendType(2), BackendIwd)
- assert.Equal(t, BackendType(3), BackendConnMan)
- assert.Equal(t, BackendType(4), BackendNetworkd)
-}
-
-func TestDetectResult_HasNetworkdField(t *testing.T) {
- result := &DetectResult{
- Backend: BackendNetworkd,
- HasNetworkd: true,
- HasIwd: true,
- }
-
- assert.True(t, result.HasNetworkd)
- assert.True(t, result.HasIwd)
- assert.Equal(t, BackendNetworkd, result.Backend)
-}
-
-func TestDetectNetworkStack_Integration(t *testing.T) {
- result, err := DetectNetworkStack()
-
- if err != nil && strings.Contains(err.Error(), "connect system bus") {
- t.Skipf("system D-Bus unavailable: %v", err)
- }
-
- assert.NoError(t, err)
- if assert.NotNil(t, result) {
- assert.NotEmpty(t, result.ChosenReason)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers.go
deleted file mode 100644
index 2b3e75e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers.go
+++ /dev/null
@@ -1,630 +0,0 @@
-package network
-
-import (
- "encoding/json"
- "fmt"
- "net"
- "os"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- switch req.Method {
- case "network.getState":
- handleGetState(conn, req, manager)
- case "network.wifi.scan":
- handleScanWiFi(conn, req, manager)
- case "network.wifi.networks":
- handleGetWiFiNetworks(conn, req, manager)
- case "network.wifi.connect":
- handleConnectWiFi(conn, req, manager)
- case "network.wifi.disconnect":
- handleDisconnectWiFi(conn, req, manager)
- case "network.wifi.forget":
- handleForgetWiFi(conn, req, manager)
- case "network.wifi.toggle":
- handleToggleWiFi(conn, req, manager)
- case "network.wifi.enable":
- handleEnableWiFi(conn, req, manager)
- case "network.wifi.disable":
- handleDisableWiFi(conn, req, manager)
- case "network.ethernet.connect.config":
- handleConnectEthernetSpecificConfig(conn, req, manager)
- case "network.ethernet.connect":
- handleConnectEthernet(conn, req, manager)
- case "network.ethernet.disconnect":
- handleDisconnectEthernet(conn, req, manager)
- case "network.preference.set":
- handleSetPreference(conn, req, manager)
- case "network.info":
- handleGetNetworkInfo(conn, req, manager)
- case "network.qrcode":
- handleGetNetworkQRCode(conn, req, manager)
- case "network.delete-qrcode":
- handleDeleteQRCode(conn, req, manager)
- case "network.ethernet.info":
- handleGetWiredNetworkInfo(conn, req, manager)
- case "network.subscribe":
- handleSubscribe(conn, req, manager)
- case "network.credentials.submit":
- handleCredentialsSubmit(conn, req, manager)
- case "network.credentials.cancel":
- handleCredentialsCancel(conn, req, manager)
- case "network.vpn.profiles":
- handleListVPNProfiles(conn, req, manager)
- case "network.vpn.active":
- handleListActiveVPN(conn, req, manager)
- case "network.vpn.connect":
- handleConnectVPN(conn, req, manager)
- case "network.vpn.disconnect":
- handleDisconnectVPN(conn, req, manager)
- case "network.vpn.disconnectAll":
- handleDisconnectAllVPN(conn, req, manager)
- case "network.vpn.clearCredentials":
- handleClearVPNCredentials(conn, req, manager)
- case "network.vpn.plugins":
- handleListVPNPlugins(conn, req, manager)
- case "network.vpn.import":
- handleImportVPN(conn, req, manager)
- case "network.vpn.getConfig":
- handleGetVPNConfig(conn, req, manager)
- case "network.vpn.updateConfig":
- handleUpdateVPNConfig(conn, req, manager)
- case "network.vpn.delete":
- handleDeleteVPN(conn, req, manager)
- case "network.vpn.setCredentials":
- handleSetVPNCredentials(conn, req, manager)
- case "network.wifi.setAutoconnect":
- handleSetWiFiAutoconnect(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleCredentialsSubmit(conn net.Conn, req models.Request, manager *Manager) {
- token, err := params.String(req.Params, "token")
- if err != nil {
- log.Warnf("handleCredentialsSubmit: missing or invalid token parameter")
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- secrets, err := params.StringMap(req.Params, "secrets")
- if err != nil {
- log.Warnf("handleCredentialsSubmit: missing or invalid secrets parameter")
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- save := params.BoolOpt(req.Params, "save", true)
-
- if err := manager.SubmitCredentials(token, secrets, save); err != nil {
- log.Warnf("handleCredentialsSubmit: failed to submit credentials: %v", err)
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- log.Infof("handleCredentialsSubmit: credentials submitted successfully")
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "credentials submitted"})
-}
-
-func handleCredentialsCancel(conn net.Conn, req models.Request, manager *Manager) {
- token, err := params.String(req.Params, "token")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.CancelCredentials(token); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "credentials cancelled"})
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleScanWiFi(conn net.Conn, req models.Request, manager *Manager) {
- device := params.StringOpt(req.Params, "device", "")
- var err error
- if device != "" {
- err = manager.ScanWiFiDevice(device)
- } else {
- err = manager.ScanWiFi()
- }
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "scanning"})
-}
-
-func handleGetWiFiNetworks(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetWiFiNetworks())
-}
-
-func handleConnectWiFi(conn net.Conn, req models.Request, manager *Manager) {
- ssid, err := params.String(req.Params, "ssid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- var connReq ConnectionRequest
- connReq.SSID = ssid
- connReq.Password = params.StringOpt(req.Params, "password", "")
- connReq.Username = params.StringOpt(req.Params, "username", "")
- connReq.Device = params.StringOpt(req.Params, "device", "")
-
- if interactive, ok := models.Get[bool](req, "interactive"); ok {
- connReq.Interactive = interactive
- } else {
- state := manager.GetState()
- alreadyConnected := state.WiFiConnected && state.WiFiSSID == ssid
-
- if alreadyConnected && connReq.Device == "" {
- connReq.Interactive = false
- } else {
- networkInfo, err := manager.GetNetworkInfo(ssid)
- isSaved := err == nil && networkInfo.Saved
-
- if isSaved {
- connReq.Interactive = false
- } else if err == nil && networkInfo.Secured && connReq.Password == "" && connReq.Username == "" {
- connReq.Interactive = true
- }
- }
- }
-
- connReq.AnonymousIdentity = params.StringOpt(req.Params, "anonymousIdentity", "")
- connReq.DomainSuffixMatch = params.StringOpt(req.Params, "domainSuffixMatch", "")
- connReq.EAPMethod = params.StringOpt(req.Params, "eapMethod", "")
- connReq.Phase2Auth = params.StringOpt(req.Params, "phase2Auth", "")
- connReq.CACertPath = params.StringOpt(req.Params, "caCertPath", "")
- connReq.ClientCertPath = params.StringOpt(req.Params, "clientCertPath", "")
- connReq.PrivateKeyPath = params.StringOpt(req.Params, "privateKeyPath", "")
-
- if useSystemCACerts, ok := models.Get[bool](req, "useSystemCACerts"); ok {
- connReq.UseSystemCACerts = &useSystemCACerts
- }
-
- if err := manager.ConnectWiFi(connReq); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
-}
-
-func handleDisconnectWiFi(conn net.Conn, req models.Request, manager *Manager) {
- device := params.StringOpt(req.Params, "device", "")
- var err error
- if device != "" {
- err = manager.DisconnectWiFiDevice(device)
- } else {
- err = manager.DisconnectWiFi()
- }
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"})
-}
-
-func handleForgetWiFi(conn net.Conn, req models.Request, manager *Manager) {
- ssid, err := params.String(req.Params, "ssid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.ForgetWiFiNetwork(ssid); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "forgotten"})
-}
-
-func handleToggleWiFi(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.ToggleWiFi(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- state := manager.GetState()
- models.Respond(conn, req.ID, map[string]bool{"enabled": state.WiFiEnabled})
-}
-
-func handleEnableWiFi(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.EnableWiFi(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, map[string]bool{"enabled": true})
-}
-
-func handleDisableWiFi(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.DisableWiFi(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, map[string]bool{"enabled": false})
-}
-
-func handleConnectEthernetSpecificConfig(conn net.Conn, req models.Request, manager *Manager) {
- uuid, err := params.String(req.Params, "uuid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- if err := manager.activateConnection(uuid); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
-}
-
-func handleConnectEthernet(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.ConnectEthernet(); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
-}
-
-func handleDisconnectEthernet(conn net.Conn, req models.Request, manager *Manager) {
- device := params.StringOpt(req.Params, "device", "")
- var err error
- if device != "" {
- err = manager.DisconnectEthernetDevice(device)
- } else {
- err = manager.DisconnectEthernet()
- }
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"})
-}
-
-func handleSetPreference(conn net.Conn, req models.Request, manager *Manager) {
- preference, err := params.String(req.Params, "preference")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetConnectionPreference(ConnectionPreference(preference)); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, map[string]string{"preference": preference})
-}
-
-func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
- ssid, err := params.String(req.Params, "ssid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- network, err := manager.GetNetworkInfoDetailed(ssid)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, network)
-}
-
-func handleGetNetworkQRCode(conn net.Conn, req models.Request, manager *Manager) {
- ssid, err := params.String(req.Params, "ssid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- content, err := manager.GetNetworkQRCode(ssid)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, content)
-}
-
-func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) {
- path, err := params.String(req.Params, "path")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if !isValidQRCodePath(path) {
- models.RespondError(conn, req.ID, "invalid QR code path")
- return
- }
-
- if err := os.Remove(path); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "QR code file deleted"})
-}
-
-func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
- uuid, err := params.String(req.Params, "uuid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- network, err := manager.GetWiredNetworkInfoDetailed(uuid)
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, network)
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- event := NetworkEvent{
- Type: EventStateChanged,
- Data: initialState,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[NetworkEvent]{
- ID: req.ID,
- Result: &event,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- event := NetworkEvent{
- Type: EventStateChanged,
- Data: state,
- }
- if err := json.NewEncoder(conn).Encode(models.Response[NetworkEvent]{
- Result: &event,
- }); err != nil {
- return
- }
- }
-}
-
-func handleListVPNProfiles(conn net.Conn, req models.Request, manager *Manager) {
- profiles, err := manager.ListVPNProfiles()
- if err != nil {
- log.Warnf("handleListVPNProfiles: failed to list profiles: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list VPN profiles: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, profiles)
-}
-
-func handleListActiveVPN(conn net.Conn, req models.Request, manager *Manager) {
- active, err := manager.ListActiveVPN()
- if err != nil {
- log.Warnf("handleListActiveVPN: failed to list active VPNs: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list active VPNs: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, active)
-}
-
-func handleConnectVPN(conn net.Conn, req models.Request, manager *Manager) {
- uuidOrName, ok := params.StringAlt(req.Params, "uuidOrName", "name", "uuid")
- if !ok {
- log.Warnf("handleConnectVPN: missing uuidOrName/name/uuid parameter")
- models.RespondError(conn, req.ID, "missing 'uuidOrName', 'name', or 'uuid' parameter")
- return
- }
-
- singleActive := params.BoolOpt(req.Params, "singleActive", true)
-
- if err := manager.ConnectVPN(uuidOrName, singleActive); err != nil {
- log.Warnf("handleConnectVPN: failed to connect: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to connect VPN: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN connection initiated"})
-}
-
-func handleDisconnectVPN(conn net.Conn, req models.Request, manager *Manager) {
- uuidOrName, ok := params.StringAlt(req.Params, "uuidOrName", "name", "uuid")
- if !ok {
- log.Warnf("handleDisconnectVPN: missing uuidOrName/name/uuid parameter")
- models.RespondError(conn, req.ID, "missing 'uuidOrName', 'name', or 'uuid' parameter")
- return
- }
-
- if err := manager.DisconnectVPN(uuidOrName); err != nil {
- log.Warnf("handleDisconnectVPN: failed to disconnect: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to disconnect VPN: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN disconnected"})
-}
-
-func handleDisconnectAllVPN(conn net.Conn, req models.Request, manager *Manager) {
- if err := manager.DisconnectAllVPN(); err != nil {
- log.Warnf("handleDisconnectAllVPN: failed: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to disconnect all VPNs: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "All VPNs disconnected"})
-}
-
-func handleClearVPNCredentials(conn net.Conn, req models.Request, manager *Manager) {
- uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName")
- if !ok {
- log.Warnf("handleClearVPNCredentials: missing uuidOrName/name/uuid parameter")
- models.RespondError(conn, req.ID, "missing uuidOrName/name/uuid parameter")
- return
- }
-
- if err := manager.ClearVPNCredentials(uuidOrName); err != nil {
- log.Warnf("handleClearVPNCredentials: failed: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to clear VPN credentials: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN credentials cleared"})
-}
-
-func handleSetWiFiAutoconnect(conn net.Conn, req models.Request, manager *Manager) {
- ssid, err := params.String(req.Params, "ssid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- autoconnect, err := params.Bool(req.Params, "autoconnect")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetWiFiAutoconnect(ssid, autoconnect); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to set autoconnect: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "autoconnect updated"})
-}
-
-func handleListVPNPlugins(conn net.Conn, req models.Request, manager *Manager) {
- plugins, err := manager.ListVPNPlugins()
- if err != nil {
- log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list VPN plugins: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, plugins)
-}
-
-func handleImportVPN(conn net.Conn, req models.Request, manager *Manager) {
- filePath, ok := params.StringAlt(req.Params, "file", "path")
- if !ok {
- models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter")
- return
- }
-
- name := params.StringOpt(req.Params, "name", "")
-
- result, err := manager.ImportVPN(filePath, name)
- if err != nil {
- log.Warnf("handleImportVPN: failed to import: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to import VPN: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func handleGetVPNConfig(conn net.Conn, req models.Request, manager *Manager) {
- uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName")
- if !ok {
- models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
- return
- }
-
- config, err := manager.GetVPNConfig(uuidOrName)
- if err != nil {
- log.Warnf("handleGetVPNConfig: failed to get config: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to get VPN config: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, config)
-}
-
-func handleUpdateVPNConfig(conn net.Conn, req models.Request, manager *Manager) {
- connUUID, err := params.String(req.Params, "uuid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- updates := make(map[string]any)
-
- if name, ok := models.Get[string](req, "name"); ok {
- updates["name"] = name
- }
- if autoconnect, ok := models.Get[bool](req, "autoconnect"); ok {
- updates["autoconnect"] = autoconnect
- }
- if data, ok := models.Get[map[string]any](req, "data"); ok {
- updates["data"] = data
- }
-
- if len(updates) == 0 {
- models.RespondError(conn, req.ID, "no updates provided")
- return
- }
-
- if err := manager.UpdateVPNConfig(connUUID, updates); err != nil {
- log.Warnf("handleUpdateVPNConfig: failed to update: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to update VPN config: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN config updated"})
-}
-
-func handleDeleteVPN(conn net.Conn, req models.Request, manager *Manager) {
- uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName")
- if !ok {
- models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
- return
- }
-
- if err := manager.DeleteVPN(uuidOrName); err != nil {
- log.Warnf("handleDeleteVPN: failed to delete: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to delete VPN: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN deleted"})
-}
-
-func handleSetVPNCredentials(conn net.Conn, req models.Request, manager *Manager) {
- connUUID, err := params.String(req.Params, "uuid")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- username := params.StringOpt(req.Params, "username", "")
- password := params.StringOpt(req.Params, "password", "")
- save := params.BoolOpt(req.Params, "save", true)
-
- if err := manager.SetVPNCredentials(connUUID, username, password, save); err != nil {
- log.Warnf("handleSetVPNCredentials: failed to set credentials: %v", err)
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to set VPN credentials: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN credentials set"})
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers_test.go
deleted file mode 100644
index ea8ae33..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/handlers_test.go
+++ /dev/null
@@ -1,269 +0,0 @@
-package network
-
-import (
- "bytes"
- "encoding/json"
- "net"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-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 TestRespondError_Network(t *testing.T) {
- conn := newMockNetConn()
- models.RespondError(conn, 123, "test error")
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Equal(t, "test error", resp.Error)
- assert.Nil(t, resp.Result)
-}
-
-func TestRespond_Network(t *testing.T) {
- conn := newMockNetConn()
- result := models.SuccessResult{Success: true, Message: "test"}
- models.Respond(conn, 123, result)
-
- var resp models.Response[models.SuccessResult]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.True(t, resp.Result.Success)
- assert.Equal(t, "test", resp.Result.Message)
-}
-
-func TestHandleGetState(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- NetworkStatus: StatusWiFi,
- WiFiSSID: "TestNetwork",
- WiFiConnected: true,
- },
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "network.getState"}
-
- handleGetState(conn, req, manager)
-
- var resp models.Response[NetworkState]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.Equal(t, StatusWiFi, resp.Result.NetworkStatus)
- assert.Equal(t, "TestNetwork", resp.Result.WiFiSSID)
-}
-
-func TestHandleGetWiFiNetworks(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- WiFiNetworks: []WiFiNetwork{
- {SSID: "Network1", Signal: 90},
- {SSID: "Network2", Signal: 80},
- },
- },
- }
-
- conn := newMockNetConn()
- req := models.Request{ID: 123, Method: "network.wifi.networks"}
-
- handleGetWiFiNetworks(conn, req, manager)
-
- var resp models.Response[[]WiFiNetwork]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.Len(t, *resp.Result, 2)
- assert.Equal(t, "Network1", (*resp.Result)[0].SSID)
-}
-
-func TestHandleConnectWiFi(t *testing.T) {
- t.Run("missing ssid parameter", func(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "network.wifi.connect",
- Params: map[string]any{},
- }
-
- handleConnectWiFi(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'ssid' parameter")
- })
-}
-
-func TestHandleSetPreference(t *testing.T) {
- t.Run("missing preference parameter", func(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "network.preference.set",
- Params: map[string]any{},
- }
-
- handleSetPreference(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'preference' parameter")
- })
-}
-
-func TestHandleGetNetworkInfo(t *testing.T) {
- t.Run("missing ssid parameter", func(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "network.info",
- Params: map[string]any{},
- }
-
- handleGetNetworkInfo(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "missing or invalid 'ssid' parameter")
- })
-}
-
-func TestHandleRequest(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- NetworkStatus: StatusWiFi,
- },
- }
-
- t.Run("unknown method", func(t *testing.T) {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "network.unknown",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[any]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Contains(t, resp.Error, "unknown method")
- })
-
- t.Run("valid method - getState", func(t *testing.T) {
- conn := newMockNetConn()
- req := models.Request{
- ID: 123,
- Method: "network.getState",
- }
-
- HandleRequest(conn, req, manager)
-
- var resp models.Response[NetworkState]
- err := json.NewDecoder(conn.writeBuf).Decode(&resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- })
-}
-
-func TestHandleSubscribe(t *testing.T) {
- // This test is complex due to the streaming nature of subscriptions
- // Better suited as an integration test
- t.Skip("Subscription test requires connection lifecycle management - integration test needed")
-}
-
-func TestManager_Subscribe_Unsubscribe(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- t.Run("subscribe creates channel", func(t *testing.T) {
- ch := manager.Subscribe("client1")
- assert.NotNil(t, ch)
- count := 0
- manager.subscribers.Range(func(key string, ch chan NetworkState) bool {
- count++
- return true
- })
- assert.Equal(t, 1, count)
- })
-
- t.Run("unsubscribe removes channel", func(t *testing.T) {
- manager.Unsubscribe("client1")
- count := 0
- manager.subscribers.Range(func(key string, ch chan NetworkState) bool { count++; return true })
- assert.Equal(t, 0, count)
- })
-
- t.Run("unsubscribe non-existent client is safe", func(t *testing.T) {
- assert.NotPanics(t, func() {
- manager.Unsubscribe("non-existent")
- })
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/helpers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/helpers.go
deleted file mode 100644
index 4669f52..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/helpers.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package network
-
-import "sort"
-
-func frequencyToChannel(freq uint32) uint32 {
- if freq >= 2412 && freq <= 2484 {
- if freq == 2484 {
- return 14
- }
- return (freq-2412)/5 + 1
- }
-
- if freq >= 5170 && freq <= 5825 {
- return (freq-5170)/5 + 34
- }
-
- if freq >= 5955 && freq <= 7115 {
- return (freq-5955)/5 + 1
- }
-
- return 0
-}
-
-func sortWiFiNetworks(networks []WiFiNetwork) {
- sort.Slice(networks, func(i, j int) bool {
- if networks[i].Connected && !networks[j].Connected {
- return true
- }
- if !networks[i].Connected && networks[j].Connected {
- return false
- }
-
- if networks[i].Saved && !networks[j].Saved {
- return true
- }
- if !networks[i].Saved && networks[j].Saved {
- return false
- }
-
- if !networks[i].Secured && networks[j].Secured {
- if networks[i].Signal >= 50 {
- return true
- }
- }
- if networks[i].Secured && !networks[j].Secured {
- if networks[j].Signal >= 50 {
- return false
- }
- }
-
- return networks[i].Signal > networks[j].Signal
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager.go
deleted file mode 100644
index 5c6657e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager.go
+++ /dev/null
@@ -1,626 +0,0 @@
-package network
-
-import (
- "fmt"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/yeqown/go-qrcode/v2"
- "github.com/yeqown/go-qrcode/writer/standard"
-)
-
-func NewManager() (*Manager, error) {
- detection, err := DetectNetworkStack()
- if err != nil {
- return nil, fmt.Errorf("failed to detect network stack: %w", err)
- }
-
- log.Infof("Network backend detection: %s", detection.ChosenReason)
-
- var backend Backend
- switch detection.Backend {
- case BackendNetworkManager:
- nm, err := NewNetworkManagerBackend()
- if err != nil {
- return nil, fmt.Errorf("failed to create NetworkManager backend: %w", err)
- }
- backend = nm
-
- case BackendIwd:
- iwd, err := NewIWDBackend()
- if err != nil {
- return nil, fmt.Errorf("failed to create iwd backend: %w", err)
- }
- backend = iwd
-
- case BackendNetworkd:
- if detection.HasIwd && !detection.HasNM {
- wifi, err := NewIWDBackend()
- if err != nil {
- return nil, fmt.Errorf("failed to create iwd backend: %w", err)
- }
- l3, err := NewSystemdNetworkdBackend()
- if err != nil {
- return nil, fmt.Errorf("failed to create networkd backend: %w", err)
- }
- hybrid, err := NewHybridIwdNetworkdBackend(wifi, l3)
- if err != nil {
- return nil, fmt.Errorf("failed to create hybrid backend: %w", err)
- }
- backend = hybrid
- } else {
- nd, err := NewSystemdNetworkdBackend()
- if err != nil {
- return nil, fmt.Errorf("failed to create networkd backend: %w", err)
- }
- backend = nd
- }
-
- default:
- return nil, fmt.Errorf("no supported network backend found: %s", detection.ChosenReason)
- }
-
- m := &Manager{
- backend: backend,
- state: &NetworkState{
- NetworkStatus: StatusDisconnected,
- Preference: PreferenceAuto,
- WiFiNetworks: []WiFiNetwork{},
- },
- stateMutex: sync.RWMutex{},
-
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- broker := NewSubscriptionBroker(m.broadcastCredentialPrompt)
- if err := backend.SetPromptBroker(broker); err != nil {
- return nil, fmt.Errorf("failed to set prompt broker: %w", err)
- }
-
- if err := backend.Initialize(); err != nil {
- return nil, fmt.Errorf("failed to initialize backend: %w", err)
- }
-
- if err := m.syncStateFromBackend(); err != nil {
- return nil, fmt.Errorf("failed to sync initial state: %w", err)
- }
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- if err := backend.StartMonitoring(m.onBackendStateChange); err != nil {
- m.Close()
- return nil, fmt.Errorf("failed to start monitoring: %w", err)
- }
-
- return m, nil
-}
-
-func (m *Manager) syncStateFromBackend() error {
- backendState, err := m.backend.GetCurrentState()
- if err != nil {
- return err
- }
-
- m.stateMutex.Lock()
- m.state.Backend = backendState.Backend
- m.state.NetworkStatus = backendState.NetworkStatus
- m.state.EthernetIP = backendState.EthernetIP
- m.state.EthernetDevice = backendState.EthernetDevice
- m.state.EthernetConnected = backendState.EthernetConnected
- m.state.EthernetConnectionUuid = backendState.EthernetConnectionUuid
- m.state.EthernetDevices = backendState.EthernetDevices
- m.state.WiFiIP = backendState.WiFiIP
- m.state.WiFiDevice = backendState.WiFiDevice
- m.state.WiFiConnected = backendState.WiFiConnected
- m.state.WiFiEnabled = backendState.WiFiEnabled
- m.state.WiFiSSID = backendState.WiFiSSID
- m.state.WiFiBSSID = backendState.WiFiBSSID
- m.state.WiFiSignal = backendState.WiFiSignal
- m.state.WiFiNetworks = backendState.WiFiNetworks
- m.state.WiFiDevices = backendState.WiFiDevices
- m.state.WiredConnections = backendState.WiredConnections
- m.state.VPNProfiles = backendState.VPNProfiles
- m.state.VPNActive = backendState.VPNActive
- m.state.IsConnecting = backendState.IsConnecting
- m.state.ConnectingSSID = backendState.ConnectingSSID
- m.state.ConnectingDevice = backendState.ConnectingDevice
- m.state.LastError = backendState.LastError
- m.stateMutex.Unlock()
-
- return nil
-}
-
-func (m *Manager) onBackendStateChange() {
- if err := m.syncStateFromBackend(); err != nil {
- log.Errorf("failed to sync state from backend: %v", err)
- }
- m.notifySubscribers()
-}
-
-func signalChangeSignificant(old, new uint8) bool {
- if old == 0 || new == 0 {
- return true
- }
- diff := int(new) - int(old)
- if diff < 0 {
- diff = -diff
- }
- return diff >= 5
-}
-
-func (m *Manager) snapshotState() NetworkState {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- s := *m.state
- s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
- s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...)
- s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
- s.EthernetDevices = append([]EthernetDevice(nil), m.state.EthernetDevices...)
- s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
- s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
- return s
-}
-
-func stateChangedMeaningfully(old, new *NetworkState) bool {
- if old.NetworkStatus != new.NetworkStatus {
- return true
- }
- if old.Preference != new.Preference {
- return true
- }
- if old.EthernetConnected != new.EthernetConnected {
- return true
- }
- if old.EthernetIP != new.EthernetIP {
- return true
- }
- if old.WiFiConnected != new.WiFiConnected {
- return true
- }
- if old.WiFiEnabled != new.WiFiEnabled {
- return true
- }
- if old.WiFiSSID != new.WiFiSSID {
- return true
- }
- if old.WiFiBSSID != new.WiFiBSSID {
- return true
- }
- if old.WiFiIP != new.WiFiIP {
- return true
- }
- if !signalChangeSignificant(old.WiFiSignal, new.WiFiSignal) {
- if old.WiFiSignal != new.WiFiSignal {
- return false
- }
- } else if old.WiFiSignal != new.WiFiSignal {
- return true
- }
- if old.IsConnecting != new.IsConnecting {
- return true
- }
- if old.ConnectingSSID != new.ConnectingSSID {
- return true
- }
- if old.LastError != new.LastError {
- return true
- }
- if len(old.WiFiNetworks) != len(new.WiFiNetworks) {
- return true
- }
- if len(old.WiFiDevices) != len(new.WiFiDevices) {
- return true
- }
- if len(old.WiredConnections) != len(new.WiredConnections) {
- return true
- }
- if len(old.EthernetDevices) != len(new.EthernetDevices) {
- return true
- }
-
- for i := range old.WiFiNetworks {
- oldNet := &old.WiFiNetworks[i]
- newNet := &new.WiFiNetworks[i]
- if oldNet.SSID != newNet.SSID {
- return true
- }
- if oldNet.Connected != newNet.Connected {
- return true
- }
- if oldNet.Saved != newNet.Saved {
- return true
- }
- if oldNet.Autoconnect != newNet.Autoconnect {
- return true
- }
- }
-
- for i := range old.WiredConnections {
- oldNet := &old.WiredConnections[i]
- newNet := &new.WiredConnections[i]
- if oldNet.ID != newNet.ID {
- return true
- }
- if oldNet.IsActive != newNet.IsActive {
- return true
- }
- }
-
- for i := range old.EthernetDevices {
- oldDev := &old.EthernetDevices[i]
- newDev := &new.EthernetDevices[i]
- if oldDev.Name != newDev.Name {
- return true
- }
- if oldDev.Connected != newDev.Connected {
- return true
- }
- if oldDev.State != newDev.State {
- return true
- }
- if oldDev.IP != newDev.IP {
- return true
- }
- }
-
- // Check VPN profiles count
- if len(old.VPNProfiles) != len(new.VPNProfiles) {
- return true
- }
-
- // Check active VPN connections count or state
- if len(old.VPNActive) != len(new.VPNActive) {
- return true
- }
-
- // Check if any active VPN changed
- for i := range old.VPNActive {
- oldVPN := &old.VPNActive[i]
- newVPN := &new.VPNActive[i]
- if oldVPN.UUID != newVPN.UUID {
- return true
- }
- if oldVPN.State != newVPN.State {
- return true
- }
- }
-
- return false
-}
-
-func (m *Manager) GetState() NetworkState {
- return m.snapshotState()
-}
-
-func (m *Manager) Subscribe(id string) chan NetworkState {
- ch := make(chan NetworkState, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) SubscribeCredentials(id string) chan CredentialPrompt {
- ch := make(chan CredentialPrompt, 16)
- m.credentialSubscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) UnsubscribeCredentials(id string) {
- if ch, ok := m.credentialSubscribers.LoadAndDelete(id); ok {
- close(ch)
- }
-}
-
-func (m *Manager) broadcastCredentialPrompt(prompt CredentialPrompt) {
- m.credentialSubscribers.Range(func(key string, ch chan CredentialPrompt) bool {
- select {
- case ch <- prompt:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.snapshotState()
-
- if m.lastNotifiedState != nil && !stateChangedMeaningfully(m.lastNotifiedState, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan NetworkState) bool {
- select {
- case ch <- currentState:
- default:
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotifiedState = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) SetPromptBroker(broker PromptBroker) error {
- return m.backend.SetPromptBroker(broker)
-}
-
-func (m *Manager) SubmitCredentials(token string, secrets map[string]string, save bool) error {
- return m.backend.SubmitCredentials(token, secrets, save)
-}
-
-func (m *Manager) CancelCredentials(token string) error {
- return m.backend.CancelCredentials(token)
-}
-
-func (m *Manager) GetPromptBroker() PromptBroker {
- return m.backend.GetPromptBroker()
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.notifierWg.Wait()
-
- if m.backend != nil {
- m.backend.Close()
- }
-
- m.subscribers.Range(func(key string, ch chan NetworkState) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-}
-
-func (m *Manager) ScanWiFi() error {
- return m.backend.ScanWiFi()
-}
-
-func (m *Manager) GetWiFiNetworks() []WiFiNetwork {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- networks := make([]WiFiNetwork, len(m.state.WiFiNetworks))
- copy(networks, m.state.WiFiNetworks)
- return networks
-}
-
-func (m *Manager) GetNetworkInfo(ssid string) (*WiFiNetwork, error) {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
-
- for _, network := range m.state.WiFiNetworks {
- if network.SSID == ssid {
- return &network, nil
- }
- }
-
- return nil, fmt.Errorf("network not found: %s", ssid)
-}
-
-func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, error) {
- return m.backend.GetWiFiNetworkDetails(ssid)
-}
-
-func (m *Manager) GetNetworkQRCode(ssid string) ([2]string, error) {
- content, err := m.backend.GetWiFiQRCodeContent(ssid)
- if err != nil {
- return [2]string{}, err
- }
-
- qrc, err := qrcode.New(content)
- if err != nil {
- return [2]string{}, fmt.Errorf("failed to create QR code for `%s`: %w", ssid, err)
- }
-
- pathThemed, pathNormal := qrCodePaths(ssid)
-
- wThemed, err := standard.New(
- pathThemed,
- standard.WithBuiltinImageEncoder(standard.PNG_FORMAT),
- standard.WithBgTransparent(),
- standard.WithFgColorRGBHex("#ffffff"),
- )
- if err != nil {
- return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
- }
- if err := qrc.Save(wThemed); err != nil {
- return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
- }
-
- wNormal, err := standard.New(pathNormal, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
- if err != nil {
- return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
- }
- if err := qrc.Save(wNormal); err != nil {
- return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
- }
-
- return [2]string{pathThemed, pathNormal}, nil
-}
-
-func (m *Manager) ToggleWiFi() error {
- enabled, err := m.backend.GetWiFiEnabled()
- if err != nil {
- return fmt.Errorf("failed to get WiFi state: %w", err)
- }
-
- err = m.backend.SetWiFiEnabled(!enabled)
- if err != nil {
- return fmt.Errorf("failed to toggle WiFi: %w", err)
- }
-
- return nil
-}
-
-func (m *Manager) EnableWiFi() error {
- err := m.backend.SetWiFiEnabled(true)
- if err != nil {
- return fmt.Errorf("failed to enable WiFi: %w", err)
- }
-
- return nil
-}
-
-func (m *Manager) DisableWiFi() error {
- err := m.backend.SetWiFiEnabled(false)
- if err != nil {
- return fmt.Errorf("failed to disable WiFi: %w", err)
- }
-
- return nil
-}
-
-func (m *Manager) ConnectWiFi(req ConnectionRequest) error {
- return m.backend.ConnectWiFi(req)
-}
-
-func (m *Manager) DisconnectWiFi() error {
- return m.backend.DisconnectWiFi()
-}
-
-func (m *Manager) ForgetWiFiNetwork(ssid string) error {
- return m.backend.ForgetWiFiNetwork(ssid)
-}
-
-func (m *Manager) GetWiredConfigs() []WiredConnection {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- configs := make([]WiredConnection, len(m.state.WiredConnections))
- copy(configs, m.state.WiredConnections)
- return configs
-}
-
-func (m *Manager) GetWiredNetworkInfoDetailed(uuid string) (*WiredNetworkInfoResponse, error) {
- return m.backend.GetWiredNetworkDetails(uuid)
-}
-
-func (m *Manager) ConnectEthernet() error {
- return m.backend.ConnectEthernet()
-}
-
-func (m *Manager) DisconnectEthernet() error {
- return m.backend.DisconnectEthernet()
-}
-
-func (m *Manager) DisconnectEthernetDevice(device string) error {
- return m.backend.DisconnectEthernetDevice(device)
-}
-
-func (m *Manager) GetEthernetDevices() []EthernetDevice {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- devices := make([]EthernetDevice, len(m.state.EthernetDevices))
- copy(devices, m.state.EthernetDevices)
- return devices
-}
-
-func (m *Manager) activateConnection(uuid string) error {
- return m.backend.ActivateWiredConnection(uuid)
-}
-
-func (m *Manager) ListVPNProfiles() ([]VPNProfile, error) {
- return m.backend.ListVPNProfiles()
-}
-
-func (m *Manager) ListActiveVPN() ([]VPNActive, error) {
- return m.backend.ListActiveVPN()
-}
-
-func (m *Manager) ConnectVPN(uuidOrName string, singleActive bool) error {
- return m.backend.ConnectVPN(uuidOrName, singleActive)
-}
-
-func (m *Manager) DisconnectVPN(uuidOrName string) error {
- return m.backend.DisconnectVPN(uuidOrName)
-}
-
-func (m *Manager) DisconnectAllVPN() error {
- return m.backend.DisconnectAllVPN()
-}
-
-func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
- return m.backend.ClearVPNCredentials(uuidOrName)
-}
-
-func (m *Manager) ListVPNPlugins() ([]VPNPlugin, error) {
- return m.backend.ListVPNPlugins()
-}
-
-func (m *Manager) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
- return m.backend.ImportVPN(filePath, name)
-}
-
-func (m *Manager) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
- return m.backend.GetVPNConfig(uuidOrName)
-}
-
-func (m *Manager) UpdateVPNConfig(uuid string, updates map[string]any) error {
- return m.backend.UpdateVPNConfig(uuid, updates)
-}
-
-func (m *Manager) DeleteVPN(uuidOrName string) error {
- return m.backend.DeleteVPN(uuidOrName)
-}
-
-func (m *Manager) SetVPNCredentials(uuid, username, password string, save bool) error {
- return m.backend.SetVPNCredentials(uuid, username, password, save)
-}
-
-func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
- return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
-}
-
-func (m *Manager) GetWiFiDevices() []WiFiDevice {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- devices := make([]WiFiDevice, len(m.state.WiFiDevices))
- copy(devices, m.state.WiFiDevices)
- return devices
-}
-
-func (m *Manager) ScanWiFiDevice(device string) error {
- return m.backend.ScanWiFiDevice(device)
-}
-
-func (m *Manager) DisconnectWiFiDevice(device string) error {
- return m.backend.DisconnectWiFiDevice(device)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager_test.go
deleted file mode 100644
index 79545c5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/manager_test.go
+++ /dev/null
@@ -1,191 +0,0 @@
-package network
-
-import (
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_GetState(t *testing.T) {
- state := &NetworkState{
- NetworkStatus: StatusWiFi,
- WiFiSSID: "TestNetwork",
- WiFiConnected: true,
- }
-
- manager := &Manager{
- state: state,
- stateMutex: sync.RWMutex{},
- }
-
- result := manager.GetState()
- assert.Equal(t, StatusWiFi, result.NetworkStatus)
- assert.Equal(t, "TestNetwork", result.WiFiSSID)
- assert.True(t, result.WiFiConnected)
-}
-
-func TestManager_NotifySubscribers(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- NetworkStatus: StatusWiFi,
- },
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
- manager.notifierWg.Add(1)
- go manager.notifier()
-
- ch := make(chan NetworkState, 10)
- manager.subscribers.Store("test-client", ch)
-
- manager.notifySubscribers()
-
- select {
- case state := <-ch:
- assert.Equal(t, StatusWiFi, state.NetworkStatus)
- case <-time.After(200 * time.Millisecond):
- t.Fatal("did not receive state update")
- }
-
- close(manager.stopChan)
- manager.notifierWg.Wait()
-}
-
-func TestManager_NotifySubscribers_Debounce(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- NetworkStatus: StatusWiFi,
- },
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
- manager.notifierWg.Add(1)
- go manager.notifier()
-
- ch := make(chan NetworkState, 10)
- manager.subscribers.Store("test-client", ch)
-
- manager.notifySubscribers()
- manager.notifySubscribers()
- manager.notifySubscribers()
-
- receivedCount := 0
- timeout := time.After(200 * time.Millisecond)
- for {
- select {
- case <-ch:
- receivedCount++
- case <-timeout:
- assert.Equal(t, 1, receivedCount, "should receive exactly one debounced update")
- close(manager.stopChan)
- manager.notifierWg.Wait()
- return
- }
- }
-}
-
-func TestManager_Close(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- stateMutex: sync.RWMutex{},
- stopChan: make(chan struct{}),
- }
-
- ch1 := make(chan NetworkState, 1)
- ch2 := make(chan NetworkState, 1)
- manager.subscribers.Store("client1", ch1)
- manager.subscribers.Store("client2", ch2)
-
- manager.Close()
-
- select {
- case <-manager.stopChan:
- case <-time.After(100 * time.Millisecond):
- t.Fatal("stopChan not closed")
- }
-
- _, ok1 := <-ch1
- _, ok2 := <-ch2
- assert.False(t, ok1, "ch1 should be closed")
- assert.False(t, ok2, "ch2 should be closed")
-
- count := 0
- manager.subscribers.Range(func(key string, ch chan NetworkState) bool { count++; return true })
- assert.Equal(t, 0, count)
-}
-
-func TestManager_Subscribe(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- ch := manager.Subscribe("test-client")
- assert.NotNil(t, ch)
- assert.Equal(t, 64, cap(ch))
-
- _, exists := manager.subscribers.Load("test-client")
- assert.True(t, exists)
-}
-
-func TestManager_Unsubscribe(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- ch := manager.Subscribe("test-client")
-
- manager.Unsubscribe("test-client")
-
- _, ok := <-ch
- assert.False(t, ok)
-
- _, exists := manager.subscribers.Load("test-client")
- assert.False(t, exists)
-}
-
-func TestNewManager(t *testing.T) {
- t.Run("attempts to create manager", func(t *testing.T) {
- manager, err := NewManager()
- if err != nil {
- assert.Nil(t, manager)
- } else {
- assert.NotNil(t, manager)
- assert.NotNil(t, manager.state)
- assert.NotNil(t, manager.subscribers)
- assert.NotNil(t, manager.stopChan)
-
- manager.Close()
- }
- })
-}
-
-func TestManager_GetState_ThreadSafe(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- NetworkStatus: StatusWiFi,
- WiFiSSID: "TestNetwork",
- },
- stateMutex: sync.RWMutex{},
- }
-
- done := make(chan bool)
- for i := 0; i < 10; i++ {
- go func() {
- state := manager.GetState()
- assert.Equal(t, StatusWiFi, state.NetworkStatus)
- done <- true
- }()
- }
-
- for i := 0; i < 10; i++ {
- select {
- case <-done:
- case <-time.After(1 * time.Second):
- t.Fatal("timeout waiting for goroutines")
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/monitor.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/monitor.go
deleted file mode 100644
index 1ae2e9d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/monitor.go
+++ /dev/null
@@ -1 +0,0 @@
-package network
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority.go
deleted file mode 100644
index 8ee7fdf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package network
-
-import (
- "fmt"
- "os/exec"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- priorityHigh = int32(100)
- priorityLow = int32(10)
- priorityDefault = int32(0)
-
- metricPreferred = int64(100)
- metricNonPreferred = int64(300)
- metricDefault = int64(100)
-)
-
-func (m *Manager) SetConnectionPreference(pref ConnectionPreference) error {
- switch pref {
- case PreferenceWiFi, PreferenceEthernet, PreferenceAuto:
- default:
- return fmt.Errorf("invalid preference: %s", pref)
- }
-
- m.stateMutex.Lock()
- m.state.Preference = pref
- m.stateMutex.Unlock()
-
- if _, ok := m.backend.(*NetworkManagerBackend); !ok {
- m.notifySubscribers()
- return nil
- }
-
- switch pref {
- case PreferenceWiFi:
- return m.prioritizeWiFi()
- case PreferenceEthernet:
- return m.prioritizeEthernet()
- case PreferenceAuto:
- return m.balancePriorities()
- }
-
- return nil
-}
-
-func (m *Manager) prioritizeWiFi() error {
- if err := m.setConnectionPriority("802-11-wireless", priorityHigh, metricPreferred); err != nil {
- log.Warnf("Failed to set WiFi priority: %v", err)
- }
-
- if err := m.setConnectionPriority("802-3-ethernet", priorityLow, metricNonPreferred); err != nil {
- log.Warnf("Failed to set Ethernet priority: %v", err)
- }
-
- m.reapplyActiveConnections()
- m.notifySubscribers()
- return nil
-}
-
-func (m *Manager) prioritizeEthernet() error {
- if err := m.setConnectionPriority("802-3-ethernet", priorityHigh, metricPreferred); err != nil {
- log.Warnf("Failed to set Ethernet priority: %v", err)
- }
-
- if err := m.setConnectionPriority("802-11-wireless", priorityLow, metricNonPreferred); err != nil {
- log.Warnf("Failed to set WiFi priority: %v", err)
- }
-
- m.reapplyActiveConnections()
- m.notifySubscribers()
- return nil
-}
-
-func (m *Manager) balancePriorities() error {
- if err := m.setConnectionPriority("802-3-ethernet", priorityDefault, metricDefault); err != nil {
- log.Warnf("Failed to reset Ethernet priority: %v", err)
- }
-
- if err := m.setConnectionPriority("802-11-wireless", priorityDefault, metricDefault); err != nil {
- log.Warnf("Failed to reset WiFi priority: %v", err)
- }
-
- m.reapplyActiveConnections()
- m.notifySubscribers()
- return nil
-}
-
-func (m *Manager) reapplyActiveConnections() {
- m.stateMutex.RLock()
- ethDev := m.state.EthernetDevice
- wifiDev := m.state.WiFiDevice
- m.stateMutex.RUnlock()
-
- if ethDev != "" {
- exec.Command("nmcli", "dev", "reapply", ethDev).Run()
- }
- if wifiDev != "" {
- exec.Command("nmcli", "dev", "reapply", wifiDev).Run()
- }
-}
-
-func (m *Manager) setConnectionPriority(connType string, autoconnectPriority int32, routeMetric int64) error {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("failed to connect to system bus: %w", err)
- }
- defer conn.Close()
-
- settingsObj := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/Settings")
-
- var connPaths []dbus.ObjectPath
- if err := settingsObj.Call("org.freedesktop.NetworkManager.Settings.ListConnections", 0).Store(&connPaths); err != nil {
- return fmt.Errorf("failed to list connections: %w", err)
- }
-
- for _, connPath := range connPaths {
- connObj := conn.Object("org.freedesktop.NetworkManager", connPath)
-
- var settings map[string]map[string]dbus.Variant
- if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&settings); err != nil {
- continue
- }
-
- connSection, ok := settings["connection"]
- if !ok {
- continue
- }
-
- typeVariant, ok := connSection["type"]
- if !ok {
- continue
- }
-
- cType, ok := typeVariant.Value().(string)
- if !ok || cType != connType {
- continue
- }
-
- connName := ""
- if idVariant, ok := connSection["id"]; ok {
- connName, _ = idVariant.Value().(string)
- }
-
- if connName == "" {
- continue
- }
-
- if err := exec.Command("nmcli", "con", "mod", connName,
- "connection.autoconnect-priority", fmt.Sprintf("%d", autoconnectPriority),
- "ipv4.route-metric", fmt.Sprintf("%d", routeMetric),
- "ipv6.route-metric", fmt.Sprintf("%d", routeMetric)).Run(); err != nil {
- log.Warnf("Failed to set priority for %s: %v", connName, err)
- continue
- }
-
- log.Infof("Updated %v: autoconnect-priority=%d, route-metric=%d", connName, autoconnectPriority, routeMetric)
- }
-
- return nil
-}
-
-func (m *Manager) GetConnectionPreference() ConnectionPreference {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- return m.state.Preference
-}
-
-func (m *Manager) WasRecentlyFailed(ssid string) bool {
- nm, ok := m.backend.(*NetworkManagerBackend)
- if !ok {
- return false
- }
-
- nm.failedMutex.RLock()
- defer nm.failedMutex.RUnlock()
-
- if nm.lastFailedSSID != ssid {
- return false
- }
-
- elapsed := time.Now().Unix() - nm.lastFailedTime
- return elapsed < 10
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority_test.go
deleted file mode 100644
index c0c65e3..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/priority_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_SetConnectionPreference(t *testing.T) {
- t.Run("invalid preference", func(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- Preference: PreferenceAuto,
- },
- }
-
- err := manager.SetConnectionPreference(ConnectionPreference("invalid"))
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "invalid preference")
- })
-}
-
-func TestManager_GetConnectionPreference(t *testing.T) {
- tests := []struct {
- name string
- preference ConnectionPreference
- }{
- {"auto", PreferenceAuto},
- {"wifi", PreferenceWiFi},
- {"ethernet", PreferenceEthernet},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- Preference: tt.preference,
- },
- }
-
- result := manager.GetConnectionPreference()
- assert.Equal(t, tt.preference, result)
- })
- }
-}
-
-// Note: Full testing of priority operations would require mocking NetworkManager
-// D-Bus interfaces. The tests above cover the basic logic and error handling.
-// Integration tests would be needed for complete coverage of network connection
-// priority updates and reactivation.
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/subscription_broker.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/subscription_broker.go
deleted file mode 100644
index 1b150a2..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/subscription_broker.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package network
-
-import (
- "context"
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type SubscriptionBroker struct {
- pending syncmap.Map[string, chan PromptReply]
- requests syncmap.Map[string, PromptRequest]
- pathSettingToToken syncmap.Map[string, string]
- broadcastPrompt func(CredentialPrompt)
-}
-
-func NewSubscriptionBroker(broadcastPrompt func(CredentialPrompt)) PromptBroker {
- return &SubscriptionBroker{
- broadcastPrompt: broadcastPrompt,
- }
-}
-
-func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string, error) {
- pathSettingKey := fmt.Sprintf("%s:%s", req.ConnectionPath, req.SettingName)
-
- if existingToken, alreadyPending := b.pathSettingToToken.Load(pathSettingKey); alreadyPending {
- log.Infof("[SubscriptionBroker] Duplicate prompt for %s, returning existing token", pathSettingKey)
- return existingToken, nil
- }
-
- token, err := generateToken()
- if err != nil {
- return "", err
- }
-
- replyChan := make(chan PromptReply, 1)
- b.pending.Store(token, replyChan)
- b.requests.Store(token, req)
- b.pathSettingToToken.Store(pathSettingKey, token)
-
- if b.broadcastPrompt != nil {
- prompt := CredentialPrompt{
- Token: token,
- Name: req.Name,
- SSID: req.SSID,
- ConnType: req.ConnType,
- VpnService: req.VpnService,
- Setting: req.SettingName,
- Fields: req.Fields,
- FieldsInfo: req.FieldsInfo,
- Hints: req.Hints,
- Reason: req.Reason,
- ConnectionId: req.ConnectionId,
- ConnectionUuid: req.ConnectionUuid,
- }
- b.broadcastPrompt(prompt)
- }
-
- return token, nil
-}
-
-func (b *SubscriptionBroker) Wait(ctx context.Context, token string) (PromptReply, error) {
- replyChan, exists := b.pending.Load(token)
- if !exists {
- return PromptReply{}, fmt.Errorf("unknown token: %s", token)
- }
-
- select {
- case <-ctx.Done():
- b.cleanup(token)
- return PromptReply{}, errdefs.ErrSecretPromptTimeout
- case reply := <-replyChan:
- b.cleanup(token)
- if reply.Cancel {
- return reply, errdefs.ErrSecretPromptCancelled
- }
- return reply, nil
- }
-}
-
-func (b *SubscriptionBroker) Resolve(token string, reply PromptReply) error {
- replyChan, exists := b.pending.Load(token)
- if !exists {
- log.Warnf("[SubscriptionBroker] Resolve: unknown or expired token: %s", token)
- return fmt.Errorf("unknown or expired token: %s", token)
- }
-
- select {
- case replyChan <- reply:
- return nil
- default:
- log.Warnf("[SubscriptionBroker] Resolve: failed to deliver reply for token %s (channel full or closed)", token)
- return fmt.Errorf("failed to deliver reply for token: %s", token)
- }
-}
-
-func (b *SubscriptionBroker) cleanup(token string) {
- if req, exists := b.requests.Load(token); exists {
- pathSettingKey := fmt.Sprintf("%s:%s", req.ConnectionPath, req.SettingName)
- b.pathSettingToToken.Delete(pathSettingKey)
- }
-
- b.pending.Delete(token)
- b.requests.Delete(token)
-}
-
-func (b *SubscriptionBroker) Cancel(path string, setting string) error {
- pathSettingKey := fmt.Sprintf("%s:%s", path, setting)
-
- token, exists := b.pathSettingToToken.Load(pathSettingKey)
- if !exists {
- log.Infof("[SubscriptionBroker] Cancel: no pending prompt for %s", pathSettingKey)
- return nil
- }
-
- log.Infof("[SubscriptionBroker] Cancelling prompt for %s (token=%s)", pathSettingKey, token)
-
- reply := PromptReply{
- Cancel: true,
- }
-
- return b.Resolve(token, reply)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/testing.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/testing.go
deleted file mode 100644
index a6c4a00..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/testing.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package network
-
-// NewTestManager creates a Manager for testing with a provided backend
-func NewTestManager(backend Backend, state *NetworkState) *Manager {
- if state == nil {
- state = &NetworkState{}
- }
- return &Manager{
- backend: backend,
- state: state,
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types.go
deleted file mode 100644
index 93448cf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types.go
+++ /dev/null
@@ -1,267 +0,0 @@
-package network
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/godbus/dbus/v5"
-)
-
-type NetworkStatus string
-
-const (
- StatusDisconnected NetworkStatus = "disconnected"
- StatusEthernet NetworkStatus = "ethernet"
- StatusWiFi NetworkStatus = "wifi"
- StatusVPN NetworkStatus = "vpn"
-)
-
-type ConnectionPreference string
-
-const (
- PreferenceAuto ConnectionPreference = "auto"
- PreferenceWiFi ConnectionPreference = "wifi"
- PreferenceEthernet ConnectionPreference = "ethernet"
-)
-
-type WiFiNetwork struct {
- SSID string `json:"ssid"`
- BSSID string `json:"bssid"`
- Signal uint8 `json:"signal"`
- Secured bool `json:"secured"`
- Enterprise bool `json:"enterprise"`
- Connected bool `json:"connected"`
- Saved bool `json:"saved"`
- Autoconnect bool `json:"autoconnect"`
- Hidden bool `json:"hidden"`
- Frequency uint32 `json:"frequency"`
- Mode string `json:"mode"`
- Rate uint32 `json:"rate"`
- Channel uint32 `json:"channel"`
- Device string `json:"device,omitempty"`
-}
-
-type WiFiDevice struct {
- Name string `json:"name"`
- HwAddress string `json:"hwAddress"`
- State string `json:"state"`
- Connected bool `json:"connected"`
- SSID string `json:"ssid,omitempty"`
- BSSID string `json:"bssid,omitempty"`
- Signal uint8 `json:"signal,omitempty"`
- IP string `json:"ip,omitempty"`
- Networks []WiFiNetwork `json:"networks"`
-}
-
-type EthernetDevice struct {
- Name string `json:"name"`
- HwAddress string `json:"hwAddress"`
- State string `json:"state"`
- Connected bool `json:"connected"`
- IP string `json:"ip,omitempty"`
- Speed uint32 `json:"speed,omitempty"`
- Driver string `json:"driver,omitempty"`
-}
-
-type VPNProfile struct {
- Name string `json:"name"`
- UUID string `json:"uuid"`
- Type string `json:"type"`
- ServiceType string `json:"serviceType"`
- RemoteHost string `json:"remoteHost,omitempty"`
- Username string `json:"username,omitempty"`
- Autoconnect bool `json:"autoconnect"`
- Data map[string]string `json:"data,omitempty"`
-}
-
-type VPNActive struct {
- Name string `json:"name"`
- UUID string `json:"uuid"`
- Device string `json:"device,omitempty"`
- State string `json:"state,omitempty"`
- Type string `json:"type"`
- Plugin string `json:"serviceType"`
- IP string `json:"ip,omitempty"`
- Gateway string `json:"gateway,omitempty"`
- RemoteHost string `json:"remoteHost,omitempty"`
- Username string `json:"username,omitempty"`
- MTU uint32 `json:"mtu,omitempty"`
- Data map[string]string `json:"data,omitempty"`
-}
-
-type VPNState struct {
- Profiles []VPNProfile `json:"profiles"`
- Active []VPNActive `json:"activeConnections"`
-}
-
-type NetworkState struct {
- Backend string `json:"backend"`
- NetworkStatus NetworkStatus `json:"networkStatus"`
- Preference ConnectionPreference `json:"preference"`
- EthernetIP string `json:"ethernetIP"`
- EthernetDevice string `json:"ethernetDevice"`
- EthernetConnected bool `json:"ethernetConnected"`
- EthernetConnectionUuid string `json:"ethernetConnectionUuid"`
- EthernetDevices []EthernetDevice `json:"ethernetDevices"`
- WiFiIP string `json:"wifiIP"`
- WiFiDevice string `json:"wifiDevice"`
- WiFiConnected bool `json:"wifiConnected"`
- WiFiEnabled bool `json:"wifiEnabled"`
- WiFiSSID string `json:"wifiSSID"`
- WiFiBSSID string `json:"wifiBSSID"`
- WiFiSignal uint8 `json:"wifiSignal"`
- WiFiNetworks []WiFiNetwork `json:"wifiNetworks"`
- WiFiDevices []WiFiDevice `json:"wifiDevices"`
- WiredConnections []WiredConnection `json:"wiredConnections"`
- VPNProfiles []VPNProfile `json:"vpnProfiles"`
- VPNActive []VPNActive `json:"vpnActive"`
- IsConnecting bool `json:"isConnecting"`
- ConnectingSSID string `json:"connectingSSID"`
- ConnectingDevice string `json:"connectingDevice,omitempty"`
- LastError string `json:"lastError"`
-}
-
-type ConnectionRequest struct {
- SSID string `json:"ssid"`
- Password string `json:"password,omitempty"`
- Username string `json:"username,omitempty"`
- AnonymousIdentity string `json:"anonymousIdentity,omitempty"`
- DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
- Interactive bool `json:"interactive,omitempty"`
- Hidden bool `json:"hidden,omitempty"`
- Device string `json:"device,omitempty"`
- EAPMethod string `json:"eapMethod,omitempty"`
- Phase2Auth string `json:"phase2Auth,omitempty"`
- CACertPath string `json:"caCertPath,omitempty"`
- ClientCertPath string `json:"clientCertPath,omitempty"`
- PrivateKeyPath string `json:"privateKeyPath,omitempty"`
- UseSystemCACerts *bool `json:"useSystemCACerts,omitempty"`
-}
-
-type WiredConnection struct {
- Path dbus.ObjectPath `json:"path"`
- ID string `json:"id"`
- UUID string `json:"uuid"`
- Type string `json:"type"`
- IsActive bool `json:"isActive"`
-}
-
-type PriorityUpdate struct {
- Preference ConnectionPreference `json:"preference"`
-}
-
-type Manager struct {
- backend Backend
- state *NetworkState
- stateMutex sync.RWMutex
- subscribers syncmap.Map[string, chan NetworkState]
- stopChan chan struct{}
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotifiedState *NetworkState
- credentialSubscribers syncmap.Map[string, chan CredentialPrompt]
-}
-
-type EventType string
-
-const (
- EventStateChanged EventType = "state_changed"
- EventNetworksUpdated EventType = "networks_updated"
- EventConnecting EventType = "connecting"
- EventConnected EventType = "connected"
- EventDisconnected EventType = "disconnected"
- EventError EventType = "error"
-)
-
-type NetworkEvent struct {
- Type EventType `json:"type"`
- Data NetworkState `json:"data"`
-}
-
-type PromptRequest struct {
- Name string `json:"name"`
- SSID string `json:"ssid"`
- ConnType string `json:"connType"`
- VpnService string `json:"vpnService"`
- SettingName string `json:"setting"`
- Fields []string `json:"fields"`
- FieldsInfo []FieldInfo `json:"fieldsInfo"`
- Hints []string `json:"hints"`
- Reason string `json:"reason"`
- ConnectionId string `json:"connectionId"`
- ConnectionUuid string `json:"connectionUuid"`
- ConnectionPath string `json:"connectionPath"`
-}
-
-type PromptReply struct {
- Secrets map[string]string `json:"secrets"`
- Save bool `json:"save"`
- Cancel bool `json:"cancel"`
-}
-
-type FieldInfo struct {
- Name string `json:"name"`
- Label string `json:"label"`
- IsSecret bool `json:"isSecret"`
-}
-
-type CredentialPrompt struct {
- Token string `json:"token"`
- Name string `json:"name"`
- SSID string `json:"ssid"`
- ConnType string `json:"connType"`
- VpnService string `json:"vpnService"`
- Setting string `json:"setting"`
- Fields []string `json:"fields"`
- FieldsInfo []FieldInfo `json:"fieldsInfo"`
- Hints []string `json:"hints"`
- Reason string `json:"reason"`
- ConnectionId string `json:"connectionId"`
- ConnectionUuid string `json:"connectionUuid"`
-}
-
-type NetworkInfoResponse struct {
- SSID string `json:"ssid"`
- Bands []WiFiNetwork `json:"bands"`
-}
-
-type WiredNetworkInfoResponse struct {
- UUID string `json:"uuid"`
- IFace string `json:"iface"`
- Driver string `json:"driver"`
- HwAddr string `json:"hwAddr"`
- Speed string `json:"speed"`
- IPv4 WiredIPConfig `json:"IPv4s"`
- IPv6 WiredIPConfig `json:"IPv6s"`
-}
-
-type WiredIPConfig struct {
- IPs []string `json:"ips"`
- Gateway string `json:"gateway"`
- DNS string `json:"dns"`
-}
-
-type VPNPlugin struct {
- Name string `json:"name"`
- ServiceType string `json:"serviceType"`
- Program string `json:"program,omitempty"`
- Supports []string `json:"supports,omitempty"`
- FileExtensions []string `json:"fileExtensions"`
-}
-
-type VPNConfig struct {
- UUID string `json:"uuid"`
- Name string `json:"name"`
- Type string `json:"type"`
- ServiceType string `json:"serviceType,omitempty"`
- Autoconnect bool `json:"autoconnect"`
- Data map[string]string `json:"data,omitempty"`
-}
-
-type VPNImportResult struct {
- Success bool `json:"success"`
- UUID string `json:"uuid,omitempty"`
- Name string `json:"name,omitempty"`
- ServiceType string `json:"serviceType,omitempty"`
- Error string `json:"error,omitempty"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types_test.go
deleted file mode 100644
index 1c3e0bf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/types_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-package network
-
-import (
- "encoding/json"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestNetworkStatus_Constants(t *testing.T) {
- assert.Equal(t, NetworkStatus("disconnected"), StatusDisconnected)
- assert.Equal(t, NetworkStatus("ethernet"), StatusEthernet)
- assert.Equal(t, NetworkStatus("wifi"), StatusWiFi)
-}
-
-func TestConnectionPreference_Constants(t *testing.T) {
- assert.Equal(t, ConnectionPreference("auto"), PreferenceAuto)
- assert.Equal(t, ConnectionPreference("wifi"), PreferenceWiFi)
- assert.Equal(t, ConnectionPreference("ethernet"), PreferenceEthernet)
-}
-
-func TestEventType_Constants(t *testing.T) {
- assert.Equal(t, EventType("state_changed"), EventStateChanged)
- assert.Equal(t, EventType("networks_updated"), EventNetworksUpdated)
- assert.Equal(t, EventType("connecting"), EventConnecting)
- assert.Equal(t, EventType("connected"), EventConnected)
- assert.Equal(t, EventType("disconnected"), EventDisconnected)
- assert.Equal(t, EventType("error"), EventError)
-}
-
-func TestWiFiNetwork_JSON(t *testing.T) {
- network := WiFiNetwork{
- SSID: "TestNetwork",
- BSSID: "00:11:22:33:44:55",
- Signal: 85,
- Secured: true,
- Enterprise: false,
- Connected: true,
- Saved: true,
- Frequency: 2437,
- Mode: "infrastructure",
- Rate: 300,
- Channel: 6,
- }
-
- data, err := json.Marshal(network)
- require.NoError(t, err)
-
- var decoded WiFiNetwork
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, network.SSID, decoded.SSID)
- assert.Equal(t, network.BSSID, decoded.BSSID)
- assert.Equal(t, network.Signal, decoded.Signal)
- assert.Equal(t, network.Secured, decoded.Secured)
- assert.Equal(t, network.Enterprise, decoded.Enterprise)
- assert.Equal(t, network.Connected, decoded.Connected)
- assert.Equal(t, network.Saved, decoded.Saved)
- assert.Equal(t, network.Frequency, decoded.Frequency)
- assert.Equal(t, network.Mode, decoded.Mode)
- assert.Equal(t, network.Rate, decoded.Rate)
- assert.Equal(t, network.Channel, decoded.Channel)
-}
-
-func TestNetworkState_JSON(t *testing.T) {
- state := NetworkState{
- NetworkStatus: StatusWiFi,
- Preference: PreferenceAuto,
- EthernetIP: "192.168.1.100",
- EthernetDevice: "eth0",
- EthernetConnected: false,
- WiFiIP: "192.168.1.101",
- WiFiDevice: "wlan0",
- WiFiConnected: true,
- WiFiEnabled: true,
- WiFiSSID: "TestNetwork",
- WiFiBSSID: "00:11:22:33:44:55",
- WiFiSignal: 85,
- WiFiNetworks: []WiFiNetwork{
- {SSID: "Network1", Signal: 90},
- {SSID: "Network2", Signal: 60},
- },
- IsConnecting: false,
- ConnectingSSID: "",
- LastError: "",
- }
-
- data, err := json.Marshal(state)
- require.NoError(t, err)
-
- var decoded NetworkState
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, state.NetworkStatus, decoded.NetworkStatus)
- assert.Equal(t, state.Preference, decoded.Preference)
- assert.Equal(t, state.WiFiIP, decoded.WiFiIP)
- assert.Equal(t, state.WiFiSSID, decoded.WiFiSSID)
- assert.Equal(t, len(state.WiFiNetworks), len(decoded.WiFiNetworks))
-}
-
-func TestConnectionRequest_JSON(t *testing.T) {
- t.Run("with password", func(t *testing.T) {
- req := ConnectionRequest{
- SSID: "TestNetwork",
- Password: "testpass123",
- }
-
- data, err := json.Marshal(req)
- require.NoError(t, err)
-
- var decoded ConnectionRequest
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, req.SSID, decoded.SSID)
- assert.Equal(t, req.Password, decoded.Password)
- assert.Empty(t, decoded.Username)
- })
-
- t.Run("with username and password (enterprise)", func(t *testing.T) {
- req := ConnectionRequest{
- SSID: "EnterpriseNetwork",
- Password: "testpass123",
- Username: "testuser",
- }
-
- data, err := json.Marshal(req)
- require.NoError(t, err)
-
- var decoded ConnectionRequest
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, req.SSID, decoded.SSID)
- assert.Equal(t, req.Password, decoded.Password)
- assert.Equal(t, req.Username, decoded.Username)
- })
-}
-
-func TestPriorityUpdate_JSON(t *testing.T) {
- update := PriorityUpdate{
- Preference: PreferenceWiFi,
- }
-
- data, err := json.Marshal(update)
- require.NoError(t, err)
-
- var decoded PriorityUpdate
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, update.Preference, decoded.Preference)
-}
-
-func TestNetworkEvent_JSON(t *testing.T) {
- event := NetworkEvent{
- Type: EventStateChanged,
- Data: NetworkState{
- NetworkStatus: StatusWiFi,
- WiFiSSID: "TestNetwork",
- WiFiConnected: true,
- },
- }
-
- data, err := json.Marshal(event)
- require.NoError(t, err)
-
- var decoded NetworkEvent
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, event.Type, decoded.Type)
- assert.Equal(t, event.Data.NetworkStatus, decoded.Data.NetworkStatus)
- assert.Equal(t, event.Data.WiFiSSID, decoded.Data.WiFiSSID)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_qrcode.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_qrcode.go
deleted file mode 100644
index 680b6d1..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_qrcode.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package network
-
-import (
- "fmt"
- "path/filepath"
- "regexp"
- "strings"
-)
-
-const qrCodeTmpPrefix = "/tmp/dank-wifi-qrcode-"
-
-func FormatWiFiQRString(securityType, ssid, password string) string {
- return fmt.Sprintf("WIFI:T:%s;S:%s;P:%s;;", securityType, ssid, password)
-}
-
-func qrCodePaths(ssid string) (themed, normal string) {
- safe := sanitizeSSIDForPath(ssid)
- themed = fmt.Sprintf("%s%s-themed.png", qrCodeTmpPrefix, safe)
- normal = fmt.Sprintf("%s%s-normal.png", qrCodeTmpPrefix, safe)
- return
-}
-
-func isValidQRCodePath(path string) bool {
- clean := filepath.Clean(path)
- return strings.HasPrefix(clean, qrCodeTmpPrefix) && strings.HasSuffix(clean, ".png")
-}
-
-var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
-
-func sanitizeSSIDForPath(ssid string) string {
- return safePathChar.ReplaceAllString(ssid, "_")
-}
-
-var iwdVerbatimSSID = regexp.MustCompile(`^[a-zA-Z0-9 _-]+$`)
-
-func iwdConfigPath(ssid string) string {
- switch {
- case iwdVerbatimSSID.MatchString(ssid):
- return fmt.Sprintf("/var/lib/iwd/%s.psk", ssid)
- default:
- return fmt.Sprintf("/var/lib/iwd/=%x.psk", []byte(ssid))
- }
-}
-
-func parseIWDPassphrase(data string) (string, error) {
- inSecurity := false
- for _, line := range strings.Split(data, "\n") {
- line = strings.TrimSpace(line)
- switch {
- case line == "[Security]":
- inSecurity = true
- case strings.HasPrefix(line, "["):
- inSecurity = false
- case inSecurity && strings.HasPrefix(line, "Passphrase="):
- return strings.TrimPrefix(line, "Passphrase="), nil
- }
- }
- return "", fmt.Errorf("no passphrase found in iwd config")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_test.go
deleted file mode 100644
index 2256b60..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wifi_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestFrequencyToChannel(t *testing.T) {
- tests := []struct {
- name string
- frequency uint32
- channel uint32
- }{
- {"2.4 GHz channel 1", 2412, 1},
- {"2.4 GHz channel 6", 2437, 6},
- {"2.4 GHz channel 11", 2462, 11},
- {"2.4 GHz channel 14", 2484, 14},
- {"5 GHz channel 36", 5180, 36},
- {"5 GHz channel 40", 5200, 40},
- {"5 GHz channel 165", 5825, 165},
- {"6 GHz channel 1", 5955, 1},
- {"6 GHz channel 233", 7115, 233},
- {"Unknown frequency", 1000, 0},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := frequencyToChannel(tt.frequency)
- assert.Equal(t, tt.channel, result)
- })
- }
-}
-
-func TestSortWiFiNetworks(t *testing.T) {
- t.Run("connected network comes first", func(t *testing.T) {
- networks := []WiFiNetwork{
- {SSID: "Network1", Signal: 90, Connected: false},
- {SSID: "Network2", Signal: 80, Connected: true},
- {SSID: "Network3", Signal: 70, Connected: false},
- }
-
- sortWiFiNetworks(networks)
-
- assert.Equal(t, "Network2", networks[0].SSID)
- assert.True(t, networks[0].Connected)
- })
-
- t.Run("sorts by signal strength", func(t *testing.T) {
- networks := []WiFiNetwork{
- {SSID: "Weak", Signal: 40, Secured: true},
- {SSID: "Strong", Signal: 90, Secured: true},
- {SSID: "Medium", Signal: 60, Secured: true},
- }
-
- sortWiFiNetworks(networks)
-
- assert.Equal(t, "Strong", networks[0].SSID)
- assert.Equal(t, "Medium", networks[1].SSID)
- assert.Equal(t, "Weak", networks[2].SSID)
- })
-
- t.Run("prioritizes open networks with good signal", func(t *testing.T) {
- networks := []WiFiNetwork{
- {SSID: "SecureWeak", Signal: 40, Secured: true},
- {SSID: "OpenStrong", Signal: 60, Secured: false},
- {SSID: "SecureStrong", Signal: 90, Secured: true},
- }
-
- sortWiFiNetworks(networks)
-
- assert.Equal(t, "OpenStrong", networks[0].SSID)
-
- openIdx := -1
- weakSecureIdx := -1
- for i, n := range networks {
- if n.SSID == "OpenStrong" {
- openIdx = i
- }
- if n.SSID == "SecureWeak" {
- weakSecureIdx = i
- }
- }
- assert.Less(t, openIdx, weakSecureIdx, "OpenStrong should come before SecureWeak")
- })
-
- t.Run("prioritizes saved networks after connected", func(t *testing.T) {
- networks := []WiFiNetwork{
- {SSID: "UnsavedStrong", Signal: 95, Saved: false},
- {SSID: "SavedMedium", Signal: 60, Saved: true},
- {SSID: "SavedWeak", Signal: 50, Saved: true},
- {SSID: "UnsavedMedium", Signal: 70, Saved: false},
- }
-
- sortWiFiNetworks(networks)
-
- assert.Equal(t, "SavedMedium", networks[0].SSID)
- assert.Equal(t, "SavedWeak", networks[1].SSID)
- assert.Equal(t, "UnsavedStrong", networks[2].SSID)
- assert.Equal(t, "UnsavedMedium", networks[3].SSID)
- })
-}
-
-func TestManager_GetWiFiNetworks(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- WiFiNetworks: []WiFiNetwork{
- {SSID: "Network1", Signal: 90},
- {SSID: "Network2", Signal: 80},
- },
- },
- }
-
- networks := manager.GetWiFiNetworks()
-
- assert.Len(t, networks, 2)
- assert.Equal(t, "Network1", networks[0].SSID)
- assert.Equal(t, "Network2", networks[1].SSID)
-
- networks[0].SSID = "Modified"
- assert.Equal(t, "Network1", manager.state.WiFiNetworks[0].SSID)
-}
-
-func TestManager_GetNetworkInfo(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- WiFiNetworks: []WiFiNetwork{
- {SSID: "Network1", Signal: 90, BSSID: "00:11:22:33:44:55"},
- {SSID: "Network2", Signal: 80, BSSID: "AA:BB:CC:DD:EE:FF"},
- },
- },
- }
-
- t.Run("finds existing network", func(t *testing.T) {
- network, err := manager.GetNetworkInfo("Network1")
- assert.NoError(t, err)
- assert.NotNil(t, network)
- assert.Equal(t, "Network1", network.SSID)
- assert.Equal(t, uint8(90), network.Signal)
- })
-
- t.Run("returns error for non-existent network", func(t *testing.T) {
- network, err := manager.GetNetworkInfo("NonExistent")
- assert.Error(t, err)
- assert.Nil(t, network)
- assert.Contains(t, err.Error(), "network not found")
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wired_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wired_test.go
deleted file mode 100644
index 5c346e7..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/network/wired_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package network
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestManager_GetWiredConfigs(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- EthernetConnected: true,
- WiredConnections: []WiredConnection{
- {ID: "Test", IsActive: true},
- },
- },
- }
-
- configs := manager.GetWiredConfigs()
-
- assert.Len(t, configs, 1)
- assert.Equal(t, "Test", configs[0].ID)
-}
-
-func TestManager_GetEthernetDevices(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{
- EthernetDevices: []EthernetDevice{
- {Name: "enp0s3", Connected: true, IP: "192.168.1.100"},
- {Name: "enp0s8", Connected: false},
- },
- },
- }
-
- devices := manager.GetEthernetDevices()
-
- assert.Len(t, devices, 2)
- assert.Equal(t, "enp0s3", devices[0].Name)
- assert.True(t, devices[0].Connected)
- assert.Equal(t, "enp0s8", devices[1].Name)
- assert.False(t, devices[1].Connected)
-}
-
-func TestManager_GetEthernetDevices_Empty(t *testing.T) {
- manager := &Manager{
- state: &NetworkState{},
- }
-
- devices := manager.GetEthernetDevices()
- assert.Empty(t, devices)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params.go
deleted file mode 100644
index 1f592c6..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package params
-
-import "fmt"
-
-func Get[T any](params map[string]any, key string) (T, error) {
- val, ok := params[key].(T)
- if !ok {
- var zero T
- return zero, fmt.Errorf("missing or invalid '%s' parameter", key)
- }
- return val, nil
-}
-
-func GetOpt[T any](params map[string]any, key string, def T) T {
- if val, ok := params[key].(T); ok {
- return val
- }
- return def
-}
-
-func String(params map[string]any, key string) (string, error) {
- return Get[string](params, key)
-}
-
-func StringNonEmpty(params map[string]any, key string) (string, error) {
- val, err := Get[string](params, key)
- if err != nil || val == "" {
- return "", fmt.Errorf("missing or invalid '%s' parameter", key)
- }
- return val, nil
-}
-
-func StringOpt(params map[string]any, key string, def string) string {
- return GetOpt(params, key, def)
-}
-
-func Int(params map[string]any, key string) (int, error) {
- val, err := Get[float64](params, key)
- if err != nil {
- return 0, err
- }
- return int(val), nil
-}
-
-func IntOpt(params map[string]any, key string, def int) int {
- if val, ok := params[key].(float64); ok {
- return int(val)
- }
- return def
-}
-
-func Float(params map[string]any, key string) (float64, error) {
- return Get[float64](params, key)
-}
-
-func FloatOpt(params map[string]any, key string, def float64) float64 {
- return GetOpt(params, key, def)
-}
-
-func Bool(params map[string]any, key string) (bool, error) {
- return Get[bool](params, key)
-}
-
-func BoolOpt(params map[string]any, key string, def bool) bool {
- return GetOpt(params, key, def)
-}
-
-func StringMap(params map[string]any, key string) (map[string]string, error) {
- rawMap, err := Get[map[string]any](params, key)
- if err != nil {
- return nil, err
- }
- result := make(map[string]string)
- for k, v := range rawMap {
- if str, ok := v.(string); ok {
- result[k] = str
- }
- }
- return result, nil
-}
-
-func StringMapOpt(params map[string]any, key string) map[string]string {
- rawMap, ok := params[key].(map[string]any)
- if !ok {
- return nil
- }
- result := make(map[string]string)
- for k, v := range rawMap {
- if str, ok := v.(string); ok {
- result[k] = str
- }
- }
- return result
-}
-
-func Any(params map[string]any, key string) (any, bool) {
- val, ok := params[key]
- return val, ok
-}
-
-func AnyMap(params map[string]any, key string) (map[string]any, bool) {
- val, ok := params[key].(map[string]any)
- return val, ok
-}
-
-func StringAlt(params map[string]any, keys ...string) (string, bool) {
- for _, key := range keys {
- if val, ok := params[key].(string); ok {
- return val, true
- }
- }
- return "", false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params_test.go
deleted file mode 100644
index 99847ff..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/params/params_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package params
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestGet(t *testing.T) {
- p := map[string]any{"key": "value"}
- val, err := Get[string](p, "key")
- assert.NoError(t, err)
- assert.Equal(t, "value", val)
-
- _, err = Get[string](p, "missing")
- assert.Error(t, err)
-
- _, err = Get[int](p, "key")
- assert.Error(t, err)
-}
-
-func TestGetOpt(t *testing.T) {
- p := map[string]any{"key": "value"}
- assert.Equal(t, "value", GetOpt(p, "key", "default"))
- assert.Equal(t, "default", GetOpt(p, "missing", "default"))
-}
-
-func TestString(t *testing.T) {
- p := map[string]any{"s": "hello", "n": 123}
- val, err := String(p, "s")
- assert.NoError(t, err)
- assert.Equal(t, "hello", val)
-
- _, err = String(p, "n")
- assert.Error(t, err)
-}
-
-func TestStringNonEmpty(t *testing.T) {
- p := map[string]any{"s": "hello", "empty": ""}
- val, err := StringNonEmpty(p, "s")
- assert.NoError(t, err)
- assert.Equal(t, "hello", val)
-
- _, err = StringNonEmpty(p, "empty")
- assert.Error(t, err)
-
- _, err = StringNonEmpty(p, "missing")
- assert.Error(t, err)
-}
-
-func TestStringOpt(t *testing.T) {
- p := map[string]any{"s": "hello"}
- assert.Equal(t, "hello", StringOpt(p, "s", "default"))
- assert.Equal(t, "default", StringOpt(p, "missing", "default"))
-}
-
-func TestInt(t *testing.T) {
- p := map[string]any{"n": float64(42), "s": "str"}
- val, err := Int(p, "n")
- assert.NoError(t, err)
- assert.Equal(t, 42, val)
-
- _, err = Int(p, "s")
- assert.Error(t, err)
-}
-
-func TestIntOpt(t *testing.T) {
- p := map[string]any{"n": float64(42)}
- assert.Equal(t, 42, IntOpt(p, "n", 0))
- assert.Equal(t, 99, IntOpt(p, "missing", 99))
-}
-
-func TestFloat(t *testing.T) {
- p := map[string]any{"f": 3.14, "s": "str"}
- val, err := Float(p, "f")
- assert.NoError(t, err)
- assert.Equal(t, 3.14, val)
-
- _, err = Float(p, "s")
- assert.Error(t, err)
-}
-
-func TestFloatOpt(t *testing.T) {
- p := map[string]any{"f": 3.14}
- assert.Equal(t, 3.14, FloatOpt(p, "f", 0))
- assert.Equal(t, 1.0, FloatOpt(p, "missing", 1.0))
-}
-
-func TestBool(t *testing.T) {
- p := map[string]any{"b": true, "s": "str"}
- val, err := Bool(p, "b")
- assert.NoError(t, err)
- assert.True(t, val)
-
- _, err = Bool(p, "s")
- assert.Error(t, err)
-}
-
-func TestBoolOpt(t *testing.T) {
- p := map[string]any{"b": true}
- assert.True(t, BoolOpt(p, "b", false))
- assert.True(t, BoolOpt(p, "missing", true))
-}
-
-func TestStringMap(t *testing.T) {
- p := map[string]any{
- "m": map[string]any{"a": "1", "b": "2", "c": 3},
- }
- val, err := StringMap(p, "m")
- assert.NoError(t, err)
- assert.Equal(t, map[string]string{"a": "1", "b": "2"}, val)
-
- _, err = StringMap(p, "missing")
- assert.Error(t, err)
-}
-
-func TestStringMapOpt(t *testing.T) {
- p := map[string]any{
- "m": map[string]any{"a": "1"},
- }
- assert.Equal(t, map[string]string{"a": "1"}, StringMapOpt(p, "m"))
- assert.Nil(t, StringMapOpt(p, "missing"))
-}
-
-func TestAny(t *testing.T) {
- p := map[string]any{"k": 123}
- val, ok := Any(p, "k")
- assert.True(t, ok)
- assert.Equal(t, 123, val)
-
- _, ok = Any(p, "missing")
- assert.False(t, ok)
-}
-
-func TestAnyMap(t *testing.T) {
- inner := map[string]any{"nested": true}
- p := map[string]any{"m": inner}
- val, ok := AnyMap(p, "m")
- assert.True(t, ok)
- assert.Equal(t, inner, val)
-
- _, ok = AnyMap(p, "missing")
- assert.False(t, ok)
-}
-
-func TestStringAlt(t *testing.T) {
- p := map[string]any{"b": "found"}
- val, ok := StringAlt(p, "a", "b", "c")
- assert.True(t, ok)
- assert.Equal(t, "found", val)
-
- _, ok = StringAlt(p, "x", "y")
- assert.False(t, ok)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go
deleted file mode 100644
index 33f6564..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleRequest(conn net.Conn, req models.Request) {
- switch req.Method {
- case "plugins.list":
- HandleList(conn, req)
- case "plugins.listInstalled":
- HandleListInstalled(conn, req)
- case "plugins.install":
- HandleInstall(conn, req)
- case "plugins.uninstall":
- HandleUninstall(conn, req)
- case "plugins.update":
- HandleUpdate(conn, req)
- case "plugins.search":
- HandleSearch(conn, req)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go
deleted file mode 100644
index 70202e3..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package plugins
-
-import (
- "encoding/json"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/net"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-func TestHandleList(t *testing.T) {
- conn := net.NewMockConn(t)
- conn.EXPECT().Write(mock.Anything).Return(0, nil).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.list",
- Params: map[string]any{},
- }
-
- HandleList(conn, req)
-}
-
-func TestHandleListInstalled(t *testing.T) {
- conn := net.NewMockConn(t)
- conn.EXPECT().Write(mock.Anything).Return(0, nil).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.listInstalled",
- Params: map[string]any{},
- }
-
- HandleListInstalled(conn, req)
-}
-
-func TestHandleInstallMissingName(t *testing.T) {
- conn := net.NewMockConn(t)
- var written []byte
- conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
- written = b
- return len(b), nil
- }).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.install",
- Params: map[string]any{},
- }
-
- HandleInstall(conn, req)
-
- var resp models.Response[SuccessResult]
- err := json.Unmarshal(written, &resp)
- assert.NoError(t, err)
- assert.NotEmpty(t, resp.Error)
- assert.Contains(t, resp.Error, "missing or invalid 'name' parameter")
-}
-
-func TestHandleInstallInvalidName(t *testing.T) {
- conn := net.NewMockConn(t)
- var written []byte
- conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
- written = b
- return len(b), nil
- }).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.install",
- Params: map[string]any{
- "name": 123,
- },
- }
-
- HandleInstall(conn, req)
-
- var resp models.Response[SuccessResult]
- err := json.Unmarshal(written, &resp)
- assert.NoError(t, err)
- assert.NotEmpty(t, resp.Error)
-}
-
-func TestHandleUninstallMissingName(t *testing.T) {
- conn := net.NewMockConn(t)
- var written []byte
- conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
- written = b
- return len(b), nil
- }).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.uninstall",
- Params: map[string]any{},
- }
-
- HandleUninstall(conn, req)
-
- var resp models.Response[SuccessResult]
- err := json.Unmarshal(written, &resp)
- assert.NoError(t, err)
- assert.NotEmpty(t, resp.Error)
-}
-
-func TestHandleUpdateMissingName(t *testing.T) {
- conn := net.NewMockConn(t)
- var written []byte
- conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
- written = b
- return len(b), nil
- }).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.update",
- Params: map[string]any{},
- }
-
- HandleUpdate(conn, req)
-
- var resp models.Response[SuccessResult]
- err := json.Unmarshal(written, &resp)
- assert.NoError(t, err)
- assert.NotEmpty(t, resp.Error)
-}
-
-func TestHandleSearchMissingQuery(t *testing.T) {
- conn := net.NewMockConn(t)
- var written []byte
- conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
- written = b
- return len(b), nil
- }).Maybe()
-
- req := models.Request{
- ID: 123,
- Method: "plugins.search",
- Params: map[string]any{},
- }
-
- HandleSearch(conn, req)
-
- var resp models.Response[[]PluginInfo]
- err := json.Unmarshal(written, &resp)
- assert.NoError(t, err)
- assert.NotEmpty(t, resp.Error)
-}
-
-func TestSortPluginInfoByFirstParty(t *testing.T) {
- plugins := []PluginInfo{
- {Name: "third-party", Repo: "https://github.com/other/test"},
- {Name: "first-party", Repo: "https://github.com/AvengeMedia/test"},
- }
-
- SortPluginInfoByFirstParty(plugins)
-
- assert.Equal(t, "first-party", plugins[0].Name)
- assert.Equal(t, "third-party", plugins[1].Name)
-}
-
-func TestPluginInfoJSON(t *testing.T) {
- info := PluginInfo{
- Name: "test",
- Description: "test description",
- Installed: true,
- FirstParty: true,
- }
-
- data, err := json.Marshal(info)
- assert.NoError(t, err)
-
- var unmarshaled PluginInfo
- err = json.Unmarshal(data, &unmarshaled)
- assert.NoError(t, err)
- assert.Equal(t, info.Name, unmarshaled.Name)
- assert.Equal(t, info.Installed, unmarshaled.Installed)
-}
-
-func TestSuccessResult(t *testing.T) {
- result := SuccessResult{
- Success: true,
- Message: "test message",
- }
-
- data, err := json.Marshal(result)
- assert.NoError(t, err)
-
- var unmarshaled SuccessResult
- err = json.Unmarshal(data, &unmarshaled)
- assert.NoError(t, err)
- assert.True(t, unmarshaled.Success)
- assert.Equal(t, "test message", unmarshaled.Message)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go
deleted file mode 100644
index 2c13a0e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleInstall(conn net.Conn, req models.Request) {
- idOrName, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- pluginList, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err))
- return
- }
-
- // First, try to find by ID (preferred method)
- var plugin *plugins.Plugin
- for _, p := range pluginList {
- if p.ID == idOrName {
- plugin = &p
- break
- }
- }
-
- // Fallback to name for backward compatibility
- if plugin == nil {
- for _, p := range pluginList {
- if p.Name == idOrName {
- plugin = &p
- break
- }
- }
- }
-
- if plugin == nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", idOrName))
- return
- }
-
- manager, err := plugins.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- if err := manager.Install(*plugin); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to install plugin: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{
- Success: true,
- Message: fmt.Sprintf("plugin installed: %s", plugin.Name),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go
deleted file mode 100644
index e44d034..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleList(conn net.Conn, req models.Request) {
- registry, err := plugins.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- pluginList, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err))
- return
- }
-
- manager, err := plugins.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- result := make([]PluginInfo, len(pluginList))
- for i, p := range pluginList {
- installed, _ := manager.IsInstalled(p)
- result[i] = PluginInfo{
- ID: p.ID,
- Name: p.Name,
- Category: p.Category,
- Author: p.Author,
- Description: p.Description,
- Repo: p.Repo,
- Path: p.Path,
- Capabilities: p.Capabilities,
- Compositors: p.Compositors,
- Dependencies: p.Dependencies,
- Installed: installed,
- FirstParty: strings.HasPrefix(p.Repo, "https://github.com/AvengeMedia"),
- Featured: p.Featured,
- RequiresDMS: p.RequiresDMS,
- }
- }
-
- models.Respond(conn, req.ID, result)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go
deleted file mode 100644
index b771b0e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleListInstalled(conn net.Conn, req models.Request) {
- manager, err := plugins.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- installedNames, err := manager.ListInstalled()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list installed plugins: %v", err))
- return
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- allPlugins, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err))
- return
- }
-
- pluginMap := make(map[string]plugins.Plugin)
- for _, p := range allPlugins {
- pluginMap[p.ID] = p
- }
-
- result := make([]PluginInfo, 0, len(installedNames))
- for _, id := range installedNames {
- if plugin, ok := pluginMap[id]; ok {
- hasUpdate := false
- if hasUpdates, err := manager.HasUpdates(id, plugin); err == nil {
- hasUpdate = hasUpdates
- }
-
- result = append(result, PluginInfo{
- ID: plugin.ID,
- Name: plugin.Name,
- Category: plugin.Category,
- Author: plugin.Author,
- Description: plugin.Description,
- Repo: plugin.Repo,
- Path: plugin.Path,
- Capabilities: plugin.Capabilities,
- Compositors: plugin.Compositors,
- Dependencies: plugin.Dependencies,
- FirstParty: strings.HasPrefix(plugin.Repo, "https://github.com/AvengeMedia"),
- HasUpdate: hasUpdate,
- RequiresDMS: plugin.RequiresDMS,
- })
- } else {
- result = append(result, PluginInfo{
- ID: id,
- Name: id,
- Note: "not in registry",
- })
- }
- }
-
- SortPluginInfoByFirstParty(result)
-
- models.Respond(conn, req.ID, result)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go
deleted file mode 100644
index 9dbea6d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleSearch(conn net.Conn, req models.Request) {
- query, ok := models.Get[string](req, "query")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'query' parameter")
- return
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- pluginList, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err))
- return
- }
-
- searchResults := plugins.FuzzySearch(query, pluginList)
-
- if category := models.GetOr(req, "category", ""); category != "" {
- searchResults = plugins.FilterByCategory(category, searchResults)
- }
-
- if compositor := models.GetOr(req, "compositor", ""); compositor != "" {
- searchResults = plugins.FilterByCompositor(compositor, searchResults)
- }
-
- if capability := models.GetOr(req, "capability", ""); capability != "" {
- searchResults = plugins.FilterByCapability(capability, searchResults)
- }
-
- searchResults = plugins.SortByFirstParty(searchResults)
-
- manager, err := plugins.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- result := make([]PluginInfo, len(searchResults))
- for i, p := range searchResults {
- installed, _ := manager.IsInstalled(p)
- result[i] = PluginInfo{
- ID: p.ID,
- Name: p.Name,
- Category: p.Category,
- Author: p.Author,
- Description: p.Description,
- Repo: p.Repo,
- Path: p.Path,
- Capabilities: p.Capabilities,
- Compositors: p.Compositors,
- Dependencies: p.Dependencies,
- Installed: installed,
- FirstParty: strings.HasPrefix(p.Repo, "https://github.com/AvengeMedia"),
- RequiresDMS: p.RequiresDMS,
- }
- }
-
- models.Respond(conn, req.ID, result)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go
deleted file mode 100644
index 8290f72..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package plugins
-
-type PluginInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Category string `json:"category,omitempty"`
- Author string `json:"author,omitempty"`
- Description string `json:"description,omitempty"`
- Repo string `json:"repo,omitempty"`
- Path string `json:"path,omitempty"`
- Capabilities []string `json:"capabilities,omitempty"`
- Compositors []string `json:"compositors,omitempty"`
- Dependencies []string `json:"dependencies,omitempty"`
- Installed bool `json:"installed,omitempty"`
- FirstParty bool `json:"firstParty,omitempty"`
- Featured bool `json:"featured,omitempty"`
- Note string `json:"note,omitempty"`
- HasUpdate bool `json:"hasUpdate,omitempty"`
- RequiresDMS string `json:"requires_dms,omitempty"`
-}
-
-type SuccessResult struct {
- Success bool `json:"success"`
- Message string `json:"message"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go
deleted file mode 100644
index 7a3d698..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleUninstall(conn net.Conn, req models.Request) {
- name, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- manager, err := plugins.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- // First try to find in registry (by name or ID)
- registry, err := plugins.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- pluginList, _ := registry.List()
- plugin := plugins.FindByIDOrName(name, pluginList)
-
- // If found in registry, use that
- if plugin != nil {
- installed, err := manager.IsInstalled(*plugin)
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
- return
- }
- if !installed {
- models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
- return
- }
- if err := manager.Uninstall(*plugin); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall plugin: %v", err))
- return
- }
- models.Respond(conn, req.ID, SuccessResult{
- Success: true,
- Message: fmt.Sprintf("plugin uninstalled: %s", plugin.Name),
- })
- return
- }
-
- // Not in registry - try to find and uninstall from installed plugins directly
- if err := manager.UninstallByIDOrName(name); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name))
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{
- Success: true,
- Message: fmt.Sprintf("plugin uninstalled: %s", name),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go
deleted file mode 100644
index 4ea762a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package plugins
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleUpdate(conn net.Conn, req models.Request) {
- name, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- manager, err := plugins.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- pluginList, _ := registry.List()
- plugin := plugins.FindByIDOrName(name, pluginList)
-
- if plugin != nil {
- installed, err := manager.IsInstalled(*plugin)
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
- return
- }
- if !installed {
- models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
- return
- }
- if err := manager.Update(*plugin); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to update plugin: %v", err))
- return
- }
- models.Respond(conn, req.ID, SuccessResult{
- Success: true,
- Message: fmt.Sprintf("plugin updated: %s", plugin.Name),
- })
- return
- }
-
- // Not in registry - try to update from installed plugins directly
- if err := manager.UpdateByIDOrName(name); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name))
- return
- }
-
- models.Respond(conn, req.ID, SuccessResult{
- Success: true,
- Message: fmt.Sprintf("plugin updated: %s", name),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go
deleted file mode 100644
index 8b89f44..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package plugins
-
-import (
- "sort"
- "strings"
-)
-
-func SortPluginInfoByFirstParty(pluginInfos []PluginInfo) {
- sort.SliceStable(pluginInfos, func(i, j int) bool {
- isFirstPartyI := strings.HasPrefix(pluginInfos[i].Repo, "https://github.com/AvengeMedia")
- isFirstPartyJ := strings.HasPrefix(pluginInfos[j].Repo, "https://github.com/AvengeMedia")
- if isFirstPartyI != isFirstPartyJ {
- return isFirstPartyI
- }
- return false
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/router.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/router.go
deleted file mode 100644
index 6b8c10b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/router.go
+++ /dev/null
@@ -1,250 +0,0 @@
-package server
-
-import (
- "fmt"
- "net"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/apppicker"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/bluez"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
- serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
- "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/freedesktop"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
- serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
- serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
-)
-
-func RouteRequest(conn net.Conn, req models.Request) {
- if strings.HasPrefix(req.Method, "network.") {
- if networkManager == nil {
- models.RespondError(conn, req.ID, "network manager not initialized")
- return
- }
- network.HandleRequest(conn, req, networkManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "plugins.") {
- serverPlugins.HandleRequest(conn, req)
- return
- }
-
- if strings.HasPrefix(req.Method, "themes.") {
- serverThemes.HandleRequest(conn, req)
- return
- }
-
- if strings.HasPrefix(req.Method, "theme.auto.") {
- if themeModeManager == nil {
- models.RespondError(conn, req.ID, "theme mode manager not initialized")
- return
- }
- thememode.HandleRequest(conn, req, themeModeManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "loginctl.") {
- if loginctlManager == nil {
- models.RespondError(conn, req.ID, "loginctl manager not initialized")
- return
- }
- loginctl.HandleRequest(conn, req, loginctlManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "freedesktop.") {
- if freedesktopManager == nil {
- models.RespondError(conn, req.ID, "freedesktop manager not initialized")
- return
- }
- freedesktop.HandleRequest(conn, req, freedesktopManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "wayland.") {
- if waylandManager == nil {
- models.RespondError(conn, req.ID, "wayland manager not initialized")
- return
- }
- wayland.HandleRequest(conn, req, waylandManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "bluetooth.") {
- if bluezManager == nil {
- models.RespondError(conn, req.ID, "bluetooth manager not initialized")
- return
- }
- bluez.HandleRequest(conn, req, bluezManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "browser.") || strings.HasPrefix(req.Method, "apppicker.") {
- if appPickerManager == nil {
- models.RespondError(conn, req.ID, "apppicker manager not initialized")
- return
- }
- apppicker.HandleRequest(conn, req, appPickerManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "cups.") {
- if cupsManager == nil {
- models.RespondError(conn, req.ID, "CUPS manager not initialized")
- return
- }
- cups.HandleRequest(conn, req, cupsManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "dwl.") {
- if dwlManager == nil {
- models.RespondError(conn, req.ID, "dwl manager not initialized")
- return
- }
- dwl.HandleRequest(conn, req, dwlManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "brightness.") {
- if brightnessManager == nil {
- models.RespondError(conn, req.ID, "brightness manager not initialized")
- return
- }
- brightness.HandleRequest(conn, req, brightnessManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "extworkspace.") {
- if extWorkspaceManager == nil {
- if extWorkspaceAvailable.Load() {
- extWorkspaceInitMutex.Lock()
- if extWorkspaceManager == nil {
- if err := InitializeExtWorkspaceManager(); err != nil {
- extWorkspaceInitMutex.Unlock()
- models.RespondError(conn, req.ID, "extworkspace manager not available")
- return
- }
- }
- extWorkspaceInitMutex.Unlock()
- } else {
- models.RespondError(conn, req.ID, "extworkspace manager not initialized")
- return
- }
- }
- extworkspace.HandleRequest(conn, req, extWorkspaceManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "wlroutput.") {
- if wlrOutputManager == nil {
- models.RespondError(conn, req.ID, "wlroutput manager not initialized")
- return
- }
- wlroutput.HandleRequest(conn, req, wlrOutputManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "evdev.") {
- if evdevManager == nil {
- models.RespondError(conn, req.ID, "evdev manager not initialized")
- return
- }
- evdev.HandleRequest(conn, req, evdevManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "dbus.") {
- if dbusManager == nil {
- models.RespondError(conn, req.ID, "dbus manager not initialized")
- return
- }
- serverDbus.HandleRequest(conn, req, dbusManager, dbusClientID)
- return
- }
-
- if strings.HasPrefix(req.Method, "clipboard.") {
- switch req.Method {
- case "clipboard.getConfig":
- cfg := clipboard.LoadConfig()
- models.Respond(conn, req.ID, cfg)
- return
- case "clipboard.setConfig":
- handleClipboardSetConfig(conn, req)
- return
- }
- if clipboardManager == nil {
- models.RespondError(conn, req.ID, "clipboard manager not initialized")
- return
- }
- clipboard.HandleRequest(conn, req, clipboardManager)
- return
- }
-
- if strings.HasPrefix(req.Method, "location.") {
- if locationManager == nil {
- models.RespondError(conn, req.ID, "location manager not initialized")
- return
- }
- location.HandleRequest(conn, req, locationManager)
- return
- }
-
- switch req.Method {
- case "ping":
- models.Respond(conn, req.ID, "pong")
- case "getServerInfo":
- info := getServerInfo()
- models.Respond(conn, req.ID, info)
- case "subscribe":
- handleSubscribe(conn, req)
- case "matugen.queue":
- handleMatugenQueue(conn, req)
- case "matugen.status":
- handleMatugenStatus(conn, req)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleClipboardSetConfig(conn net.Conn, req models.Request) {
- cfg := clipboard.LoadConfig()
-
- if v, ok := models.Get[float64](req, "maxHistory"); ok {
- cfg.MaxHistory = int(v)
- }
- if v, ok := models.Get[float64](req, "maxEntrySize"); ok {
- cfg.MaxEntrySize = int64(v)
- }
- if v, ok := models.Get[float64](req, "autoClearDays"); ok {
- cfg.AutoClearDays = int(v)
- }
- if v, ok := models.Get[bool](req, "clearAtStartup"); ok {
- cfg.ClearAtStartup = v
- }
- if v, ok := models.Get[bool](req, "disabled"); ok {
- cfg.Disabled = v
- }
- if v, ok := models.Get[float64](req, "maxPinned"); ok {
- cfg.MaxPinned = int(v)
- }
-
- if err := clipboard.SaveConfig(cfg); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "config updated"})
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server.go
deleted file mode 100644
index 58acb62..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server.go
+++ /dev/null
@@ -1,1758 +0,0 @@
-package server
-
-import (
- "bufio"
- "encoding/json"
- "fmt"
- "net"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/apppicker"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/bluez"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
- serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
- "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/freedesktop"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/trayrecovery"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-const APIVersion = 24
-
-var CLIVersion = "dev"
-
-type Capabilities struct {
- Capabilities []string `json:"capabilities"`
-}
-
-type ServerInfo struct {
- APIVersion int `json:"apiVersion"`
- CLIVersion string `json:"cliVersion,omitempty"`
- Capabilities []string `json:"capabilities"`
-}
-
-type ServiceEvent struct {
- Service string `json:"service"`
- Data any `json:"data"`
-}
-
-var networkManager *network.Manager
-var loginctlManager *loginctl.Manager
-var freedesktopManager *freedesktop.Manager
-var waylandManager *wayland.Manager
-var bluezManager *bluez.Manager
-var appPickerManager *apppicker.Manager
-var cupsManager *cups.Manager
-var dwlManager *dwl.Manager
-var extWorkspaceManager *extworkspace.Manager
-var brightnessManager *brightness.Manager
-var wlrOutputManager *wlroutput.Manager
-var evdevManager *evdev.Manager
-var clipboardManager *clipboard.Manager
-var dbusManager *serverDbus.Manager
-var wlContext *wlcontext.SharedContext
-var themeModeManager *thememode.Manager
-var trayRecoveryManager *trayrecovery.Manager
-var locationManager *location.Manager
-var geoClientInstance geolocation.Client
-
-const dbusClientID = "dms-dbus-client"
-
-var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
-var cupsSubscribers syncmap.Map[string, bool]
-var cupsSubscriberCount atomic.Int32
-var extWorkspaceAvailable atomic.Bool
-var extWorkspaceInitMutex sync.Mutex
-
-func getSocketDir() string {
- if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
- return runtime
- }
-
- if os.Getuid() == 0 {
- if _, err := os.Stat("/run"); err == nil {
- return "/run/dankdots"
- }
- return "/var/run/dankdots"
- }
-
- return os.TempDir()
-}
-
-func GetSocketPath() string {
- return filepath.Join(getSocketDir(), fmt.Sprintf("danklinux-%d.sock", os.Getpid()))
-}
-
-func FindSocket() (string, error) {
- dir := getSocketDir()
- entries, err := os.ReadDir(dir)
- if err != nil {
- return "", err
- }
-
- for _, entry := range entries {
- if strings.HasPrefix(entry.Name(), "danklinux-") && strings.HasSuffix(entry.Name(), ".sock") {
- return filepath.Join(dir, entry.Name()), nil
- }
- }
- return "", fmt.Errorf("no dms socket found")
-}
-
-func cleanupStaleSockets() {
- dir := getSocketDir()
- entries, err := os.ReadDir(dir)
- if err != nil {
- return
- }
-
- for _, entry := range entries {
- if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".sock") {
- continue
- }
-
- pidStr := strings.TrimPrefix(entry.Name(), "danklinux-")
- pidStr = strings.TrimSuffix(pidStr, ".sock")
- pid, err := strconv.Atoi(pidStr)
- if err != nil {
- continue
- }
-
- process, err := os.FindProcess(pid)
- if err != nil {
- socketPath := filepath.Join(dir, entry.Name())
- os.Remove(socketPath)
- log.Debugf("Removed stale socket: %s", socketPath)
- continue
- }
-
- err = process.Signal(syscall.Signal(0))
- if err != nil {
- socketPath := filepath.Join(dir, entry.Name())
- os.Remove(socketPath)
- log.Debugf("Removed stale socket: %s", socketPath)
- }
- }
-}
-
-func InitializeNetworkManager() error {
- manager, err := network.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize network manager: %v", err)
- return err
- }
-
- networkManager = manager
-
- log.Info("Network manager initialized")
- return nil
-}
-
-func InitializeLoginctlManager() error {
- manager, err := loginctl.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize loginctl manager: %v", err)
- return err
- }
-
- loginctlManager = manager
-
- log.Info("Loginctl manager initialized")
- return nil
-}
-
-func InitializeFreedeskManager() error {
- manager, err := freedesktop.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize freedesktop manager: %v", err)
- return err
- }
-
- freedesktopManager = manager
-
- log.Info("Freedesktop manager initialized")
- return nil
-}
-
-func InitializeWaylandManager() error {
- log.Info("Attempting to initialize Wayland gamma control...")
-
- if wlContext == nil {
- ctx, err := wlcontext.New()
- if err != nil {
- log.Errorf("Failed to create shared Wayland context: %v", err)
- return err
- }
- wlContext = ctx
- }
-
- config := wayland.DefaultConfig()
- manager, err := wayland.NewManager(wlContext.Display(), config)
- if err != nil {
- log.Errorf("Failed to initialize wayland manager: %v", err)
- return err
- }
-
- waylandManager = manager
-
- log.Info("Wayland gamma control initialized successfully")
- return nil
-}
-
-func InitializeBluezManager() error {
- manager, err := bluez.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize bluez manager: %v", err)
- return err
- }
-
- bluezManager = manager
-
- log.Info("Bluez manager initialized")
- return nil
-}
-
-func InitializeAppPickerManager() error {
- manager := apppicker.NewManager()
- appPickerManager = manager
- log.Info("AppPicker manager initialized")
- return nil
-}
-
-func InitializeCupsManager() error {
- manager, err := cups.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize cups manager: %v", err)
- return err
- }
-
- cupsManager = manager
-
- log.Info("CUPS manager initialized")
- return nil
-}
-
-func InitializeDwlManager() error {
- log.Info("Attempting to initialize DWL IPC...")
-
- if wlContext == nil {
- ctx, err := wlcontext.New()
- if err != nil {
- log.Errorf("Failed to create shared Wayland context: %v", err)
- return err
- }
- wlContext = ctx
- }
-
- manager, err := dwl.NewManager(wlContext.Display())
- if err != nil {
- log.Debug("Failed to initialize dwl manager: %v", err)
- return err
- }
-
- dwlManager = manager
-
- log.Info("DWL IPC initialized successfully")
- return nil
-}
-
-func InitializeBrightnessManager() error {
- manager, err := brightness.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize brightness manager: %v", err)
- return err
- }
-
- brightnessManager = manager
-
- log.Info("Brightness manager initialized")
- return nil
-}
-
-func InitializeExtWorkspaceManager() error {
- log.Info("Attempting to initialize ExtWorkspace...")
-
- if wlContext == nil {
- ctx, err := wlcontext.New()
- if err != nil {
- log.Errorf("Failed to create shared Wayland context: %v", err)
- return err
- }
- wlContext = ctx
- }
-
- manager, err := extworkspace.NewManager(wlContext.Display())
- if err != nil {
- log.Debug("Failed to initialize extworkspace manager: %v", err)
- return err
- }
-
- extWorkspaceManager = manager
-
- log.Info("ExtWorkspace initialized successfully")
- return nil
-}
-
-func InitializeWlrOutputManager() error {
- log.Info("Attempting to initialize WlrOutput management...")
-
- if wlContext == nil {
- ctx, err := wlcontext.New()
- if err != nil {
- log.Errorf("Failed to create shared Wayland context: %v", err)
- return err
- }
- wlContext = ctx
- }
-
- manager, err := wlroutput.NewManager(wlContext.Display())
- if err != nil {
- log.Debug("Failed to initialize wlroutput manager: %v", err)
- return err
- }
-
- wlrOutputManager = manager
-
- log.Info("WlrOutput management initialized successfully")
- 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 InitializeClipboardManager() error {
- log.Info("Attempting to initialize clipboard manager...")
-
- if wlContext == nil {
- ctx, err := wlcontext.New()
- if err != nil {
- log.Errorf("Failed to create shared Wayland context: %v", err)
- return err
- }
- wlContext = ctx
- }
-
- config := clipboard.LoadConfig()
- manager, err := clipboard.NewManager(wlContext, config)
- if err != nil {
- log.Errorf("Failed to initialize clipboard manager: %v", err)
- return err
- }
-
- clipboardManager = manager
-
- log.Info("Clipboard manager initialized successfully")
- return nil
-}
-
-func InitializeDbusManager() error {
- manager, err := serverDbus.NewManager()
- if err != nil {
- log.Warnf("Failed to initialize dbus manager: %v", err)
- return err
- }
-
- dbusManager = manager
-
- log.Info("DBus manager initialized")
- return nil
-}
-
-func InitializeThemeModeManager() error {
- manager := thememode.NewManager()
- themeModeManager = manager
-
- log.Info("Theme mode automation manager initialized")
- return nil
-}
-
-func InitializeTrayRecoveryManager() error {
- manager, err := trayrecovery.NewManager()
- if err != nil {
- return err
- }
-
- trayRecoveryManager = manager
-
- log.Info("TrayRecovery manager initialized")
- return nil
-}
-
-func InitializeLocationManager(geoClient geolocation.Client) error {
- manager, err := location.NewManager(geoClient)
- if err != nil {
- log.Warnf("Failed to initialize location manager: %v", err)
- return err
- }
-
- locationManager = manager
-
- log.Info("Location manager initialized")
- return nil
-}
-
-func handleConnection(conn net.Conn) {
- defer conn.Close()
-
- caps := getCapabilities()
- capsData, _ := json.Marshal(caps)
- conn.Write(capsData)
- conn.Write([]byte("\n"))
- scanner := bufio.NewScanner(conn)
- for scanner.Scan() {
- line := scanner.Bytes()
-
- var req models.Request
- if err := json.Unmarshal(line, &req); err != nil {
- log.Warnf("handleConnection: Failed to unmarshal JSON: %v, line: %s", err, string(line))
- models.RespondError(conn, 0, "invalid json")
- continue
- }
-
- go RouteRequest(conn, req)
- }
-}
-
-func getCapabilities() Capabilities {
- caps := []string{"plugins"}
-
- if networkManager != nil {
- caps = append(caps, "network")
- }
-
- if loginctlManager != nil {
- caps = append(caps, "loginctl")
- }
-
- if freedesktopManager != nil {
- caps = append(caps, "freedesktop")
- }
-
- if waylandManager != nil {
- caps = append(caps, "gamma")
- }
-
- if bluezManager != nil {
- caps = append(caps, "bluetooth")
- }
-
- if appPickerManager != nil {
- caps = append(caps, "browser")
- }
-
- if cupsManager != nil {
- caps = append(caps, "cups")
- }
-
- if dwlManager != nil {
- caps = append(caps, "dwl")
- }
-
- if extWorkspaceAvailable.Load() {
- caps = append(caps, "extworkspace")
- }
-
- if brightnessManager != nil {
- caps = append(caps, "brightness")
- }
-
- if wlrOutputManager != nil {
- caps = append(caps, "wlroutput")
- }
-
- if evdevManager != nil {
- caps = append(caps, "evdev")
- }
-
- if clipboardManager != nil {
- caps = append(caps, "clipboard")
- }
-
- if themeModeManager != nil {
- caps = append(caps, "theme.auto")
- }
-
- if dbusManager != nil {
- caps = append(caps, "dbus")
- }
-
- return Capabilities{Capabilities: caps}
-}
-
-func getServerInfo() ServerInfo {
- caps := []string{"plugins"}
-
- if networkManager != nil {
- caps = append(caps, "network")
- }
-
- if loginctlManager != nil {
- caps = append(caps, "loginctl")
- }
-
- if freedesktopManager != nil {
- caps = append(caps, "freedesktop")
- }
-
- if waylandManager != nil {
- caps = append(caps, "gamma")
- }
-
- if bluezManager != nil {
- caps = append(caps, "bluetooth")
- }
-
- if appPickerManager != nil {
- caps = append(caps, "browser")
- }
-
- if cupsManager != nil {
- caps = append(caps, "cups")
- }
-
- if dwlManager != nil {
- caps = append(caps, "dwl")
- }
-
- if extWorkspaceAvailable.Load() {
- caps = append(caps, "extworkspace")
- }
-
- if brightnessManager != nil {
- caps = append(caps, "brightness")
- }
-
- if wlrOutputManager != nil {
- caps = append(caps, "wlroutput")
- }
-
- if evdevManager != nil {
- caps = append(caps, "evdev")
- }
-
- if clipboardManager != nil {
- caps = append(caps, "clipboard")
- }
-
- if themeModeManager != nil {
- caps = append(caps, "theme.auto")
- }
-
- if locationManager != nil {
- caps = append(caps, "location")
- }
-
- if dbusManager != nil {
- caps = append(caps, "dbus")
- }
-
- return ServerInfo{
- APIVersion: APIVersion,
- CLIVersion: CLIVersion,
- Capabilities: caps,
- }
-}
-
-func notifyCapabilityChange() {
- info := getServerInfo()
- capabilitySubscribers.Range(func(key string, ch chan ServerInfo) bool {
- select {
- case ch <- info:
- default:
- }
- return true
- })
-}
-
-func handleSubscribe(conn net.Conn, req models.Request) {
- clientID := fmt.Sprintf("meta-client-%p", conn)
-
- var services []string
- if servicesParam, ok := models.Get[[]any](req, "services"); ok {
- for _, s := range servicesParam {
- if str, ok := s.(string); ok {
- services = append(services, str)
- }
- }
- }
-
- if len(services) == 0 {
- services = []string{"all"}
- }
-
- subscribeAll := false
- for _, s := range services {
- if s == "all" {
- subscribeAll = true
- break
- }
- }
-
- var wg sync.WaitGroup
- eventChan := make(chan ServiceEvent, 256)
- stopChan := make(chan struct{})
-
- capChan := make(chan ServerInfo, 64)
- capabilitySubscribers.Store(clientID+"-capabilities", capChan)
-
- wg.Add(1)
- go func() {
- defer wg.Done()
- defer capabilitySubscribers.Delete(clientID + "-capabilities")
-
- for {
- select {
- case info, ok := <-capChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "server", Data: info}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
-
- shouldSubscribe := func(service string) bool {
- if subscribeAll {
- return true
- }
- for _, s := range services {
- if s == service {
- return true
- }
- }
- return false
- }
-
- if shouldSubscribe("network") && networkManager != nil {
- wg.Add(1)
- netChan := networkManager.Subscribe(clientID + "-network")
- go func() {
- defer wg.Done()
- defer networkManager.Unsubscribe(clientID + "-network")
-
- initialState := networkManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "network", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-netChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "network", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("network.credentials") && networkManager != nil {
- wg.Add(1)
- credChan := networkManager.SubscribeCredentials(clientID + "-credentials")
- go func() {
- defer wg.Done()
- defer networkManager.UnsubscribeCredentials(clientID + "-credentials")
-
- for {
- select {
- case prompt, ok := <-credChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "network.credentials", Data: prompt}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("loginctl") && loginctlManager != nil {
- wg.Add(1)
- loginChan := loginctlManager.Subscribe(clientID + "-loginctl")
- go func() {
- defer wg.Done()
- defer loginctlManager.Unsubscribe(clientID + "-loginctl")
-
- initialState := loginctlManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "loginctl", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-loginChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "loginctl", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("freedesktop") && freedesktopManager != nil {
- wg.Add(1)
- freedesktopChan := freedesktopManager.Subscribe(clientID + "-freedesktop")
- go func() {
- defer wg.Done()
- defer freedesktopManager.Unsubscribe(clientID + "-freedesktop")
-
- initialState := freedesktopManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "freedesktop", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-freedesktopChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "freedesktop", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("freedesktop.screensaver") && freedesktopManager != nil {
- wg.Add(1)
- screensaverChan := freedesktopManager.SubscribeScreensaver(clientID + "-screensaver")
- go func() {
- defer wg.Done()
- defer freedesktopManager.UnsubscribeScreensaver(clientID + "-screensaver")
-
- initialState := freedesktopManager.GetScreensaverState()
- select {
- case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-screensaverChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("gamma") && waylandManager != nil {
- wg.Add(1)
- waylandChan := waylandManager.Subscribe(clientID + "-gamma")
- go func() {
- defer wg.Done()
- defer waylandManager.Unsubscribe(clientID + "-gamma")
-
- initialState := waylandManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "gamma", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-waylandChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "gamma", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("theme.auto") && themeModeManager != nil {
- wg.Add(1)
- themeAutoChan := themeModeManager.Subscribe(clientID + "-theme-auto")
- go func() {
- defer wg.Done()
- defer themeModeManager.Unsubscribe(clientID + "-theme-auto")
-
- initialState := themeModeManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "theme.auto", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-themeAutoChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "theme.auto", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("bluetooth") && bluezManager != nil {
- wg.Add(1)
- bluezChan := bluezManager.Subscribe(clientID + "-bluetooth")
- go func() {
- defer wg.Done()
- defer bluezManager.Unsubscribe(clientID + "-bluetooth")
-
- initialState := bluezManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "bluetooth", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-bluezChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "bluetooth", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("bluetooth.pairing") && bluezManager != nil {
- wg.Add(1)
- pairingChan := bluezManager.SubscribePairing(clientID + "-pairing")
- go func() {
- defer wg.Done()
- defer bluezManager.UnsubscribePairing(clientID + "-pairing")
-
- for {
- select {
- case prompt, ok := <-pairingChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "bluetooth.pairing", Data: prompt}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("browser") && appPickerManager != nil {
- wg.Add(1)
- appPickerChan := appPickerManager.Subscribe(clientID + "-browser")
- go func() {
- defer wg.Done()
- defer appPickerManager.Unsubscribe(clientID + "-browser")
-
- for {
- select {
- case event, ok := <-appPickerChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "browser.open_requested", Data: event}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("cups") {
- cupsSubscribers.Store(clientID+"-cups", true)
- count := cupsSubscriberCount.Add(1)
-
- if count == 1 {
- if err := InitializeCupsManager(); err != nil {
- log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }
-
- if cupsManager != nil {
- wg.Add(1)
- cupsChan := cupsManager.Subscribe(clientID + "-cups")
- go func() {
- defer wg.Done()
- defer func() {
- cupsManager.Unsubscribe(clientID + "-cups")
- cupsSubscribers.Delete(clientID + "-cups")
- count := cupsSubscriberCount.Add(-1)
-
- if count == 0 {
- log.Info("Last CUPS subscriber disconnected, shutting down CUPS manager")
- if cupsManager != nil {
- cupsManager.Close()
- cupsManager = nil
- notifyCapabilityChange()
- }
- }
- }()
-
- initialState := cupsManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-cupsChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "cups", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
- }
-
- if shouldSubscribe("dwl") && dwlManager != nil {
- wg.Add(1)
- dwlChan := dwlManager.Subscribe(clientID + "-dwl")
- go func() {
- defer wg.Done()
- defer dwlManager.Unsubscribe(clientID + "-dwl")
-
- initialState := dwlManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "dwl", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-dwlChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "dwl", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("extworkspace") {
- if extWorkspaceManager == nil && extWorkspaceAvailable.Load() {
- extWorkspaceInitMutex.Lock()
- if extWorkspaceManager == nil {
- if err := InitializeExtWorkspaceManager(); err != nil {
- log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err)
- }
- }
- extWorkspaceInitMutex.Unlock()
- }
-
- if extWorkspaceManager != nil {
- wg.Add(1)
- extWorkspaceChan := extWorkspaceManager.Subscribe(clientID + "-extworkspace")
- go func() {
- defer wg.Done()
- defer extWorkspaceManager.Unsubscribe(clientID + "-extworkspace")
-
- initialState := extWorkspaceManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "extworkspace", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-extWorkspaceChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "extworkspace", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
- }
-
- if shouldSubscribe("brightness") && brightnessManager != nil {
- wg.Add(2)
- brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state")
- brightnessUpdateChan := brightnessManager.SubscribeUpdates(clientID + "-brightness-updates")
-
- go func() {
- defer wg.Done()
- defer brightnessManager.Unsubscribe(clientID + "-brightness-state")
-
- initialState := brightnessManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "brightness", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-brightnessStateChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "brightness", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
-
- go func() {
- defer wg.Done()
- defer brightnessManager.UnsubscribeUpdates(clientID + "-brightness-updates")
-
- for {
- select {
- case update, ok := <-brightnessUpdateChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "brightness.update", Data: update}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("wlroutput") && wlrOutputManager != nil {
- wg.Add(1)
- wlrOutputChan := wlrOutputManager.Subscribe(clientID + "-wlroutput")
- go func() {
- defer wg.Done()
- defer wlrOutputManager.Unsubscribe(clientID + "-wlroutput")
-
- initialState := wlrOutputManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "wlroutput", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-wlrOutputChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "wlroutput", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- 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
- }
- }
- }()
- }
-
- if shouldSubscribe("clipboard") && clipboardManager != nil {
- wg.Add(1)
- clipboardChan := clipboardManager.Subscribe(clientID + "-clipboard")
- go func() {
- defer wg.Done()
- defer clipboardManager.Unsubscribe(clientID + "-clipboard")
-
- initialState := clipboardManager.GetState()
- select {
- case eventChan <- ServiceEvent{Service: "clipboard", Data: initialState}:
- case <-stopChan:
- return
- }
-
- for {
- select {
- case state, ok := <-clipboardChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "clipboard", Data: state}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- if shouldSubscribe("dbus") && dbusManager != nil {
- wg.Add(1)
- dbusChan := dbusManager.SubscribeSignals(dbusClientID)
- go func() {
- defer wg.Done()
- defer dbusManager.UnsubscribeSignals(dbusClientID)
-
- for {
- select {
- case event, ok := <-dbusChan:
- if !ok {
- return
- }
- select {
- case eventChan <- ServiceEvent{Service: "dbus", Data: event}:
- case <-stopChan:
- return
- }
- case <-stopChan:
- return
- }
- }
- }()
- }
-
- go func() {
- wg.Wait()
- close(eventChan)
- }()
-
- info := getServerInfo()
- if err := json.NewEncoder(conn).Encode(models.Response[ServiceEvent]{
- ID: req.ID,
- Result: &ServiceEvent{Service: "server", Data: info},
- }); err != nil {
- close(stopChan)
- return
- }
-
- for event := range eventChan {
- if err := json.NewEncoder(conn).Encode(models.Response[ServiceEvent]{
- ID: req.ID,
- Result: &event,
- }); err != nil {
- close(stopChan)
- return
- }
- }
-}
-
-func cleanupManagers() {
- if networkManager != nil {
- networkManager.Close()
- }
- if loginctlManager != nil {
- loginctlManager.Close()
- }
- if freedesktopManager != nil {
- freedesktopManager.Close()
- }
- if waylandManager != nil {
- waylandManager.Close()
- }
- if bluezManager != nil {
- bluezManager.Close()
- }
- if appPickerManager != nil {
- appPickerManager.Close()
- }
- if cupsManager != nil {
- cupsManager.Close()
- }
- if dwlManager != nil {
- dwlManager.Close()
- }
- if extWorkspaceManager != nil {
- extWorkspaceManager.Close()
- }
- if brightnessManager != nil {
- brightnessManager.Close()
- }
- if wlrOutputManager != nil {
- wlrOutputManager.Close()
- }
- if evdevManager != nil {
- evdevManager.Close()
- }
- if clipboardManager != nil {
- clipboardManager.Close()
- }
- if dbusManager != nil {
- dbusManager.Close()
- }
- if themeModeManager != nil {
- themeModeManager.Close()
- }
- if trayRecoveryManager != nil {
- trayRecoveryManager.Close()
- }
- if wlContext != nil {
- wlContext.Close()
- }
- if locationManager != nil {
- locationManager.Close()
- }
- if geoClientInstance != nil {
- geoClientInstance.Close()
- }
-}
-
-func Start(printDocs bool) error {
- cleanupStaleSockets()
-
- socketPath := GetSocketPath()
- os.Remove(socketPath)
-
- listener, err := net.Listen("unix", socketPath)
- if err != nil {
- return err
- }
- defer listener.Close()
- defer cleanupManagers()
-
- log.Infof("DMS API Server listening on: %s", socketPath)
- log.Infof("API Version: %d", APIVersion)
- log.Info("Protocol: JSON over Unix socket")
- log.Info("Request format: {\"id\": <any>, \"method\": \"...\", \"params\": {...}}")
- log.Info("Response format: {\"id\": <any>, \"result\": {...}} or {\"id\": <any>, \"error\": \"...\"}")
- log.Info("")
- if printDocs {
- log.Info("Available methods:")
- log.Info(" ping - Test connection")
- log.Info(" getServerInfo - Get server info (API version and capabilities)")
- log.Info(" subscribe - Subscribe to multiple services (params: services [default: all])")
- log.Info("Plugins:")
- log.Info(" plugins.list - List all plugins")
- log.Info(" plugins.listInstalled - List installed plugins")
- log.Info(" plugins.install - Install plugin (params: name)")
- log.Info(" plugins.uninstall - Uninstall plugin (params: name)")
- log.Info(" plugins.update - Update plugin (params: name)")
- log.Info(" plugins.search - Search plugins (params: query, category?, compositor?, capability?)")
- log.Info("Network:")
- log.Info(" network.getState - Get current network state")
- log.Info(" network.wifi.scan - Scan for WiFi networks (params: device?)")
- log.Info(" network.wifi.networks - Get WiFi network list")
- log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?, eapMethod?, phase2Auth?, caCertPath?, clientCertPath?, privateKeyPath?, useSystemCACerts?)")
- log.Info(" network.wifi.disconnect - Disconnect WiFi (params: device?)")
- log.Info(" network.wifi.forget - Forget network (params: ssid)")
- log.Info(" network.wifi.toggle - Toggle WiFi radio")
- log.Info(" network.wifi.enable - Enable WiFi")
- log.Info(" network.wifi.disable - Disable WiFi")
- log.Info(" network.wifi.setAutoconnect - Set network autoconnect (params: ssid, autoconnect)")
- log.Info(" network.ethernet.connect - Connect Ethernet")
- log.Info(" network.ethernet.connect.config - Connect Ethernet to a specific configuration")
- log.Info(" network.ethernet.disconnect - Disconnect Ethernet")
- log.Info(" network.vpn.profiles - List VPN profiles")
- log.Info(" network.vpn.active - List active VPN connections")
- log.Info(" network.vpn.connect - Connect VPN (params: uuidOrName|name|uuid, singleActive?)")
- log.Info(" network.vpn.disconnect - Disconnect VPN (params: uuidOrName|name|uuid)")
- log.Info(" network.vpn.disconnectAll - Disconnect all VPNs")
- log.Info(" network.vpn.clearCredentials - Clear saved VPN credentials (params: uuidOrName|name|uuid)")
- log.Info(" network.vpn.plugins - List available VPN plugins")
- log.Info(" network.vpn.import - Import VPN from file (params: file|path, name?)")
- log.Info(" network.vpn.getConfig - Get VPN configuration (params: uuid|name|uuidOrName)")
- log.Info(" network.vpn.updateConfig - Update VPN configuration (params: uuid, name?, autoconnect?, data?)")
- log.Info(" network.vpn.delete - Delete VPN connection (params: uuid|name|uuidOrName)")
- log.Info(" network.preference.set - Set preference (params: preference [auto|wifi|ethernet])")
- log.Info(" network.info - Get network info (params: ssid)")
- log.Info(" network.credentials.submit - Submit credentials for prompt (params: token, secrets, save?)")
- log.Info(" network.credentials.cancel - Cancel credential prompt (params: token)")
- log.Info(" network.subscribe - Subscribe to network state changes (streaming)")
- log.Info("Loginctl:")
- log.Info(" loginctl.getState - Get current session state")
- log.Info(" loginctl.lock - Lock session")
- log.Info(" loginctl.unlock - Unlock session")
- log.Info(" loginctl.activate - Activate session")
- log.Info(" loginctl.setIdleHint - Set idle hint (params: idle)")
- log.Info(" loginctl.setLockBeforeSuspend - Set lock before suspend (params: enabled)")
- log.Info(" loginctl.setSleepInhibitorEnabled - Enable/disable sleep inhibitor (params: enabled)")
- log.Info(" loginctl.lockerReady - Signal locker UI is ready (releases sleep inhibitor)")
- log.Info(" loginctl.terminate - Terminate session")
- log.Info(" loginctl.subscribe - Subscribe to session state changes (streaming)")
- log.Info("Freedesktop:")
- log.Info(" freedesktop.getState - Get accounts & settings state")
- log.Info(" freedesktop.accounts.setIconFile - Set profile icon (params: path)")
- log.Info(" freedesktop.accounts.setRealName - Set real name (params: name)")
- log.Info(" freedesktop.accounts.setEmail - Set email (params: email)")
- log.Info(" freedesktop.accounts.setLanguage - Set language (params: language)")
- log.Info(" freedesktop.accounts.setLocation - Set location (params: location)")
- log.Info(" freedesktop.accounts.getUserIconFile - Get user icon (params: username)")
- log.Info(" freedesktop.settings.getColorScheme - Get color scheme")
- log.Info(" freedesktop.settings.setIconTheme - Set icon theme (params: iconTheme)")
- log.Info("Wayland:")
- log.Info(" wayland.gamma.getState - Get current gamma control state")
- log.Info(" wayland.gamma.setTemperature - Set temperature range (params: low, high)")
- log.Info(" wayland.gamma.setLocation - Set location (params: latitude, longitude)")
- log.Info(" wayland.gamma.setManualTimes - Set manual times (params: sunrise, sunset)")
- log.Info(" wayland.gamma.setGamma - Set gamma value (params: gamma)")
- log.Info(" wayland.gamma.setEnabled - Enable/disable gamma control (params: enabled)")
- log.Info(" wayland.gamma.subscribe - Subscribe to gamma state changes (streaming)")
- log.Info("Theme automation:")
- log.Info(" theme.auto.getState - Get current theme automation state")
- log.Info(" theme.auto.setEnabled - Enable/disable theme automation (params: enabled)")
- log.Info(" theme.auto.setMode - Set automation mode (params: mode [time|location])")
- log.Info(" theme.auto.setSchedule - Set time schedule (params: startHour, startMinute, endHour, endMinute)")
- log.Info(" theme.auto.setLocation - Set location (params: latitude, longitude)")
- log.Info(" theme.auto.setUseIPLocation - Use IP location (params: use)")
- log.Info(" theme.auto.trigger - Trigger immediate re-evaluation")
- log.Info(" theme.auto.subscribe - Subscribe to theme automation state changes (streaming)")
- log.Info("Bluetooth:")
- log.Info(" bluetooth.getState - Get current bluetooth state")
- log.Info(" bluetooth.startDiscovery - Start device discovery")
- log.Info(" bluetooth.stopDiscovery - Stop device discovery")
- log.Info(" bluetooth.setPowered - Set adapter power state (params: powered)")
- log.Info(" bluetooth.pair - Pair with device (params: device)")
- log.Info(" bluetooth.connect - Connect to device (params: device)")
- log.Info(" bluetooth.disconnect - Disconnect from device (params: device)")
- log.Info(" bluetooth.remove - Remove/unpair device (params: device)")
- log.Info(" bluetooth.trust - Trust device (params: device)")
- log.Info(" bluetooth.untrust - Untrust device (params: device)")
- log.Info(" bluetooth.pairing.submit - Submit pairing response (params: token, secrets?, accept?)")
- log.Info(" bluetooth.pairing.cancel - Cancel pairing prompt (params: token)")
- log.Info(" bluetooth.subscribe - Subscribe to bluetooth state changes (streaming)")
- log.Info("CUPS:")
- log.Info(" cups.getPrinters - Get printers list")
- log.Info(" cups.getJobs - Get non-completed jobs list (params: printerName)")
- log.Info(" cups.pausePrinter - Pause printer (params: printerName)")
- log.Info(" cups.resumePrinter - Resume printer (params: printerName)")
- log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)")
- log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)")
- log.Info("DWL:")
- log.Info(" dwl.getState - Get current dwl state (tags, windows, layouts, keyboard)")
- log.Info(" dwl.setTags - Set active tags (params: output, tagmask, toggleTagset)")
- log.Info(" dwl.setClientTags - Set focused client tags (params: output, andTags, xorTags)")
- log.Info(" dwl.setLayout - Set layout (params: output, index)")
- log.Info(" dwl.subscribe - Subscribe to dwl state changes (streaming)")
- log.Info(" Output state includes:")
- log.Info(" - tags : Tag states (active, clients, focused)")
- log.Info(" - layoutSymbol : Current layout name")
- log.Info(" - title : Focused window title")
- log.Info(" - appId : Focused window app ID")
- log.Info(" - kbLayout : Current keyboard layout")
- log.Info(" - keymode : Current keybind mode")
- log.Info("ExtWorkspace:")
- log.Info(" extworkspace.getState - Get current workspace state (groups, workspaces)")
- log.Info(" extworkspace.activateWorkspace - Activate workspace (params: groupID, workspaceID)")
- log.Info(" extworkspace.deactivateWorkspace - Deactivate workspace (params: groupID, workspaceID)")
- log.Info(" extworkspace.removeWorkspace - Remove workspace (params: groupID, workspaceID)")
- log.Info(" extworkspace.createWorkspace - Create workspace (params: groupID, name)")
- log.Info(" extworkspace.subscribe - Subscribe to workspace state changes (streaming)")
- log.Info("Brightness:")
- log.Info(" brightness.getState - Get current brightness state for all devices")
- log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)")
- log.Info(" brightness.increment - Increment device brightness (params: device, step?)")
- log.Info(" brightness.decrement - Decrement device brightness (params: device, step?)")
- log.Info(" brightness.rescan - Rescan for brightness devices (e.g., after plugging in monitor)")
- log.Info(" brightness.subscribe - Subscribe to brightness state changes (streaming)")
- log.Info(" Subscription events:")
- log.Info(" - brightness : Full device list (on rescan, DDC discovery, device changes)")
- log.Info(" - brightness.update: Single device update (on brightness change for efficiency)")
- log.Info("WlrOutput:")
- log.Info(" wlroutput.getState - Get current output configuration state")
- log.Info(" wlroutput.applyConfiguration - Apply output configuration (params: heads)")
- log.Info(" wlroutput.testConfiguration - Test output configuration without applying (params: heads)")
- log.Info(" wlroutput.subscribe - Subscribe to output state changes (streaming)")
- log.Info(" Head configuration params:")
- log.Info(" - name : Output name (required)")
- log.Info(" - enabled : Enable/disable output (required)")
- log.Info(" - modeId : Mode ID from available modes (optional)")
- log.Info(" - customMode : Custom mode {width, height, refresh} (optional)")
- log.Info(" - position : Position {x, y} (optional)")
- log.Info(" - transform : Transform value (optional)")
- log.Info(" - scale : Scale value (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("Clipboard:")
- log.Info(" clipboard.getState - Get clipboard state (enabled, history, current)")
- log.Info(" clipboard.getHistory - Get clipboard history with previews")
- log.Info(" clipboard.getEntry - Get full entry by ID (params: id)")
- log.Info(" clipboard.deleteEntry - Delete entry by ID (params: id)")
- log.Info(" clipboard.clearHistory - Clear all clipboard history")
- log.Info(" clipboard.copy - Copy text to clipboard (params: text)")
- log.Info(" clipboard.paste - Get current clipboard text")
- log.Info(" clipboard.search - Search history (params: query?, mimeType?, isImage?, limit?, offset?, before?, after?)")
- log.Info(" clipboard.getConfig - Get clipboard configuration")
- log.Info(" clipboard.setConfig - Set configuration (params: maxHistory?, maxEntrySize?, autoClearDays?, clearAtStartup?)")
- log.Info(" clipboard.subscribe - Subscribe to clipboard state changes (streaming)")
- log.Info("Location:")
- log.Info(" location.getState - Get current location state")
- log.Info(" location.subscribe - Subscribe to location changes (streaming)")
- log.Info("")
- }
- log.Info("Initializing managers...")
- log.Info("")
-
- go func() {
- ticker := time.NewTicker(30 * time.Second)
- defer ticker.Stop()
-
- if err := InitializeNetworkManager(); err != nil {
- log.Warnf("Network manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- return
- }
-
- for range ticker.C {
- if networkManager != nil {
- return
- }
- if err := InitializeNetworkManager(); err == nil {
- log.Info("Network manager initialized")
- notifyCapabilityChange()
- return
- }
- }
- }()
-
- loginctlReady := make(chan struct{})
- freedesktopReady := make(chan struct{})
-
- go func() {
- defer close(loginctlReady)
- if err := InitializeLoginctlManager(); err != nil {
- log.Warnf("Loginctl manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }()
-
- go func() {
- defer close(freedesktopReady)
- if err := InitializeFreedeskManager(); err != nil {
- log.Warnf("Freedesktop manager unavailable: %v", err)
- } else if freedesktopManager != nil {
- freedesktopManager.NotifySubscribers()
- notifyCapabilityChange()
- }
- }()
-
- // Bridge loginctl lock state to the freedesktop/gnome screensaver
- // ActiveChanged signal so apps like Bitwarden can detect screen lock.
- go func() {
- <-loginctlReady
- <-freedesktopReady
-
- if loginctlManager == nil || freedesktopManager == nil {
- return
- }
-
- ch := loginctlManager.Subscribe("dms-lock-bridge")
- defer loginctlManager.Unsubscribe("dms-lock-bridge")
-
- initial := loginctlManager.GetState()
- lastLocked := initial.Locked
- freedesktopManager.SetScreenLockActive(lastLocked)
-
- for state := range ch {
- if state.Locked != lastLocked {
- lastLocked = state.Locked
- freedesktopManager.SetScreenLockActive(lastLocked)
- }
- }
- }()
-
- if err := InitializeWaylandManager(); err != nil {
- log.Warnf("Wayland manager unavailable: %v", err)
- }
-
- if err := InitializeThemeModeManager(); err != nil {
- log.Warnf("Theme mode manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- go func() {
- <-loginctlReady
- if loginctlManager == nil {
- return
- }
- themeModeManager.WatchLoginctl(loginctlManager)
- }()
- }
-
- go func() {
- <-loginctlReady
- if loginctlManager == nil {
- return
- }
- if err := InitializeTrayRecoveryManager(); err != nil {
- log.Warnf("TrayRecovery manager unavailable: %v", err)
- } else {
- trayRecoveryManager.WatchLoginctl(loginctlManager)
- }
- }()
-
- go func() {
- geoClient := geolocation.NewClient()
- geoClientInstance = geoClient
-
- if waylandManager != nil {
- waylandManager.SetGeoClient(geoClient)
- }
- if themeModeManager != nil {
- themeModeManager.SetGeoClient(geoClient)
- }
-
- if err := InitializeLocationManager(geoClient); err != nil {
- log.Warnf("Location manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }()
-
- go func() {
- if err := InitializeBluezManager(); err != nil {
- log.Warnf("Bluez manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }()
-
- if err := InitializeAppPickerManager(); err != nil {
- log.Debugf("AppPicker manager unavailable: %v", err)
- }
-
- if err := InitializeDwlManager(); err != nil {
- log.Debugf("DWL manager unavailable: %v", err)
- }
-
- if extworkspace.CheckCapability() {
- extWorkspaceAvailable.Store(true)
- log.Info("ExtWorkspace capability detected and will be available on subscription")
- } else {
- log.Debug("ExtWorkspace capability not available")
- extWorkspaceAvailable.Store(false)
- }
-
- if err := InitializeWlrOutputManager(); err != nil {
- log.Debugf("WlrOutput manager unavailable: %v", err)
- }
-
- fatalErrChan := make(chan error, 1)
- if wlrOutputManager != nil {
- go func() {
- err := <-wlrOutputManager.FatalError()
- fatalErrChan <- fmt.Errorf("WlrOutput fatal error: %w", err)
- }()
- }
- if wlContext != nil {
- go func() {
- err := <-wlContext.FatalError()
- fatalErrChan <- fmt.Errorf("wayland context fatal error: %w", err)
- }()
- }
-
- go func() {
- if err := InitializeBrightnessManager(); err != nil {
- log.Warnf("Brightness manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }()
-
- go func() {
- if err := InitializeEvdevManager(); err != nil {
- log.Debugf("Evdev manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }()
-
- go func() {
- if err := InitializeClipboardManager(); err != nil {
- log.Warnf("Clipboard manager unavailable: %v", err)
- }
- if wlContext != nil {
- wlContext.Start()
- log.Info("Wayland event dispatcher started")
- }
- }()
-
- go func() {
- if err := InitializeDbusManager(); err != nil {
- log.Warnf("DBus manager unavailable: %v", err)
- } else {
- notifyCapabilityChange()
- }
- }()
-
- log.Info("")
- log.Infof("Ready! Capabilities: %v", getCapabilities().Capabilities)
-
- listenerErrChan := make(chan error, 1)
-
- go func() {
- for {
- conn, err := listener.Accept()
- if err != nil {
- listenerErrChan <- err
- return
- }
- go handleConnection(conn)
- }
- }()
-
- select {
- case err := <-listenerErrChan:
- return err
- case err := <-fatalErrChan:
- return err
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server_test.go
deleted file mode 100644
index 404f624..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/server_test.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package server
-
-import (
- "encoding/json"
- "fmt"
- "net"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestGetSocketDir(t *testing.T) {
- tests := []struct {
- name string
- xdgRuntimeDir string
- uid int
- expectedSubstr string
- }{
- {
- name: "uses XDG_RUNTIME_DIR when set",
- xdgRuntimeDir: "/run/user/1000",
- expectedSubstr: "/run/user/1000",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.xdgRuntimeDir != "" {
- t.Setenv("XDG_RUNTIME_DIR", tt.xdgRuntimeDir)
- }
-
- result := getSocketDir()
- assert.Contains(t, result, tt.expectedSubstr)
- })
- }
-}
-
-func TestGetSocketPath(t *testing.T) {
- path := GetSocketPath()
- assert.Contains(t, path, "danklinux-")
- assert.Contains(t, path, ".sock")
- assert.Contains(t, path, fmt.Sprintf("%d", os.Getpid()))
-}
-
-func TestGetCapabilities(t *testing.T) {
- originalNetworkManager := networkManager
- defer func() { networkManager = originalNetworkManager }()
-
- t.Run("capabilities without network manager", func(t *testing.T) {
- networkManager = nil
- caps := getCapabilities()
- assert.Contains(t, caps.Capabilities, "plugins")
- assert.NotContains(t, caps.Capabilities, "network")
- })
-
- t.Run("capabilities with network manager", func(t *testing.T) {
- networkManager = &network.Manager{}
- caps := getCapabilities()
- assert.Contains(t, caps.Capabilities, "plugins")
- assert.Contains(t, caps.Capabilities, "network")
- })
-}
-
-type mockConn struct {
- net.Conn
- written []byte
-}
-
-func (m *mockConn) Write(b []byte) (n int, err error) {
- m.written = append(m.written, b...)
- return len(b), nil
-}
-
-func (m *mockConn) Close() error {
- return nil
-}
-
-func TestRespondError(t *testing.T) {
- conn := &mockConn{}
- models.RespondError(conn, 123, "test error")
-
- var resp models.Response[any]
- err := json.Unmarshal(conn.written, &resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Equal(t, "test error", resp.Error)
- assert.Nil(t, resp.Result)
-}
-
-func TestRespond(t *testing.T) {
- conn := &mockConn{}
- result := map[string]string{"foo": "bar"}
- models.Respond(conn, 123, result)
-
- var resp models.Response[map[string]string]
- err := json.Unmarshal(conn.written, &resp)
- require.NoError(t, err)
-
- assert.Equal(t, 123, resp.ID)
- assert.Empty(t, resp.Error)
- require.NotNil(t, resp.Result)
- assert.Equal(t, "bar", (*resp.Result)["foo"])
-}
-
-func TestRequest_JSON(t *testing.T) {
- jsonStr := `{"id":123,"method":"test.method","params":{"key":"value"}}`
- var req models.Request
- err := json.Unmarshal([]byte(jsonStr), &req)
- require.NoError(t, err)
-
- assert.Equal(t, 123, req.ID)
- assert.Equal(t, "test.method", req.Method)
- assert.Equal(t, "value", req.Params["key"])
-}
-
-func TestResponse_JSON(t *testing.T) {
- t.Run("success response", func(t *testing.T) {
- result := "success"
- resp := models.Response[string]{
- ID: 123,
- Result: &result,
- }
-
- data, err := json.Marshal(resp)
- require.NoError(t, err)
-
- var decoded models.Response[string]
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, 123, decoded.ID)
- assert.Equal(t, "success", *decoded.Result)
- assert.Empty(t, decoded.Error)
- })
-
- t.Run("error response", func(t *testing.T) {
- resp := models.Response[any]{
- ID: 123,
- Error: "test error",
- }
-
- data, err := json.Marshal(resp)
- require.NoError(t, err)
-
- var decoded models.Response[any]
- err = json.Unmarshal(data, &decoded)
- require.NoError(t, err)
-
- assert.Equal(t, 123, decoded.ID)
- assert.Equal(t, "test error", decoded.Error)
- assert.Nil(t, decoded.Result)
- })
-}
-
-func TestCleanupStaleSockets(t *testing.T) {
- tempDir := t.TempDir()
- t.Setenv("XDG_RUNTIME_DIR", tempDir)
-
- staleSocket := filepath.Join(tempDir, "danklinux-4194305.sock")
- err := os.WriteFile(staleSocket, []byte{}, 0o600)
- require.NoError(t, err)
-
- activeSocket := filepath.Join(tempDir, fmt.Sprintf("danklinux-%d.sock", os.Getpid()))
- err = os.WriteFile(activeSocket, []byte{}, 0o600)
- require.NoError(t, err)
-
- cleanupStaleSockets()
-
- _, err = os.Stat(staleSocket)
- assert.True(t, os.IsNotExist(err))
-
- _, err = os.Stat(activeSocket)
- assert.NoError(t, err)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/handlers.go
deleted file mode 100644
index 6891875..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/handlers.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package thememode
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- if manager == nil {
- models.RespondError(conn, req.ID, "theme mode manager not initialized")
- return
- }
-
- switch req.Method {
- case "theme.auto.getState":
- handleGetState(conn, req, manager)
- case "theme.auto.setEnabled":
- handleSetEnabled(conn, req, manager)
- case "theme.auto.setMode":
- handleSetMode(conn, req, manager)
- case "theme.auto.setSchedule":
- handleSetSchedule(conn, req, manager)
- case "theme.auto.setLocation":
- handleSetLocation(conn, req, manager)
- case "theme.auto.setUseIPLocation":
- handleSetUseIPLocation(conn, req, manager)
- case "theme.auto.trigger":
- handleTrigger(conn, req, manager)
- case "theme.auto.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) {
- enabled, err := params.Bool(req.Params, "enabled")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetEnabled(enabled)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto enabled set"})
-}
-
-func handleSetMode(conn net.Conn, req models.Request, manager *Manager) {
- mode, err := params.String(req.Params, "mode")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if mode != "time" && mode != "location" {
- models.RespondError(conn, req.ID, "invalid mode")
- return
- }
-
- manager.SetMode(mode)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto mode set"})
-}
-
-func handleSetSchedule(conn net.Conn, req models.Request, manager *Manager) {
- startHour, err := params.Int(req.Params, "startHour")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- startMinute, err := params.Int(req.Params, "startMinute")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- endHour, err := params.Int(req.Params, "endHour")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- endMinute, err := params.Int(req.Params, "endMinute")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.ValidateSchedule(startHour, startMinute, endHour, endMinute); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetSchedule(startHour, startMinute, endHour, endMinute)
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
- lat, err := params.Float(req.Params, "latitude")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
- lon, err := params.Float(req.Params, "longitude")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetLocation(lat, lon)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto location set"})
-}
-
-func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) {
- use, err := params.Bool(req.Params, "use")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetUseIPLocation(use)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto IP location set"})
-}
-
-func handleTrigger(conn net.Conn, req models.Request, manager *Manager) {
- manager.TriggerUpdate()
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto update triggered"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/manager.go
deleted file mode 100644
index f7644ad..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/manager.go
+++ /dev/null
@@ -1,462 +0,0 @@
-package thememode
-
-import (
- "errors"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-const (
- defaultStartHour = 18
- defaultStartMinute = 0
- defaultEndHour = 6
- defaultEndMinute = 0
- defaultElevationTwilight = -6.0
- defaultElevationDaylight = 3.0
-)
-
-type Manager struct {
- config Config
- configMutex sync.RWMutex
-
- state *State
- stateMutex sync.RWMutex
-
- subscribers syncmap.Map[string, chan State]
-
- locationMutex sync.RWMutex
- cachedIPLat *float64
- cachedIPLon *float64
-
- geoClient geolocation.Client
-
- stopChan chan struct{}
- updateTrigger chan struct{}
- wg sync.WaitGroup
-}
-
-func NewManager() *Manager {
- m := &Manager{
- config: Config{
- Enabled: false,
- Mode: "time",
- StartHour: defaultStartHour,
- StartMinute: defaultStartMinute,
- EndHour: defaultEndHour,
- EndMinute: defaultEndMinute,
- ElevationTwilight: defaultElevationTwilight,
- ElevationDaylight: defaultElevationDaylight,
- },
- stopChan: make(chan struct{}),
- updateTrigger: make(chan struct{}, 1),
- }
-
- m.updateState(time.Now())
-
- m.wg.Add(1)
- go m.schedulerLoop()
-
- return m
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{Config: m.getConfig()}
- }
- stateCopy := *m.state
- return stateCopy
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) SetEnabled(enabled bool) {
- m.configMutex.Lock()
- if m.config.Enabled == enabled {
- m.configMutex.Unlock()
- return
- }
- m.config.Enabled = enabled
- m.configMutex.Unlock()
- m.TriggerUpdate()
-}
-
-func (m *Manager) SetMode(mode string) {
- m.configMutex.Lock()
- if m.config.Mode == mode {
- m.configMutex.Unlock()
- return
- }
- m.config.Mode = mode
- m.configMutex.Unlock()
- m.TriggerUpdate()
-}
-
-func (m *Manager) SetSchedule(startHour, startMinute, endHour, endMinute int) {
- m.configMutex.Lock()
- changed := m.config.StartHour != startHour ||
- m.config.StartMinute != startMinute ||
- m.config.EndHour != endHour ||
- m.config.EndMinute != endMinute
- if !changed {
- m.configMutex.Unlock()
- return
- }
- m.config.StartHour = startHour
- m.config.StartMinute = startMinute
- m.config.EndHour = endHour
- m.config.EndMinute = endMinute
- m.configMutex.Unlock()
- m.TriggerUpdate()
-}
-
-func (m *Manager) SetLocation(lat, lon float64) {
- m.configMutex.Lock()
- if m.config.Latitude != nil && m.config.Longitude != nil &&
- *m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
- m.configMutex.Unlock()
- return
- }
- m.config.Latitude = &lat
- m.config.Longitude = &lon
- m.config.UseIPLocation = false
- m.configMutex.Unlock()
-
- m.locationMutex.Lock()
- m.cachedIPLat = nil
- m.cachedIPLon = nil
- m.locationMutex.Unlock()
-
- m.TriggerUpdate()
-}
-
-func (m *Manager) SetUseIPLocation(use bool) {
- m.configMutex.Lock()
- if m.config.UseIPLocation == use {
- m.configMutex.Unlock()
- return
- }
- m.config.UseIPLocation = use
- if use {
- m.config.Latitude = nil
- m.config.Longitude = nil
- }
- m.configMutex.Unlock()
-
- if use {
- m.locationMutex.Lock()
- m.cachedIPLat = nil
- m.cachedIPLon = nil
- m.locationMutex.Unlock()
- }
-
- m.TriggerUpdate()
-}
-
-func (m *Manager) TriggerUpdate() {
- select {
- case m.updateTrigger <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) Close() {
- select {
- case <-m.stopChan:
- return
- default:
- close(m.stopChan)
- }
- m.wg.Wait()
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-}
-
-func (m *Manager) WatchLoginctl(lm *loginctl.Manager) {
- ch := lm.Subscribe("thememode")
- m.wg.Add(1)
- go func() {
- defer m.wg.Done()
- defer lm.Unsubscribe("thememode")
- for {
- select {
- case <-m.stopChan:
- return
- case state, ok := <-ch:
- if !ok {
- return
- }
- if state.PreparingForSleep {
- continue
- }
- m.TriggerUpdate()
- }
- }
- }()
-}
-
-func (m *Manager) schedulerLoop() {
- defer m.wg.Done()
-
- var timer *time.Timer
- for {
- config := m.getConfig()
- now := time.Now()
- var isLight bool
- var next time.Time
- if config.Enabled {
- isLight, next = m.computeSchedule(now, config)
- } else {
- m.stateMutex.RLock()
- if m.state != nil {
- isLight = m.state.IsLight
- }
- m.stateMutex.RUnlock()
- next = now.Add(24 * time.Hour)
- }
-
- m.updateStateWithValues(config, isLight, next)
-
- waitDur := time.Until(next)
- if !config.Enabled {
- waitDur = 24 * time.Hour
- }
- if waitDur < time.Second {
- waitDur = time.Second
- }
-
- if timer != nil {
- timer.Stop()
- }
- timer = time.NewTimer(waitDur)
-
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.updateTrigger:
- timer.Stop()
- continue
- case <-timer.C:
- continue
- }
- }
-}
-
-func (m *Manager) updateState(now time.Time) {
- config := m.getConfig()
- var isLight bool
- var next time.Time
- if config.Enabled {
- isLight, next = m.computeSchedule(now, config)
- } else {
- m.stateMutex.RLock()
- if m.state != nil {
- isLight = m.state.IsLight
- }
- m.stateMutex.RUnlock()
- next = now.Add(24 * time.Hour)
- }
- m.updateStateWithValues(config, isLight, next)
-}
-
-func (m *Manager) updateStateWithValues(config Config, isLight bool, next time.Time) {
- newState := State{
- Config: config,
- IsLight: isLight,
- NextTransition: next,
- }
-
- m.stateMutex.Lock()
- if m.state != nil && statesEqual(m.state, &newState) {
- m.stateMutex.Unlock()
- return
- }
- m.state = &newState
- m.stateMutex.Unlock()
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifySubscribers() {
- state := m.GetState()
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- state:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) getConfig() Config {
- m.configMutex.RLock()
- defer m.configMutex.RUnlock()
- return m.config
-}
-
-func (m *Manager) SetGeoClient(client geolocation.Client) {
- m.geoClient = client
-}
-
-func (m *Manager) getLocation(config Config) (*float64, *float64) {
- if config.Latitude != nil && config.Longitude != nil {
- return config.Latitude, config.Longitude
- }
- if !config.UseIPLocation {
- return nil, nil
- }
- if m.geoClient == nil {
- return nil, nil
- }
-
- m.locationMutex.RLock()
- if m.cachedIPLat != nil && m.cachedIPLon != nil {
- lat, lon := m.cachedIPLat, m.cachedIPLon
- m.locationMutex.RUnlock()
- return lat, lon
- }
- m.locationMutex.RUnlock()
-
- location, err := m.geoClient.GetLocation()
- if err != nil {
- return nil, nil
- }
-
- m.locationMutex.Lock()
- m.cachedIPLat = &location.Latitude
- m.cachedIPLon = &location.Longitude
- m.locationMutex.Unlock()
-
- return m.cachedIPLat, m.cachedIPLon
-}
-
-func statesEqual(a, b *State) bool {
- if a == nil || b == nil {
- return a == b
- }
- if a.IsLight != b.IsLight || !a.NextTransition.Equal(b.NextTransition) {
- return false
- }
- return a.Config == b.Config
-}
-
-func (m *Manager) computeSchedule(now time.Time, config Config) (bool, time.Time) {
- switch config.Mode {
- case "location":
- return m.computeLocationSchedule(now, config)
- default:
- return computeTimeSchedule(now, config)
- }
-}
-
-func computeTimeSchedule(now time.Time, config Config) (bool, time.Time) {
- startMinutes := config.StartHour*60 + config.StartMinute
- endMinutes := config.EndHour*60 + config.EndMinute
- currentMinutes := now.Hour()*60 + now.Minute()
-
- startTime := time.Date(now.Year(), now.Month(), now.Day(), config.StartHour, config.StartMinute, 0, 0, now.Location())
- endTime := time.Date(now.Year(), now.Month(), now.Day(), config.EndHour, config.EndMinute, 0, 0, now.Location())
-
- if startMinutes == endMinutes {
- next := startTime
- if !next.After(now) {
- next = next.Add(24 * time.Hour)
- }
- return true, next
- }
-
- if startMinutes < endMinutes {
- if currentMinutes < startMinutes {
- return true, startTime
- }
- if currentMinutes >= endMinutes {
- return true, startTime.Add(24 * time.Hour)
- }
- return false, endTime
- }
-
- if currentMinutes >= startMinutes {
- return false, endTime.Add(24 * time.Hour)
- }
- if currentMinutes < endMinutes {
- return false, endTime
- }
- return true, startTime
-}
-
-func (m *Manager) computeLocationSchedule(now time.Time, config Config) (bool, time.Time) {
- lat, lon := m.getLocation(config)
- if lat == nil || lon == nil {
- currentIsLight := false
- m.stateMutex.RLock()
- if m.state != nil {
- currentIsLight = m.state.IsLight
- }
- m.stateMutex.RUnlock()
- return currentIsLight, now.Add(10 * time.Minute)
- }
-
- times, cond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
- switch cond {
- case wayland.SunMidnightSun:
- return true, startOfNextDay(now)
- case wayland.SunPolarNight:
- return false, startOfNextDay(now)
- }
-
- if now.Before(times.Sunrise) {
- return false, times.Sunrise
- }
- if now.Before(times.Sunset) {
- return true, times.Sunset
- }
-
- nextDay := startOfNextDay(now)
- nextTimes, nextCond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, nextDay, config.ElevationTwilight, config.ElevationDaylight)
- switch nextCond {
- case wayland.SunMidnightSun:
- return true, startOfNextDay(nextDay)
- case wayland.SunPolarNight:
- return false, startOfNextDay(nextDay)
- }
-
- return false, nextTimes.Sunrise
-}
-
-func startOfNextDay(t time.Time) time.Time {
- next := t.Add(24 * time.Hour)
- return time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
-}
-
-func validateHourMinute(hour, minute int) bool {
- return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59
-}
-
-func (m *Manager) ValidateSchedule(startHour, startMinute, endHour, endMinute int) error {
- if !validateHourMinute(startHour, startMinute) || !validateHourMinute(endHour, endMinute) {
- return errInvalidTime
- }
- return nil
-}
-
-var errInvalidTime = errors.New("invalid schedule time")
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/types.go
deleted file mode 100644
index 72e6468..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/thememode/types.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package thememode
-
-import "time"
-
-type Config struct {
- Enabled bool `json:"enabled"`
- Mode string `json:"mode"`
- StartHour int `json:"startHour"`
- StartMinute int `json:"startMinute"`
- EndHour int `json:"endHour"`
- EndMinute int `json:"endMinute"`
- Latitude *float64 `json:"latitude,omitempty"`
- Longitude *float64 `json:"longitude,omitempty"`
- UseIPLocation bool `json:"useIPLocation"`
- ElevationTwilight float64 `json:"elevationTwilight"`
- ElevationDaylight float64 `json:"elevationDaylight"`
-}
-
-type State struct {
- Config Config `json:"config"`
- IsLight bool `json:"isLight"`
- NextTransition time.Time `json:"nextTransition"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/handlers.go
deleted file mode 100644
index b3b308a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/handlers.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func HandleRequest(conn net.Conn, req models.Request) {
- switch req.Method {
- case "themes.list":
- HandleList(conn, req)
- case "themes.listInstalled":
- HandleListInstalled(conn, req)
- case "themes.install":
- HandleInstall(conn, req)
- case "themes.uninstall":
- HandleUninstall(conn, req)
- case "themes.update":
- HandleUpdate(conn, req)
- case "themes.search":
- HandleSearch(conn, req)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/install.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/install.go
deleted file mode 100644
index 26d22ba..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/install.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
-)
-
-func HandleInstall(conn net.Conn, req models.Request) {
- idOrName, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- registry, err := themes.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- themeList, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
- return
- }
-
- theme := themes.FindByIDOrName(idOrName, themeList)
- if theme == nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("theme not found: %s", idOrName))
- return
- }
-
- manager, err := themes.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- registryThemeDir := registry.GetThemeDir(theme.SourceDir)
- if err := manager.Install(*theme, registryThemeDir); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to install theme: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{
- Success: true,
- Message: fmt.Sprintf("theme installed: %s", theme.Name),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list.go
deleted file mode 100644
index 9564be1..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
-)
-
-func HandleList(conn net.Conn, req models.Request) {
- registry, err := themes.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- themeList, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
- return
- }
-
- manager, err := themes.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- result := make([]ThemeInfo, len(themeList))
- for i, t := range themeList {
- installed, _ := manager.IsInstalled(t)
- info := ThemeInfo{
- ID: t.ID,
- Name: t.Name,
- Version: t.Version,
- Author: t.Author,
- Description: t.Description,
- PreviewPath: t.PreviewPath,
- SourceDir: t.SourceDir,
- Installed: installed,
- FirstParty: isFirstParty(t.Author),
- }
- addVariantsInfo(&info, t.Variants)
- result[i] = info
- }
-
- models.Respond(conn, req.ID, result)
-}
-
-func isFirstParty(author string) bool {
- return strings.EqualFold(author, "Avenge Media") || strings.EqualFold(author, "AvengeMedia")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list_installed.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list_installed.go
deleted file mode 100644
index 49be81b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/list_installed.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
-)
-
-func addVariantsInfo(info *ThemeInfo, variants *themes.ThemeVariants) {
- if variants == nil {
- return
- }
-
- if variants.Type == "multi" {
- if len(variants.Flavors) == 0 && len(variants.Accents) == 0 {
- return
- }
- info.HasVariants = true
- info.Variants = &VariantsInfo{
- Type: "multi",
- Flavors: make([]FlavorInfo, len(variants.Flavors)),
- Accents: make([]AccentInfo, len(variants.Accents)),
- }
- if variants.Defaults != nil {
- info.Variants.Defaults = &MultiDefaults{
- Dark: variants.Defaults.Dark,
- Light: variants.Defaults.Light,
- }
- }
- for i, f := range variants.Flavors {
- mode := ""
- switch {
- case f.Dark.Primary != "" && f.Light.Primary != "":
- mode = "both"
- case f.Dark.Primary != "":
- mode = "dark"
- case f.Light.Primary != "":
- mode = "light"
- default:
- if f.Dark.Surface != "" {
- mode = "dark"
- } else if f.Light.Surface != "" {
- mode = "light"
- }
- }
- info.Variants.Flavors[i] = FlavorInfo{ID: f.ID, Name: f.Name, Mode: mode}
- }
- for i, a := range variants.Accents {
- color := ""
- if colors, ok := a.FlavorColors["mocha"]; ok && colors.Primary != "" {
- color = colors.Primary
- } else if colors, ok := a.FlavorColors["latte"]; ok && colors.Primary != "" {
- color = colors.Primary
- } else {
- for _, c := range a.FlavorColors {
- if c.Primary != "" {
- color = c.Primary
- break
- }
- }
- }
- info.Variants.Accents[i] = AccentInfo{ID: a.ID, Name: a.Name, Color: color}
- }
- return
- }
-
- if len(variants.Options) == 0 {
- return
- }
- info.HasVariants = true
- info.Variants = &VariantsInfo{
- Default: variants.Default,
- Options: make([]VariantInfo, len(variants.Options)),
- }
- for i, v := range variants.Options {
- info.Variants.Options[i] = VariantInfo{ID: v.ID, Name: v.Name}
- }
-}
-
-func HandleListInstalled(conn net.Conn, req models.Request) {
- manager, err := themes.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- installedIDs, err := manager.ListInstalled()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list installed themes: %v", err))
- return
- }
-
- themeMap := make(map[string]themes.Theme)
- if registry, err := themes.NewRegistry(); err == nil {
- if allThemes, err := registry.List(); err == nil {
- for _, t := range allThemes {
- themeMap[t.ID] = t
- }
- }
- }
-
- result := make([]ThemeInfo, 0, len(installedIDs))
- for _, id := range installedIDs {
- if theme, ok := themeMap[id]; ok {
- hasUpdate := false
- if hasUpdates, err := manager.HasUpdates(id, theme); err == nil {
- hasUpdate = hasUpdates
- }
-
- info := ThemeInfo{
- ID: theme.ID,
- Name: theme.Name,
- Version: theme.Version,
- Author: theme.Author,
- Description: theme.Description,
- SourceDir: id,
- FirstParty: isFirstParty(theme.Author),
- HasUpdate: hasUpdate,
- }
- addVariantsInfo(&info, theme.Variants)
- result = append(result, info)
- } else {
- installed, err := manager.GetInstalledTheme(id)
- if err != nil {
- result = append(result, ThemeInfo{
- ID: id,
- Name: id,
- SourceDir: id,
- })
- continue
- }
- info := ThemeInfo{
- ID: installed.ID,
- Name: installed.Name,
- Version: installed.Version,
- Author: installed.Author,
- Description: installed.Description,
- SourceDir: id,
- FirstParty: isFirstParty(installed.Author),
- }
- addVariantsInfo(&info, installed.Variants)
- result = append(result, info)
- }
- }
-
- models.Respond(conn, req.ID, result)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/search.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/search.go
deleted file mode 100644
index 2823fe0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/search.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
-)
-
-func HandleSearch(conn net.Conn, req models.Request) {
- query, ok := models.Get[string](req, "query")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'query' parameter")
- return
- }
-
- registry, err := themes.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- themeList, err := registry.List()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to list themes: %v", err))
- return
- }
-
- searchResults := themes.FuzzySearch(query, themeList)
-
- manager, err := themes.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- result := make([]ThemeInfo, len(searchResults))
- for i, t := range searchResults {
- installed, _ := manager.IsInstalled(t)
- result[i] = ThemeInfo{
- ID: t.ID,
- Name: t.Name,
- Version: t.Version,
- Author: t.Author,
- Description: t.Description,
- Installed: installed,
- FirstParty: isFirstParty(t.Author),
- }
- }
-
- models.Respond(conn, req.ID, result)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/types.go
deleted file mode 100644
index 0ad19b4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/types.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package themes
-
-type VariantInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-type FlavorInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Mode string `json:"mode,omitempty"`
-}
-
-type AccentInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Color string `json:"color,omitempty"`
-}
-
-type MultiDefaults struct {
- Dark map[string]string `json:"dark,omitempty"`
- Light map[string]string `json:"light,omitempty"`
-}
-
-type VariantsInfo struct {
- Type string `json:"type,omitempty"`
- Default string `json:"default,omitempty"`
- Defaults *MultiDefaults `json:"defaults,omitempty"`
- Options []VariantInfo `json:"options,omitempty"`
- Flavors []FlavorInfo `json:"flavors,omitempty"`
- Accents []AccentInfo `json:"accents,omitempty"`
-}
-
-type ThemeInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Version string `json:"version"`
- Author string `json:"author,omitempty"`
- Description string `json:"description,omitempty"`
- PreviewPath string `json:"previewPath,omitempty"`
- SourceDir string `json:"sourceDir,omitempty"`
- Installed bool `json:"installed,omitempty"`
- FirstParty bool `json:"firstParty,omitempty"`
- HasUpdate bool `json:"hasUpdate,omitempty"`
- HasVariants bool `json:"hasVariants,omitempty"`
- Variants *VariantsInfo `json:"variants,omitempty"`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/uninstall.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/uninstall.go
deleted file mode 100644
index 9b453e4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/uninstall.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
-)
-
-func HandleUninstall(conn net.Conn, req models.Request) {
- idOrName, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- manager, err := themes.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- registry, err := themes.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- themeList, _ := registry.List()
- theme := themes.FindByIDOrName(idOrName, themeList)
-
- if theme != nil {
- installed, err := manager.IsInstalled(*theme)
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if theme is installed: %v", err))
- return
- }
- if !installed {
- models.RespondError(conn, req.ID, fmt.Sprintf("theme not installed: %s", idOrName))
- return
- }
- if err := manager.Uninstall(*theme); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall theme: %v", err))
- return
- }
- models.Respond(conn, req.ID, models.SuccessResult{
- Success: true,
- Message: fmt.Sprintf("theme uninstalled: %s", theme.Name),
- })
- return
- }
-
- if err := manager.UninstallByID(idOrName); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("theme not found: %s", idOrName))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{
- Success: true,
- Message: fmt.Sprintf("theme uninstalled: %s", idOrName),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/update.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/update.go
deleted file mode 100644
index fb9f81f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/themes/update.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package themes
-
-import (
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
-)
-
-func HandleUpdate(conn net.Conn, req models.Request) {
- idOrName, ok := models.Get[string](req, "name")
- if !ok {
- models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
- return
- }
-
- manager, err := themes.NewManager()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
- return
- }
-
- registry, err := themes.NewRegistry()
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
- return
- }
-
- themeList, _ := registry.List()
- theme := themes.FindByIDOrName(idOrName, themeList)
-
- if theme == nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("theme not found in registry: %s", idOrName))
- return
- }
-
- installed, err := manager.IsInstalled(*theme)
- if err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if theme is installed: %v", err))
- return
- }
- if !installed {
- models.RespondError(conn, req.ID, fmt.Sprintf("theme not installed: %s", idOrName))
- return
- }
-
- if err := manager.Update(*theme); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("failed to update theme: %v", err))
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{
- Success: true,
- Message: fmt.Sprintf("theme updated: %s", theme.Name),
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/manager.go
deleted file mode 100644
index 3610f1b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/manager.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package trayrecovery
-
-import (
- "fmt"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
- "github.com/godbus/dbus/v5"
-)
-
-const resumeDelay = 3 * time.Second
-
-type Manager struct {
- conn *dbus.Conn
- stopChan chan struct{}
- wg sync.WaitGroup
-}
-
-func NewManager() (*Manager, error) {
- conn, err := dbus.ConnectSessionBus()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to session bus: %w", err)
- }
-
- m := &Manager{
- conn: conn,
- stopChan: make(chan struct{}),
- }
-
- // Run a startup scan after a delay — covers the case where the process
- // was killed during suspend and restarted by systemd (Type=dbus).
- // The fresh process never sees the PrepareForSleep true→false transition,
- // so the loginctl watcher alone is not enough.
- go m.scheduleRecovery()
-
- return m, nil
-}
-
-// WatchLoginctl subscribes to loginctl session state changes and triggers
-// tray recovery after resume from suspend (PrepareForSleep false transition).
-// This handles the case where the process survives suspend.
-func (m *Manager) WatchLoginctl(lm *loginctl.Manager) {
- ch := lm.Subscribe("tray-recovery")
- m.wg.Add(1)
- go func() {
- defer m.wg.Done()
- defer lm.Unsubscribe("tray-recovery")
-
- wasSleeping := false
- for {
- select {
- case <-m.stopChan:
- return
- case state, ok := <-ch:
- if !ok {
- return
- }
- if state.PreparingForSleep {
- wasSleeping = true
- continue
- }
- if wasSleeping {
- wasSleeping = false
- go m.scheduleRecovery()
- }
- }
- }
- }()
-}
-
-func (m *Manager) scheduleRecovery() {
- select {
- case <-time.After(resumeDelay):
- m.recoverTrayItems()
- case <-m.stopChan:
- }
-}
-
-func (m *Manager) Close() {
- select {
- case <-m.stopChan:
- return
- default:
- close(m.stopChan)
- }
- m.wg.Wait()
- if m.conn != nil {
- m.conn.Close()
- }
- log.Info("TrayRecovery manager closed")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/recovery.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/recovery.go
deleted file mode 100644
index c7b98fb..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/trayrecovery/recovery.go
+++ /dev/null
@@ -1,262 +0,0 @@
-package trayrecovery
-
-import (
- "context"
- "strings"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-const (
- sniWatcherDest = "org.kde.StatusNotifierWatcher"
- sniWatcherPath = "/StatusNotifierWatcher"
- sniWatcherIface = "org.kde.StatusNotifierWatcher"
- sniItemIface = "org.kde.StatusNotifierItem"
- dbusIface = "org.freedesktop.DBus"
- propsIface = "org.freedesktop.DBus.Properties"
- probeTimeout = 300 * time.Millisecond
- connProbeTimeout = 150 * time.Millisecond
- batchSize = 30
-)
-
-var excludedPrefixes = []string{
- "org.freedesktop.",
- "org.gnome.",
- "org.kde.StatusNotifier",
- "com.canonical.AppMenu",
- "org.mpris.",
- "org.pipewire.",
- "org.pulseaudio",
- "fi.epitaph",
- "quickshell",
- "org.kde.quickshell",
-}
-
-func (m *Manager) recoverTrayItems() {
- registeredItems := m.getRegisteredItems()
- allNames := m.getDBusNames()
- if allNames == nil {
- return
- }
-
- registeredConnIDs := m.buildRegisteredConnIDs(registeredItems)
-
- count := len(registeredItems)
- log.Infof("TrayRecoveryService: scanning DBus for unregistered SNI items (%d already registered)...", count)
-
- m.scanWellKnownNames(allNames, registeredItems, registeredConnIDs)
- m.scanConnectionIDs(allNames, registeredItems, registeredConnIDs)
-}
-
-func (m *Manager) getRegisteredItems() []string {
- obj := m.conn.Object(sniWatcherDest, sniWatcherPath)
- variant, err := obj.GetProperty(sniWatcherIface + ".RegisteredStatusNotifierItems")
- if err != nil {
- log.Warnf("TrayRecoveryService: failed to get registered items: %v", err)
- return nil
- }
-
- switch v := variant.Value().(type) {
- case []string:
- return v
- case []any:
- items := make([]string, 0, len(v))
- for _, elem := range v {
- if s, ok := elem.(string); ok {
- items = append(items, s)
- }
- }
- return items
- }
- return nil
-}
-
-func (m *Manager) getDBusNames() []string {
- var names []string
- err := m.conn.BusObject().Call(dbusIface+".ListNames", 0).Store(&names)
- if err != nil {
- log.Warnf("TrayRecoveryService: failed to list bus names: %v", err)
- return nil
- }
- return names
-}
-
-func (m *Manager) getNameOwner(name string) string {
- var owner string
- err := m.conn.BusObject().Call(dbusIface+".GetNameOwner", 0, name).Store(&owner)
- if err != nil {
- return ""
- }
- return owner
-}
-
-// buildRegisteredConnIDs resolves every registered SNI item (well-known name
-// or :1.xxx connection ID) to a canonical connection ID. This prevents
-// duplicates in both directions.
-func (m *Manager) buildRegisteredConnIDs(registeredItems []string) map[string]bool {
- connIDs := make(map[string]bool, len(registeredItems))
- for _, item := range registeredItems {
- name := extractName(item)
- if strings.HasPrefix(name, ":1.") {
- connIDs[name] = true
- } else {
- owner := m.getNameOwner(name)
- if owner != "" {
- connIDs[owner] = true
- }
- }
- }
- return connIDs
-}
-
-// scanWellKnownNames probes well-known names (e.g. DinoX, nm-applet) for
-// unregistered SNI items and re-registers them.
-func (m *Manager) scanWellKnownNames(allNames []string, registeredItems []string, registeredConnIDs map[string]bool) {
- registeredRaw := strings.Join(registeredItems, "\n")
-
- for _, name := range allNames {
- if strings.HasPrefix(name, ":") {
- continue
- }
-
- if strings.Contains(registeredRaw, name) {
- continue
- }
-
- // Skip if this name's connection ID is already in the registered set
- // (handles the case where the app registered via connection ID instead)
- connForName := m.getNameOwner(name)
- if connForName != "" && registeredConnIDs[connForName] {
- continue
- }
-
- if isExcludedName(name) {
- continue
- }
-
- short := shortName(name)
- objectPaths := []string{
- "/StatusNotifierItem",
- "/org/ayatana/NotificationItem/" + short,
- }
-
- for _, objPath := range objectPaths {
- if m.probeSNI(name, objPath, probeTimeout) {
- m.registerSNI(name)
- // Update set so the connection-ID section won't double-register this app
- if connForName != "" {
- registeredConnIDs[connForName] = true
- }
- break
- }
- }
- }
-}
-
-// scanConnectionIDs probes all :1.xxx connections in parallel for unregistered
-// SNI items (e.g. Vesktop, Electron apps). Most non-SNI connections return an
-// error instantly, so this is fast.
-func (m *Manager) scanConnectionIDs(allNames []string, registeredItems []string, registeredConnIDs map[string]bool) {
- registeredRaw := strings.Join(registeredItems, "\n")
- registeredLower := strings.ToLower(registeredRaw)
-
- var wg sync.WaitGroup
- sem := make(chan struct{}, batchSize)
-
- for _, name := range allNames {
- if !strings.HasPrefix(name, ":1.") {
- continue
- }
- if registeredConnIDs[name] {
- continue
- }
-
- sem <- struct{}{}
- wg.Add(1)
- go func(conn string) {
- defer wg.Done()
- defer func() { <-sem }()
-
- sniID := m.getSNIId(conn, connProbeTimeout)
- if sniID == "" {
- return
- }
-
- // Skip if an item with the same Id is already registered (case-insensitive)
- if strings.Contains(registeredLower, strings.ToLower(sniID)) {
- return
- }
-
- m.registerSNI(conn)
- log.Infof("TrayRecovery: re-registered %s (Id: %s)", conn, sniID)
- }(name)
- }
- wg.Wait()
-}
-
-func (m *Manager) probeSNI(dest, path string, timeout time.Duration) bool {
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
-
- obj := m.conn.Object(dest, dbus.ObjectPath(path))
- var props map[string]dbus.Variant
- err := obj.CallWithContext(ctx, propsIface+".GetAll", 0, sniItemIface).Store(&props)
- if err != nil {
- return false
- }
-
- _, hasID := props["Id"]
- return hasID
-}
-
-func (m *Manager) getSNIId(dest string, timeout time.Duration) string {
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
-
- obj := m.conn.Object(dest, "/StatusNotifierItem")
- var variant dbus.Variant
- err := obj.CallWithContext(ctx, propsIface+".Get", 0, sniItemIface, "Id").Store(&variant)
- if err != nil {
- return ""
- }
-
- id, _ := variant.Value().(string)
- return id
-}
-
-func (m *Manager) registerSNI(name string) {
- obj := m.conn.Object(sniWatcherDest, sniWatcherPath)
- call := obj.Call(sniWatcherIface+".RegisterStatusNotifierItem", 0, name)
- if call.Err != nil {
- log.Warnf("TrayRecovery: failed to register %s: %v", name, call.Err)
- return
- }
- log.Infof("TrayRecovery: re-registered %s", name)
-}
-
-func extractName(item string) string {
- if idx := strings.IndexByte(item, '/'); idx != -1 {
- return item[:idx]
- }
- return item
-}
-
-func shortName(name string) string {
- parts := strings.Split(name, ".")
- if len(parts) > 0 {
- return parts[len(parts)-1]
- }
- return name
-}
-
-func isExcludedName(name string) bool {
- for _, prefix := range excludedPrefixes {
- if strings.HasPrefix(name, prefix) {
- return true
- }
- }
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go
deleted file mode 100644
index 1fff03f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package wayland
-
-import (
- "math"
-)
-
-type GammaRamp struct {
- Red []uint16
- Green []uint16
- Blue []uint16
-}
-
-type rgb struct {
- r, g, b float64
-}
-
-type xyz struct {
- x, y, z float64
-}
-
-func illuminantD(temp int) (float64, float64, bool) {
- var x float64
- switch {
- case temp >= 2500 && temp <= 7000:
- t := float64(temp)
- x = 0.244063 + 0.09911e3/t + 2.9678e6/(t*t) - 4.6070e9/(t*t*t)
- case temp > 7000 && temp <= 25000:
- t := float64(temp)
- x = 0.237040 + 0.24748e3/t + 1.9018e6/(t*t) - 2.0064e9/(t*t*t)
- default:
- return 0, 0, false
- }
- y := -3*(x*x) + 2.870*x - 0.275
- return x, y, true
-}
-
-func planckianLocus(temp int) (float64, float64, bool) {
- var x, y float64
- switch {
- case temp >= 1667 && temp <= 4000:
- t := float64(temp)
- x = -0.2661239e9/(t*t*t) - 0.2343589e6/(t*t) + 0.8776956e3/t + 0.179910
- if temp <= 2222 {
- y = -1.1064814*(x*x*x) - 1.34811020*(x*x) + 2.18555832*x - 0.20219683
- } else {
- y = -0.9549476*(x*x*x) - 1.37418593*(x*x) + 2.09137015*x - 0.16748867
- }
- case temp > 4000 && temp < 25000:
- t := float64(temp)
- x = -3.0258469e9/(t*t*t) + 2.1070379e6/(t*t) + 0.2226347e3/t + 0.240390
- y = 3.0817580*(x*x*x) - 5.87338670*(x*x) + 3.75112997*x - 0.37001483
- default:
- return 0, 0, false
- }
- return x, y, true
-}
-
-func srgbGamma(value, gamma float64) float64 {
- if value <= 0.0031308 {
- return 12.92 * value
- }
- return math.Pow(1.055*value, 1.0/gamma) - 0.055
-}
-
-func clamp01(v float64) float64 {
- switch {
- case v > 1.0:
- return 1.0
- case v < 0.0:
- return 0.0
- default:
- return v
- }
-}
-
-func xyzToSRGB(c xyz) rgb {
- return rgb{
- r: srgbGamma(clamp01(3.2404542*c.x-1.5371385*c.y-0.4985314*c.z), 2.2),
- g: srgbGamma(clamp01(-0.9692660*c.x+1.8760108*c.y+0.0415560*c.z), 2.2),
- b: srgbGamma(clamp01(0.0556434*c.x-0.2040259*c.y+1.0572252*c.z), 2.2),
- }
-}
-
-func normalizeRGB(c *rgb) {
- maxw := math.Max(c.r, math.Max(c.g, c.b))
- if maxw > 0 {
- c.r /= maxw
- c.g /= maxw
- c.b /= maxw
- }
-}
-
-func calcWhitepoint(temp int) rgb {
- if temp == 6500 {
- return rgb{r: 1.0, g: 1.0, b: 1.0}
- }
-
- var wp xyz
-
- switch {
- case temp >= 25000:
- x, y, _ := illuminantD(25000)
- wp.x = x
- wp.y = y
- case temp >= 4000:
- x, y, _ := illuminantD(temp)
- wp.x = x
- wp.y = y
- case temp >= 2500:
- x1, y1, _ := illuminantD(temp)
- x2, y2, _ := planckianLocus(temp)
- factor := float64(4000-temp) / 1500.0
- sineFactor := (math.Cos(math.Pi*factor) + 1.0) / 2.0
- wp.x = x1*sineFactor + x2*(1.0-sineFactor)
- wp.y = y1*sineFactor + y2*(1.0-sineFactor)
- default:
- t := temp
- if t < 1667 {
- t = 1667
- }
- x, y, _ := planckianLocus(t)
- wp.x = x
- wp.y = y
- }
-
- wp.z = 1.0 - wp.x - wp.y
-
- wpRGB := xyzToSRGB(wp)
- normalizeRGB(&wpRGB)
- return wpRGB
-}
-
-func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
- ramp := GammaRamp{
- Red: make([]uint16, size),
- Green: make([]uint16, size),
- Blue: make([]uint16, size),
- }
-
- wp := calcWhitepoint(temp)
-
- for i := uint32(0); i < size; i++ {
- val := float64(i) / float64(size-1)
- ramp.Red[i] = uint16(clamp01(math.Pow(val*wp.r, 1.0/gamma)) * 65535.0)
- ramp.Green[i] = uint16(clamp01(math.Pow(val*wp.g, 1.0/gamma)) * 65535.0)
- ramp.Blue[i] = uint16(clamp01(math.Pow(val*wp.b, 1.0/gamma)) * 65535.0)
- }
-
- return ramp
-}
-
-func GenerateIdentityRamp(size uint32) GammaRamp {
- ramp := GammaRamp{
- Red: make([]uint16, size),
- Green: make([]uint16, size),
- Blue: make([]uint16, size),
- }
-
- for i := uint32(0); i < size; i++ {
- val := uint16((float64(i) / float64(size-1)) * 65535.0)
- ramp.Red[i] = val
- ramp.Green[i] = val
- ramp.Blue[i] = val
- }
-
- return ramp
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go
deleted file mode 100644
index 0bf8e71..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package wayland
-
-import (
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-func TestGenerateGammaRamp(t *testing.T) {
- tests := []struct {
- name string
- size uint32
- temp int
- gamma float64
- }{
- {"small_warm", 16, 6500, 1.0},
- {"small_cool", 16, 4000, 1.0},
- {"large_warm", 256, 6500, 1.0},
- {"large_cool", 256, 4000, 1.0},
- {"custom_gamma", 64, 5500, 1.2},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- ramp := GenerateGammaRamp(tt.size, tt.temp, tt.gamma)
-
- if len(ramp.Red) != int(tt.size) {
- t.Errorf("expected %d red values, got %d", tt.size, len(ramp.Red))
- }
- if len(ramp.Green) != int(tt.size) {
- t.Errorf("expected %d green values, got %d", tt.size, len(ramp.Green))
- }
- if len(ramp.Blue) != int(tt.size) {
- t.Errorf("expected %d blue values, got %d", tt.size, len(ramp.Blue))
- }
-
- if ramp.Red[0] != 0 || ramp.Green[0] != 0 || ramp.Blue[0] != 0 {
- t.Errorf("first values should be 0, got R:%d G:%d B:%d",
- ramp.Red[0], ramp.Green[0], ramp.Blue[0])
- }
-
- lastIdx := tt.size - 1
- if ramp.Red[lastIdx] == 0 || ramp.Green[lastIdx] == 0 || ramp.Blue[lastIdx] == 0 {
- t.Errorf("last values should be non-zero, got R:%d G:%d B:%d",
- ramp.Red[lastIdx], ramp.Green[lastIdx], ramp.Blue[lastIdx])
- }
-
- for i := uint32(1); i < tt.size; i++ {
- if ramp.Red[i] < ramp.Red[i-1] {
- t.Errorf("red ramp not monotonic at index %d", i)
- }
- }
- })
- }
-}
-
-func TestCalcWhitepoint(t *testing.T) {
- tests := []struct {
- name string
- temp int
- }{
- {"very_warm", 6500},
- {"neutral", 5500},
- {"cool", 4000},
- {"very_cool", 3000},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- wp := calcWhitepoint(tt.temp)
-
- if wp.r < 0 || wp.r > 1 {
- t.Errorf("red out of range: %f", wp.r)
- }
- if wp.g < 0 || wp.g > 1 {
- t.Errorf("green out of range: %f", wp.g)
- }
- if wp.b < 0 || wp.b > 1 {
- t.Errorf("blue out of range: %f", wp.b)
- }
- })
- }
-}
-
-func TestWhitepointProgression(t *testing.T) {
- temps := []int{3000, 4000, 5000, 6000, 6500}
-
- var prevBlue float64
- for i, temp := range temps {
- wp := calcWhitepoint(temp)
- if i > 0 && wp.b < prevBlue {
- t.Errorf("blue should increase with temperature, %d->%d: %f->%f",
- temps[i-1], temp, prevBlue, wp.b)
- }
- prevBlue = wp.b
- }
-}
-
-func TestClamp(t *testing.T) {
- tests := []struct {
- val float64
- min float64
- max float64
- expected float64
- }{
- {5, 0, 10, 5},
- {-5, 0, 10, 0},
- {15, 0, 10, 10},
- {0, 0, 10, 0},
- {10, 0, 10, 10},
- }
-
- for _, tt := range tests {
- result := utils.Clamp(tt.val, tt.min, tt.max)
- if result != tt.expected {
- t.Errorf("clamp(%f, %f, %f) = %f, want %f",
- tt.val, tt.min, tt.max, result, tt.expected)
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go
deleted file mode 100644
index ba99b5f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package wayland
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-type ipAPIResponse struct {
- Lat float64 `json:"lat"`
- Lon float64 `json:"lon"`
- City string `json:"city"`
-}
-
-func FetchIPLocation() (*float64, *float64, error) {
- client := &http.Client{
- Timeout: 10 * time.Second,
- }
-
- resp, err := client.Get("http://ip-api.com/json/")
- if err != nil {
- return nil, nil, fmt.Errorf("failed to fetch IP location: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, nil, fmt.Errorf("ip-api.com returned status %d", resp.StatusCode)
- }
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to read response: %w", err)
- }
-
- var data ipAPIResponse
- if err := json.Unmarshal(body, &data); err != nil {
- return nil, nil, fmt.Errorf("failed to parse response: %w", err)
- }
-
- if data.Lat == 0 && data.Lon == 0 {
- return nil, nil, fmt.Errorf("missing location data in response")
- }
-
- log.Infof("Fetched IP-based location: %s (%.4f, %.4f)", data.City, data.Lat, data.Lon)
- return &data.Lat, &data.Lon, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go
deleted file mode 100644
index 76b08b5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package wayland
-
-import (
- "encoding/json"
- "fmt"
- "net"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- if manager == nil {
- models.RespondError(conn, req.ID, "wayland manager not initialized")
- return
- }
-
- switch req.Method {
- case "wayland.gamma.getState":
- handleGetState(conn, req, manager)
- case "wayland.gamma.setTemperature":
- handleSetTemperature(conn, req, manager)
- case "wayland.gamma.setLocation":
- handleSetLocation(conn, req, manager)
- case "wayland.gamma.setManualTimes":
- handleSetManualTimes(conn, req, manager)
- case "wayland.gamma.setUseIPLocation":
- handleSetUseIPLocation(conn, req, manager)
- case "wayland.gamma.setGamma":
- handleSetGamma(conn, req, manager)
- case "wayland.gamma.setEnabled":
- handleSetEnabled(conn, req, manager)
- case "wayland.gamma.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleSetTemperature(conn net.Conn, req models.Request, manager *Manager) {
- var lowTemp, highTemp int
-
- if temp, ok := models.Get[float64](req, "temp"); ok {
- lowTemp = int(temp)
- highTemp = int(temp)
- } else {
- low, err := params.Float(req.Params, "low")
- if err != nil {
- models.RespondError(conn, req.ID, "missing temperature parameters (provide 'temp' or both 'low' and 'high')")
- return
- }
- high, err := params.Float(req.Params, "high")
- if err != nil {
- models.RespondError(conn, req.ID, "missing temperature parameters (provide 'temp' or both 'low' and 'high')")
- return
- }
- lowTemp = int(low)
- highTemp = int(high)
- }
-
- if err := manager.SetTemperature(lowTemp, highTemp); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "temperature set"})
-}
-
-func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
- lat, err := params.Float(req.Params, "latitude")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- lon, err := params.Float(req.Params, "longitude")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetLocation(lat, lon); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"})
-}
-
-func handleSetManualTimes(conn net.Conn, req models.Request, manager *Manager) {
- sunriseStr, sunriseOK := models.Get[string](req, "sunrise")
- sunsetStr, sunsetOK := models.Get[string](req, "sunset")
-
- if !sunriseOK || !sunsetOK || sunriseStr == "" || sunsetStr == "" {
- manager.ClearManualTimes()
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times cleared"})
- return
- }
-
- sunrise, err := time.Parse("15:04", sunriseStr)
- if err != nil {
- models.RespondError(conn, req.ID, "invalid sunrise format (use HH:MM)")
- return
- }
-
- sunset, err := time.Parse("15:04", sunsetStr)
- if err != nil {
- models.RespondError(conn, req.ID, "invalid sunset format (use HH:MM)")
- return
- }
-
- if err := manager.SetManualTimes(sunrise, sunset); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times set"})
-}
-
-func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) {
- use, err := params.Bool(req.Params, "use")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetUseIPLocation(use)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "IP location preference set"})
-}
-
-func handleSetGamma(conn net.Conn, req models.Request, manager *Manager) {
- gamma, err := params.Float(req.Params, "gamma")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetGamma(gamma); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "gamma set"})
-}
-
-func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) {
- enabled, err := params.Bool(req.Params, "enabled")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetEnabled(enabled)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "enabled state set"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go
deleted file mode 100644
index 3540e3d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go
+++ /dev/null
@@ -1,1256 +0,0 @@
-package wayland
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "os"
- "slices"
- "syscall"
- "time"
-
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/godbus/dbus/v5"
- "golang.org/x/sys/unix"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_gamma_control"
-)
-
-const animKelvinStep = 25
-
-func NewManager(display wlclient.WaylandDisplay, config Config) (*Manager, error) {
- if err := config.Validate(); err != nil {
- return nil, err
- }
-
- if config.ElevationTwilight == 0 {
- config.ElevationTwilight = -6.0
- }
- if config.ElevationDaylight == 0 {
- config.ElevationDaylight = 3.0
- }
-
- m := &Manager{
- config: config,
- display: display,
- ctx: display.Context(),
- cmdq: make(chan cmd, 128),
- stopChan: make(chan struct{}),
- updateTrigger: make(chan struct{}, 1),
- dirty: make(chan struct{}, 1),
- dbusSignal: make(chan *dbus.Signal, 16),
- }
-
- if err := m.setupRegistry(); err != nil {
- return nil, err
- }
-
- if err := m.setupDBusMonitor(); err != nil {
- log.Warnf("Failed to setup D-Bus monitoring: %v", err)
- }
-
- m.alive = true
- m.recalcSchedule(time.Now())
- m.updateStateFromSchedule()
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- m.wg.Add(1)
- go m.schedulerLoop()
-
- if m.dbusConn != nil {
- m.wg.Add(1)
- go m.dbusMonitor()
- }
-
- m.wg.Add(1)
- go m.waylandActor()
-
- if config.Enabled {
- m.post(func() {
- log.Info("Gamma control enabled at startup")
- gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
- m.availOutputsMu.RLock()
- outs := slices.Clone(m.availableOutputs)
- m.availOutputsMu.RUnlock()
- if err := m.setupOutputControls(outs, gammaMgr); err != nil {
- log.Errorf("Failed to initialize gamma controls: %v", err)
- return
- }
- m.controlsInitialized = true
- })
- }
-
- return m, nil
-}
-
-func (m *Manager) post(fn func()) {
- select {
- case m.cmdq <- cmd{fn: fn}:
- default:
- log.Warn("Actor command queue full")
- }
-}
-
-func (m *Manager) waylandActor() {
- defer m.wg.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case c := <-m.cmdq:
- c.fn()
- }
- }
-}
-
-func (m *Manager) anyOutputReady() bool {
- anyReady := false
- m.outputs.Range(func(_ uint32, out *outputState) bool {
- if out.rampSize > 0 && !out.failed {
- anyReady = true
- return false // stop iteration
- }
- return true
- })
- return anyReady
-}
-
-func (m *Manager) setupDBusMonitor() error {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("system bus: %w", err)
- }
-
- matchRule := "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep',path='/org/freedesktop/login1'"
- if err := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err; err != nil {
- conn.Close()
- return fmt.Errorf("add match: %w", err)
- }
-
- conn.Signal(m.dbusSignal)
- m.dbusConn = conn
- return nil
-}
-
-func (m *Manager) setupRegistry() error {
- registry, err := m.display.GetRegistry()
- if err != nil {
- return fmt.Errorf("get registry: %w", err)
- }
- m.registry = registry
-
- outputs := make([]*wlclient.Output, 0)
- outputNames := make(map[uint32]string)
- var gammaMgr *wlr_gamma_control.ZwlrGammaControlManagerV1
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- switch e.Interface {
- case wlr_gamma_control.ZwlrGammaControlManagerV1InterfaceName:
- manager := wlr_gamma_control.NewZwlrGammaControlManagerV1(m.ctx)
- version := e.Version
- if version > 1 {
- version = 1
- }
- if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
- gammaMgr = manager
- }
- case "wl_output":
- output := wlclient.NewOutput(m.ctx)
- version := e.Version
- if version > 4 {
- version = 4
- }
- if err := registry.Bind(e.Name, e.Interface, version, output); err != nil {
- return
- }
- outputID := output.ID()
- output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
- outputNames[outputID] = ev.Name
- })
- if gammaMgr != nil {
- outputs = append(outputs, output)
- m.addAvailableOutput(output)
- }
- m.outputRegNames.Store(outputID, e.Name)
-
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- if enabled && m.controlsInitialized {
- m.post(func() {
- if err := m.addOutputControl(output); err != nil {
- log.Warnf("Failed to add output control: %v", err)
- }
- })
- }
- }
- })
-
- registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
- m.post(func() {
- var foundID uint32
- var foundOut *outputState
- m.outputs.Range(func(id uint32, out *outputState) bool {
- if out.registryName == e.Name {
- foundID = id
- foundOut = out
- return false
- }
- return true
- })
- if foundOut == nil {
- return
- }
- if foundOut.gammaControl != nil {
- foundOut.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1).Destroy()
- foundOut.gammaControl = nil
- }
- m.removeAvailableOutput(foundOut.output)
- if foundOut.output != nil && !foundOut.output.IsZombie() {
- _ = foundOut.output.Release()
- }
- m.outputs.Delete(foundID)
-
- hasOutputs := false
- m.outputs.Range(func(_ uint32, _ *outputState) bool {
- hasOutputs = true
- return false
- })
- if !hasOutputs {
- m.controlsInitialized = false
- }
- })
- })
-
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("roundtrip 1: %w", err)
- }
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("roundtrip 2: %w", err)
- }
-
- if gammaMgr == nil {
- return errdefs.ErrNoGammaControl
- }
- if len(outputs) == 0 {
- return fmt.Errorf("no outputs")
- }
-
- physicalOutputs := make([]*wlclient.Output, 0, len(outputs))
- for _, output := range outputs {
- name := outputNames[output.ID()]
- if len(name) >= 9 && name[:9] == "HEADLESS-" {
- continue
- }
- physicalOutputs = append(physicalOutputs, output)
- }
-
- m.gammaControl = gammaMgr
- m.availableOutputs = physicalOutputs
- return nil
-}
-
-func (m *Manager) setupOutputControls(outputs []*wlclient.Output, manager *wlr_gamma_control.ZwlrGammaControlManagerV1) error {
- for _, output := range outputs {
- control, err := manager.GetGammaControl(output)
- if err != nil {
- continue
- }
- outputID := output.ID()
- registryName, _ := m.outputRegNames.Load(outputID)
- outState := &outputState{
- id: outputID,
- registryName: registryName,
- output: output,
- gammaControl: control,
- }
- m.setupControlHandlers(outState, control)
- m.outputs.Store(outputID, outState)
- }
- return nil
-}
-
-func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_control.ZwlrGammaControlV1) {
- outputID := state.id
-
- control.SetGammaSizeHandler(func(e wlr_gamma_control.ZwlrGammaControlV1GammaSizeEvent) {
- size := e.Size
- m.post(func() {
- if out, ok := m.outputs.Load(outputID); ok {
- out.rampSize = size
- out.failed = false
- out.retryCount = 0
- }
- m.lastAppliedTemp = 0
- m.applyCurrentTemp("gamma_size")
- })
- })
-
- control.SetFailedHandler(func(_ wlr_gamma_control.ZwlrGammaControlV1FailedEvent) {
- m.post(func() {
- out, ok := m.outputs.Load(outputID)
- if !ok {
- return
- }
- if ctrl, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1); ok && ctrl != nil && !ctrl.IsZombie() {
- ctrl.Destroy()
- }
- out.gammaControl = nil
- out.failed = true
- out.rampSize = 0
- out.retryCount++
- out.lastFailTime = time.Now()
-
- if !m.outputStillValid(out) {
- return
- }
-
- backoff := time.Duration(300<<uint(min(out.retryCount-1, 4))) * time.Millisecond
- time.AfterFunc(backoff, func() {
- m.post(func() {
- if !m.outputStillValid(out) {
- return
- }
- if _, stillTracked := m.outputs.Load(outputID); !stillTracked {
- return
- }
- m.recreateOutputControl(out)
- })
- })
- })
- })
-}
-
-func (m *Manager) addAvailableOutput(o *wlclient.Output) {
- if o == nil {
- return
- }
- m.availOutputsMu.Lock()
- defer m.availOutputsMu.Unlock()
- if slices.Contains(m.availableOutputs, o) {
- return
- }
- m.availableOutputs = append(m.availableOutputs, o)
-}
-
-func (m *Manager) removeAvailableOutput(o *wlclient.Output) {
- if o == nil {
- return
- }
- m.availOutputsMu.Lock()
- defer m.availOutputsMu.Unlock()
- m.availableOutputs = slices.DeleteFunc(m.availableOutputs, func(existing *wlclient.Output) bool {
- return existing == o
- })
-}
-
-func (m *Manager) outputStillValid(out *outputState) bool {
- switch {
- case out == nil:
- return false
- case out.output == nil:
- return false
- case out.output.IsZombie():
- return false
- }
- m.availOutputsMu.RLock()
- defer m.availOutputsMu.RUnlock()
- return slices.Contains(m.availableOutputs, out.output)
-}
-
-func isConnectionDeadErr(err error) bool {
- switch {
- case err == nil:
- return false
- case errors.Is(err, syscall.EPIPE):
- return true
- case errors.Is(err, syscall.ECONNRESET):
- return true
- case errors.Is(err, syscall.EBADF):
- return true
- case errors.Is(err, io.EOF):
- return true
- }
- return false
-}
-
-func (m *Manager) addOutputControl(output *wlclient.Output) error {
- switch {
- case m.connectionDead.Load():
- return nil
- case output == nil || output.IsZombie():
- return nil
- }
-
- outputID := output.ID()
- gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
-
- control, err := gammaMgr.GetGammaControl(output)
- if err != nil {
- if isConnectionDeadErr(err) {
- m.markConnectionDead(err)
- }
- return err
- }
-
- registryName, _ := m.outputRegNames.Load(outputID)
- outState := &outputState{
- id: outputID,
- registryName: registryName,
- output: output,
- gammaControl: control,
- }
- m.setupControlHandlers(outState, control)
- m.outputs.Store(outputID, outState)
- return nil
-}
-
-func (m *Manager) recreateOutputControl(out *outputState) error {
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- switch {
- case m.connectionDead.Load():
- return nil
- case !enabled || !m.controlsInitialized:
- return nil
- case out.isVirtual:
- return nil
- case out.retryCount >= 10:
- return nil
- case !m.outputStillValid(out):
- return nil
- }
- if _, ok := m.outputs.Load(out.id); !ok {
- return nil
- }
-
- gammaMgr, ok := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
- if !ok {
- return fmt.Errorf("no gamma manager")
- }
-
- if existing, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1); ok && existing != nil && !existing.IsZombie() {
- existing.Destroy()
- out.gammaControl = nil
- }
-
- control, err := gammaMgr.GetGammaControl(out.output)
- if err != nil {
- if isConnectionDeadErr(err) {
- m.markConnectionDead(err)
- }
- return err
- }
-
- m.setupControlHandlers(out, control)
- out.gammaControl = control
- out.failed = false
- return nil
-}
-
-func (m *Manager) markConnectionDead(err error) {
- if m.connectionDead.Swap(true) {
- return
- }
- log.Errorf("gamma: wayland connection appears dead (%v); pausing gamma operations", err)
-}
-
-func (m *Manager) recalcSchedule(now time.Time) {
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- m.scheduleMutex.Lock()
- defer m.scheduleMutex.Unlock()
-
- dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- alreadyValid := !m.schedule.times.Sunrise.IsZero()
- if m.schedule.calcDay.Equal(dayStart) && alreadyValid {
- return
- }
-
- var times SunTimes
- var cond SunCondition
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- dur := time.Hour
- if config.ManualDuration != nil {
- dur = *config.ManualDuration
- }
- sunrise := time.Date(now.Year(), now.Month(), now.Day(),
- config.ManualSunrise.Hour(), config.ManualSunrise.Minute(), config.ManualSunrise.Second(), 0, now.Location())
- sunset := time.Date(now.Year(), now.Month(), now.Day(),
- config.ManualSunset.Hour(), config.ManualSunset.Minute(), config.ManualSunset.Second(), 0, now.Location())
- times = SunTimes{
- Dawn: sunrise.Add(-dur),
- Sunrise: sunrise,
- Sunset: sunset,
- Night: sunset.Add(dur),
- }
- cond = SunNormal
- } else {
- lat, lon := m.getLocation()
- if lat == nil || lon == nil {
- m.gammaState = StateStatic
- return
- }
- times, cond = CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
- }
-
- m.schedule.calcDay = dayStart
- m.schedule.times = times
- m.schedule.condition = cond
-
- switch cond {
- case SunNormal:
- m.gammaState = StateNormal
- tempDiff := config.HighTemp - config.LowTemp
- if tempDiff > 0 {
- dawnDur := times.Sunrise.Sub(times.Dawn)
- nightDur := times.Night.Sub(times.Sunset)
- m.schedule.dawnStepTime = time.Duration(max(1, int(dawnDur.Seconds())*animKelvinStep/tempDiff)) * time.Second
- m.schedule.nightStepTime = time.Duration(max(1, int(nightDur.Seconds())*animKelvinStep/tempDiff)) * time.Second
- }
- case SunMidnightSun:
- m.gammaState = StateStatic
- case SunPolarNight:
- m.gammaState = StateStatic
- }
-}
-
-func (m *Manager) SetGeoClient(client geolocation.Client) {
- m.geoClient = client
-}
-
-func (m *Manager) getLocation() (*float64, *float64) {
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- if config.Latitude != nil && config.Longitude != nil {
- return config.Latitude, config.Longitude
- }
- if !config.UseIPLocation {
- return nil, nil
- }
- if m.geoClient == nil {
- return nil, nil
- }
-
- m.locationMutex.RLock()
- if m.cachedIPLat != nil && m.cachedIPLon != nil {
- lat, lon := m.cachedIPLat, m.cachedIPLon
- m.locationMutex.RUnlock()
- return lat, lon
- }
- m.locationMutex.RUnlock()
-
- location, err := m.geoClient.GetLocation()
- if err != nil {
- return nil, nil
- }
-
- m.locationMutex.Lock()
- m.cachedIPLat = &location.Latitude
- m.cachedIPLon = &location.Longitude
- m.locationMutex.Unlock()
- return m.cachedIPLat, m.cachedIPLon
-}
-
-func (m *Manager) hasValidSchedule() bool {
- m.scheduleMutex.RLock()
- defer m.scheduleMutex.RUnlock()
- return !m.schedule.times.Sunrise.IsZero()
-}
-
-func (m *Manager) getSunPosition(now time.Time) float64 {
- m.scheduleMutex.RLock()
- sched := m.schedule
- state := m.gammaState
- m.scheduleMutex.RUnlock()
-
- if sched.times.Sunrise.IsZero() {
- return 1.0
- }
-
- switch state {
- case StateStatic:
- if sched.condition == SunMidnightSun {
- return 1.0
- }
- return 0.0
- case StateNormal:
- return m.getSunPositionNormal(now, sched.times)
- }
- return 1.0
-}
-
-func (m *Manager) getSunPositionNormal(now time.Time, times SunTimes) float64 {
- if now.Before(times.Dawn) {
- return 0.0
- }
- if now.Before(times.Sunrise) {
- return interpolate(now, times.Dawn, times.Sunrise)
- }
- if now.Before(times.Sunset) {
- return 1.0
- }
- if now.Before(times.Night) {
- return interpolate(now, times.Night, times.Sunset)
- }
- return 0.0
-}
-
-func interpolate(now time.Time, start, stop time.Time) float64 {
- if start.Equal(stop) {
- return 1.0
- }
- pos := float64(now.Sub(start)) / float64(stop.Sub(start))
- switch {
- case pos > 1.0:
- return 1.0
- case pos < 0.0:
- return 0.0
- default:
- return pos
- }
-}
-
-func (m *Manager) getTempFromPosition(pos float64) int {
- m.configMutex.RLock()
- low, high := m.config.LowTemp, m.config.HighTemp
- m.configMutex.RUnlock()
- return low + int(float64(high-low)*pos)
-}
-
-func (m *Manager) getNextDeadline(now time.Time) time.Time {
- m.scheduleMutex.RLock()
- sched := m.schedule
- state := m.gammaState
- m.scheduleMutex.RUnlock()
-
- switch state {
- case StateStatic:
- return m.tomorrow(now)
- case StateNormal:
- return m.getDeadlineNormal(now, sched)
- default:
- return m.tomorrow(now)
- }
-}
-
-func (m *Manager) getDeadlineNormal(now time.Time, sched sunSchedule) time.Time {
- times := sched.times
- switch {
- case now.Before(times.Dawn):
- return times.Dawn
- case now.Before(times.Sunrise):
- return now.Add(sched.dawnStepTime)
- case now.Before(times.Sunset):
- return times.Sunset
- case now.Before(times.Night):
- return now.Add(sched.nightStepTime)
- default:
- return m.tomorrowDawn(now)
- }
-}
-
-func (m *Manager) tomorrowDawn(now time.Time) time.Time {
- tomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
-
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- if config.ManualSunrise != nil {
- dur := time.Hour
- if config.ManualDuration != nil {
- dur = *config.ManualDuration
- }
- return time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(),
- config.ManualSunrise.Hour(), config.ManualSunrise.Minute(), config.ManualSunrise.Second(), 0, tomorrow.Location()).Add(-dur)
- }
-
- lat, lon := m.getLocation()
- if lat == nil || lon == nil {
- return tomorrow
- }
-
- times, cond := CalculateSunTimesWithTwilight(*lat, *lon, tomorrow, config.ElevationTwilight, config.ElevationDaylight)
- if cond != SunNormal {
- return tomorrow
- }
- return times.Dawn
-}
-
-func (m *Manager) tomorrow(now time.Time) time.Time {
- return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
-}
-
-func (m *Manager) schedulerLoop() {
- defer m.wg.Done()
-
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- if enabled {
- m.post(func() { m.applyCurrentTemp("startup") })
- }
-
- var timer *time.Timer
- for {
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- now := time.Now()
- m.recalcSchedule(now)
-
- var waitDur time.Duration
- if enabled {
- deadline := m.getNextDeadline(now)
- waitDur = time.Until(deadline)
- if waitDur < time.Second {
- waitDur = time.Second
- }
- } else {
- waitDur = 24 * time.Hour
- }
-
- if timer != nil {
- timer.Stop()
- }
- timer = time.NewTimer(waitDur)
-
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.updateTrigger:
- timer.Stop()
- m.scheduleMutex.Lock()
- m.schedule.calcDay = time.Time{}
- m.scheduleMutex.Unlock()
- m.recalcSchedule(time.Now())
- m.updateStateFromSchedule()
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
- if enabled {
- m.post(func() { m.applyCurrentTemp("updateTrigger") })
- }
- case <-timer.C:
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
- if enabled {
- m.post(func() { m.applyCurrentTemp("timer") })
- }
- }
- }
-}
-
-func (m *Manager) applyCurrentTemp(_ string) {
- if !m.controlsInitialized || !m.anyOutputReady() {
- return
- }
-
- // Ensure schedule is up-to-date (handles display wake after overnight sleep)
- m.recalcSchedule(time.Now())
-
- m.configMutex.RLock()
- low, high := m.config.LowTemp, m.config.HighTemp
- m.configMutex.RUnlock()
-
- if low == high {
- m.applyGamma(low)
- m.updateStateFromSchedule()
- return
- }
-
- if !m.hasValidSchedule() {
- m.updateStateFromSchedule()
- return
- }
-
- now := time.Now()
- pos := m.getSunPosition(now)
- temp := m.getTempFromPosition(pos)
-
- m.applyGamma(temp)
- m.updateStateFromSchedule()
-}
-
-func (m *Manager) applyGamma(temp int) {
- m.configMutex.RLock()
- gamma := m.config.Gamma
- m.configMutex.RUnlock()
-
- switch {
- case m.connectionDead.Load():
- return
- case !m.controlsInitialized:
- return
- case m.lastAppliedTemp == temp && m.lastAppliedGamma == gamma:
- return
- }
-
- var outs []*outputState
- m.outputs.Range(func(_ uint32, out *outputState) bool {
- outs = append(outs, out)
- return true
- })
- if len(outs) == 0 {
- return
- }
-
- type job struct {
- out *outputState
- data []byte
- }
- var jobs []job
-
- for _, out := range outs {
- switch {
- case out.failed:
- continue
- case out.rampSize == 0:
- continue
- case out.gammaControl == nil:
- continue
- case !m.outputStillValid(out):
- continue
- }
- ramp := GenerateGammaRamp(out.rampSize, temp, gamma)
- buf := bytes.NewBuffer(make([]byte, 0, int(out.rampSize)*6))
- for _, v := range ramp.Red {
- binary.Write(buf, binary.LittleEndian, v)
- }
- for _, v := range ramp.Green {
- binary.Write(buf, binary.LittleEndian, v)
- }
- for _, v := range ramp.Blue {
- binary.Write(buf, binary.LittleEndian, v)
- }
- jobs = append(jobs, job{out: out, data: buf.Bytes()})
- }
-
- for _, j := range jobs {
- err := m.setGammaBytes(j.out, j.data)
- if err == nil {
- continue
- }
- log.Warnf("gamma: failed to set output %d: %v", j.out.id, err)
- j.out.failed = true
- j.out.rampSize = 0
- if isConnectionDeadErr(err) {
- m.markConnectionDead(err)
- return
- }
- }
-
- m.lastAppliedTemp = temp
- m.lastAppliedGamma = gamma
-}
-
-func (m *Manager) setGammaBytes(out *outputState, data []byte) error {
- if out.gammaControl == nil {
- return fmt.Errorf("no gamma control")
- }
- ctrl, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1)
- if !ok || ctrl == nil || ctrl.IsZombie() {
- return fmt.Errorf("gamma control invalid")
- }
-
- fd, err := MemfdCreate("gamma-ramp", 0)
- if err != nil {
- return err
- }
- defer syscall.Close(fd)
-
- if err := syscall.Ftruncate(fd, int64(len(data))); err != nil {
- return err
- }
-
- dupFd, err := syscall.Dup(fd)
- if err != nil {
- return err
- }
- f := os.NewFile(uintptr(dupFd), "gamma")
- defer f.Close()
-
- if _, err := f.Write(data); err != nil {
- return err
- }
- syscall.Seek(fd, 0, 0)
-
- return ctrl.SetGamma(fd)
-}
-
-func (m *Manager) updateStateFromSchedule() {
- now := time.Now()
-
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- m.scheduleMutex.RLock()
- times := m.schedule.times
- m.scheduleMutex.RUnlock()
-
- var pos float64
- var temp int
- var isDay bool
- var deadline time.Time
-
- if times.Sunrise.IsZero() {
- pos = 1.0
- temp = config.HighTemp
- isDay = true
- deadline = m.tomorrow(now)
- } else {
- pos = m.getSunPosition(now)
- temp = m.getTempFromPosition(pos)
- deadline = m.getNextDeadline(now)
- isDay = now.After(times.Sunrise) && now.Before(times.Sunset)
- }
-
- newState := State{
- Config: config,
- CurrentTemp: temp,
- NextTransition: deadline,
- SunriseTime: times.Sunrise,
- SunsetTime: times.Sunset,
- DawnTime: times.Dawn,
- NightTime: times.Night,
- IsDay: isDay,
- SunPosition: pos,
- }
-
- m.stateMutex.Lock()
- m.state = &newState
- m.stateMutex.Unlock()
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
- currentState := m.GetState()
- if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
- pending = false
- continue
- }
- m.subscribers.Range(func(_ string, ch chan State) bool {
- select {
- case ch <- currentState:
- default:
- }
- return true
- })
- stateCopy := currentState
- m.lastNotified = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) dbusMonitor() {
- defer m.wg.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case sig := <-m.dbusSignal:
- if sig == nil {
- continue
- }
- m.handleDBusSignal(sig)
- }
- }
-}
-
-func (m *Manager) handleDBusSignal(sig *dbus.Signal) {
- switch {
- case sig.Name != "org.freedesktop.login1.Manager.PrepareForSleep":
- return
- case len(sig.Body) == 0:
- return
- }
- preparing, ok := sig.Body[0].(bool)
- if !ok || preparing {
- return
- }
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
- if !enabled {
- return
- }
- time.AfterFunc(500*time.Millisecond, func() {
- m.post(m.handleResume)
- })
-}
-
-func (m *Manager) handleResume() {
- m.configMutex.RLock()
- stillEnabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- switch {
- case !stillEnabled:
- return
- case !m.controlsInitialized:
- return
- case m.connectionDead.Load():
- return
- }
-
- // Compositors (Niri, Hyprland, wlroots-based) re-apply the cached gamma
- // ramp to DRM on resume; gamma_control objects stay valid. We just need
- // to force a resend so the schedule catches up with the current time of
- // day — the original #1235 regression was caused by lastAppliedTemp
- // matching and the send being skipped.
- m.recalcSchedule(time.Now())
- m.lastAppliedTemp = 0
- m.applyCurrentTemp("resume")
-}
-
-func (m *Manager) triggerUpdate() {
- select {
- case m.updateTrigger <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) SetConfig(config Config) error {
- if err := config.Validate(); err != nil {
- return err
- }
- m.configMutex.Lock()
- m.config = config
- m.configMutex.Unlock()
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetTemperature(low, high int) error {
- m.configMutex.Lock()
- if m.config.LowTemp == low && m.config.HighTemp == high {
- m.configMutex.Unlock()
- return nil
- }
- m.config.LowTemp = low
- m.config.HighTemp = high
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetLocation(lat, lon float64) error {
- m.configMutex.Lock()
- if m.config.Latitude != nil && m.config.Longitude != nil &&
- *m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
- m.configMutex.Unlock()
- return nil
- }
- m.config.Latitude = &lat
- m.config.Longitude = &lon
- m.config.UseIPLocation = false
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetUseIPLocation(use bool) {
- m.configMutex.Lock()
- if m.config.UseIPLocation == use {
- m.configMutex.Unlock()
- return
- }
- m.config.UseIPLocation = use
- if use {
- m.config.Latitude = nil
- m.config.Longitude = nil
- }
- m.configMutex.Unlock()
-
- if use {
- m.locationMutex.Lock()
- m.cachedIPLat = nil
- m.cachedIPLon = nil
- m.locationMutex.Unlock()
- }
- m.triggerUpdate()
-}
-
-func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
- m.configMutex.Lock()
- if m.config.ManualSunrise != nil && m.config.ManualSunset != nil &&
- m.config.ManualSunrise.Hour() == sunrise.Hour() && m.config.ManualSunrise.Minute() == sunrise.Minute() &&
- m.config.ManualSunset.Hour() == sunset.Hour() && m.config.ManualSunset.Minute() == sunset.Minute() {
- m.configMutex.Unlock()
- return nil
- }
- m.config.ManualSunrise = &sunrise
- m.config.ManualSunset = &sunset
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) ClearManualTimes() {
- m.configMutex.Lock()
- if m.config.ManualSunrise == nil && m.config.ManualSunset == nil {
- m.configMutex.Unlock()
- return
- }
- m.config.ManualSunrise = nil
- m.config.ManualSunset = nil
- m.configMutex.Unlock()
- m.triggerUpdate()
-}
-
-func (m *Manager) SetGamma(gamma float64) error {
- m.configMutex.Lock()
- if m.config.Gamma == gamma {
- m.configMutex.Unlock()
- return nil
- }
- m.config.Gamma = gamma
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetEnabled(enabled bool) {
- m.configMutex.Lock()
- wasEnabled := m.config.Enabled
- if wasEnabled == enabled {
- m.configMutex.Unlock()
- return
- }
- m.config.Enabled = enabled
- highTemp := m.config.HighTemp
- m.configMutex.Unlock()
-
- switch {
- case enabled && !m.controlsInitialized:
- m.post(func() {
- gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
- m.availOutputsMu.RLock()
- outs := slices.Clone(m.availableOutputs)
- m.availOutputsMu.RUnlock()
- if err := m.setupOutputControls(outs, gammaMgr); err != nil {
- log.Errorf("gamma: failed to create controls: %v", err)
- return
- }
- m.controlsInitialized = true
- m.triggerUpdate()
- })
- case enabled && !wasEnabled:
- m.triggerUpdate()
- case !enabled && m.controlsInitialized:
- m.post(func() {
- m.outputs.Range(func(id uint32, out *outputState) bool {
- if out.gammaControl != nil {
- out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1).Destroy()
- }
- return true
- })
- m.outputs.Range(func(key uint32, _ *outputState) bool {
- m.outputs.Delete(key)
- return true
- })
- m.controlsInitialized = false
- })
- _ = highTemp
- }
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.wg.Wait()
- m.notifierWg.Wait()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.outputs.Range(func(_ uint32, out *outputState) bool {
- if ctrl, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1); ok {
- ctrl.Destroy()
- }
- return true
- })
- m.outputs.Range(func(key uint32, _ *outputState) bool {
- m.outputs.Delete(key)
- return true
- })
-
- if manager, ok := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1); ok {
- manager.Destroy()
- }
-
- if m.dbusConn != nil {
- m.dbusConn.RemoveSignal(m.dbusSignal)
- m.dbusConn.Close()
- }
-}
-
-func MemfdCreate(name string, flags int) (int, error) {
- fd, err := unix.MemfdCreate(name, flags)
- if err != nil {
- return -1, err
- }
- return fd, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go
deleted file mode 100644
index 8c00129..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go
+++ /dev/null
@@ -1,414 +0,0 @@
-package wayland
-
-import (
- "errors"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-
- mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
-)
-
-func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
- m := &Manager{
- cmdq: make(chan cmd, 8192),
- stopChan: make(chan struct{}),
- }
-
- m.wg.Add(1)
- go m.waylandActor()
-
- state := &outputState{
- id: 1,
- registryName: 100,
- rampSize: 256,
- }
- m.outputs.Store(state.id, state)
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.post(func() {
- if out, ok := m.outputs.Load(state.id); ok {
- out.rampSize = uint32(j)
- out.failed = j%2 == 0
- out.retryCount = j
- out.lastFailTime = time.Now()
- }
- })
- }
- }(i)
- }
-
- wg.Wait()
-
- done := make(chan struct{})
- m.post(func() { close(done) })
- <-done
-
- close(m.stopChan)
- m.wg.Wait()
-}
-
-func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
- m := &Manager{
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- updateTrigger: make(chan struct{}, 1),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- subID := string(rune('a' + id))
- ch := m.Subscribe(subID)
- assert.NotNil(t, ch)
- time.Sleep(time.Millisecond)
- m.Unsubscribe(subID)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentGetState(t *testing.T) {
- m := &Manager{
- state: &State{
- CurrentTemp: 5000,
- IsDay: true,
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- s := m.GetState()
- assert.GreaterOrEqual(t, s.CurrentTemp, 0)
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.stateMutex.Lock()
- m.state = &State{
- CurrentTemp: 4000 + i*100,
- IsDay: j%2 == 0,
- }
- m.stateMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentConfigAccess(t *testing.T) {
- m := &Manager{
- config: DefaultConfig(),
- }
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.configMutex.RLock()
- _ = m.config.LowTemp
- _ = m.config.HighTemp
- _ = m.config.Enabled
- m.configMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.configMutex.Lock()
- m.config.LowTemp = 3000 + j
- m.config.HighTemp = 7000 - j
- m.config.Enabled = j%2 == 0
- m.configMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &outputState{
- id: key,
- rampSize: uint32(j),
- failed: j%2 == 0,
- }
- m.outputs.Store(key, state)
-
- if loaded, ok := m.outputs.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.outputs.Range(func(k uint32, v *outputState) bool {
- _ = v.rampSize
- _ = v.failed
- return true
- })
- }
-
- m.outputs.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_LocationCacheConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 20
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.locationMutex.RLock()
- _ = m.cachedIPLat
- _ = m.cachedIPLon
- m.locationMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- lat := float64(40 + i)
- lon := float64(-74 + j)
- m.locationMutex.Lock()
- m.cachedIPLat = &lat
- m.cachedIPLon = &lon
- m.locationMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ScheduleConcurrentAccess(t *testing.T) {
- now := time.Now()
- m := &Manager{
- schedule: sunSchedule{
- times: SunTimes{
- Dawn: now,
- Sunrise: now.Add(time.Hour),
- Sunset: now.Add(12 * time.Hour),
- Night: now.Add(13 * time.Hour),
- },
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.scheduleMutex.RLock()
- _ = m.schedule.times.Dawn
- _ = m.schedule.times.Sunrise
- _ = m.schedule.times.Sunset
- _ = m.schedule.condition
- m.scheduleMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.scheduleMutex.Lock()
- m.schedule.times.Dawn = time.Now()
- m.schedule.times.Sunrise = time.Now().Add(time.Hour)
- m.schedule.condition = SunNormal
- m.scheduleMutex.Unlock()
- }
- }()
- }
-
- wg.Wait()
-}
-
-func TestInterpolate_EdgeCases(t *testing.T) {
- now := time.Now()
-
- tests := []struct {
- name string
- now time.Time
- start time.Time
- stop time.Time
- expected float64
- }{
- {
- name: "same start and stop",
- now: now,
- start: now,
- stop: now,
- expected: 1.0,
- },
- {
- name: "now before start",
- now: now,
- start: now.Add(time.Hour),
- stop: now.Add(2 * time.Hour),
- expected: 0.0,
- },
- {
- name: "now after stop",
- now: now.Add(3 * time.Hour),
- start: now,
- stop: now.Add(time.Hour),
- expected: 1.0,
- },
- {
- name: "now at midpoint",
- now: now.Add(30 * time.Minute),
- start: now,
- stop: now.Add(time.Hour),
- expected: 0.5,
- },
- {
- name: "now equals start",
- now: now,
- start: now,
- stop: now.Add(time.Hour),
- expected: 0.0,
- },
- {
- name: "now equals stop",
- now: now.Add(time.Hour),
- start: now,
- stop: now.Add(time.Hour),
- expected: 1.0,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := interpolate(tt.now, tt.start, tt.stop)
- assert.InDelta(t, tt.expected, result, 0.01)
- })
- }
-}
-
-func TestGenerateGammaRamp_ZeroSize(t *testing.T) {
- ramp := GenerateGammaRamp(0, 5000, 1.0)
- assert.Empty(t, ramp.Red)
- assert.Empty(t, ramp.Green)
- assert.Empty(t, ramp.Blue)
-}
-
-func TestGenerateGammaRamp_ValidSizes(t *testing.T) {
- sizes := []uint32{1, 256, 1024}
- temps := []int{1000, 4000, 6500, 10000}
- gammas := []float64{0.5, 1.0, 2.0}
-
- for _, size := range sizes {
- for _, temp := range temps {
- for _, gamma := range gammas {
- ramp := GenerateGammaRamp(size, temp, gamma)
- assert.Len(t, ramp.Red, int(size))
- assert.Len(t, ramp.Green, int(size))
- assert.Len(t, ramp.Blue, int(size))
- }
- }
- }
-}
-
-func TestNotifySubscribers_NonBlocking(t *testing.T) {
- m := &Manager{
- dirty: make(chan struct{}, 1),
- }
-
- for i := 0; i < 10; i++ {
- m.notifySubscribers()
- }
-
- assert.Len(t, m.dirty, 1)
-}
-
-func TestNewManager_GetRegistryError(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- mockDisplay.EXPECT().Context().Return(nil)
- mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
-
- config := DefaultConfig()
- _, err := NewManager(mockDisplay, config)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "get registry")
-}
-
-func TestNewManager_InvalidConfig(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- config := Config{
- LowTemp: 500,
- HighTemp: 6500,
- Gamma: 1.0,
- }
-
- _, err := NewManager(mockDisplay, config)
- assert.Error(t, err)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go
deleted file mode 100644
index c978a8e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package wayland
-
-import (
- "math"
- "time"
-)
-
-const (
- degToRad = math.Pi / 180.0
- radToDeg = 180.0 / math.Pi
-)
-
-type SunCondition int
-
-const (
- SunNormal SunCondition = iota
- SunMidnightSun
- SunPolarNight
-)
-
-type SunTimes struct {
- Dawn time.Time
- Sunrise time.Time
- Sunset time.Time
- Night time.Time
-}
-
-func daysInYear(year int) int {
- if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
- return 366
- }
- return 365
-}
-
-func dateOrbitAngle(t time.Time) float64 {
- return 2 * math.Pi / float64(daysInYear(t.Year())) * float64(t.YearDay()-1)
-}
-
-func equationOfTime(orbitAngle float64) float64 {
- return 4 * (0.000075 +
- 0.001868*math.Cos(orbitAngle) -
- 0.032077*math.Sin(orbitAngle) -
- 0.014615*math.Cos(2*orbitAngle) -
- 0.040849*math.Sin(2*orbitAngle))
-}
-
-func sunDeclination(orbitAngle float64) float64 {
- return 0.006918 -
- 0.399912*math.Cos(orbitAngle) +
- 0.070257*math.Sin(orbitAngle) -
- 0.006758*math.Cos(2*orbitAngle) +
- 0.000907*math.Sin(2*orbitAngle) -
- 0.002697*math.Cos(3*orbitAngle) +
- 0.00148*math.Sin(3*orbitAngle)
-}
-
-func sunHourAngle(latRad, declination, targetSunRad float64) float64 {
- return math.Acos(math.Cos(targetSunRad)/
- math.Cos(latRad)*math.Cos(declination) -
- math.Tan(latRad)*math.Tan(declination))
-}
-
-func hourAngleToSeconds(hourAngle, eqtime float64) float64 {
- return radToDeg * (4.0*math.Pi - 4*hourAngle - eqtime) * 60
-}
-
-func sunCondition(latRad, declination float64) SunCondition {
- signLat := latRad >= 0
- signDecl := declination >= 0
- if signLat == signDecl {
- return SunMidnightSun
- }
- return SunPolarNight
-}
-
-func CalculateSunTimesWithTwilight(lat, lon float64, date time.Time, elevTwilight, elevDaylight float64) (SunTimes, SunCondition) {
- latRad := lat * degToRad
- elevTwilightRad := (90.833 - elevTwilight) * degToRad
- elevDaylightRad := (90.833 - elevDaylight) * degToRad
-
- utc := date.UTC()
- orbitAngle := dateOrbitAngle(utc)
- decl := sunDeclination(orbitAngle)
- eqtime := equationOfTime(orbitAngle)
-
- haTwilight := sunHourAngle(latRad, decl, elevTwilightRad)
- haDaylight := sunHourAngle(latRad, decl, elevDaylightRad)
-
- if math.IsNaN(haTwilight) || math.IsNaN(haDaylight) {
- cond := sunCondition(latRad, decl)
- return SunTimes{}, cond
- }
-
- dayStart := time.Date(utc.Year(), utc.Month(), utc.Day(), 0, 0, 0, 0, time.UTC)
- lonOffset := time.Duration(-lon*4) * time.Minute
-
- dawnSecs := hourAngleToSeconds(math.Abs(haTwilight), eqtime)
- sunriseSecs := hourAngleToSeconds(math.Abs(haDaylight), eqtime)
- sunsetSecs := hourAngleToSeconds(-math.Abs(haDaylight), eqtime)
- nightSecs := hourAngleToSeconds(-math.Abs(haTwilight), eqtime)
-
- return SunTimes{
- Dawn: dayStart.Add(time.Duration(dawnSecs)*time.Second + lonOffset).In(date.Location()),
- Sunrise: dayStart.Add(time.Duration(sunriseSecs)*time.Second + lonOffset).In(date.Location()),
- Sunset: dayStart.Add(time.Duration(sunsetSecs)*time.Second + lonOffset).In(date.Location()),
- Night: dayStart.Add(time.Duration(nightSecs)*time.Second + lonOffset).In(date.Location()),
- }, SunNormal
-}
-
-func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
- times, cond := CalculateSunTimesWithTwilight(lat, lon, date, -6.0, 3.0)
- switch cond {
- case SunMidnightSun:
- dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
- dayEnd := dayStart.Add(24*time.Hour - time.Second)
- return SunTimes{Dawn: dayStart, Sunrise: dayStart, Sunset: dayEnd, Night: dayEnd}
- case SunPolarNight:
- dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
- return SunTimes{Dawn: dayStart, Sunrise: dayStart, Sunset: dayStart, Night: dayStart}
- }
- return times
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go
deleted file mode 100644
index b2815d9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go
+++ /dev/null
@@ -1,387 +0,0 @@
-package wayland
-
-import (
- "math"
- "testing"
- "time"
-)
-
-func calculateTemperature(config Config, now time.Time) int {
- if !config.Enabled {
- return config.HighTemp
- }
-
- var sunrise, sunset time.Time
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- year, month, day := now.Date()
- loc := now.Location()
-
- sunrise = time.Date(year, month, day,
- config.ManualSunrise.Hour(),
- config.ManualSunrise.Minute(),
- config.ManualSunrise.Second(), 0, loc)
- sunset = time.Date(year, month, day,
- config.ManualSunset.Hour(),
- config.ManualSunset.Minute(),
- config.ManualSunset.Second(), 0, loc)
-
- if sunset.Before(sunrise) {
- sunset = sunset.Add(24 * time.Hour)
- }
- } else if config.UseIPLocation {
- lat, lon, err := FetchIPLocation()
- if err != nil {
- return config.HighTemp
- }
- times := CalculateSunTimes(*lat, *lon, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else if config.Latitude != nil && config.Longitude != nil {
- times := CalculateSunTimes(*config.Latitude, *config.Longitude, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else {
- return config.HighTemp
- }
-
- if now.Before(sunrise) || now.After(sunset) {
- return config.LowTemp
- }
- return config.HighTemp
-}
-
-func calculateNextTransition(config Config, now time.Time) time.Time {
- if !config.Enabled {
- return now.Add(24 * time.Hour)
- }
-
- var sunrise, sunset time.Time
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- year, month, day := now.Date()
- loc := now.Location()
-
- sunrise = time.Date(year, month, day,
- config.ManualSunrise.Hour(),
- config.ManualSunrise.Minute(),
- config.ManualSunrise.Second(), 0, loc)
- sunset = time.Date(year, month, day,
- config.ManualSunset.Hour(),
- config.ManualSunset.Minute(),
- config.ManualSunset.Second(), 0, loc)
-
- if sunset.Before(sunrise) {
- sunset = sunset.Add(24 * time.Hour)
- }
- } else if config.UseIPLocation {
- lat, lon, err := FetchIPLocation()
- if err != nil {
- return now.Add(24 * time.Hour)
- }
- times := CalculateSunTimes(*lat, *lon, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else if config.Latitude != nil && config.Longitude != nil {
- times := CalculateSunTimes(*config.Latitude, *config.Longitude, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else {
- return now.Add(24 * time.Hour)
- }
-
- if now.Before(sunrise) {
- return sunrise
- }
- if now.Before(sunset) {
- return sunset
- }
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- year, month, day := now.Add(24 * time.Hour).Date()
- loc := now.Location()
- nextSunrise := time.Date(year, month, day,
- config.ManualSunrise.Hour(),
- config.ManualSunrise.Minute(),
- config.ManualSunrise.Second(), 0, loc)
- return nextSunrise
- }
-
- if config.UseIPLocation {
- lat, lon, err := FetchIPLocation()
- if err != nil {
- return now.Add(24 * time.Hour)
- }
- nextDayTimes := CalculateSunTimes(*lat, *lon, now.Add(24*time.Hour))
- return nextDayTimes.Sunrise
- }
-
- if config.Latitude != nil && config.Longitude != nil {
- nextDayTimes := CalculateSunTimes(*config.Latitude, *config.Longitude, now.Add(24*time.Hour))
- return nextDayTimes.Sunrise
- }
-
- return now.Add(24 * time.Hour)
-}
-
-func TestCalculateSunTimes(t *testing.T) {
- tests := []struct {
- name string
- lat float64
- lon float64
- date time.Time
- checkFunc func(*testing.T, SunTimes)
- }{
- {
- name: "new_york_summer",
- lat: 40.7128,
- lon: -74.0060,
- date: time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local),
- checkFunc: func(t *testing.T, times SunTimes) {
- if times.Sunrise.Hour() < 4 || times.Sunrise.Hour() > 6 {
- t.Logf("sunrise: %v", times.Sunrise)
- }
- if times.Sunset.Hour() < 19 || times.Sunset.Hour() > 21 {
- t.Logf("sunset: %v", times.Sunset)
- }
- if !times.Sunset.After(times.Sunrise) {
- t.Error("sunset should be after sunrise")
- }
- },
- },
- {
- name: "london_winter",
- lat: 51.5074,
- lon: -0.1278,
- date: time.Date(2024, 12, 21, 12, 0, 0, 0, time.UTC),
- checkFunc: func(t *testing.T, times SunTimes) {
- if times.Sunrise.Hour() < 7 || times.Sunrise.Hour() > 9 {
- t.Errorf("unexpected sunrise hour: %d", times.Sunrise.Hour())
- }
- if times.Sunset.Hour() < 15 || times.Sunset.Hour() > 17 {
- t.Errorf("unexpected sunset hour: %d", times.Sunset.Hour())
- }
- },
- },
- {
- name: "equator_equinox",
- lat: 0.0,
- lon: 0.0,
- date: time.Date(2024, 3, 20, 12, 0, 0, 0, time.UTC),
- checkFunc: func(t *testing.T, times SunTimes) {
- if times.Sunrise.Hour() < 5 || times.Sunrise.Hour() > 7 {
- t.Errorf("unexpected sunrise hour: %d", times.Sunrise.Hour())
- }
- if times.Sunset.Hour() < 17 || times.Sunset.Hour() > 19 {
- t.Errorf("unexpected sunset hour: %d", times.Sunset.Hour())
- }
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- times := CalculateSunTimes(tt.lat, tt.lon, tt.date)
- tt.checkFunc(t, times)
- })
- }
-}
-
-func TestCalculateTemperature(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
- date := time.Date(2024, 6, 21, 0, 0, 0, 0, time.Local)
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: &lat,
- Longitude: &lon,
- Enabled: true,
- }
-
- times := CalculateSunTimes(lat, lon, date)
-
- tests := []struct {
- name string
- timeFunc func() time.Time
- wantTemp int
- }{
- {
- name: "midnight",
- timeFunc: func() time.Time { return times.Sunrise.Add(-4 * time.Hour) },
- wantTemp: 4000,
- },
- {
- name: "sunrise",
- timeFunc: func() time.Time { return times.Sunrise },
- wantTemp: 6500,
- },
- {
- name: "noon",
- timeFunc: func() time.Time { return times.Sunrise.Add(6 * time.Hour) },
- wantTemp: 6500,
- },
- {
- name: "after_sunset_transition",
- timeFunc: func() time.Time { return times.Sunset.Add(2 * time.Hour) },
- wantTemp: 4000,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- temp := calculateTemperature(config, tt.timeFunc())
-
- if math.Abs(float64(temp-tt.wantTemp)) > 500 {
- t.Errorf("temperature = %d, want approximately %d", temp, tt.wantTemp)
- }
- })
- }
-}
-
-func TestCalculateTemperatureManualTimes(t *testing.T) {
- sunrise := time.Date(0, 1, 1, 6, 30, 0, 0, time.Local)
- sunset := time.Date(0, 1, 1, 18, 30, 0, 0, time.Local)
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunrise: &sunrise,
- ManualSunset: &sunset,
- Enabled: true,
- }
-
- tests := []struct {
- name string
- time time.Time
- want int
- }{
- {"before_sunrise", time.Date(2024, 1, 1, 3, 0, 0, 0, time.Local), 4000},
- {"at_sunrise", time.Date(2024, 1, 1, 6, 30, 0, 0, time.Local), 6500},
- {"midday", time.Date(2024, 1, 1, 12, 0, 0, 0, time.Local), 6500},
- {"at_sunset", time.Date(2024, 1, 1, 18, 30, 0, 0, time.Local), 6500},
- {"after_sunset", time.Date(2024, 1, 1, 22, 0, 0, 0, time.Local), 4000},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- temp := calculateTemperature(config, tt.time)
- if math.Abs(float64(temp-tt.want)) > 500 {
- t.Errorf("temperature = %d, want approximately %d", temp, tt.want)
- }
- })
- }
-}
-
-func TestCalculateTemperatureDisabled(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: &lat,
- Longitude: &lon,
- Enabled: false,
- }
-
- temp := calculateTemperature(config, time.Now())
- if temp != 6500 {
- t.Errorf("disabled should return high temp, got %d", temp)
- }
-}
-
-func TestCalculateNextTransition(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
- date := time.Date(2024, 6, 21, 0, 0, 0, 0, time.Local)
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: &lat,
- Longitude: &lon,
- Enabled: true,
- }
-
- times := CalculateSunTimes(lat, lon, date)
-
- tests := []struct {
- name string
- now time.Time
- checkFunc func(*testing.T, time.Time)
- }{
- {
- name: "before_sunrise",
- now: times.Sunrise.Add(-2 * time.Hour),
- checkFunc: func(t *testing.T, next time.Time) {
- if !next.Equal(times.Sunrise) && !next.After(times.Sunrise.Add(-1*time.Minute)) {
- t.Error("next transition should be at or near sunrise")
- }
- },
- },
- {
- name: "after_sunrise",
- now: times.Sunrise.Add(2 * time.Hour),
- checkFunc: func(t *testing.T, next time.Time) {
- if !next.After(times.Sunrise) {
- t.Error("next transition should be after sunrise")
- }
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- next := calculateNextTransition(config, tt.now)
- tt.checkFunc(t, next)
- })
- }
-}
-
-func TestSunTimesWithTwilight(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
- date := time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local)
-
- times, cond := CalculateSunTimesWithTwilight(lat, lon, date, -6.0, 3.0)
-
- if cond != SunNormal {
- t.Errorf("expected SunNormal, got %v", cond)
- }
- if !times.Dawn.Before(times.Sunrise) {
- t.Error("dawn should be before sunrise")
- }
- if !times.Sunrise.Before(times.Sunset) {
- t.Error("sunrise should be before sunset")
- }
- if !times.Sunset.Before(times.Night) {
- t.Error("sunset should be before night")
- }
-}
-
-func TestSunConditions(t *testing.T) {
- tests := []struct {
- name string
- lat float64
- date time.Time
- expected SunCondition
- }{
- {
- name: "normal_conditions",
- lat: 40.0,
- date: time.Date(2024, 6, 21, 12, 0, 0, 0, time.UTC),
- expected: SunNormal,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, cond := CalculateSunTimesWithTwilight(tt.lat, 0, tt.date, -6.0, 3.0)
- if cond != tt.expected {
- t.Errorf("expected condition %v, got %v", tt.expected, cond)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go
deleted file mode 100644
index 31bac15..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go
+++ /dev/null
@@ -1,216 +0,0 @@
-package wayland
-
-import (
- "math"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "github.com/godbus/dbus/v5"
-)
-
-type GammaState int
-
-const (
- StateNormal GammaState = iota
- StateTransition
- StateStatic
-)
-
-type Config struct {
- Outputs []string
- LowTemp int
- HighTemp int
- Latitude *float64
- Longitude *float64
- UseIPLocation bool
- ManualSunrise *time.Time
- ManualSunset *time.Time
- ManualDuration *time.Duration
- Gamma float64
- Enabled bool
- ElevationTwilight float64
- ElevationDaylight float64
-}
-
-type State struct {
- Config Config `json:"config"`
- CurrentTemp int `json:"currentTemp"`
- NextTransition time.Time `json:"nextTransition"`
- SunriseTime time.Time `json:"sunriseTime"`
- SunsetTime time.Time `json:"sunsetTime"`
- DawnTime time.Time `json:"dawnTime"`
- NightTime time.Time `json:"nightTime"`
- IsDay bool `json:"isDay"`
- SunPosition float64 `json:"sunPosition"`
-}
-
-type cmd struct {
- fn func()
-}
-
-type sunSchedule struct {
- times SunTimes
- condition SunCondition
- dawnStepTime time.Duration
- nightStepTime time.Duration
- calcDay time.Time
-}
-
-type Manager struct {
- config Config
- configMutex sync.RWMutex
- state *State
- stateMutex sync.RWMutex
-
- display wlclient.WaylandDisplay
- ctx *wlclient.Context
- registry *wlclient.Registry
- gammaControl any
- availableOutputs []*wlclient.Output
- availOutputsMu sync.RWMutex
- outputRegNames syncmap.Map[uint32, uint32]
- outputs syncmap.Map[uint32, *outputState]
- controlsInitialized bool
- connectionDead atomic.Bool
-
- cmdq chan cmd
- alive bool
-
- stopChan chan struct{}
- updateTrigger chan struct{}
- wg sync.WaitGroup
-
- schedule sunSchedule
- scheduleMutex sync.RWMutex
- gammaState GammaState
-
- cachedIPLat *float64
- cachedIPLon *float64
- locationMutex sync.RWMutex
-
- subscribers syncmap.Map[string, chan State]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotified *State
-
- dbusConn *dbus.Conn
- dbusSignal chan *dbus.Signal
-
- geoClient geolocation.Client
-
- lastAppliedTemp int
- lastAppliedGamma float64
-}
-
-type outputState struct {
- id uint32
- registryName uint32
- output *wlclient.Output
- gammaControl any
- rampSize uint32
- failed bool
- isVirtual bool
- retryCount int
- lastFailTime time.Time
-}
-
-func DefaultConfig() Config {
- return Config{
- Outputs: []string{},
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 1.0,
- Enabled: false,
- ElevationTwilight: -6.0,
- ElevationDaylight: 3.0,
- }
-}
-
-func (c *Config) Validate() error {
- if c.LowTemp < 1000 || c.LowTemp > 10000 {
- return errdefs.ErrInvalidTemperature
- }
- if c.HighTemp < 1000 || c.HighTemp > 10000 {
- return errdefs.ErrInvalidTemperature
- }
- if c.LowTemp > c.HighTemp {
- return errdefs.ErrInvalidTemperature
- }
- if c.Gamma <= 0 || c.Gamma > 10 {
- return errdefs.ErrInvalidGamma
- }
- if c.Latitude != nil && (math.Abs(*c.Latitude) > 90) {
- return errdefs.ErrInvalidLocation
- }
- if c.Longitude != nil && (math.Abs(*c.Longitude) > 180) {
- return errdefs.ErrInvalidLocation
- }
- if (c.Latitude != nil) != (c.Longitude != nil) {
- return errdefs.ErrInvalidLocation
- }
- if (c.ManualSunrise != nil) != (c.ManualSunset != nil) {
- return errdefs.ErrInvalidManualTimes
- }
- return nil
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{}
- }
- return *m.state
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func stateChanged(old, new *State) bool {
- if old == nil || new == nil {
- return true
- }
- if old.CurrentTemp != new.CurrentTemp {
- return true
- }
- if old.IsDay != new.IsDay {
- return true
- }
- if !old.NextTransition.Equal(new.NextTransition) {
- return true
- }
- if !old.SunriseTime.Equal(new.SunriseTime) {
- return true
- }
- if !old.SunsetTime.Equal(new.SunsetTime) {
- return true
- }
- if old.Config.Enabled != new.Config.Enabled {
- return true
- }
- if old.SunPosition != new.SunPosition {
- return true
- }
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go
deleted file mode 100644
index f5bbb42..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go
+++ /dev/null
@@ -1,330 +0,0 @@
-package wayland
-
-import (
- "testing"
- "time"
-)
-
-func TestConfigValidate(t *testing.T) {
- tests := []struct {
- name string
- config Config
- wantErr bool
- }{
- {
- name: "valid_default",
- config: DefaultConfig(),
- wantErr: false,
- },
- {
- name: "valid_with_location",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40.7128),
- Longitude: floatPtr(-74.0060),
- Gamma: 1.0,
- Enabled: true,
- },
- wantErr: false,
- },
- {
- name: "valid_manual_times",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunrise: timePtr(time.Date(0, 1, 1, 6, 30, 0, 0, time.Local)),
- ManualSunset: timePtr(time.Date(0, 1, 1, 18, 30, 0, 0, time.Local)),
- Gamma: 1.0,
- Enabled: true,
- },
- wantErr: false,
- },
- {
- name: "invalid_low_temp_too_low",
- config: Config{
- LowTemp: 500,
- HighTemp: 6500,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_low_temp_too_high",
- config: Config{
- LowTemp: 15000,
- HighTemp: 20000,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_high_temp_too_low",
- config: Config{
- LowTemp: 4000,
- HighTemp: 500,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "valid_temps_equal",
- config: Config{
- LowTemp: 5000,
- HighTemp: 5000,
- Gamma: 1.0,
- },
- wantErr: false,
- },
- {
- name: "invalid_temps_reversed",
- config: Config{
- LowTemp: 6500,
- HighTemp: 4000,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_gamma_zero",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 0,
- },
- wantErr: true,
- },
- {
- name: "invalid_gamma_negative",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: -1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_gamma_too_high",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 15.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_latitude_too_high",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(100),
- Longitude: floatPtr(0),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_latitude_too_low",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(-100),
- Longitude: floatPtr(0),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_longitude_too_high",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40),
- Longitude: floatPtr(200),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_longitude_too_low",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40),
- Longitude: floatPtr(-200),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_latitude_without_longitude",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_longitude_without_latitude",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Longitude: floatPtr(-74),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_sunrise_without_sunset",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunrise: timePtr(time.Date(0, 1, 1, 6, 30, 0, 0, time.Local)),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_sunset_without_sunrise",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunset: timePtr(time.Date(0, 1, 1, 18, 30, 0, 0, time.Local)),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := tt.config.Validate()
- if (err != nil) != tt.wantErr {
- t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
- }
-}
-
-func TestDefaultConfig(t *testing.T) {
- config := DefaultConfig()
-
- if config.LowTemp != 4000 {
- t.Errorf("default low temp = %d, want 4000", config.LowTemp)
- }
- if config.HighTemp != 6500 {
- t.Errorf("default high temp = %d, want 6500", config.HighTemp)
- }
- if config.Gamma != 1.0 {
- t.Errorf("default gamma = %f, want 1.0", config.Gamma)
- }
- if config.Enabled {
- t.Error("default should be disabled")
- }
- if config.Latitude != nil {
- t.Error("default should not have latitude")
- }
- if config.Longitude != nil {
- t.Error("default should not have longitude")
- }
-}
-
-func TestStateChanged(t *testing.T) {
- baseState := &State{
- CurrentTemp: 5000,
- NextTransition: time.Now(),
- SunriseTime: time.Now().Add(6 * time.Hour),
- SunsetTime: time.Now().Add(18 * time.Hour),
- IsDay: true,
- Config: DefaultConfig(),
- }
-
- tests := []struct {
- name string
- old *State
- new *State
- wantChanged bool
- }{
- {
- name: "nil_old",
- old: nil,
- new: baseState,
- wantChanged: true,
- },
- {
- name: "nil_new",
- old: baseState,
- new: nil,
- wantChanged: true,
- },
- {
- name: "same_state",
- old: baseState,
- new: baseState,
- wantChanged: false,
- },
- {
- name: "temp_changed",
- old: baseState,
- new: &State{
- CurrentTemp: 6000,
- NextTransition: baseState.NextTransition,
- SunriseTime: baseState.SunriseTime,
- SunsetTime: baseState.SunsetTime,
- IsDay: baseState.IsDay,
- Config: baseState.Config,
- },
- wantChanged: true,
- },
- {
- name: "is_day_changed",
- old: baseState,
- new: &State{
- CurrentTemp: baseState.CurrentTemp,
- NextTransition: baseState.NextTransition,
- SunriseTime: baseState.SunriseTime,
- SunsetTime: baseState.SunsetTime,
- IsDay: false,
- Config: baseState.Config,
- },
- wantChanged: true,
- },
- {
- name: "enabled_changed",
- old: baseState,
- new: &State{
- CurrentTemp: baseState.CurrentTemp,
- NextTransition: baseState.NextTransition,
- SunriseTime: baseState.SunriseTime,
- SunsetTime: baseState.SunsetTime,
- IsDay: baseState.IsDay,
- Config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 1.0,
- Enabled: true,
- },
- },
- wantChanged: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- changed := stateChanged(tt.old, tt.new)
- if changed != tt.wantChanged {
- t.Errorf("stateChanged() = %v, want %v", changed, tt.wantChanged)
- }
- })
- }
-}
-
-func floatPtr(f float64) *float64 {
- return &f
-}
-
-func timePtr(t time.Time) *time.Time {
- return &t
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go
deleted file mode 100644
index 9bbb30a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go
+++ /dev/null
@@ -1,200 +0,0 @@
-package wlcontext
-
-import (
- "fmt"
- "golang.org/x/sys/unix"
- "os"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-type WaylandContext interface {
- Display() *wlclient.Display
- Post(fn func())
- FatalError() <-chan error
- Start()
- Close()
-}
-
-var _ WaylandContext = (*SharedContext)(nil)
-
-type SharedContext struct {
- display *wlclient.Display
- stopChan chan struct{}
- fatalError chan error
- cmdQueue chan func()
- wakeR int
- wakeW int
- wg sync.WaitGroup
- mu sync.Mutex
- started bool
-}
-
-func New() (*SharedContext, error) {
- display, err := wlclient.Connect("")
- if err != nil {
- return nil, fmt.Errorf("%w: %v", errdefs.ErrNoWaylandDisplay, err)
- }
-
- fds := make([]int, 2)
- if err := unix.Pipe(fds); err != nil {
- display.Context().Close()
- return nil, fmt.Errorf("failed to create wake pipe: %w", err)
- }
- if err := unix.SetNonblock(fds[0], true); err != nil {
- unix.Close(fds[0])
- unix.Close(fds[1])
- display.Context().Close()
- return nil, fmt.Errorf("failed to set wake pipe nonblock: %w", err)
- }
- if err := unix.SetNonblock(fds[1], true); err != nil {
- unix.Close(fds[0])
- unix.Close(fds[1])
- display.Context().Close()
- return nil, fmt.Errorf("failed to set wake pipe nonblock: %w", err)
- }
-
- sc := &SharedContext{
- display: display,
- stopChan: make(chan struct{}),
- fatalError: make(chan error, 1),
- cmdQueue: make(chan func(), 256),
- wakeR: fds[0],
- wakeW: fds[1],
- started: false,
- }
-
- return sc, nil
-}
-
-func (sc *SharedContext) Start() {
- sc.mu.Lock()
- defer sc.mu.Unlock()
-
- if sc.started {
- return
- }
-
- sc.started = true
- sc.wg.Add(1)
- go sc.eventDispatcher()
-}
-
-func (sc *SharedContext) Display() *wlclient.Display {
- return sc.display
-}
-
-func (sc *SharedContext) Post(fn func()) {
- select {
- case sc.cmdQueue <- fn:
- if _, err := unix.Write(sc.wakeW, []byte{1}); err != nil && err != unix.EAGAIN {
- log.Errorf("wake pipe write error: %v", err)
- }
- default:
- }
-}
-
-func (sc *SharedContext) FatalError() <-chan error {
- return sc.fatalError
-}
-
-func (sc *SharedContext) eventDispatcher() {
- defer sc.wg.Done()
- defer func() {
- if r := recover(); r != nil {
- err := fmt.Errorf("FATAL: Wayland event dispatcher panic: %v", r)
- log.Error(err)
- select {
- case sc.fatalError <- err:
- default:
- }
- }
- }()
-
- ctx := sc.display.Context()
- wlFd := ctx.Fd()
-
- pollFds := []unix.PollFd{
- {Fd: int32(wlFd), Events: unix.POLLIN},
- {Fd: int32(sc.wakeR), Events: unix.POLLIN},
- }
-
- consecutiveErrors := 0
- const maxConsecutiveErrors = 20
-
- for {
- sc.drainCmdQueue()
-
- select {
- case <-sc.stopChan:
- return
- default:
- }
-
- _, err := unix.Poll(pollFds, -1)
- switch {
- case err == unix.EINTR:
- continue
- case err != nil:
- log.Errorf("Poll error: %v", err)
- return
- }
-
- if pollFds[1].Revents&unix.POLLIN != 0 {
- var buf [64]byte
- if _, err := unix.Read(sc.wakeR, buf[:]); err != nil && err != unix.EAGAIN {
- log.Errorf("wake pipe read error: %v", err)
- }
- }
-
- if pollFds[0].Revents&unix.POLLIN == 0 {
- continue
- }
-
- if err := ctx.Dispatch(); err != nil && !os.IsTimeout(err) {
- consecutiveErrors++
- log.Warnf("Wayland connection error (%d/%d): %v", consecutiveErrors, maxConsecutiveErrors, err)
-
- if consecutiveErrors >= maxConsecutiveErrors {
- log.Errorf("Fatal: Wayland connection unrecoverable after %d attempts. Exiting dispatcher.", maxConsecutiveErrors)
- return
- }
-
- time.Sleep(100 * time.Millisecond * time.Duration(consecutiveErrors))
- continue
- }
-
- consecutiveErrors = 0
- }
-}
-
-func (sc *SharedContext) drainCmdQueue() {
- for {
- select {
- case fn := <-sc.cmdQueue:
- fn()
- default:
- return
- }
- }
-}
-
-func (sc *SharedContext) Close() {
- close(sc.stopChan)
- if _, err := unix.Write(sc.wakeW, []byte{1}); err != nil && err != unix.EAGAIN {
- log.Errorf("wake pipe write error on close: %v", err)
- }
- sc.wg.Wait()
-
- unix.Close(sc.wakeR)
- unix.Close(sc.wakeW)
-
- if sc.display == nil {
- return
- }
- sc.display.Context().Close()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go
deleted file mode 100644
index 11ecfbf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package wlcontext
-
-import (
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "golang.org/x/sys/unix"
-)
-
-func newTestSharedContext(t *testing.T, queueSize int) *SharedContext {
- t.Helper()
- fds := make([]int, 2)
- if err := unix.Pipe(fds); err != nil {
- t.Fatalf("failed to create test pipe: %v", err)
- }
- t.Cleanup(func() {
- unix.Close(fds[0])
- unix.Close(fds[1])
- })
- return &SharedContext{
- cmdQueue: make(chan func(), queueSize),
- stopChan: make(chan struct{}),
- wakeR: fds[0],
- wakeW: fds[1],
- }
-}
-
-func TestSharedContext_ConcurrentPostNonBlocking(t *testing.T) {
- sc := newTestSharedContext(t, 256)
-
- var wg sync.WaitGroup
- const goroutines = 100
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- sc.Post(func() {
- _ = id + j
- })
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestSharedContext_PostQueueFull(t *testing.T) {
- sc := newTestSharedContext(t, 2)
-
- sc.Post(func() {})
- sc.Post(func() {})
- sc.Post(func() {})
- sc.Post(func() {})
-
- assert.Len(t, sc.cmdQueue, 2)
-}
-
-func TestSharedContext_StartMultipleTimes(t *testing.T) {
- sc := newTestSharedContext(t, 256)
- sc.started = true
-
- var wg sync.WaitGroup
- const goroutines = 10
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- sc.Start()
- }()
- }
-
- wg.Wait()
-
- assert.True(t, sc.started)
-}
-
-func TestSharedContext_DrainCmdQueue(t *testing.T) {
- sc := newTestSharedContext(t, 256)
-
- counter := 0
- for i := 0; i < 10; i++ {
- sc.cmdQueue <- func() {
- counter++
- }
- }
-
- sc.drainCmdQueue()
-
- assert.Equal(t, 10, counter)
- assert.Len(t, sc.cmdQueue, 0)
-}
-
-func TestSharedContext_DrainCmdQueueEmpty(t *testing.T) {
- sc := newTestSharedContext(t, 256)
-
- sc.drainCmdQueue()
-
- assert.Len(t, sc.cmdQueue, 0)
-}
-
-func TestSharedContext_ConcurrentDrainAndPost(t *testing.T) {
- sc := newTestSharedContext(t, 256)
-
- var wg sync.WaitGroup
-
- wg.Add(1)
- go func() {
- defer wg.Done()
- for i := 0; i < 100; i++ {
- sc.Post(func() {})
- }
- }()
-
- wg.Add(1)
- go func() {
- defer wg.Done()
- for i := 0; i < 50; i++ {
- sc.drainCmdQueue()
- }
- }()
-
- wg.Wait()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go
deleted file mode 100644
index c885e13..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go
+++ /dev/null
@@ -1,282 +0,0 @@
-package wlroutput
-
-import (
- "encoding/json"
- "fmt"
- "net"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-type HeadConfig struct {
- Name string `json:"name"`
- Enabled bool `json:"enabled"`
- ModeID *uint32 `json:"modeId,omitempty"`
- CustomMode *struct {
- Width int32 `json:"width"`
- Height int32 `json:"height"`
- Refresh int32 `json:"refresh"`
- } `json:"customMode,omitempty"`
- Position *struct{ X, Y int32 } `json:"position,omitempty"`
- Transform *int32 `json:"transform,omitempty"`
- Scale *float64 `json:"scale,omitempty"`
- AdaptiveSync *uint32 `json:"adaptiveSync,omitempty"`
-}
-
-type ConfigurationRequest struct {
- Heads []HeadConfig `json:"heads"`
- Test bool `json:"test"`
-}
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- if manager == nil {
- models.RespondError(conn, req.ID, "wlroutput manager not initialized")
- return
- }
-
- switch req.Method {
- case "wlroutput.getState":
- handleGetState(conn, req, manager)
- case "wlroutput.applyConfiguration":
- handleApplyConfiguration(conn, req, manager, false)
- case "wlroutput.testConfiguration":
- handleApplyConfiguration(conn, req, manager, true)
- case "wlroutput.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleApplyConfiguration(conn net.Conn, req models.Request, manager *Manager, test bool) {
- headsParam, ok := models.Get[any](req, "heads")
- if !ok {
- models.RespondError(conn, req.ID, "missing 'heads' parameter")
- return
- }
-
- headsJSON, err := json.Marshal(headsParam)
- if err != nil {
- models.RespondError(conn, req.ID, "invalid 'heads' parameter format")
- return
- }
-
- var heads []HeadConfig
- if err := json.Unmarshal(headsJSON, &heads); err != nil {
- models.RespondError(conn, req.ID, fmt.Sprintf("invalid heads configuration: %v", err))
- return
- }
-
- if err := manager.ApplyConfiguration(heads, test); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- msg := "configuration applied"
- if test {
- msg = "configuration test succeeded"
- }
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: msg})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
-
-func (m *Manager) ApplyConfiguration(heads []HeadConfig, test bool) error {
- if m.manager == nil {
- return fmt.Errorf("output manager not initialized")
- }
-
- resultChan := make(chan error, 1)
-
- m.post(func() {
- m.wlMutex.Lock()
- defer m.wlMutex.Unlock()
-
- config, err := m.manager.CreateConfiguration(m.serial)
- if err != nil {
- resultChan <- fmt.Errorf("failed to create configuration: %w", err)
- return
- }
-
- responded := false
-
- config.SetSucceededHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1SucceededEvent) {
- if responded {
- return
- }
- responded = true
- log.Info("WlrOutput: configuration succeeded")
- config.Destroy()
- resultChan <- nil
- })
-
- config.SetFailedHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1FailedEvent) {
- if responded {
- return
- }
- responded = true
- log.Warn("WlrOutput: configuration failed")
- config.Destroy()
- resultChan <- fmt.Errorf("compositor rejected configuration")
- })
-
- config.SetCancelledHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1CancelledEvent) {
- if responded {
- return
- }
- responded = true
- log.Warn("WlrOutput: configuration cancelled")
- config.Destroy()
- resultChan <- fmt.Errorf("configuration cancelled (outdated serial)")
- })
-
- time.AfterFunc(time.Second, func() {
- m.post(func() {
- if responded {
- return
- }
- responded = true
- config.Destroy()
- resultChan <- fmt.Errorf("timeout waiting for configuration response")
- })
- })
-
- headsByName := make(map[string]*headState)
- m.heads.Range(func(key uint32, head *headState) bool {
- if !head.finished {
- headsByName[head.name] = head
- }
- return true
- })
-
- for _, headCfg := range heads {
- head, exists := headsByName[headCfg.Name]
- if !exists {
- config.Destroy()
- resultChan <- fmt.Errorf("head not found: %s", headCfg.Name)
- return
- }
-
- if !headCfg.Enabled {
- if err := config.DisableHead(head.handle); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to disable head %s: %w", headCfg.Name, err)
- return
- }
- continue
- }
-
- headConfig, err := config.EnableHead(head.handle)
- if err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to enable head %s: %w", headCfg.Name, err)
- return
- }
-
- if headCfg.ModeID != nil {
- mode, exists := m.modes.Load(*headCfg.ModeID)
-
- if !exists {
- config.Destroy()
- resultChan <- fmt.Errorf("mode not found: %d", *headCfg.ModeID)
- return
- }
-
- if err := headConfig.SetMode(mode.handle); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to set mode for %s: %w", headCfg.Name, err)
- return
- }
- } else if headCfg.CustomMode != nil {
- if err := headConfig.SetCustomMode(
- headCfg.CustomMode.Width,
- headCfg.CustomMode.Height,
- headCfg.CustomMode.Refresh,
- ); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to set custom mode for %s: %w", headCfg.Name, err)
- return
- }
- }
-
- if headCfg.Position != nil {
- if err := headConfig.SetPosition(headCfg.Position.X, headCfg.Position.Y); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to set position for %s: %w", headCfg.Name, err)
- return
- }
- }
-
- if headCfg.Transform != nil {
- if err := headConfig.SetTransform(*headCfg.Transform); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to set transform for %s: %w", headCfg.Name, err)
- return
- }
- }
-
- if headCfg.Scale != nil {
- if err := headConfig.SetScale(*headCfg.Scale); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to set scale for %s: %w", headCfg.Name, err)
- return
- }
- }
-
- if headCfg.AdaptiveSync != nil {
- if err := headConfig.SetAdaptiveSync(*headCfg.AdaptiveSync); err != nil {
- config.Destroy()
- resultChan <- fmt.Errorf("failed to set adaptive sync for %s: %w", headCfg.Name, err)
- return
- }
- }
- }
-
- var applyErr error
- if test {
- applyErr = config.Test()
- } else {
- applyErr = config.Apply()
- }
-
- if applyErr != nil {
- responded = true
- config.Destroy()
- action := "apply"
- if test {
- action = "test"
- }
- resultChan <- fmt.Errorf("failed to %s configuration: %w", action, applyErr)
- return
- }
- })
-
- return <-resultChan
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go
deleted file mode 100644
index cbd89a3..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go
+++ /dev/null
@@ -1,492 +0,0 @@
-package wlroutput
-
-import (
- "fmt"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-func NewManager(display wlclient.WaylandDisplay) (*Manager, error) {
- m := &Manager{
- display: display,
- ctx: display.Context(),
- cmdq: make(chan cmd, 512),
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- fatalError: make(chan error, 1),
- }
-
- m.wg.Add(1)
- go m.waylandActor()
-
- if err := m.setupRegistry(); err != nil {
- close(m.stopChan)
- m.wg.Wait()
- return nil, err
- }
-
- m.updateState()
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- return m, nil
-}
-
-func (m *Manager) post(fn func()) {
- select {
- case m.cmdq <- cmd{fn: fn}:
- default:
- log.Warn("WlrOutput actor command queue full, dropping command")
- }
-}
-
-func (m *Manager) waylandActor() {
- defer m.wg.Done()
- defer func() {
- if r := recover(); r != nil {
- err := fmt.Errorf("waylandActor panic: %v", r)
- log.Errorf("WlrOutput: %v", err)
-
- select {
- case m.fatalError <- err:
- default:
- }
-
- select {
- case <-m.stopChan:
- default:
- close(m.stopChan)
- }
- }
- }()
-
- for {
- select {
- case <-m.stopChan:
- return
- case c := <-m.cmdq:
- func() {
- defer func() {
- if r := recover(); r != nil {
- log.Errorf("WlrOutput: command execution panic: %v", r)
- }
- }()
- c.fn()
- }()
- }
- }
-}
-
-func (m *Manager) setupRegistry() error {
- log.Info("WlrOutput: starting registry setup")
-
- registry, err := m.display.GetRegistry()
- if err != nil {
- return fmt.Errorf("failed to get registry: %w", err)
- }
- m.registry = registry
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName {
- log.Infof("WlrOutput: found %s", wlr_output_management.ZwlrOutputManagerV1InterfaceName)
- manager := wlr_output_management.NewZwlrOutputManagerV1(m.ctx)
- version := e.Version
- if version > 4 {
- version = 4
- }
-
- manager.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
- m.handleHead(e)
- })
-
- manager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
- log.Debugf("WlrOutput: done event received (serial=%d)", e.Serial)
- m.serial = e.Serial
- m.post(func() {
- m.updateState()
- })
- })
-
- manager.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputManagerV1FinishedEvent) {
- log.Info("WlrOutput: finished event received")
- })
-
- if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
- m.manager = manager
- log.Info("WlrOutput: manager bound successfully")
- } else {
- log.Errorf("WlrOutput: failed to bind manager: %v", err)
- }
- }
- })
-
- log.Info("WlrOutput: registry setup complete (events will be processed async)")
- return nil
-}
-
-func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
- handle := e.Head
- headID := handle.ID()
-
- log.Debugf("WlrOutput: New head (id=%d)", headID)
-
- head := &headState{
- id: headID,
- handle: handle,
- modeIDs: make([]uint32, 0),
- }
-
- m.heads.Store(headID, head)
-
- handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
- log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
- head.name = e.Name
- head.ready = true
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetDescriptionHandler(func(e wlr_output_management.ZwlrOutputHeadV1DescriptionEvent) {
- log.Debugf("WlrOutput: Head %d description: %s", headID, e.Description)
- head.description = e.Description
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetPhysicalSizeHandler(func(e wlr_output_management.ZwlrOutputHeadV1PhysicalSizeEvent) {
- log.Debugf("WlrOutput: Head %d physical size: %dx%d", headID, e.Width, e.Height)
- head.physicalWidth = e.Width
- head.physicalHeight = e.Height
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
- m.handleMode(headID, e)
- })
-
- handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) {
- log.Debugf("WlrOutput: Head %d enabled: %d", headID, e.Enabled)
- head.enabled = e.Enabled != 0
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) {
- modeID := e.Mode.ID()
- log.Debugf("WlrOutput: Head %d current mode: %d", headID, modeID)
- head.currentModeID = modeID
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetPositionHandler(func(e wlr_output_management.ZwlrOutputHeadV1PositionEvent) {
- log.Debugf("WlrOutput: Head %d position: %d,%d", headID, e.X, e.Y)
- head.x = e.X
- head.y = e.Y
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetTransformHandler(func(e wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
- log.Debugf("WlrOutput: Head %d transform: %d", headID, e.Transform)
- head.transform = e.Transform
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
- log.Debugf("WlrOutput: Head %d scale: %f", headID, e.Scale)
- head.scale = e.Scale
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetMakeHandler(func(e wlr_output_management.ZwlrOutputHeadV1MakeEvent) {
- log.Debugf("WlrOutput: Head %d make: %s", headID, e.Make)
- head.make = e.Make
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetModelHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModelEvent) {
- log.Debugf("WlrOutput: Head %d model: %s", headID, e.Model)
- head.model = e.Model
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetSerialNumberHandler(func(e wlr_output_management.ZwlrOutputHeadV1SerialNumberEvent) {
- log.Debugf("WlrOutput: Head %d serial: %s", headID, e.SerialNumber)
- head.serialNumber = e.SerialNumber
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) {
- log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State)
- head.adaptiveSync = e.State
- head.adaptiveSyncSupported = true
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputHeadV1FinishedEvent) {
- log.Debugf("WlrOutput: Head %d finished", headID)
- head.finished = true
-
- m.heads.Delete(headID)
-
- m.wlMutex.Lock()
- handle.Release()
- m.wlMutex.Unlock()
-
- m.post(func() {
- m.updateState()
- })
- })
-}
-
-func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
- handle := e.Mode
- modeID := handle.ID()
-
- log.Debugf("WlrOutput: Head %d new mode (id=%d)", headID, modeID)
-
- mode := &modeState{
- id: modeID,
- handle: handle,
- }
-
- m.modes.Store(modeID, mode)
-
- if head, ok := m.heads.Load(headID); ok {
- head.modeIDs = append(head.modeIDs, modeID)
- m.heads.Store(headID, head)
- }
-
- handle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) {
- log.Debugf("WlrOutput: Mode %d size: %dx%d", modeID, e.Width, e.Height)
- mode.width = e.Width
- mode.height = e.Height
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) {
- log.Debugf("WlrOutput: Mode %d refresh: %d", modeID, e.Refresh)
- mode.refresh = e.Refresh
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetPreferredHandler(func(e wlr_output_management.ZwlrOutputModeV1PreferredEvent) {
- log.Debugf("WlrOutput: Mode %d preferred", modeID)
- mode.preferred = true
- m.post(func() {
- m.updateState()
- })
- })
-
- handle.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputModeV1FinishedEvent) {
- log.Debugf("WlrOutput: Mode %d finished", modeID)
- mode.finished = true
-
- m.modes.Delete(modeID)
-
- m.wlMutex.Lock()
- handle.Release()
- m.wlMutex.Unlock()
-
- m.post(func() {
- m.updateState()
- })
- })
-}
-
-func (m *Manager) updateState() {
- outputs := make([]Output, 0)
-
- m.heads.Range(func(key uint32, head *headState) bool {
- if head.finished {
- return true
- }
-
- if !head.ready {
- return true
- }
-
- modes := make([]OutputMode, 0)
- var currentMode *OutputMode
-
- for _, modeID := range head.modeIDs {
- mode, exists := m.modes.Load(modeID)
- if !exists {
- continue
- }
- if mode.finished {
- continue
- }
-
- outMode := OutputMode{
- Width: mode.width,
- Height: mode.height,
- Refresh: mode.refresh,
- Preferred: mode.preferred,
- ID: modeID,
- }
- modes = append(modes, outMode)
-
- if modeID == head.currentModeID {
- currentMode = &outMode
- }
- }
-
- output := Output{
- Name: head.name,
- Description: head.description,
- Make: head.make,
- Model: head.model,
- SerialNumber: head.serialNumber,
- PhysicalWidth: head.physicalWidth,
- PhysicalHeight: head.physicalHeight,
- Enabled: head.enabled,
- X: head.x,
- Y: head.y,
- Transform: head.transform,
- Scale: head.scale,
- CurrentMode: currentMode,
- Modes: modes,
- AdaptiveSync: head.adaptiveSync,
- AdaptiveSyncSupported: head.adaptiveSyncSupported,
- ID: head.id,
- }
- outputs = append(outputs, output)
- return true
- })
-
- newState := State{
- Outputs: outputs,
- Serial: m.serial,
- }
-
- m.stateMutex.Lock()
- m.state = &newState
- m.stateMutex.Unlock()
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- defer func() {
- if r := recover(); r != nil {
- err := fmt.Errorf("notifier panic: %v", r)
- log.Errorf("WlrOutput: %v", err)
-
- select {
- case m.fatalError <- err:
- default:
- }
-
- select {
- case <-m.stopChan:
- default:
- close(m.stopChan)
- }
- }
- }()
-
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
-
- currentState := m.GetState()
-
- if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
- pending = false
- continue
- }
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- currentState:
- default:
- log.Warn("WlrOutput: subscriber channel full, dropping update")
- }
- return true
- })
-
- stateCopy := currentState
- m.lastNotified = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.wg.Wait()
- m.notifierWg.Wait()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.modes.Range(func(key uint32, mode *modeState) bool {
- if mode.handle != nil {
- mode.handle.Release()
- }
- m.modes.Delete(key)
- return true
- })
-
- m.heads.Range(func(key uint32, head *headState) bool {
- if head.handle != nil {
- head.handle.Release()
- }
- m.heads.Delete(key)
- return true
- })
-
- if m.manager != nil {
- m.manager.Stop()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go
deleted file mode 100644
index e90c70c..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go
+++ /dev/null
@@ -1,414 +0,0 @@
-package wlroutput
-
-import (
- "errors"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-
- mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
-)
-
-func TestStateChanged_BothNil(t *testing.T) {
- assert.True(t, stateChanged(nil, nil))
-}
-
-func TestStateChanged_OneNil(t *testing.T) {
- s := &State{Serial: 1}
- assert.True(t, stateChanged(s, nil))
- assert.True(t, stateChanged(nil, s))
-}
-
-func TestStateChanged_SerialDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{}}
- b := &State{Serial: 2, Outputs: []Output{}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputCountDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}}
- b := &State{Serial: 1, Outputs: []Output{}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputNameDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "HDMI-A-1", Enabled: true}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputEnabledDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: false}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputPositionDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 0, Y: 0}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 1920, Y: 0}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputTransformDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 0}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 1}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputScaleDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 1.0}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 2.0}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_OutputAdaptiveSyncDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 0}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 1}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_CurrentModeNilVsNonNil(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: &OutputMode{Width: 1920}}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_CurrentModeDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{
- Name: "eDP-1",
- CurrentMode: &OutputMode{Width: 1920, Height: 1080, Refresh: 60000},
- }}}
- b := &State{Serial: 1, Outputs: []Output{{
- Name: "eDP-1",
- CurrentMode: &OutputMode{Width: 2560, Height: 1440, Refresh: 60000},
- }}}
- assert.True(t, stateChanged(a, b))
-
- b.Outputs[0].CurrentMode.Width = 1920
- b.Outputs[0].CurrentMode.Height = 1080
- b.Outputs[0].CurrentMode.Refresh = 144000
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_ModesLengthDiffers(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}}}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}, {Width: 1280}}}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestStateChanged_Equal(t *testing.T) {
- mode := OutputMode{Width: 1920, Height: 1080, Refresh: 60000, Preferred: true}
- a := &State{
- Serial: 5,
- Outputs: []Output{{
- Name: "eDP-1",
- Description: "Built-in display",
- Make: "BOE",
- Model: "0x0ABC",
- SerialNumber: "12345",
- PhysicalWidth: 309,
- PhysicalHeight: 174,
- Enabled: true,
- X: 0,
- Y: 0,
- Transform: 0,
- Scale: 1.0,
- CurrentMode: &mode,
- Modes: []OutputMode{mode},
- AdaptiveSync: 0,
- }},
- }
- b := &State{
- Serial: 5,
- Outputs: []Output{{
- Name: "eDP-1",
- Description: "Built-in display",
- Make: "BOE",
- Model: "0x0ABC",
- SerialNumber: "12345",
- PhysicalWidth: 309,
- PhysicalHeight: 174,
- Enabled: true,
- X: 0,
- Y: 0,
- Transform: 0,
- Scale: 1.0,
- CurrentMode: &mode,
- Modes: []OutputMode{mode},
- AdaptiveSync: 0,
- }},
- }
- assert.False(t, stateChanged(a, b))
-}
-
-func TestManager_ConcurrentGetState(t *testing.T) {
- m := &Manager{
- state: &State{
- Serial: 1,
- Outputs: []Output{{Name: "eDP-1", Enabled: true}},
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- s := m.GetState()
- _ = s.Serial
- _ = s.Outputs
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.stateMutex.Lock()
- m.state = &State{
- Serial: uint32(j),
- Outputs: []Output{{Name: "eDP-1", Scale: float64(j % 3)}},
- }
- m.stateMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
- m := &Manager{
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- subID := string(rune('a' + id))
- ch := m.Subscribe(subID)
- assert.NotNil(t, ch)
- time.Sleep(time.Millisecond)
- m.Unsubscribe(subID)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapHeadsConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &headState{
- id: key,
- name: "test-head",
- enabled: j%2 == 0,
- scale: float64(j % 3),
- modeIDs: []uint32{uint32(j)},
- }
- m.heads.Store(key, state)
-
- if loaded, ok := m.heads.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.heads.Range(func(k uint32, v *headState) bool {
- _ = v.name
- _ = v.enabled
- return true
- })
- }
-
- m.heads.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapModesConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &modeState{
- id: key,
- width: int32(1920 + j),
- height: int32(1080 + j),
- refresh: 60000,
- preferred: j == 0,
- }
- m.modes.Store(key, state)
-
- if loaded, ok := m.modes.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.modes.Range(func(k uint32, v *modeState) bool {
- _ = v.width
- _ = v.height
- return true
- })
- }
-
- m.modes.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
- m := &Manager{
- dirty: make(chan struct{}, 1),
- }
-
- for i := 0; i < 10; i++ {
- m.notifySubscribers()
- }
-
- assert.Len(t, m.dirty, 1)
-}
-
-func TestManager_PostQueueFull(t *testing.T) {
- m := &Manager{
- cmdq: make(chan cmd, 2),
- stopChan: make(chan struct{}),
- }
-
- m.post(func() {})
- m.post(func() {})
- m.post(func() {})
- m.post(func() {})
-
- assert.Len(t, m.cmdq, 2)
-}
-
-func TestManager_GetStateNilState(t *testing.T) {
- m := &Manager{}
-
- s := m.GetState()
- assert.NotNil(t, s.Outputs)
- assert.Equal(t, uint32(0), s.Serial)
-}
-
-func TestManager_FatalErrorChannel(t *testing.T) {
- m := &Manager{
- fatalError: make(chan error, 1),
- }
-
- ch := m.FatalError()
- assert.NotNil(t, ch)
-
- m.fatalError <- assert.AnError
- err := <-ch
- assert.Error(t, err)
-}
-
-func TestOutputMode_Fields(t *testing.T) {
- mode := OutputMode{
- Width: 1920,
- Height: 1080,
- Refresh: 60000,
- Preferred: true,
- ID: 42,
- }
-
- assert.Equal(t, int32(1920), mode.Width)
- assert.Equal(t, int32(1080), mode.Height)
- assert.Equal(t, int32(60000), mode.Refresh)
- assert.True(t, mode.Preferred)
- assert.Equal(t, uint32(42), mode.ID)
-}
-
-func TestOutput_Fields(t *testing.T) {
- out := Output{
- Name: "eDP-1",
- Description: "Built-in display",
- Make: "BOE",
- Model: "0x0ABC",
- SerialNumber: "12345",
- PhysicalWidth: 309,
- PhysicalHeight: 174,
- Enabled: true,
- X: 0,
- Y: 0,
- Transform: 0,
- Scale: 1.5,
- AdaptiveSync: 1,
- ID: 1,
- }
-
- assert.Equal(t, "eDP-1", out.Name)
- assert.Equal(t, "Built-in display", out.Description)
- assert.True(t, out.Enabled)
- assert.Equal(t, float64(1.5), out.Scale)
- assert.Equal(t, uint32(1), out.AdaptiveSync)
-}
-
-func TestHeadState_ModeIDsSlice(t *testing.T) {
- head := &headState{
- id: 1,
- modeIDs: make([]uint32, 0),
- }
-
- head.modeIDs = append(head.modeIDs, 1, 2, 3)
- assert.Len(t, head.modeIDs, 3)
- assert.Equal(t, uint32(1), head.modeIDs[0])
-}
-
-func TestStateChanged_BothCurrentModeNil(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
- assert.False(t, stateChanged(a, b))
-}
-
-func TestStateChanged_IndexOutOfBounds(t *testing.T) {
- a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}}
- b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}, {Name: "HDMI-A-1"}}}
- assert.True(t, stateChanged(a, b))
-}
-
-func TestNewManager_GetRegistryError(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- mockDisplay.EXPECT().Context().Return(nil)
- mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
-
- _, err := NewManager(mockDisplay)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get registry")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go
deleted file mode 100644
index 9c79b1a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go
+++ /dev/null
@@ -1,192 +0,0 @@
-package wlroutput
-
-import (
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type OutputMode struct {
- Width int32 `json:"width"`
- Height int32 `json:"height"`
- Refresh int32 `json:"refresh"`
- Preferred bool `json:"preferred"`
- ID uint32 `json:"id"`
-}
-
-type Output struct {
- Name string `json:"name"`
- Description string `json:"description"`
- Make string `json:"make"`
- Model string `json:"model"`
- SerialNumber string `json:"serialNumber"`
- PhysicalWidth int32 `json:"physicalWidth"`
- PhysicalHeight int32 `json:"physicalHeight"`
- Enabled bool `json:"enabled"`
- X int32 `json:"x"`
- Y int32 `json:"y"`
- Transform int32 `json:"transform"`
- Scale float64 `json:"scale"`
- CurrentMode *OutputMode `json:"currentMode"`
- Modes []OutputMode `json:"modes"`
- AdaptiveSync uint32 `json:"adaptiveSync"`
- AdaptiveSyncSupported bool `json:"adaptiveSyncSupported"`
- ID uint32 `json:"id"`
-}
-
-type State struct {
- Outputs []Output `json:"outputs"`
- Serial uint32 `json:"serial"`
-}
-
-type cmd struct {
- fn func()
-}
-
-type Manager struct {
- display wlclient.WaylandDisplay
- ctx *wlclient.Context
- registry *wlclient.Registry
- manager *wlr_output_management.ZwlrOutputManagerV1
-
- heads syncmap.Map[uint32, *headState]
- modes syncmap.Map[uint32, *modeState]
-
- serial uint32
-
- wlMutex sync.Mutex
- cmdq chan cmd
- stopChan chan struct{}
- wg sync.WaitGroup
-
- subscribers syncmap.Map[string, chan State]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotified *State
-
- stateMutex sync.RWMutex
- state *State
-
- fatalError chan error
-}
-
-type headState struct {
- id uint32
- handle *wlr_output_management.ZwlrOutputHeadV1
- name string
- description string
- make string
- model string
- serialNumber string
- physicalWidth int32
- physicalHeight int32
- enabled bool
- x int32
- y int32
- transform int32
- scale float64
- currentModeID uint32
- modeIDs []uint32
- adaptiveSync uint32
- adaptiveSyncSupported bool
- finished bool
- ready bool
-}
-
-type modeState struct {
- id uint32
- handle *wlr_output_management.ZwlrOutputModeV1
- width int32
- height int32
- refresh int32
- preferred bool
- finished bool
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{
- Outputs: []Output{},
- Serial: 0,
- }
- }
- stateCopy := *m.state
- return stateCopy
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
-
- m.subscribers.Store(id, ch)
-
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
-
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
-
- }
-
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) FatalError() <-chan error {
- return m.fatalError
-}
-
-func stateChanged(old, new *State) bool {
- if old == nil || new == nil {
- return true
- }
- if old.Serial != new.Serial {
- return true
- }
- if len(old.Outputs) != len(new.Outputs) {
- return true
- }
- for i := range new.Outputs {
- if i >= len(old.Outputs) {
- return true
- }
- oldOut := &old.Outputs[i]
- newOut := &new.Outputs[i]
- if oldOut.Name != newOut.Name || oldOut.Enabled != newOut.Enabled {
- return true
- }
- if oldOut.X != newOut.X || oldOut.Y != newOut.Y {
- return true
- }
- if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale {
- return true
- }
- if oldOut.AdaptiveSync != newOut.AdaptiveSync || oldOut.AdaptiveSyncSupported != newOut.AdaptiveSyncSupported {
- return true
- }
- if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) {
- return true
- }
- if oldOut.CurrentMode != nil && newOut.CurrentMode != nil {
- if oldOut.CurrentMode.Width != newOut.CurrentMode.Width ||
- oldOut.CurrentMode.Height != newOut.CurrentMode.Height ||
- oldOut.CurrentMode.Refresh != newOut.CurrentMode.Refresh {
- return true
- }
- }
- if len(oldOut.Modes) != len(newOut.Modes) {
- return true
- }
- }
- return false
-}