mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-30 16:32:50 -05:00
Compare commits
12 Commits
bc27253cbf
...
e307de83e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e307de83e2 | ||
|
|
85968ec417 | ||
|
|
993f14a31f | ||
|
|
566d617508 | ||
|
|
542a279fcb | ||
|
|
e784bb89e1 | ||
|
|
f680ace258 | ||
|
|
7aa5976e07 | ||
|
|
f88f1ea951 | ||
|
|
da4561cb35 | ||
|
|
1f89ae9813 | ||
|
|
5647323449 |
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -511,7 +511,7 @@ jobs:
|
|||||||
|
|
||||||
Requires: (quickshell or quickshell-git)
|
Requires: (quickshell or quickshell-git)
|
||||||
Requires: accountsservice
|
Requires: accountsservice
|
||||||
Requires: dms-cli
|
Requires: dms-cli = %{version}-%{release}
|
||||||
Requires: dgop
|
Requires: dgop
|
||||||
|
|
||||||
Recommends: cava
|
Recommends: cava
|
||||||
@@ -541,17 +541,6 @@ jobs:
|
|||||||
Command-line interface for DankMaterialShell configuration and management.
|
Command-line interface for DankMaterialShell configuration and management.
|
||||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||||
|
|
||||||
%package -n dgop
|
|
||||||
Summary: Stateless CPU/GPU monitor for DankMaterialShell
|
|
||||||
License: MIT
|
|
||||||
URL: https://github.com/AvengeMedia/dgop
|
|
||||||
Provides: dgop
|
|
||||||
|
|
||||||
%description -n dgop
|
|
||||||
DGOP is a stateless system monitoring tool that provides CPU, GPU, memory, and
|
|
||||||
network statistics. Designed for integration with DankMaterialShell but can be
|
|
||||||
used standalone. This package always includes the latest stable dgop release.
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%setup -q -c -n dms-qml
|
%setup -q -c -n dms-qml
|
||||||
|
|
||||||
@@ -576,18 +565,10 @@ jobs:
|
|||||||
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||||
chmod +x %{_builddir}/dms-cli
|
chmod +x %{_builddir}/dms-cli
|
||||||
|
|
||||||
wget -O %{_builddir}/dgop.gz "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-${ARCH_SUFFIX}.gz" || {
|
|
||||||
echo "Failed to download dgop for architecture %{_arch}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
gunzip -c %{_builddir}/dgop.gz > %{_builddir}/dgop
|
|
||||||
chmod +x %{_builddir}/dgop
|
|
||||||
|
|
||||||
%build
|
%build
|
||||||
|
|
||||||
%install
|
%install
|
||||||
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||||
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
|
|
||||||
|
|
||||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||||
@@ -617,10 +598,8 @@ jobs:
|
|||||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||||
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
# Signal running DMS instances to reload
|
||||||
if [ "$1" -ge 2 ]; then
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
@@ -636,14 +615,10 @@ jobs:
|
|||||||
%{_datadir}/zsh/site-functions/_dms
|
%{_datadir}/zsh/site-functions/_dms
|
||||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||||
|
|
||||||
%files -n dgop
|
|
||||||
%{_bindir}/dgop
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
||||||
- Stable release VERSION_PLACEHOLDER
|
- Stable release VERSION_PLACEHOLDER
|
||||||
- Built from GitHub release
|
- Built from GitHub release
|
||||||
- Includes latest dms-cli and dgop binaries
|
|
||||||
SPECEOF
|
SPECEOF
|
||||||
|
|
||||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
|||||||
35
.github/workflows/run-copr.yml
vendored
35
.github/workflows/run-copr.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
echo "✅ Source downloaded"
|
echo "✅ Source downloaded"
|
||||||
echo "Note: dms-cli and dgop binaries will be downloaded during build based on target architecture"
|
echo "Note: dms-cli binary will be downloaded during build based on target architecture"
|
||||||
ls -lh
|
ls -lh
|
||||||
|
|
||||||
- name: Generate stable spec file
|
- name: Generate stable spec file
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
|
|
||||||
Requires: (quickshell or quickshell-git)
|
Requires: (quickshell or quickshell-git)
|
||||||
Requires: accountsservice
|
Requires: accountsservice
|
||||||
Requires: dms-cli
|
Requires: dms-cli = %{version}-%{release}
|
||||||
Requires: dgop
|
Requires: dgop
|
||||||
|
|
||||||
Recommends: cava
|
Recommends: cava
|
||||||
@@ -125,17 +125,6 @@ jobs:
|
|||||||
Command-line interface for DankMaterialShell configuration and management.
|
Command-line interface for DankMaterialShell configuration and management.
|
||||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||||
|
|
||||||
%package -n dgop
|
|
||||||
Summary: Stateless CPU/GPU monitor for DankMaterialShell
|
|
||||||
License: MIT
|
|
||||||
URL: https://github.com/AvengeMedia/dgop
|
|
||||||
Provides: dgop
|
|
||||||
|
|
||||||
%description -n dgop
|
|
||||||
DGOP is a stateless system monitoring tool that provides CPU, GPU, memory, and
|
|
||||||
network statistics. Designed for integration with DankMaterialShell but can be
|
|
||||||
used standalone. This package always includes the latest stable dgop release.
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%setup -q -c -n dms-qml
|
%setup -q -c -n dms-qml
|
||||||
|
|
||||||
@@ -162,19 +151,10 @@ jobs:
|
|||||||
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||||
chmod +x %{_builddir}/dms-cli
|
chmod +x %{_builddir}/dms-cli
|
||||||
|
|
||||||
# Download dgop for target architecture
|
|
||||||
wget -O %{_builddir}/dgop.gz "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-${ARCH_SUFFIX}.gz" || {
|
|
||||||
echo "Failed to download dgop for architecture %{_arch}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
gunzip -c %{_builddir}/dgop.gz > %{_builddir}/dgop
|
|
||||||
chmod +x %{_builddir}/dgop
|
|
||||||
|
|
||||||
%build
|
%build
|
||||||
|
|
||||||
%install
|
%install
|
||||||
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||||
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
|
|
||||||
|
|
||||||
# Shell completions
|
# Shell completions
|
||||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||||
@@ -202,11 +182,8 @@ jobs:
|
|||||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||||
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
# Signal running DMS instances to reload (harmless if none running)
|
||||||
# Restart DMS for active users after upgrade
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
if [ "$1" -ge 2 ]; then
|
|
||||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
@@ -220,14 +197,10 @@ jobs:
|
|||||||
%{_datadir}/zsh/site-functions/_dms
|
%{_datadir}/zsh/site-functions/_dms
|
||||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||||
|
|
||||||
%files -n dgop
|
|
||||||
%{_bindir}/dgop
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
||||||
- Stable release VERSION_PLACEHOLDER
|
- Stable release VERSION_PLACEHOLDER
|
||||||
- Built from GitHub release
|
- Built from GitHub release
|
||||||
- Includes latest dms-cli and dgop binaries
|
|
||||||
SPECEOF
|
SPECEOF
|
||||||
|
|
||||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ linters:
|
|||||||
- (*os.Process).Signal
|
- (*os.Process).Signal
|
||||||
- (*os.Process).Kill
|
- (*os.Process).Kill
|
||||||
- syscall.Kill
|
- syscall.Kill
|
||||||
|
# Seek on memfd (reset position before passing fd)
|
||||||
|
- syscall.Seek
|
||||||
# DBus cleanup
|
# DBus cleanup
|
||||||
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
||||||
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
||||||
|
|||||||
@@ -154,11 +154,12 @@ func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind := keybinds.Keybind{
|
bind := keybinds.Keybind{
|
||||||
Key: keyStr,
|
Key: keyStr,
|
||||||
Description: kb.Description,
|
Description: kb.Description,
|
||||||
Action: rawAction,
|
Action: rawAction,
|
||||||
Subcategory: subcategory,
|
Subcategory: subcategory,
|
||||||
Source: source,
|
Source: source,
|
||||||
|
HideOnOverlay: kb.HideOnOverlay,
|
||||||
}
|
}
|
||||||
|
|
||||||
if source == "dms" && conflicts != nil {
|
if source == "dms" && conflicts != nil {
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NiriKeyBinding struct {
|
type NiriKeyBinding struct {
|
||||||
Mods []string
|
Mods []string
|
||||||
Key string
|
Key string
|
||||||
Action string
|
Action string
|
||||||
Args []string
|
Args []string
|
||||||
Description string
|
Description string
|
||||||
Source string
|
HideOnOverlay bool
|
||||||
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
type NiriSection struct {
|
type NiriSection struct {
|
||||||
@@ -273,19 +274,26 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description string
|
var description string
|
||||||
|
var hideOnOverlay bool
|
||||||
if node.Properties != nil {
|
if node.Properties != nil {
|
||||||
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
||||||
description = val.ValueString()
|
switch val.ValueString() {
|
||||||
|
case "null", "":
|
||||||
|
hideOnOverlay = true
|
||||||
|
default:
|
||||||
|
description = val.ValueString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &NiriKeyBinding{
|
return &NiriKeyBinding{
|
||||||
Mods: mods,
|
Mods: mods,
|
||||||
Key: key,
|
Key: key,
|
||||||
Action: action,
|
Action: action,
|
||||||
Args: args,
|
Args: args,
|
||||||
Description: description,
|
Description: description,
|
||||||
Source: p.currentSource,
|
HideOnOverlay: hideOnOverlay,
|
||||||
|
Source: p.currentSource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package keybinds
|
package keybinds
|
||||||
|
|
||||||
type Keybind struct {
|
type Keybind struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Description string `json:"desc"`
|
Description string `json:"desc"`
|
||||||
Action string `json:"action,omitempty"`
|
Action string `json:"action,omitempty"`
|
||||||
Subcategory string `json:"subcat,omitempty"`
|
Subcategory string `json:"subcat,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
Conflict *Keybind `json:"conflict,omitempty"`
|
HideOnOverlay bool `json:"hideOnOverlay,omitempty"`
|
||||||
|
Conflict *Keybind `json:"conflict,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DMSBindsStatus struct {
|
type DMSBindsStatus struct {
|
||||||
|
|||||||
@@ -238,9 +238,17 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy != nil {
|
if proxy == nil {
|
||||||
e.Head = proxy.(*ZwlrOutputHeadV1)
|
head := &ZwlrOutputHeadV1{}
|
||||||
|
head.SetContext(i.Context())
|
||||||
|
head.SetID(objectID)
|
||||||
|
registerServerProxy(i.Context(), head, objectID)
|
||||||
|
e.Head = head
|
||||||
|
} else if head, ok := proxy.(*ZwlrOutputHeadV1); ok {
|
||||||
|
e.Head = head
|
||||||
} else {
|
} else {
|
||||||
|
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||||
|
// Replace it with the correct type
|
||||||
head := &ZwlrOutputHeadV1{}
|
head := &ZwlrOutputHeadV1{}
|
||||||
head.SetContext(i.Context())
|
head.SetContext(i.Context())
|
||||||
head.SetID(objectID)
|
head.SetID(objectID)
|
||||||
@@ -715,9 +723,17 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy != nil {
|
if proxy == nil {
|
||||||
e.Mode = proxy.(*ZwlrOutputModeV1)
|
mode := &ZwlrOutputModeV1{}
|
||||||
|
mode.SetContext(i.Context())
|
||||||
|
mode.SetID(objectID)
|
||||||
|
registerServerProxy(i.Context(), mode, objectID)
|
||||||
|
e.Mode = mode
|
||||||
|
} else if mode, ok := proxy.(*ZwlrOutputModeV1); ok {
|
||||||
|
e.Mode = mode
|
||||||
} else {
|
} else {
|
||||||
|
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||||
|
// Replace it with the correct type
|
||||||
mode := &ZwlrOutputModeV1{}
|
mode := &ZwlrOutputModeV1{}
|
||||||
mode.SetContext(i.Context())
|
mode.SetContext(i.Context())
|
||||||
mode.SetID(objectID)
|
mode.SetID(objectID)
|
||||||
@@ -743,7 +759,26 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
}
|
}
|
||||||
var e ZwlrOutputHeadV1CurrentModeEvent
|
var e ZwlrOutputHeadV1CurrentModeEvent
|
||||||
l := 0
|
l := 0
|
||||||
e.Mode = i.Context().GetProxy(client.Uint32(data[l : l+4])).(*ZwlrOutputModeV1)
|
objectID := client.Uint32(data[l : l+4])
|
||||||
|
proxy := i.Context().GetProxy(objectID)
|
||||||
|
if proxy == nil {
|
||||||
|
// Mode not yet registered, create it
|
||||||
|
mode := &ZwlrOutputModeV1{}
|
||||||
|
mode.SetContext(i.Context())
|
||||||
|
mode.SetID(objectID)
|
||||||
|
registerServerProxy(i.Context(), mode, objectID)
|
||||||
|
e.Mode = mode
|
||||||
|
} else if mode, ok := proxy.(*ZwlrOutputModeV1); ok {
|
||||||
|
e.Mode = mode
|
||||||
|
} else {
|
||||||
|
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||||
|
// Replace it with the correct type
|
||||||
|
mode := &ZwlrOutputModeV1{}
|
||||||
|
mode.SetContext(i.Context())
|
||||||
|
mode.SetID(objectID)
|
||||||
|
registerServerProxy(i.Context(), mode, objectID)
|
||||||
|
e.Mode = mode
|
||||||
|
}
|
||||||
l += 4
|
l += 4
|
||||||
|
|
||||||
i.currentModeHandler(e)
|
i.currentModeHandler(e)
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ID int `json:"id"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "apppicker.open", "browser.open":
|
case "apppicker.open", "browser.open":
|
||||||
handleOpen(conn, req, manager)
|
handleOpen(conn, req, manager)
|
||||||
@@ -22,7 +16,7 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleOpen(conn net.Conn, req Request, manager *Manager) {
|
func handleOpen(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
log.Infof("AppPicker: Received %s request with params: %+v", req.Method, req.Params)
|
log.Infof("AppPicker: Received %s request with params: %+v", req.Method, req.Params)
|
||||||
|
|
||||||
target, ok := req.Params["target"].(string)
|
target, ok := req.Params["target"].(string)
|
||||||
|
|||||||
@@ -6,25 +6,15 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BluetoothEvent struct {
|
type BluetoothEvent struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data BluetoothState `json:"data"`
|
Data BluetoothState `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "bluetooth.getState":
|
case "bluetooth.getState":
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
@@ -57,31 +47,30 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStartDiscovery(conn net.Conn, req Request, manager *Manager) {
|
func handleStartDiscovery(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.StartDiscovery(); err != nil {
|
if err := manager.StartDiscovery(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "discovery started"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "discovery started"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStopDiscovery(conn net.Conn, req Request, manager *Manager) {
|
func handleStopDiscovery(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.StopDiscovery(); err != nil {
|
if err := manager.StopDiscovery(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "discovery stopped"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "discovery stopped"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetPowered(conn net.Conn, req Request, manager *Manager) {
|
func handleSetPowered(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
powered, ok := req.Params["powered"].(bool)
|
powered, err := params.Bool(req.Params, "powered")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'powered' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,13 +79,13 @@ func handleSetPowered(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "powered state updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "powered state updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePairDevice(conn net.Conn, req Request, manager *Manager) {
|
func handlePairDevice(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devicePath, ok := req.Params["device"].(string)
|
devicePath, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'device' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,13 +94,13 @@ func handlePairDevice(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "pairing initiated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing initiated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnectDevice(conn net.Conn, req Request, manager *Manager) {
|
func handleConnectDevice(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devicePath, ok := req.Params["device"].(string)
|
devicePath, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'device' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,13 +109,13 @@ func handleConnectDevice(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "connecting"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisconnectDevice(conn net.Conn, req Request, manager *Manager) {
|
func handleDisconnectDevice(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devicePath, ok := req.Params["device"].(string)
|
devicePath, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'device' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,13 +124,13 @@ func handleDisconnectDevice(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "disconnected"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRemoveDevice(conn net.Conn, req Request, manager *Manager) {
|
func handleRemoveDevice(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devicePath, ok := req.Params["device"].(string)
|
devicePath, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'device' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,13 +139,13 @@ func handleRemoveDevice(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "device removed"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device removed"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTrustDevice(conn net.Conn, req Request, manager *Manager) {
|
func handleTrustDevice(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devicePath, ok := req.Params["device"].(string)
|
devicePath, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'device' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,13 +154,13 @@ func handleTrustDevice(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "device trusted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device trusted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUntrustDevice(conn net.Conn, req Request, manager *Manager) {
|
func handleUntrustDevice(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devicePath, ok := req.Params["device"].(string)
|
devicePath, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'device' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,43 +169,31 @@ func handleUntrustDevice(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "device untrusted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device untrusted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePairingSubmit(conn net.Conn, req Request, manager *Manager) {
|
func handlePairingSubmit(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
token, ok := req.Params["token"].(string)
|
token, err := params.String(req.Params, "token")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'token' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsRaw, ok := req.Params["secrets"].(map[string]any)
|
secrets := params.StringMapOpt(req.Params, "secrets")
|
||||||
secrets := make(map[string]string)
|
accept := params.BoolOpt(req.Params, "accept", false)
|
||||||
if ok {
|
|
||||||
for k, v := range secretsRaw {
|
|
||||||
if str, ok := v.(string); ok {
|
|
||||||
secrets[k] = str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accept := false
|
|
||||||
if acceptParam, ok := req.Params["accept"].(bool); ok {
|
|
||||||
accept = acceptParam
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.SubmitPairing(token, secrets, accept); err != nil {
|
if err := manager.SubmitPairing(token, secrets, accept); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "pairing response submitted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing response submitted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePairingCancel(conn net.Conn, req Request, manager *Manager) {
|
func handlePairingCancel(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
token, ok := req.Params["token"].(string)
|
token, err := params.String(req.Params, "token")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'token' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,10 +202,10 @@ func handlePairingCancel(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "pairing cancelled"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing cancelled"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package brightness
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, m *Manager) {
|
func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "brightness.getState":
|
case "brightness.getState":
|
||||||
handleGetState(conn, req, m)
|
handleGetState(conn, req, m)
|
||||||
@@ -22,131 +24,90 @@ func HandleRequest(conn net.Conn, req Request, m *Manager) {
|
|||||||
case "brightness.subscribe":
|
case "brightness.subscribe":
|
||||||
handleSubscribe(conn, req, m)
|
handleSubscribe(conn, req, m)
|
||||||
default:
|
default:
|
||||||
models.RespondError(conn, req.ID.(int), "unknown method: "+req.Method)
|
models.RespondError(conn, req.ID, "unknown method: "+req.Method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, m *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, m *Manager) {
|
||||||
state := m.GetState()
|
models.Respond(conn, req.ID, m.GetState())
|
||||||
models.Respond(conn, req.ID.(int), state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetBrightness(conn net.Conn, req Request, m *Manager) {
|
func handleSetBrightness(conn net.Conn, req models.Request, m *Manager) {
|
||||||
var params SetBrightnessParams
|
device, err := params.String(req.Params, "device")
|
||||||
|
if err != nil {
|
||||||
device, ok := req.Params["device"].(string)
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID.(int), "missing or invalid device parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
params.Device = device
|
|
||||||
|
|
||||||
percentFloat, ok := req.Params["percent"].(float64)
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID.(int), "missing or invalid percent parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
params.Percent = int(percentFloat)
|
|
||||||
|
|
||||||
if exponential, ok := req.Params["exponential"].(bool); ok {
|
|
||||||
params.Exponential = exponential
|
|
||||||
}
|
|
||||||
|
|
||||||
exponent := 1.2
|
|
||||||
if exponentFloat, ok := req.Params["exponent"].(float64); ok {
|
|
||||||
params.Exponent = exponentFloat
|
|
||||||
exponent = exponentFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.SetBrightnessWithExponent(params.Device, params.Percent, params.Exponential, exponent); err != nil {
|
|
||||||
models.RespondError(conn, req.ID.(int), err.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := m.GetState()
|
percent, err := params.Int(req.Params, "percent")
|
||||||
models.Respond(conn, req.ID.(int), state)
|
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 Request, m *Manager) {
|
func handleIncrement(conn net.Conn, req models.Request, m *Manager) {
|
||||||
device, ok := req.Params["device"].(string)
|
device, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID.(int), "missing or invalid device parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
step := 10
|
step := params.IntOpt(req.Params, "step", 10)
|
||||||
if stepFloat, ok := req.Params["step"].(float64); ok {
|
exponential := params.BoolOpt(req.Params, "exponential", false)
|
||||||
step = int(stepFloat)
|
exponent := params.FloatOpt(req.Params, "exponent", 1.2)
|
||||||
}
|
|
||||||
|
|
||||||
exponential := false
|
|
||||||
if expBool, ok := req.Params["exponential"].(bool); ok {
|
|
||||||
exponential = expBool
|
|
||||||
}
|
|
||||||
|
|
||||||
exponent := 1.2
|
|
||||||
if exponentFloat, ok := req.Params["exponent"].(float64); ok {
|
|
||||||
exponent = exponentFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.IncrementBrightnessWithExponent(device, step, exponential, exponent); err != nil {
|
if err := m.IncrementBrightnessWithExponent(device, step, exponential, exponent); err != nil {
|
||||||
models.RespondError(conn, req.ID.(int), err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := m.GetState()
|
models.Respond(conn, req.ID, m.GetState())
|
||||||
models.Respond(conn, req.ID.(int), state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDecrement(conn net.Conn, req Request, m *Manager) {
|
func handleDecrement(conn net.Conn, req models.Request, m *Manager) {
|
||||||
device, ok := req.Params["device"].(string)
|
device, err := params.String(req.Params, "device")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID.(int), "missing or invalid device parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
step := 10
|
step := params.IntOpt(req.Params, "step", 10)
|
||||||
if stepFloat, ok := req.Params["step"].(float64); ok {
|
exponential := params.BoolOpt(req.Params, "exponential", false)
|
||||||
step = int(stepFloat)
|
exponent := params.FloatOpt(req.Params, "exponent", 1.2)
|
||||||
}
|
|
||||||
|
|
||||||
exponential := false
|
|
||||||
if expBool, ok := req.Params["exponential"].(bool); ok {
|
|
||||||
exponential = expBool
|
|
||||||
}
|
|
||||||
|
|
||||||
exponent := 1.2
|
|
||||||
if exponentFloat, ok := req.Params["exponent"].(float64); ok {
|
|
||||||
exponent = exponentFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.IncrementBrightnessWithExponent(device, -step, exponential, exponent); err != nil {
|
if err := m.IncrementBrightnessWithExponent(device, -step, exponential, exponent); err != nil {
|
||||||
models.RespondError(conn, req.ID.(int), err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := m.GetState()
|
models.Respond(conn, req.ID, m.GetState())
|
||||||
models.Respond(conn, req.ID.(int), state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRescan(conn net.Conn, req Request, m *Manager) {
|
func handleRescan(conn net.Conn, req models.Request, m *Manager) {
|
||||||
m.Rescan()
|
m.Rescan()
|
||||||
state := m.GetState()
|
models.Respond(conn, req.ID, m.GetState())
|
||||||
models.Respond(conn, req.ID.(int), state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, m *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, m *Manager) {
|
||||||
clientID := "brightness-subscriber"
|
clientID := fmt.Sprintf("brightness-%d", req.ID)
|
||||||
if idStr, ok := req.ID.(string); ok && idStr != "" {
|
|
||||||
clientID = idStr
|
|
||||||
}
|
|
||||||
|
|
||||||
ch := m.Subscribe(clientID)
|
ch := m.Subscribe(clientID)
|
||||||
defer m.Unsubscribe(clientID)
|
defer m.Unsubscribe(clientID)
|
||||||
|
|
||||||
initialState := m.GetState()
|
initialState := m.GetState()
|
||||||
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||||
ID: req.ID.(int),
|
ID: req.ID,
|
||||||
Result: &initialState,
|
Result: &initialState,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return
|
||||||
@@ -154,7 +115,7 @@ func handleSubscribe(conn net.Conn, req Request, m *Manager) {
|
|||||||
|
|
||||||
for state := range ch {
|
for state := range ch {
|
||||||
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||||
ID: req.ID.(int),
|
ID: req.ID,
|
||||||
Result: &state,
|
Result: &state,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -33,12 +33,6 @@ type DeviceUpdate struct {
|
|||||||
Device Device `json:"device"`
|
Device Device `json:"device"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
ID any `json:"id"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
logindBackend *LogindBackend
|
logindBackend *LogindBackend
|
||||||
sysfsBackend *SysfsBackend
|
sysfsBackend *SysfsBackend
|
||||||
@@ -112,13 +106,6 @@ type ddcCapability struct {
|
|||||||
current int
|
current int
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetBrightnessParams struct {
|
|
||||||
Device string `json:"device"`
|
|
||||||
Percent int `json:"percent"`
|
|
||||||
Exponential bool `json:"exponential,omitempty"`
|
|
||||||
Exponent float64 `json:"exponent,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
ch := make(chan State, 16)
|
ch := make(chan State, 16)
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ID int `json:"id"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "browser.open":
|
case "browser.open":
|
||||||
url, ok := req.Params["url"].(string)
|
url, ok := req.Params["url"].(string)
|
||||||
|
|||||||
@@ -6,25 +6,21 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CUPSEvent struct {
|
type CUPSEvent struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data CUPSState `json:"data"`
|
Data CUPSState `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
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 {
|
switch req.Method {
|
||||||
case "cups.subscribe":
|
case "cups.subscribe":
|
||||||
handleSubscribe(conn, req, manager)
|
handleSubscribe(conn, req, manager)
|
||||||
@@ -79,20 +75,19 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetPrinters(conn net.Conn, req Request, manager *Manager) {
|
func handleGetPrinters(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printers, err := manager.GetPrinters()
|
printers, err := manager.GetPrinters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, printers)
|
models.Respond(conn, req.ID, printers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetJobs(conn net.Conn, req Request, manager *Manager) {
|
func handleGetJobs(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.String(req.Params, "printerName")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,14 +96,13 @@ func handleGetJobs(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, jobs)
|
models.Respond(conn, req.ID, jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePausePrinter(conn net.Conn, req Request, manager *Manager) {
|
func handlePausePrinter(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.String(req.Params, "printerName")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,13 +110,13 @@ func handlePausePrinter(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "paused"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "paused"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleResumePrinter(conn net.Conn, req Request, manager *Manager) {
|
func handleResumePrinter(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.String(req.Params, "printerName")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,28 +124,27 @@ func handleResumePrinter(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "resumed"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "resumed"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCancelJob(conn net.Conn, req Request, manager *Manager) {
|
func handleCancelJob(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
jobID, err := params.Int(req.Params, "jobID")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'jobid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jobID := int(jobIDFloat)
|
|
||||||
|
|
||||||
if err := manager.CancelJob(jobID); err != nil {
|
if err := manager.CancelJob(jobID); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job canceled"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job canceled"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePurgeJobs(conn net.Conn, req Request, manager *Manager) {
|
func handlePurgeJobs(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.String(req.Params, "printerName")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,10 +152,10 @@ func handlePurgeJobs(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "jobs canceled"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "jobs canceled"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
@@ -193,7 +186,7 @@ func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetDevices(conn net.Conn, req Request, manager *Manager) {
|
func handleGetDevices(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
devices, err := manager.GetDevices()
|
devices, err := manager.GetDevices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
@@ -202,7 +195,7 @@ func handleGetDevices(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, devices)
|
models.Respond(conn, req.ID, devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetPPDs(conn net.Conn, req Request, manager *Manager) {
|
func handleGetPPDs(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ppds, err := manager.GetPPDs()
|
ppds, err := manager.GetPPDs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
@@ -211,7 +204,7 @@ func handleGetPPDs(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, ppds)
|
models.Respond(conn, req.ID, ppds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetClasses(conn net.Conn, req Request, manager *Manager) {
|
func handleGetClasses(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
classes, err := manager.GetClasses()
|
classes, err := manager.GetClasses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
@@ -220,41 +213,41 @@ func handleGetClasses(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, classes)
|
models.Respond(conn, req.ID, classes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreatePrinter(conn net.Conn, req Request, manager *Manager) {
|
func handleCreatePrinter(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
name, ok := req.Params["name"].(string)
|
name, err := params.StringNonEmpty(req.Params, "name")
|
||||||
if !ok || name == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceURI, ok := req.Params["deviceURI"].(string)
|
deviceURI, err := params.StringNonEmpty(req.Params, "deviceURI")
|
||||||
if !ok || deviceURI == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'deviceURI' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ppd, ok := req.Params["ppd"].(string)
|
ppd, err := params.StringNonEmpty(req.Params, "ppd")
|
||||||
if !ok || ppd == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'ppd' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
shared, _ := req.Params["shared"].(bool)
|
shared := params.BoolOpt(req.Params, "shared", false)
|
||||||
errorPolicy, _ := req.Params["errorPolicy"].(string)
|
errorPolicy := params.StringOpt(req.Params, "errorPolicy", "")
|
||||||
information, _ := req.Params["information"].(string)
|
information := params.StringOpt(req.Params, "information", "")
|
||||||
location, _ := req.Params["location"].(string)
|
location := params.StringOpt(req.Params, "location", "")
|
||||||
|
|
||||||
if err := manager.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location); err != nil {
|
if err := manager.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer created"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer created"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeletePrinter(conn net.Conn, req Request, manager *Manager) {
|
func handleDeletePrinter(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,13 +255,13 @@ func handleDeletePrinter(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer deleted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAcceptJobs(conn net.Conn, req Request, manager *Manager) {
|
func handleAcceptJobs(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,13 +269,13 @@ func handleAcceptJobs(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "accepting jobs"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "accepting jobs"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRejectJobs(conn net.Conn, req Request, manager *Manager) {
|
func handleRejectJobs(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,19 +283,19 @@ func handleRejectJobs(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "rejecting jobs"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "rejecting jobs"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetPrinterShared(conn net.Conn, req Request, manager *Manager) {
|
func handleSetPrinterShared(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
shared, ok := req.Params["shared"].(bool)
|
shared, err := params.Bool(req.Params, "shared")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'shared' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,19 +303,19 @@ func handleSetPrinterShared(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "sharing updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sharing updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetPrinterLocation(conn net.Conn, req Request, manager *Manager) {
|
func handleSetPrinterLocation(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
location, ok := req.Params["location"].(string)
|
location, err := params.String(req.Params, "location")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'location' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,19 +323,19 @@ func handleSetPrinterLocation(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "location updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetPrinterInfo(conn net.Conn, req Request, manager *Manager) {
|
func handleSetPrinterInfo(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info, ok := req.Params["info"].(string)
|
info, err := params.String(req.Params, "info")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'info' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,39 +343,33 @@ func handleSetPrinterInfo(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "info updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "info updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMoveJob(conn net.Conn, req Request, manager *Manager) {
|
func handleMoveJob(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
jobID, err := params.Int(req.Params, "jobID")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
destPrinter, ok := req.Params["destPrinter"].(string)
|
|
||||||
if !ok || destPrinter == "" {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'destPrinter' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.MoveJob(int(jobIDFloat), destPrinter); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job moved"})
|
|
||||||
|
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"})
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestPageResult struct {
|
func handlePrintTestPage(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
Success bool `json:"success"`
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
JobID int `json:"jobId"`
|
if err != nil {
|
||||||
Message string `json:"message"`
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
}
|
|
||||||
|
|
||||||
func handlePrintTestPage(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
|
||||||
if !ok || printerName == "" {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,16 +381,16 @@ func handlePrintTestPage(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"})
|
models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAddPrinterToClass(conn net.Conn, req Request, manager *Manager) {
|
func handleAddPrinterToClass(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
className, ok := req.Params["className"].(string)
|
className, err := params.StringNonEmpty(req.Params, "className")
|
||||||
if !ok || className == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,19 +398,19 @@ func handleAddPrinterToClass(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer added to class"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer added to class"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRemovePrinterFromClass(conn net.Conn, req Request, manager *Manager) {
|
func handleRemovePrinterFromClass(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
className, ok := req.Params["className"].(string)
|
className, err := params.StringNonEmpty(req.Params, "className")
|
||||||
if !ok || className == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
printerName, ok := req.Params["printerName"].(string)
|
printerName, err := params.StringNonEmpty(req.Params, "printerName")
|
||||||
if !ok || printerName == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,13 +418,13 @@ func handleRemovePrinterFromClass(conn net.Conn, req Request, manager *Manager)
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer removed from class"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer removed from class"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeleteClass(conn net.Conn, req Request, manager *Manager) {
|
func handleDeleteClass(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
className, ok := req.Params["className"].(string)
|
className, err := params.StringNonEmpty(req.Params, "className")
|
||||||
if !ok || className == "" {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,38 +432,35 @@ func handleDeleteClass(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "class deleted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "class deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRestartJob(conn net.Conn, req Request, manager *Manager) {
|
func handleRestartJob(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
jobID, err := params.Int(req.Params, "jobID")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.RestartJob(int(jobIDFloat)); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job restarted"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleHoldJob(conn net.Conn, req Request, manager *Manager) {
|
if err := manager.RestartJob(jobID); err != nil {
|
||||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
holdUntil, _ := req.Params["holdUntil"].(string)
|
|
||||||
if holdUntil == "" {
|
|
||||||
holdUntil = "indefinite"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.HoldJob(int(jobIDFloat), holdUntil); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job held"})
|
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"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func TestHandleGetPrinters(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.getPrinters",
|
Method: "cups.getPrinters",
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ func TestHandleGetPrinters_Error(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.getPrinters",
|
Method: "cups.getPrinters",
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func TestHandleGetJobs(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.getJobs",
|
Method: "cups.getJobs",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -127,7 +127,7 @@ func TestHandleGetJobs_MissingParam(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.getJobs",
|
Method: "cups.getJobs",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -152,7 +152,7 @@ func TestHandlePausePrinter(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.pausePrinter",
|
Method: "cups.pausePrinter",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -162,7 +162,7 @@ func TestHandlePausePrinter(t *testing.T) {
|
|||||||
|
|
||||||
handlePausePrinter(conn, req, m)
|
handlePausePrinter(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -179,7 +179,7 @@ func TestHandleResumePrinter(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.resumePrinter",
|
Method: "cups.resumePrinter",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -189,7 +189,7 @@ func TestHandleResumePrinter(t *testing.T) {
|
|||||||
|
|
||||||
handleResumePrinter(conn, req, m)
|
handleResumePrinter(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -206,7 +206,7 @@ func TestHandleCancelJob(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.cancelJob",
|
Method: "cups.cancelJob",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -216,7 +216,7 @@ func TestHandleCancelJob(t *testing.T) {
|
|||||||
|
|
||||||
handleCancelJob(conn, req, m)
|
handleCancelJob(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -233,7 +233,7 @@ func TestHandlePurgeJobs(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.purgeJobs",
|
Method: "cups.purgeJobs",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -243,7 +243,7 @@ func TestHandlePurgeJobs(t *testing.T) {
|
|||||||
|
|
||||||
handlePurgeJobs(conn, req, m)
|
handlePurgeJobs(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -260,7 +260,7 @@ func TestHandleRequest_UnknownMethod(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.unknownMethod",
|
Method: "cups.unknownMethod",
|
||||||
}
|
}
|
||||||
@@ -287,7 +287,7 @@ func TestHandleGetDevices(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{ID: 1, Method: "cups.getDevices"}
|
req := models.Request{ID: 1, Method: "cups.getDevices"}
|
||||||
handleGetDevices(conn, req, m)
|
handleGetDevices(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[[]Device]
|
var resp models.Response[[]Device]
|
||||||
@@ -309,7 +309,7 @@ func TestHandleGetPPDs(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{ID: 1, Method: "cups.getPPDs"}
|
req := models.Request{ID: 1, Method: "cups.getPPDs"}
|
||||||
handleGetPPDs(conn, req, m)
|
handleGetPPDs(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[[]PPD]
|
var resp models.Response[[]PPD]
|
||||||
@@ -332,7 +332,7 @@ func TestHandleGetClasses(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{ID: 1, Method: "cups.getClasses"}
|
req := models.Request{ID: 1, Method: "cups.getClasses"}
|
||||||
handleGetClasses(conn, req, m)
|
handleGetClasses(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[[]PrinterClass]
|
var resp models.Response[[]PrinterClass]
|
||||||
@@ -353,7 +353,7 @@ func TestHandleCreatePrinter(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.createPrinter",
|
Method: "cups.createPrinter",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -364,7 +364,7 @@ func TestHandleCreatePrinter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
handleCreatePrinter(conn, req, m)
|
handleCreatePrinter(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -377,7 +377,7 @@ func TestHandleCreatePrinter_MissingParams(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{ID: 1, Method: "cups.createPrinter", Params: map[string]any{}}
|
req := models.Request{ID: 1, Method: "cups.createPrinter", Params: map[string]any{}}
|
||||||
handleCreatePrinter(conn, req, m)
|
handleCreatePrinter(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[any]
|
var resp models.Response[any]
|
||||||
@@ -396,14 +396,14 @@ func TestHandleDeletePrinter(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.deletePrinter",
|
Method: "cups.deletePrinter",
|
||||||
Params: map[string]any{"printerName": "printer1"},
|
Params: map[string]any{"printerName": "printer1"},
|
||||||
}
|
}
|
||||||
handleDeletePrinter(conn, req, m)
|
handleDeletePrinter(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -419,14 +419,14 @@ func TestHandleAcceptJobs(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.acceptJobs",
|
Method: "cups.acceptJobs",
|
||||||
Params: map[string]any{"printerName": "printer1"},
|
Params: map[string]any{"printerName": "printer1"},
|
||||||
}
|
}
|
||||||
handleAcceptJobs(conn, req, m)
|
handleAcceptJobs(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -442,14 +442,14 @@ func TestHandleRejectJobs(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.rejectJobs",
|
Method: "cups.rejectJobs",
|
||||||
Params: map[string]any{"printerName": "printer1"},
|
Params: map[string]any{"printerName": "printer1"},
|
||||||
}
|
}
|
||||||
handleRejectJobs(conn, req, m)
|
handleRejectJobs(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -465,14 +465,14 @@ func TestHandleSetPrinterShared(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.setPrinterShared",
|
Method: "cups.setPrinterShared",
|
||||||
Params: map[string]any{"printerName": "printer1", "shared": true},
|
Params: map[string]any{"printerName": "printer1", "shared": true},
|
||||||
}
|
}
|
||||||
handleSetPrinterShared(conn, req, m)
|
handleSetPrinterShared(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -488,14 +488,14 @@ func TestHandleSetPrinterLocation(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.setPrinterLocation",
|
Method: "cups.setPrinterLocation",
|
||||||
Params: map[string]any{"printerName": "printer1", "location": "Office"},
|
Params: map[string]any{"printerName": "printer1", "location": "Office"},
|
||||||
}
|
}
|
||||||
handleSetPrinterLocation(conn, req, m)
|
handleSetPrinterLocation(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -511,14 +511,14 @@ func TestHandleSetPrinterInfo(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.setPrinterInfo",
|
Method: "cups.setPrinterInfo",
|
||||||
Params: map[string]any{"printerName": "printer1", "info": "Main Printer"},
|
Params: map[string]any{"printerName": "printer1", "info": "Main Printer"},
|
||||||
}
|
}
|
||||||
handleSetPrinterInfo(conn, req, m)
|
handleSetPrinterInfo(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -534,14 +534,14 @@ func TestHandleMoveJob(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.moveJob",
|
Method: "cups.moveJob",
|
||||||
Params: map[string]any{"jobID": float64(1), "destPrinter": "printer2"},
|
Params: map[string]any{"jobID": float64(1), "destPrinter": "printer2"},
|
||||||
}
|
}
|
||||||
handleMoveJob(conn, req, m)
|
handleMoveJob(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -557,7 +557,7 @@ func TestHandlePrintTestPage(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.printTestPage",
|
Method: "cups.printTestPage",
|
||||||
Params: map[string]any{"printerName": "printer1"},
|
Params: map[string]any{"printerName": "printer1"},
|
||||||
@@ -581,14 +581,14 @@ func TestHandleAddPrinterToClass(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.addPrinterToClass",
|
Method: "cups.addPrinterToClass",
|
||||||
Params: map[string]any{"className": "office", "printerName": "printer1"},
|
Params: map[string]any{"className": "office", "printerName": "printer1"},
|
||||||
}
|
}
|
||||||
handleAddPrinterToClass(conn, req, m)
|
handleAddPrinterToClass(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -604,14 +604,14 @@ func TestHandleRemovePrinterFromClass(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.removePrinterFromClass",
|
Method: "cups.removePrinterFromClass",
|
||||||
Params: map[string]any{"className": "office", "printerName": "printer1"},
|
Params: map[string]any{"className": "office", "printerName": "printer1"},
|
||||||
}
|
}
|
||||||
handleRemovePrinterFromClass(conn, req, m)
|
handleRemovePrinterFromClass(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -627,14 +627,14 @@ func TestHandleDeleteClass(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.deleteClass",
|
Method: "cups.deleteClass",
|
||||||
Params: map[string]any{"className": "office"},
|
Params: map[string]any{"className": "office"},
|
||||||
}
|
}
|
||||||
handleDeleteClass(conn, req, m)
|
handleDeleteClass(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -650,14 +650,14 @@ func TestHandleRestartJob(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.restartJob",
|
Method: "cups.restartJob",
|
||||||
Params: map[string]any{"jobID": float64(1)},
|
Params: map[string]any{"jobID": float64(1)},
|
||||||
}
|
}
|
||||||
handleRestartJob(conn, req, m)
|
handleRestartJob(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -673,14 +673,14 @@ func TestHandleHoldJob(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.holdJob",
|
Method: "cups.holdJob",
|
||||||
Params: map[string]any{"jobID": float64(1)},
|
Params: map[string]any{"jobID": float64(1)},
|
||||||
}
|
}
|
||||||
handleHoldJob(conn, req, m)
|
handleHoldJob(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
@@ -696,14 +696,14 @@ func TestHandleHoldJob_WithHoldUntil(t *testing.T) {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
|
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Method: "cups.holdJob",
|
Method: "cups.holdJob",
|
||||||
Params: map[string]any{"jobID": float64(1), "holdUntil": "no-hold"},
|
Params: map[string]any{"jobID": float64(1), "holdUntil": "no-hold"},
|
||||||
}
|
}
|
||||||
handleHoldJob(conn, req, m)
|
handleHoldJob(conn, req, m)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(buf).Decode(&resp)
|
err := json.NewDecoder(buf).Decode(&resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, resp.Result)
|
assert.NotNil(t, resp.Result)
|
||||||
|
|||||||
@@ -8,18 +8,12 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
type SuccessResult struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
||||||
return
|
return
|
||||||
@@ -41,12 +35,12 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
state := manager.GetState()
|
||||||
models.Respond(conn, req.ID, state)
|
models.Respond(conn, req.ID, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetTags(conn net.Conn, req Request, manager *Manager) {
|
func handleSetTags(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
output, ok := req.Params["output"].(string)
|
output, ok := req.Params["output"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||||
@@ -73,7 +67,7 @@ func handleSetTags(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetClientTags(conn net.Conn, req Request, manager *Manager) {
|
func handleSetClientTags(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
output, ok := req.Params["output"].(string)
|
output, ok := req.Params["output"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||||
@@ -100,7 +94,7 @@ func handleSetClientTags(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetLayout(conn net.Conn, req Request, manager *Manager) {
|
func handleSetLayout(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
output, ok := req.Params["output"].(string)
|
output, ok := req.Params["output"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||||
@@ -121,7 +115,7 @@ func handleSetLayout(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|||||||
@@ -6,22 +6,15 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
|
||||||
ID any `json:"id"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, m *Manager) {
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "evdev.getState":
|
case "evdev.getState":
|
||||||
handleGetState(conn, req, m)
|
handleGetState(conn, req, m)
|
||||||
default:
|
default:
|
||||||
models.RespondError(conn, req.ID.(int), "unknown method: "+req.Method)
|
models.RespondError(conn, req.ID, "unknown method: "+req.Method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, m *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, m *Manager) {
|
||||||
state := m.GetState()
|
models.Respond(conn, req.ID, m.GetState())
|
||||||
models.Respond(conn, req.ID.(int), state)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "evdev.getState",
|
Method: "evdev.getState",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -82,7 +82,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 456,
|
ID: 456,
|
||||||
Method: "evdev.unknownMethod",
|
Method: "evdev.unknownMethod",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -111,7 +111,7 @@ func TestHandleGetState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 789,
|
ID: 789,
|
||||||
Method: "evdev.getState",
|
Method: "evdev.getState",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
|
|||||||
@@ -8,18 +8,12 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
type SuccessResult struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
models.RespondError(conn, req.ID, "extworkspace manager not initialized")
|
models.RespondError(conn, req.ID, "extworkspace manager not initialized")
|
||||||
return
|
return
|
||||||
@@ -43,12 +37,12 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
state := manager.GetState()
|
||||||
models.Respond(conn, req.ID, state)
|
models.Respond(conn, req.ID, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleActivateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
func handleActivateWorkspace(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
groupID, ok := req.Params["groupID"].(string)
|
groupID, ok := req.Params["groupID"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
groupID = ""
|
groupID = ""
|
||||||
@@ -68,7 +62,7 @@ func handleActivateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace activated"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace activated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeactivateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
func handleDeactivateWorkspace(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
groupID, ok := req.Params["groupID"].(string)
|
groupID, ok := req.Params["groupID"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
groupID = ""
|
groupID = ""
|
||||||
@@ -88,7 +82,7 @@ func handleDeactivateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace deactivated"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace deactivated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRemoveWorkspace(conn net.Conn, req Request, manager *Manager) {
|
func handleRemoveWorkspace(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
groupID, ok := req.Params["groupID"].(string)
|
groupID, ok := req.Params["groupID"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
groupID = ""
|
groupID = ""
|
||||||
@@ -108,7 +102,7 @@ func handleRemoveWorkspace(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace removed"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace removed"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
func handleCreateWorkspace(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
groupID, ok := req.Params["groupID"].(string)
|
groupID, ok := req.Params["groupID"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'groupID' parameter")
|
models.RespondError(conn, req.ID, "missing or invalid 'groupID' parameter")
|
||||||
@@ -129,7 +123,7 @@ func handleCreateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace create requested"})
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace create requested"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|||||||
@@ -5,21 +5,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Value string `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "freedesktop.getState":
|
case "freedesktop.getState":
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
@@ -44,15 +33,14 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetIconFile(conn net.Conn, req Request, manager *Manager) {
|
func handleSetIconFile(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
iconPath, ok := req.Params["path"].(string)
|
iconPath, err := params.String(req.Params, "path")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'path' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,13 +49,13 @@ func handleSetIconFile(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "icon file set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "icon file set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetRealName(conn net.Conn, req Request, manager *Manager) {
|
func handleSetRealName(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
name, ok := req.Params["name"].(string)
|
name, err := params.String(req.Params, "name")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,13 +64,13 @@ func handleSetRealName(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "real name set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "real name set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetEmail(conn net.Conn, req Request, manager *Manager) {
|
func handleSetEmail(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
email, ok := req.Params["email"].(string)
|
email, err := params.String(req.Params, "email")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'email' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,13 +79,13 @@ func handleSetEmail(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "email set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "email set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetLanguage(conn net.Conn, req Request, manager *Manager) {
|
func handleSetLanguage(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
language, ok := req.Params["language"].(string)
|
language, err := params.String(req.Params, "language")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'language' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +94,13 @@ func handleSetLanguage(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "language set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "language set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetLocation(conn net.Conn, req Request, manager *Manager) {
|
func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
location, ok := req.Params["location"].(string)
|
location, err := params.String(req.Params, "location")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'location' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,13 +109,13 @@ func handleSetLocation(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "location set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetUserIconFile(conn net.Conn, req Request, manager *Manager) {
|
func handleGetUserIconFile(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
username, ok := req.Params["username"].(string)
|
username, err := params.String(req.Params, "username")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'username' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +125,10 @@ func handleGetUserIconFile(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Value: iconFile})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Value: iconFile})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetColorScheme(conn net.Conn, req Request, manager *Manager) {
|
func handleGetColorScheme(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.updateSettingsState(); err != nil {
|
if err := manager.updateSettingsState(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
@@ -150,10 +138,10 @@ func handleGetColorScheme(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, map[string]uint32{"colorScheme": state.Settings.ColorScheme})
|
models.Respond(conn, req.ID, map[string]uint32{"colorScheme": state.Settings.ColorScheme})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetIconTheme(conn net.Conn, req Request, manager *Manager) {
|
func handleSetIconTheme(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
iconTheme, ok := req.Params["iconTheme"].(string)
|
iconTheme, err := params.String(req.Params, "iconTheme")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'iconTheme' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,5 +150,5 @@ func handleSetIconTheme(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "icon theme set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "icon theme set"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,10 +74,10 @@ func TestRespondError_Freedesktop(t *testing.T) {
|
|||||||
|
|
||||||
func TestRespond_Freedesktop(t *testing.T) {
|
func TestRespond_Freedesktop(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
result := SuccessResult{Success: true, Message: "test"}
|
result := models.SuccessResult{Success: true, Message: "test"}
|
||||||
models.Respond(conn, 123, result)
|
models.Respond(conn, 123, result)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ func TestHandleGetState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "freedesktop.getState"}
|
req := models.Request{ID: 123, Method: "freedesktop.getState"}
|
||||||
|
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ func TestHandleSetIconFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setIconFile",
|
Method: "freedesktop.accounts.setIconFile",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -164,7 +164,7 @@ func TestHandleSetIconFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setIconFile",
|
Method: "freedesktop.accounts.setIconFile",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -174,7 +174,7 @@ func TestHandleSetIconFile(t *testing.T) {
|
|||||||
|
|
||||||
handleSetIconFile(conn, req, manager)
|
handleSetIconFile(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ func TestHandleSetIconFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setIconFile",
|
Method: "freedesktop.accounts.setIconFile",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -206,7 +206,7 @@ func TestHandleSetIconFile(t *testing.T) {
|
|||||||
|
|
||||||
handleSetIconFile(conn, req, manager)
|
handleSetIconFile(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ func TestHandleSetRealName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setRealName",
|
Method: "freedesktop.accounts.setRealName",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -256,7 +256,7 @@ func TestHandleSetRealName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setRealName",
|
Method: "freedesktop.accounts.setRealName",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -266,7 +266,7 @@ func TestHandleSetRealName(t *testing.T) {
|
|||||||
|
|
||||||
handleSetRealName(conn, req, manager)
|
handleSetRealName(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ func TestHandleSetEmail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setEmail",
|
Method: "freedesktop.accounts.setEmail",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -319,7 +319,7 @@ func TestHandleSetEmail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setEmail",
|
Method: "freedesktop.accounts.setEmail",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -329,7 +329,7 @@ func TestHandleSetEmail(t *testing.T) {
|
|||||||
|
|
||||||
handleSetEmail(conn, req, manager)
|
handleSetEmail(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -349,7 +349,7 @@ func TestHandleSetLanguage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setLanguage",
|
Method: "freedesktop.accounts.setLanguage",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -374,7 +374,7 @@ func TestHandleSetLocation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.setLocation",
|
Method: "freedesktop.accounts.setLocation",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -399,7 +399,7 @@ func TestHandleGetUserIconFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.getUserIconFile",
|
Method: "freedesktop.accounts.getUserIconFile",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -426,7 +426,7 @@ func TestHandleGetUserIconFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.accounts.getUserIconFile",
|
Method: "freedesktop.accounts.getUserIconFile",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -436,7 +436,7 @@ func TestHandleGetUserIconFile(t *testing.T) {
|
|||||||
|
|
||||||
handleGetUserIconFile(conn, req, manager)
|
handleGetUserIconFile(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ func TestHandleGetColorScheme(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "freedesktop.settings.getColorScheme"}
|
req := models.Request{ID: 123, Method: "freedesktop.settings.getColorScheme"}
|
||||||
|
|
||||||
handleGetColorScheme(conn, req, manager)
|
handleGetColorScheme(conn, req, manager)
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ func TestHandleGetColorScheme(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "freedesktop.settings.getColorScheme"}
|
req := models.Request{ID: 123, Method: "freedesktop.settings.getColorScheme"}
|
||||||
|
|
||||||
handleGetColorScheme(conn, req, manager)
|
handleGetColorScheme(conn, req, manager)
|
||||||
|
|
||||||
@@ -516,7 +516,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("unknown method", func(t *testing.T) {
|
t.Run("unknown method", func(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.unknown",
|
Method: "freedesktop.unknown",
|
||||||
}
|
}
|
||||||
@@ -533,7 +533,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("valid method - getState", func(t *testing.T) {
|
t.Run("valid method - getState", func(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "freedesktop.getState",
|
Method: "freedesktop.getState",
|
||||||
}
|
}
|
||||||
@@ -561,7 +561,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
for _, method := range tests {
|
for _, method := range tests {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
|
|||||||
@@ -6,20 +6,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "loginctl.getState":
|
case "loginctl.getState":
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
@@ -46,39 +36,38 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLock(conn net.Conn, req Request, manager *Manager) {
|
func handleLock(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.Lock(); err != nil {
|
if err := manager.Lock(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "locked"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "locked"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUnlock(conn net.Conn, req Request, manager *Manager) {
|
func handleUnlock(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.Unlock(); err != nil {
|
if err := manager.Unlock(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "unlocked"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "unlocked"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleActivate(conn net.Conn, req Request, manager *Manager) {
|
func handleActivate(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.Activate(); err != nil {
|
if err := manager.Activate(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "activated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "activated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetIdleHint(conn net.Conn, req Request, manager *Manager) {
|
func handleSetIdleHint(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
idle, ok := req.Params["idle"].(bool)
|
idle, err := params.Bool(req.Params, "idle")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'idle' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,32 +75,32 @@ func handleSetIdleHint(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "idle hint set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "idle hint set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetLockBeforeSuspend(conn net.Conn, req Request, manager *Manager) {
|
func handleSetLockBeforeSuspend(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
enabled, ok := req.Params["enabled"].(bool)
|
enabled, err := params.Bool(req.Params, "enabled")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'enabled' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.SetLockBeforeSuspend(enabled)
|
manager.SetLockBeforeSuspend(enabled)
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "lock before suspend set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "lock before suspend set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetSleepInhibitorEnabled(conn net.Conn, req Request, manager *Manager) {
|
func handleSetSleepInhibitorEnabled(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
enabled, ok := req.Params["enabled"].(bool)
|
enabled, err := params.Bool(req.Params, "enabled")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'enabled' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.SetSleepInhibitorEnabled(enabled)
|
manager.SetSleepInhibitorEnabled(enabled)
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "sleep inhibitor setting updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sleep inhibitor setting updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLockerReady(conn net.Conn, req Request, manager *Manager) {
|
func handleLockerReady(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
manager.lockTimerMu.Lock()
|
manager.lockTimerMu.Lock()
|
||||||
if manager.lockTimer != nil {
|
if manager.lockTimer != nil {
|
||||||
manager.lockTimer.Stop()
|
manager.lockTimer.Stop()
|
||||||
@@ -125,18 +114,18 @@ func handleLockerReady(conn net.Conn, req Request, manager *Manager) {
|
|||||||
if manager.inSleepCycle.Load() {
|
if manager.inSleepCycle.Load() {
|
||||||
manager.signalLockerReady()
|
manager.signalLockerReady()
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "ok"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "ok"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTerminate(conn net.Conn, req Request, manager *Manager) {
|
func handleTerminate(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.Terminate(); err != nil {
|
if err := manager.Terminate(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "terminated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "terminated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ func TestRespondError_Loginctl(t *testing.T) {
|
|||||||
|
|
||||||
func TestRespond_Loginctl(t *testing.T) {
|
func TestRespond_Loginctl(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
result := SuccessResult{Success: true, Message: "test"}
|
result := models.SuccessResult{Success: true, Message: "test"}
|
||||||
models.Respond(conn, 123, result)
|
models.Respond(conn, 123, result)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ func TestHandleGetState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.getState"}
|
req := models.Request{ID: 123, Method: "loginctl.getState"}
|
||||||
|
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
|
|
||||||
@@ -115,10 +115,10 @@ func TestHandleLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.lock"}
|
req := models.Request{ID: 123, Method: "loginctl.lock"}
|
||||||
handleLock(conn, req, manager)
|
handleLock(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -141,10 +141,10 @@ func TestHandleLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.lock"}
|
req := models.Request{ID: 123, Method: "loginctl.lock"}
|
||||||
handleLock(conn, req, manager)
|
handleLock(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -166,10 +166,10 @@ func TestHandleUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.unlock"}
|
req := models.Request{ID: 123, Method: "loginctl.unlock"}
|
||||||
handleUnlock(conn, req, manager)
|
handleUnlock(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -192,10 +192,10 @@ func TestHandleUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.unlock"}
|
req := models.Request{ID: 123, Method: "loginctl.unlock"}
|
||||||
handleUnlock(conn, req, manager)
|
handleUnlock(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -217,10 +217,10 @@ func TestHandleActivate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.activate"}
|
req := models.Request{ID: 123, Method: "loginctl.activate"}
|
||||||
handleActivate(conn, req, manager)
|
handleActivate(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -243,10 +243,10 @@ func TestHandleActivate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.activate"}
|
req := models.Request{ID: 123, Method: "loginctl.activate"}
|
||||||
handleActivate(conn, req, manager)
|
handleActivate(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ func TestHandleSetIdleHint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "loginctl.setIdleHint",
|
Method: "loginctl.setIdleHint",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -291,7 +291,7 @@ func TestHandleSetIdleHint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "loginctl.setIdleHint",
|
Method: "loginctl.setIdleHint",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -301,7 +301,7 @@ func TestHandleSetIdleHint(t *testing.T) {
|
|||||||
|
|
||||||
handleSetIdleHint(conn, req, manager)
|
handleSetIdleHint(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -324,7 +324,7 @@ func TestHandleSetIdleHint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "loginctl.setIdleHint",
|
Method: "loginctl.setIdleHint",
|
||||||
Params: map[string]any{
|
Params: map[string]any{
|
||||||
@@ -334,7 +334,7 @@ func TestHandleSetIdleHint(t *testing.T) {
|
|||||||
|
|
||||||
handleSetIdleHint(conn, req, manager)
|
handleSetIdleHint(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -356,10 +356,10 @@ func TestHandleTerminate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.terminate"}
|
req := models.Request{ID: 123, Method: "loginctl.terminate"}
|
||||||
handleTerminate(conn, req, manager)
|
handleTerminate(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -382,10 +382,10 @@ func TestHandleTerminate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.terminate"}
|
req := models.Request{ID: 123, Method: "loginctl.terminate"}
|
||||||
handleTerminate(conn, req, manager)
|
handleTerminate(conn, req, manager)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("unknown method", func(t *testing.T) {
|
t.Run("unknown method", func(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "loginctl.unknown",
|
Method: "loginctl.unknown",
|
||||||
}
|
}
|
||||||
@@ -422,7 +422,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("valid method - getState", func(t *testing.T) {
|
t.Run("valid method - getState", func(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "loginctl.getState",
|
Method: "loginctl.getState",
|
||||||
}
|
}
|
||||||
@@ -445,7 +445,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
manager.sessionObj = mockSessionObj
|
manager.sessionObj = mockSessionObj
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "loginctl.lock",
|
Method: "loginctl.lock",
|
||||||
}
|
}
|
||||||
@@ -470,7 +470,7 @@ func TestHandleSubscribe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "loginctl.subscribe"}
|
req := models.Request{ID: 123, Method: "loginctl.subscribe"}
|
||||||
|
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -29,3 +29,9 @@ func Respond[T any](conn net.Conn, id int, result T) {
|
|||||||
resp := Response[T]{ID: id, Result: &result}
|
resp := Response[T]{ID: id, Result: &result}
|
||||||
json.NewEncoder(conn).Encode(resp)
|
json.NewEncoder(conn).Encode(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SuccessResult struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,20 +7,10 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "network.getState":
|
case "network.getState":
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
@@ -89,32 +79,22 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCredentialsSubmit(conn net.Conn, req Request, manager *Manager) {
|
func handleCredentialsSubmit(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
token, ok := req.Params["token"].(string)
|
token, err := params.String(req.Params, "token")
|
||||||
if !ok {
|
if err != nil {
|
||||||
log.Warnf("handleCredentialsSubmit: missing or invalid token parameter")
|
log.Warnf("handleCredentialsSubmit: missing or invalid token parameter")
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'token' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsRaw, ok := req.Params["secrets"].(map[string]any)
|
secrets, err := params.StringMap(req.Params, "secrets")
|
||||||
if !ok {
|
if err != nil {
|
||||||
log.Warnf("handleCredentialsSubmit: missing or invalid secrets parameter")
|
log.Warnf("handleCredentialsSubmit: missing or invalid secrets parameter")
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'secrets' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets := make(map[string]string)
|
save := params.BoolOpt(req.Params, "save", true)
|
||||||
for k, v := range secretsRaw {
|
|
||||||
if str, ok := v.(string); ok {
|
|
||||||
secrets[k] = str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
save := true
|
|
||||||
if saveParam, ok := req.Params["save"].(bool); ok {
|
|
||||||
save = saveParam
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.SubmitCredentials(token, secrets, save); err != nil {
|
if err := manager.SubmitCredentials(token, secrets, save); err != nil {
|
||||||
log.Warnf("handleCredentialsSubmit: failed to submit credentials: %v", err)
|
log.Warnf("handleCredentialsSubmit: failed to submit credentials: %v", err)
|
||||||
@@ -123,13 +103,13 @@ func handleCredentialsSubmit(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("handleCredentialsSubmit: credentials submitted successfully")
|
log.Infof("handleCredentialsSubmit: credentials submitted successfully")
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "credentials submitted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "credentials submitted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCredentialsCancel(conn net.Conn, req Request, manager *Manager) {
|
func handleCredentialsCancel(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
token, ok := req.Params["token"].(string)
|
token, err := params.String(req.Params, "token")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'token' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,16 +118,15 @@ func handleCredentialsCancel(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "credentials cancelled"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "credentials cancelled"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleScanWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleScanWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
device, _ := req.Params["device"].(string)
|
device := params.StringOpt(req.Params, "device", "")
|
||||||
var err error
|
var err error
|
||||||
if device != "" {
|
if device != "" {
|
||||||
err = manager.ScanWiFiDevice(device)
|
err = manager.ScanWiFiDevice(device)
|
||||||
@@ -158,33 +137,25 @@ func handleScanWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "scanning"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "scanning"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetWiFiNetworks(conn net.Conn, req Request, manager *Manager) {
|
func handleGetWiFiNetworks(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
networks := manager.GetWiFiNetworks()
|
models.Respond(conn, req.ID, manager.GetWiFiNetworks())
|
||||||
models.Respond(conn, req.ID, networks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleConnectWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ssid, ok := req.Params["ssid"].(string)
|
ssid, err := params.String(req.Params, "ssid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'ssid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var connReq ConnectionRequest
|
var connReq ConnectionRequest
|
||||||
connReq.SSID = ssid
|
connReq.SSID = ssid
|
||||||
|
connReq.Password = params.StringOpt(req.Params, "password", "")
|
||||||
if password, ok := req.Params["password"].(string); ok {
|
connReq.Username = params.StringOpt(req.Params, "username", "")
|
||||||
connReq.Password = password
|
connReq.Device = params.StringOpt(req.Params, "device", "")
|
||||||
}
|
|
||||||
if username, ok := req.Params["username"].(string); ok {
|
|
||||||
connReq.Username = username
|
|
||||||
}
|
|
||||||
if device, ok := req.Params["device"].(string); ok {
|
|
||||||
connReq.Device = device
|
|
||||||
}
|
|
||||||
|
|
||||||
if interactive, ok := req.Params["interactive"].(bool); ok {
|
if interactive, ok := req.Params["interactive"].(bool); ok {
|
||||||
connReq.Interactive = interactive
|
connReq.Interactive = interactive
|
||||||
@@ -206,27 +177,14 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if anonymousIdentity, ok := req.Params["anonymousIdentity"].(string); ok {
|
connReq.AnonymousIdentity = params.StringOpt(req.Params, "anonymousIdentity", "")
|
||||||
connReq.AnonymousIdentity = anonymousIdentity
|
connReq.DomainSuffixMatch = params.StringOpt(req.Params, "domainSuffixMatch", "")
|
||||||
}
|
connReq.EAPMethod = params.StringOpt(req.Params, "eapMethod", "")
|
||||||
if domainSuffixMatch, ok := req.Params["domainSuffixMatch"].(string); ok {
|
connReq.Phase2Auth = params.StringOpt(req.Params, "phase2Auth", "")
|
||||||
connReq.DomainSuffixMatch = domainSuffixMatch
|
connReq.CACertPath = params.StringOpt(req.Params, "caCertPath", "")
|
||||||
}
|
connReq.ClientCertPath = params.StringOpt(req.Params, "clientCertPath", "")
|
||||||
if eapMethod, ok := req.Params["eapMethod"].(string); ok {
|
connReq.PrivateKeyPath = params.StringOpt(req.Params, "privateKeyPath", "")
|
||||||
connReq.EAPMethod = eapMethod
|
|
||||||
}
|
|
||||||
if phase2Auth, ok := req.Params["phase2Auth"].(string); ok {
|
|
||||||
connReq.Phase2Auth = phase2Auth
|
|
||||||
}
|
|
||||||
if caCertPath, ok := req.Params["caCertPath"].(string); ok {
|
|
||||||
connReq.CACertPath = caCertPath
|
|
||||||
}
|
|
||||||
if clientCertPath, ok := req.Params["clientCertPath"].(string); ok {
|
|
||||||
connReq.ClientCertPath = clientCertPath
|
|
||||||
}
|
|
||||||
if privateKeyPath, ok := req.Params["privateKeyPath"].(string); ok {
|
|
||||||
connReq.PrivateKeyPath = privateKeyPath
|
|
||||||
}
|
|
||||||
if useSystemCACerts, ok := req.Params["useSystemCACerts"].(bool); ok {
|
if useSystemCACerts, ok := req.Params["useSystemCACerts"].(bool); ok {
|
||||||
connReq.UseSystemCACerts = &useSystemCACerts
|
connReq.UseSystemCACerts = &useSystemCACerts
|
||||||
}
|
}
|
||||||
@@ -236,11 +194,11 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "connecting"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisconnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleDisconnectWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
device, _ := req.Params["device"].(string)
|
device := params.StringOpt(req.Params, "device", "")
|
||||||
var err error
|
var err error
|
||||||
if device != "" {
|
if device != "" {
|
||||||
err = manager.DisconnectWiFiDevice(device)
|
err = manager.DisconnectWiFiDevice(device)
|
||||||
@@ -251,13 +209,13 @@ func handleDisconnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "disconnected"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleForgetWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleForgetWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ssid, ok := req.Params["ssid"].(string)
|
ssid, err := params.String(req.Params, "ssid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'ssid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,10 +224,10 @@ func handleForgetWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "forgotten"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "forgotten"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleToggleWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleToggleWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.ToggleWiFi(); err != nil {
|
if err := manager.ToggleWiFi(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
@@ -279,7 +237,7 @@ func handleToggleWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, map[string]bool{"enabled": state.WiFiEnabled})
|
models.Respond(conn, req.ID, map[string]bool{"enabled": state.WiFiEnabled})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleEnableWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleEnableWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.EnableWiFi(); err != nil {
|
if err := manager.EnableWiFi(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
@@ -287,7 +245,7 @@ func handleEnableWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, map[string]bool{"enabled": true})
|
models.Respond(conn, req.ID, map[string]bool{"enabled": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisableWiFi(conn net.Conn, req Request, manager *Manager) {
|
func handleDisableWiFi(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.DisableWiFi(); err != nil {
|
if err := manager.DisableWiFi(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
@@ -295,29 +253,29 @@ func handleDisableWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, map[string]bool{"enabled": false})
|
models.Respond(conn, req.ID, map[string]bool{"enabled": false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnectEthernetSpecificConfig(conn net.Conn, req Request, manager *Manager) {
|
func handleConnectEthernetSpecificConfig(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuid, ok := req.Params["uuid"].(string)
|
uuid, err := params.String(req.Params, "uuid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'uuid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := manager.activateConnection(uuid); err != nil {
|
if err := manager.activateConnection(uuid); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "connecting"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
func handleConnectEthernet(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.ConnectEthernet(); err != nil {
|
if err := manager.ConnectEthernet(); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "connecting"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisconnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
func handleDisconnectEthernet(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
device, _ := req.Params["device"].(string)
|
device := params.StringOpt(req.Params, "device", "")
|
||||||
var err error
|
var err error
|
||||||
if device != "" {
|
if device != "" {
|
||||||
err = manager.DisconnectEthernetDevice(device)
|
err = manager.DisconnectEthernetDevice(device)
|
||||||
@@ -328,13 +286,13 @@ func handleDisconnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "disconnected"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetPreference(conn net.Conn, req Request, manager *Manager) {
|
func handleSetPreference(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
preference, ok := req.Params["preference"].(string)
|
preference, err := params.String(req.Params, "preference")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'preference' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,10 +304,10 @@ func handleSetPreference(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, map[string]string{"preference": preference})
|
models.Respond(conn, req.ID, map[string]string{"preference": preference})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetNetworkInfo(conn net.Conn, req Request, manager *Manager) {
|
func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ssid, ok := req.Params["ssid"].(string)
|
ssid, err := params.String(req.Params, "ssid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'ssid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,10 +320,10 @@ func handleGetNetworkInfo(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, network)
|
models.Respond(conn, req.ID, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetWiredNetworkInfo(conn net.Conn, req Request, manager *Manager) {
|
func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuid, ok := req.Params["uuid"].(string)
|
uuid, err := params.String(req.Params, "uuid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'uuid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +336,7 @@ func handleGetWiredNetworkInfo(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, network)
|
models.Respond(conn, req.ID, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
@@ -408,7 +366,7 @@ func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleListVPNProfiles(conn net.Conn, req Request, manager *Manager) {
|
func handleListVPNProfiles(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
profiles, err := manager.ListVPNProfiles()
|
profiles, err := manager.ListVPNProfiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("handleListVPNProfiles: failed to list profiles: %v", err)
|
log.Warnf("handleListVPNProfiles: failed to list profiles: %v", err)
|
||||||
@@ -419,7 +377,7 @@ func handleListVPNProfiles(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, profiles)
|
models.Respond(conn, req.ID, profiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleListActiveVPN(conn net.Conn, req Request, manager *Manager) {
|
func handleListActiveVPN(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
active, err := manager.ListActiveVPN()
|
active, err := manager.ListActiveVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("handleListActiveVPN: failed to list active VPNs: %v", err)
|
log.Warnf("handleListActiveVPN: failed to list active VPNs: %v", err)
|
||||||
@@ -430,27 +388,15 @@ func handleListActiveVPN(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, active)
|
models.Respond(conn, req.ID, active)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnectVPN(conn net.Conn, req Request, manager *Manager) {
|
func handleConnectVPN(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuidOrName, ok := req.Params["uuidOrName"].(string)
|
uuidOrName, ok := params.StringAlt(req.Params, "uuidOrName", "name", "uuid")
|
||||||
if !ok {
|
if !ok {
|
||||||
name, nameOk := req.Params["name"].(string)
|
log.Warnf("handleConnectVPN: missing uuidOrName/name/uuid parameter")
|
||||||
uuid, uuidOk := req.Params["uuid"].(string)
|
models.RespondError(conn, req.ID, "missing 'uuidOrName', 'name', or 'uuid' parameter")
|
||||||
if nameOk {
|
return
|
||||||
uuidOrName = name
|
|
||||||
} else if uuidOk {
|
|
||||||
uuidOrName = uuid
|
|
||||||
} else {
|
|
||||||
log.Warnf("handleConnectVPN: missing uuidOrName/name/uuid parameter")
|
|
||||||
models.RespondError(conn, req.ID, "missing 'uuidOrName', 'name', or 'uuid' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to true - only allow one VPN connection at a time
|
singleActive := params.BoolOpt(req.Params, "singleActive", true)
|
||||||
singleActive := true
|
|
||||||
if sa, ok := req.Params["singleActive"].(bool); ok {
|
|
||||||
singleActive = sa
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.ConnectVPN(uuidOrName, singleActive); err != nil {
|
if err := manager.ConnectVPN(uuidOrName, singleActive); err != nil {
|
||||||
log.Warnf("handleConnectVPN: failed to connect: %v", err)
|
log.Warnf("handleConnectVPN: failed to connect: %v", err)
|
||||||
@@ -458,23 +404,15 @@ func handleConnectVPN(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN connection initiated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN connection initiated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisconnectVPN(conn net.Conn, req Request, manager *Manager) {
|
func handleDisconnectVPN(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuidOrName, ok := req.Params["uuidOrName"].(string)
|
uuidOrName, ok := params.StringAlt(req.Params, "uuidOrName", "name", "uuid")
|
||||||
if !ok {
|
if !ok {
|
||||||
name, nameOk := req.Params["name"].(string)
|
log.Warnf("handleDisconnectVPN: missing uuidOrName/name/uuid parameter")
|
||||||
uuid, uuidOk := req.Params["uuid"].(string)
|
models.RespondError(conn, req.ID, "missing 'uuidOrName', 'name', or 'uuid' parameter")
|
||||||
if nameOk {
|
return
|
||||||
uuidOrName = name
|
|
||||||
} else if uuidOk {
|
|
||||||
uuidOrName = uuid
|
|
||||||
} else {
|
|
||||||
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 {
|
if err := manager.DisconnectVPN(uuidOrName); err != nil {
|
||||||
@@ -483,27 +421,21 @@ func handleDisconnectVPN(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN disconnected"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN disconnected"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisconnectAllVPN(conn net.Conn, req Request, manager *Manager) {
|
func handleDisconnectAllVPN(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if err := manager.DisconnectAllVPN(); err != nil {
|
if err := manager.DisconnectAllVPN(); err != nil {
|
||||||
log.Warnf("handleDisconnectAllVPN: failed: %v", err)
|
log.Warnf("handleDisconnectAllVPN: failed: %v", err)
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to disconnect all VPNs: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to disconnect all VPNs: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "All VPNs disconnected"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "All VPNs disconnected"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleClearVPNCredentials(conn net.Conn, req Request, manager *Manager) {
|
func handleClearVPNCredentials(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuidOrName, ok := req.Params["uuid"].(string)
|
uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName")
|
||||||
if !ok {
|
|
||||||
uuidOrName, ok = req.Params["name"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warnf("handleClearVPNCredentials: missing uuidOrName/name/uuid parameter")
|
log.Warnf("handleClearVPNCredentials: missing uuidOrName/name/uuid parameter")
|
||||||
models.RespondError(conn, req.ID, "missing uuidOrName/name/uuid parameter")
|
models.RespondError(conn, req.ID, "missing uuidOrName/name/uuid parameter")
|
||||||
@@ -516,19 +448,19 @@ func handleClearVPNCredentials(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN credentials cleared"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN credentials cleared"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetWiFiAutoconnect(conn net.Conn, req Request, manager *Manager) {
|
func handleSetWiFiAutoconnect(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ssid, ok := req.Params["ssid"].(string)
|
ssid, err := params.String(req.Params, "ssid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'ssid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
autoconnect, ok := req.Params["autoconnect"].(bool)
|
autoconnect, err := params.Bool(req.Params, "autoconnect")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'autoconnect' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,10 +469,10 @@ func handleSetWiFiAutoconnect(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "autoconnect updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "autoconnect updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleListVPNPlugins(conn net.Conn, req Request, manager *Manager) {
|
func handleListVPNPlugins(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
plugins, err := manager.ListVPNPlugins()
|
plugins, err := manager.ListVPNPlugins()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err)
|
log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err)
|
||||||
@@ -551,17 +483,14 @@ func handleListVPNPlugins(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, plugins)
|
models.Respond(conn, req.ID, plugins)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleImportVPN(conn net.Conn, req Request, manager *Manager) {
|
func handleImportVPN(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
filePath, ok := req.Params["file"].(string)
|
filePath, ok := params.StringAlt(req.Params, "file", "path")
|
||||||
if !ok {
|
|
||||||
filePath, ok = req.Params["path"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter")
|
models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name, _ := req.Params["name"].(string)
|
name := params.StringOpt(req.Params, "name", "")
|
||||||
|
|
||||||
result, err := manager.ImportVPN(filePath, name)
|
result, err := manager.ImportVPN(filePath, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -573,14 +502,8 @@ func handleImportVPN(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, result)
|
models.Respond(conn, req.ID, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
func handleGetVPNConfig(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuidOrName, ok := req.Params["uuid"].(string)
|
uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName")
|
||||||
if !ok {
|
|
||||||
uuidOrName, ok = req.Params["name"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
||||||
return
|
return
|
||||||
@@ -596,10 +519,10 @@ func handleGetVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, config)
|
models.Respond(conn, req.ID, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUpdateVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
func handleUpdateVPNConfig(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
connUUID, ok := req.Params["uuid"].(string)
|
connUUID, err := params.String(req.Params, "uuid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,17 +549,11 @@ func handleUpdateVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN config updated"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN config updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeleteVPN(conn net.Conn, req Request, manager *Manager) {
|
func handleDeleteVPN(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuidOrName, ok := req.Params["uuid"].(string)
|
uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName")
|
||||||
if !ok {
|
|
||||||
uuidOrName, ok = req.Params["name"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
||||||
return
|
return
|
||||||
@@ -648,23 +565,19 @@ func handleDeleteVPN(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN deleted"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetVPNCredentials(conn net.Conn, req Request, manager *Manager) {
|
func handleSetVPNCredentials(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
connUUID, ok := req.Params["uuid"].(string)
|
connUUID, err := params.String(req.Params, "uuid")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username, _ := req.Params["username"].(string)
|
username := params.StringOpt(req.Params, "username", "")
|
||||||
password, _ := req.Params["password"].(string)
|
password := params.StringOpt(req.Params, "password", "")
|
||||||
|
save := params.BoolOpt(req.Params, "save", true)
|
||||||
save := true
|
|
||||||
if saveParam, ok := req.Params["save"].(bool); ok {
|
|
||||||
save = saveParam
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.SetVPNCredentials(connUUID, username, password, save); err != nil {
|
if err := manager.SetVPNCredentials(connUUID, username, password, save); err != nil {
|
||||||
log.Warnf("handleSetVPNCredentials: failed to set credentials: %v", err)
|
log.Warnf("handleSetVPNCredentials: failed to set credentials: %v", err)
|
||||||
@@ -672,5 +585,5 @@ func handleSetVPNCredentials(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN credentials set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN credentials set"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ func TestRespondError_Network(t *testing.T) {
|
|||||||
|
|
||||||
func TestRespond_Network(t *testing.T) {
|
func TestRespond_Network(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
result := SuccessResult{Success: true, Message: "test"}
|
result := models.SuccessResult{Success: true, Message: "test"}
|
||||||
models.Respond(conn, 123, result)
|
models.Respond(conn, 123, result)
|
||||||
|
|
||||||
var resp models.Response[SuccessResult]
|
var resp models.Response[models.SuccessResult]
|
||||||
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func TestHandleGetState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "network.getState"}
|
req := models.Request{ID: 123, Method: "network.getState"}
|
||||||
|
|
||||||
handleGetState(conn, req, manager)
|
handleGetState(conn, req, manager)
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ func TestHandleGetWiFiNetworks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{ID: 123, Method: "network.wifi.networks"}
|
req := models.Request{ID: 123, Method: "network.wifi.networks"}
|
||||||
|
|
||||||
handleGetWiFiNetworks(conn, req, manager)
|
handleGetWiFiNetworks(conn, req, manager)
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ func TestHandleConnectWiFi(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "network.wifi.connect",
|
Method: "network.wifi.connect",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -149,7 +149,7 @@ func TestHandleSetPreference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "network.preference.set",
|
Method: "network.preference.set",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -173,7 +173,7 @@ func TestHandleGetNetworkInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "network.info",
|
Method: "network.info",
|
||||||
Params: map[string]any{},
|
Params: map[string]any{},
|
||||||
@@ -199,7 +199,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("unknown method", func(t *testing.T) {
|
t.Run("unknown method", func(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "network.unknown",
|
Method: "network.unknown",
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ func TestHandleRequest(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("valid method - getState", func(t *testing.T) {
|
t.Run("valid method - getState", func(t *testing.T) {
|
||||||
conn := newMockNetConn()
|
conn := newMockNetConn()
|
||||||
req := Request{
|
req := models.Request{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Method: "network.getState",
|
Method: "network.getState",
|
||||||
}
|
}
|
||||||
|
|||||||
113
core/internal/server/params/params.go
Normal file
113
core/internal/server/params/params.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
154
core/internal/server/params/params_test.go
Normal file
154
core/internal/server/params/params_test.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -27,12 +27,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "network manager not initialized")
|
models.RespondError(conn, req.ID, "network manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
netReq := network.Request{
|
network.HandleRequest(conn, req, networkManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
network.HandleRequest(conn, netReq, networkManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +41,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "loginctl manager not initialized")
|
models.RespondError(conn, req.ID, "loginctl manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loginReq := loginctl.Request{
|
loginctl.HandleRequest(conn, req, loginctlManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
loginctl.HandleRequest(conn, loginReq, loginctlManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,12 +50,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "freedesktop manager not initialized")
|
models.RespondError(conn, req.ID, "freedesktop manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
freedeskReq := freedesktop.Request{
|
freedesktop.HandleRequest(conn, req, freedesktopManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
freedesktop.HandleRequest(conn, freedeskReq, freedesktopManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +59,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "wayland manager not initialized")
|
models.RespondError(conn, req.ID, "wayland manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
waylandReq := wayland.Request{
|
wayland.HandleRequest(conn, req, waylandManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
wayland.HandleRequest(conn, waylandReq, waylandManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +68,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "bluetooth manager not initialized")
|
models.RespondError(conn, req.ID, "bluetooth manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bluezReq := bluez.Request{
|
bluez.HandleRequest(conn, req, bluezManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
bluez.HandleRequest(conn, bluezReq, bluezManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,12 +77,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "apppicker manager not initialized")
|
models.RespondError(conn, req.ID, "apppicker manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
appPickerReq := apppicker.Request{
|
apppicker.HandleRequest(conn, req, appPickerManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
apppicker.HandleRequest(conn, appPickerReq, appPickerManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +86,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cupsReq := cups.Request{
|
cups.HandleRequest(conn, req, cupsManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
cups.HandleRequest(conn, cupsReq, cupsManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +95,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dwlReq := dwl.Request{
|
dwl.HandleRequest(conn, req, dwlManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
dwl.HandleRequest(conn, dwlReq, dwlManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,12 +104,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "brightness manager not initialized")
|
models.RespondError(conn, req.ID, "brightness manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
brightnessReq := brightness.Request{
|
brightness.HandleRequest(conn, req, brightnessManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
brightness.HandleRequest(conn, brightnessReq, brightnessManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,12 +125,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extWorkspaceReq := extworkspace.Request{
|
extworkspace.HandleRequest(conn, req, extWorkspaceManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
extworkspace.HandleRequest(conn, extWorkspaceReq, extWorkspaceManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,12 +134,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "wlroutput manager not initialized")
|
models.RespondError(conn, req.ID, "wlroutput manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wlrOutputReq := wlroutput.Request{
|
wlroutput.HandleRequest(conn, req, wlrOutputManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
wlroutput.HandleRequest(conn, wlrOutputReq, wlrOutputManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,12 +143,7 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.RespondError(conn, req.ID, "evdev manager not initialized")
|
models.RespondError(conn, req.ID, "evdev manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
evdevReq := evdev.Request{
|
evdev.HandleRequest(conn, req, evdevManager)
|
||||||
ID: req.ID,
|
|
||||||
Method: req.Method,
|
|
||||||
Params: req.Params,
|
|
||||||
}
|
|
||||||
evdev.HandleRequest(conn, evdevReq, evdevManager)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package wayland
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GammaRamp struct {
|
type GammaRamp struct {
|
||||||
@@ -12,6 +10,126 @@ type GammaRamp struct {
|
|||||||
Blue []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 {
|
func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
|
||||||
ramp := GammaRamp{
|
ramp := GammaRamp{
|
||||||
Red: make([]uint16, size),
|
Red: make([]uint16, size),
|
||||||
@@ -19,16 +137,13 @@ func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
|
|||||||
Blue: make([]uint16, size),
|
Blue: make([]uint16, size),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wp := calcWhitepoint(temp)
|
||||||
|
|
||||||
for i := uint32(0); i < size; i++ {
|
for i := uint32(0); i < size; i++ {
|
||||||
val := float64(i) / float64(size-1)
|
val := float64(i) / float64(size-1)
|
||||||
|
ramp.Red[i] = uint16(clamp01(math.Pow(val*wp.r, 1.0/gamma)) * 65535.0)
|
||||||
valGamma := math.Pow(val, 1.0/gamma)
|
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)
|
||||||
r, g, b := temperatureToRGB(temp)
|
|
||||||
|
|
||||||
ramp.Red[i] = uint16(utils.Clamp(valGamma*r*65535.0, 0, 65535))
|
|
||||||
ramp.Green[i] = uint16(utils.Clamp(valGamma*g*65535.0, 0, 65535))
|
|
||||||
ramp.Blue[i] = uint16(utils.Clamp(valGamma*b*65535.0, 0, 65535))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ramp
|
return ramp
|
||||||
@@ -50,39 +165,3 @@ func GenerateIdentityRamp(size uint32) GammaRamp {
|
|||||||
|
|
||||||
return ramp
|
return ramp
|
||||||
}
|
}
|
||||||
|
|
||||||
func temperatureToRGB(temp int) (float64, float64, float64) {
|
|
||||||
tempK := float64(temp) / 100.0
|
|
||||||
|
|
||||||
var r, g, b float64
|
|
||||||
|
|
||||||
if tempK <= 66 {
|
|
||||||
r = 1.0
|
|
||||||
} else {
|
|
||||||
r = tempK - 60
|
|
||||||
r = 329.698727446 * math.Pow(r, -0.1332047592)
|
|
||||||
r = utils.Clamp(r, 0, 255) / 255.0
|
|
||||||
}
|
|
||||||
|
|
||||||
if tempK <= 66 {
|
|
||||||
g = tempK
|
|
||||||
g = 99.4708025861*math.Log(g) - 161.1195681661
|
|
||||||
g = utils.Clamp(g, 0, 255) / 255.0
|
|
||||||
} else {
|
|
||||||
g = tempK - 60
|
|
||||||
g = 288.1221695283 * math.Pow(g, -0.0755148492)
|
|
||||||
g = utils.Clamp(g, 0, 255) / 255.0
|
|
||||||
}
|
|
||||||
|
|
||||||
if tempK >= 66 {
|
|
||||||
b = 1.0
|
|
||||||
} else if tempK <= 19 {
|
|
||||||
b = 0.0
|
|
||||||
} else {
|
|
||||||
b = tempK - 10
|
|
||||||
b = 138.5177312231*math.Log(b) - 305.0447927307
|
|
||||||
b = utils.Clamp(b, 0, 255) / 255.0
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, g, b
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func TestGenerateGammaRamp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemperatureToRGB(t *testing.T) {
|
func TestCalcWhitepoint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
temp int
|
temp int
|
||||||
@@ -67,32 +67,32 @@ func TestTemperatureToRGB(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
r, g, b := temperatureToRGB(tt.temp)
|
wp := calcWhitepoint(tt.temp)
|
||||||
|
|
||||||
if r < 0 || r > 1 {
|
if wp.r < 0 || wp.r > 1 {
|
||||||
t.Errorf("red out of range: %f", r)
|
t.Errorf("red out of range: %f", wp.r)
|
||||||
}
|
}
|
||||||
if g < 0 || g > 1 {
|
if wp.g < 0 || wp.g > 1 {
|
||||||
t.Errorf("green out of range: %f", g)
|
t.Errorf("green out of range: %f", wp.g)
|
||||||
}
|
}
|
||||||
if b < 0 || b > 1 {
|
if wp.b < 0 || wp.b > 1 {
|
||||||
t.Errorf("blue out of range: %f", b)
|
t.Errorf("blue out of range: %f", wp.b)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemperatureProgression(t *testing.T) {
|
func TestWhitepointProgression(t *testing.T) {
|
||||||
temps := []int{3000, 4000, 5000, 6000, 6500}
|
temps := []int{3000, 4000, 5000, 6000, 6500}
|
||||||
|
|
||||||
var prevBlue float64
|
var prevBlue float64
|
||||||
for i, temp := range temps {
|
for i, temp := range temps {
|
||||||
_, _, b := temperatureToRGB(temp)
|
wp := calcWhitepoint(temp)
|
||||||
if i > 0 && b < prevBlue {
|
if i > 0 && wp.b < prevBlue {
|
||||||
t.Errorf("blue should increase with temperature, %d->%d: %f->%f",
|
t.Errorf("blue should increase with temperature, %d->%d: %f->%f",
|
||||||
temps[i-1], temp, prevBlue, b)
|
temps[i-1], temp, prevBlue, wp.b)
|
||||||
}
|
}
|
||||||
prevBlue = b
|
prevBlue = wp.b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
models.RespondError(conn, req.ID, "wayland manager not initialized")
|
models.RespondError(conn, req.ID, "wayland manager not initialized")
|
||||||
return
|
return
|
||||||
@@ -48,26 +38,27 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetTemperature(conn net.Conn, req Request, manager *Manager) {
|
func handleSetTemperature(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
var lowTemp, highTemp int
|
var lowTemp, highTemp int
|
||||||
|
|
||||||
if temp, ok := req.Params["temp"].(float64); ok {
|
if temp, ok := req.Params["temp"].(float64); ok {
|
||||||
lowTemp = int(temp)
|
lowTemp = int(temp)
|
||||||
highTemp = int(temp)
|
highTemp = int(temp)
|
||||||
} else {
|
} else {
|
||||||
low, okLow := req.Params["low"].(float64)
|
low, err := params.Float(req.Params, "low")
|
||||||
high, okHigh := req.Params["high"].(float64)
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, "missing temperature parameters (provide 'temp' or both 'low' and 'high')")
|
||||||
if !okLow || !okHigh {
|
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')")
|
models.RespondError(conn, req.ID, "missing temperature parameters (provide 'temp' or both 'low' and 'high')")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lowTemp = int(low)
|
lowTemp = int(low)
|
||||||
highTemp = int(high)
|
highTemp = int(high)
|
||||||
}
|
}
|
||||||
@@ -77,19 +68,19 @@ func handleSetTemperature(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "temperature set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "temperature set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetLocation(conn net.Conn, req Request, manager *Manager) {
|
func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
lat, ok := req.Params["latitude"].(float64)
|
lat, err := params.Float(req.Params, "latitude")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'latitude' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lon, ok := req.Params["longitude"].(float64)
|
lon, err := params.Float(req.Params, "longitude")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'longitude' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,30 +89,30 @@ func handleSetLocation(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "location set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetManualTimes(conn net.Conn, req Request, manager *Manager) {
|
func handleSetManualTimes(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
sunriseParam := req.Params["sunrise"]
|
sunriseParam := req.Params["sunrise"]
|
||||||
sunsetParam := req.Params["sunset"]
|
sunsetParam := req.Params["sunset"]
|
||||||
|
|
||||||
if sunriseParam == nil || sunsetParam == nil {
|
if sunriseParam == nil || sunsetParam == nil {
|
||||||
manager.ClearManualTimes()
|
manager.ClearManualTimes()
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "manual times cleared"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times cleared"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sunriseStr, ok := sunriseParam.(string)
|
sunriseStr, ok := sunriseParam.(string)
|
||||||
if !ok || sunriseStr == "" {
|
if !ok || sunriseStr == "" {
|
||||||
manager.ClearManualTimes()
|
manager.ClearManualTimes()
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "manual times cleared"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times cleared"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sunsetStr, ok := sunsetParam.(string)
|
sunsetStr, ok := sunsetParam.(string)
|
||||||
if !ok || sunsetStr == "" {
|
if !ok || sunsetStr == "" {
|
||||||
manager.ClearManualTimes()
|
manager.ClearManualTimes()
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "manual times cleared"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times cleared"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,24 +133,24 @@ func handleSetManualTimes(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "manual times set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetUseIPLocation(conn net.Conn, req Request, manager *Manager) {
|
func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
use, ok := req.Params["use"].(bool)
|
use, err := params.Bool(req.Params, "use")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'use' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.SetUseIPLocation(use)
|
manager.SetUseIPLocation(use)
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "IP location preference set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "IP location preference set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetGamma(conn net.Conn, req Request, manager *Manager) {
|
func handleSetGamma(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
gamma, ok := req.Params["gamma"].(float64)
|
gamma, err := params.Float(req.Params, "gamma")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'gamma' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,21 +159,21 @@ func handleSetGamma(conn net.Conn, req Request, manager *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "gamma set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "gamma set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSetEnabled(conn net.Conn, req Request, manager *Manager) {
|
func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
enabled, ok := req.Params["enabled"].(bool)
|
enabled, err := params.Bool(req.Params, "enabled")
|
||||||
if !ok {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "missing or invalid 'enabled' parameter")
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.SetEnabled(enabled)
|
manager.SetEnabled(enabled)
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "enabled state set"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "enabled state set"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,81 +6,117 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
degToRad = math.Pi / 180.0
|
degToRad = math.Pi / 180.0
|
||||||
radToDeg = 180.0 / math.Pi
|
radToDeg = 180.0 / math.Pi
|
||||||
solarNoon = 12.0
|
|
||||||
sunriseAngle = -0.833
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
|
type SunCondition int
|
||||||
utcDate := date.UTC()
|
|
||||||
year, month, day := utcDate.Date()
|
|
||||||
loc := date.Location()
|
|
||||||
|
|
||||||
dayOfYear := utcDate.YearDay()
|
const (
|
||||||
|
SunNormal SunCondition = iota
|
||||||
|
SunMidnightSun
|
||||||
|
SunPolarNight
|
||||||
|
)
|
||||||
|
|
||||||
gamma := 2 * math.Pi / 365 * float64(dayOfYear-1)
|
type SunTimes struct {
|
||||||
|
Dawn time.Time
|
||||||
|
Sunrise time.Time
|
||||||
|
Sunset time.Time
|
||||||
|
Night time.Time
|
||||||
|
}
|
||||||
|
|
||||||
eqTime := 229.18 * (0.000075 +
|
func daysInYear(year int) int {
|
||||||
0.001868*math.Cos(gamma) -
|
if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
|
||||||
0.032077*math.Sin(gamma) -
|
return 366
|
||||||
0.014615*math.Cos(2*gamma) -
|
}
|
||||||
0.040849*math.Sin(2*gamma))
|
return 365
|
||||||
|
}
|
||||||
|
|
||||||
decl := 0.006918 -
|
func dateOrbitAngle(t time.Time) float64 {
|
||||||
0.399912*math.Cos(gamma) +
|
return 2 * math.Pi / float64(daysInYear(t.Year())) * float64(t.YearDay()-1)
|
||||||
0.070257*math.Sin(gamma) -
|
}
|
||||||
0.006758*math.Cos(2*gamma) +
|
|
||||||
0.000907*math.Sin(2*gamma) -
|
|
||||||
0.002697*math.Cos(3*gamma) +
|
|
||||||
0.00148*math.Sin(3*gamma)
|
|
||||||
|
|
||||||
|
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
|
latRad := lat * degToRad
|
||||||
|
elevTwilightRad := (90.833 - elevTwilight) * degToRad
|
||||||
|
elevDaylightRad := (90.833 - elevDaylight) * degToRad
|
||||||
|
|
||||||
cosHourAngle := (math.Sin(sunriseAngle*degToRad) -
|
utc := date.UTC()
|
||||||
math.Sin(latRad)*math.Sin(decl)) /
|
orbitAngle := dateOrbitAngle(utc)
|
||||||
(math.Cos(latRad) * math.Cos(decl))
|
decl := sunDeclination(orbitAngle)
|
||||||
|
eqtime := equationOfTime(orbitAngle)
|
||||||
|
|
||||||
if cosHourAngle > 1 {
|
haTwilight := sunHourAngle(latRad, decl, elevTwilightRad)
|
||||||
return SunTimes{
|
haDaylight := sunHourAngle(latRad, decl, elevDaylightRad)
|
||||||
Sunrise: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).In(loc),
|
|
||||||
Sunset: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).In(loc),
|
if math.IsNaN(haTwilight) || math.IsNaN(haDaylight) {
|
||||||
}
|
cond := sunCondition(latRad, decl)
|
||||||
}
|
return SunTimes{}, cond
|
||||||
if cosHourAngle < -1 {
|
|
||||||
return SunTimes{
|
|
||||||
Sunrise: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).In(loc),
|
|
||||||
Sunset: time.Date(year, month, day, 23, 59, 59, 0, time.UTC).In(loc),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hourAngle := math.Acos(cosHourAngle) * radToDeg
|
dayStart := time.Date(utc.Year(), utc.Month(), utc.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
lonOffset := time.Duration(-lon*4) * time.Minute
|
||||||
|
|
||||||
sunriseTime := solarNoon - hourAngle/15.0 - lon/15.0 - eqTime/60.0
|
dawnSecs := hourAngleToSeconds(math.Abs(haTwilight), eqtime)
|
||||||
sunsetTime := solarNoon + hourAngle/15.0 - lon/15.0 - eqTime/60.0
|
sunriseSecs := hourAngleToSeconds(math.Abs(haDaylight), eqtime)
|
||||||
|
sunsetSecs := hourAngleToSeconds(-math.Abs(haDaylight), eqtime)
|
||||||
sunrise := timeOfDayToTime(sunriseTime, year, month, day, time.UTC).In(loc)
|
nightSecs := hourAngleToSeconds(-math.Abs(haTwilight), eqtime)
|
||||||
sunset := timeOfDayToTime(sunsetTime, year, month, day, time.UTC).In(loc)
|
|
||||||
|
|
||||||
return SunTimes{
|
return SunTimes{
|
||||||
Sunrise: sunrise,
|
Dawn: dayStart.Add(time.Duration(dawnSecs)*time.Second + lonOffset).In(date.Location()),
|
||||||
Sunset: sunset,
|
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 timeOfDayToTime(hours float64, year int, month time.Month, day int, loc *time.Location) time.Time {
|
func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
|
||||||
h := int(hours)
|
times, cond := CalculateSunTimesWithTwilight(lat, lon, date, -6.0, 3.0)
|
||||||
m := int((hours - float64(h)) * 60)
|
switch cond {
|
||||||
s := int(((hours-float64(h))*60 - float64(m)) * 60)
|
case SunMidnightSun:
|
||||||
|
dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||||
if h < 0 {
|
dayEnd := dayStart.Add(24*time.Hour - time.Second)
|
||||||
h += 24
|
return SunTimes{Dawn: dayStart, Sunrise: dayStart, Sunset: dayEnd, Night: dayEnd}
|
||||||
day--
|
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}
|
||||||
}
|
}
|
||||||
if h >= 24 {
|
return times
|
||||||
h -= 24
|
|
||||||
day++
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Date(year, month, day, h, m, s, 0, loc)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,38 +340,47 @@ func TestCalculateNextTransition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeOfDayToTime(t *testing.T) {
|
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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
hours float64
|
lat float64
|
||||||
expected time.Time
|
date time.Time
|
||||||
|
expected SunCondition
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "noon",
|
name: "normal_conditions",
|
||||||
hours: 12.0,
|
lat: 40.0,
|
||||||
expected: time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local),
|
date: time.Date(2024, 6, 21, 12, 0, 0, 0, time.UTC),
|
||||||
},
|
expected: SunNormal,
|
||||||
{
|
|
||||||
name: "half_past",
|
|
||||||
hours: 12.5,
|
|
||||||
expected: time.Date(2024, 6, 21, 12, 30, 0, 0, time.Local),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "early_morning",
|
|
||||||
hours: 6.25,
|
|
||||||
expected: time.Date(2024, 6, 21, 6, 15, 0, 0, time.Local),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := timeOfDayToTime(tt.hours, 2024, 6, 21, time.Local)
|
_, cond := CalculateSunTimesWithTwilight(tt.lat, 0, tt.date, -6.0, 3.0)
|
||||||
|
if cond != tt.expected {
|
||||||
if result.Hour() != tt.expected.Hour() {
|
t.Errorf("expected condition %v, got %v", tt.expected, cond)
|
||||||
t.Errorf("hour = %d, want %d", result.Hour(), tt.expected.Hour())
|
|
||||||
}
|
|
||||||
if result.Minute() != tt.expected.Minute() {
|
|
||||||
t.Errorf("minute = %d, want %d", result.Minute(), tt.expected.Minute())
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,28 @@ import (
|
|||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GammaState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateNormal GammaState = iota
|
||||||
|
StateTransition
|
||||||
|
StateStatic
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Outputs []string
|
Outputs []string
|
||||||
LowTemp int
|
LowTemp int
|
||||||
HighTemp int
|
HighTemp int
|
||||||
Latitude *float64
|
Latitude *float64
|
||||||
Longitude *float64
|
Longitude *float64
|
||||||
UseIPLocation bool
|
UseIPLocation bool
|
||||||
ManualSunrise *time.Time
|
ManualSunrise *time.Time
|
||||||
ManualSunset *time.Time
|
ManualSunset *time.Time
|
||||||
ManualDuration *time.Duration
|
ManualDuration *time.Duration
|
||||||
Gamma float64
|
Gamma float64
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
ElevationTwilight float64
|
||||||
|
ElevationDaylight float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
@@ -31,13 +41,24 @@ type State struct {
|
|||||||
NextTransition time.Time `json:"nextTransition"`
|
NextTransition time.Time `json:"nextTransition"`
|
||||||
SunriseTime time.Time `json:"sunriseTime"`
|
SunriseTime time.Time `json:"sunriseTime"`
|
||||||
SunsetTime time.Time `json:"sunsetTime"`
|
SunsetTime time.Time `json:"sunsetTime"`
|
||||||
|
DawnTime time.Time `json:"dawnTime"`
|
||||||
|
NightTime time.Time `json:"nightTime"`
|
||||||
IsDay bool `json:"isDay"`
|
IsDay bool `json:"isDay"`
|
||||||
|
SunPosition float64 `json:"sunPosition"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cmd struct {
|
type cmd struct {
|
||||||
fn func()
|
fn func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sunSchedule struct {
|
||||||
|
times SunTimes
|
||||||
|
condition SunCondition
|
||||||
|
dawnStepTime time.Duration
|
||||||
|
nightStepTime time.Duration
|
||||||
|
calcDay time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
config Config
|
config Config
|
||||||
configMutex sync.RWMutex
|
configMutex sync.RWMutex
|
||||||
@@ -60,10 +81,9 @@ type Manager struct {
|
|||||||
updateTrigger chan struct{}
|
updateTrigger chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
currentTemp int
|
schedule sunSchedule
|
||||||
targetTemp int
|
scheduleMutex sync.RWMutex
|
||||||
transitionMutex sync.RWMutex
|
gammaState GammaState
|
||||||
transitionChan chan int
|
|
||||||
|
|
||||||
cachedIPLat *float64
|
cachedIPLat *float64
|
||||||
cachedIPLon *float64
|
cachedIPLon *float64
|
||||||
@@ -80,7 +100,6 @@ type Manager struct {
|
|||||||
|
|
||||||
type outputState struct {
|
type outputState struct {
|
||||||
id uint32
|
id uint32
|
||||||
name string
|
|
||||||
registryName uint32
|
registryName uint32
|
||||||
output *wlclient.Output
|
output *wlclient.Output
|
||||||
gammaControl any
|
gammaControl any
|
||||||
@@ -91,18 +110,15 @@ type outputState struct {
|
|||||||
lastFailTime time.Time
|
lastFailTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type SunTimes struct {
|
|
||||||
Sunrise time.Time
|
|
||||||
Sunset time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Outputs: []string{},
|
Outputs: []string{},
|
||||||
LowTemp: 4000,
|
LowTemp: 4000,
|
||||||
HighTemp: 6500,
|
HighTemp: 6500,
|
||||||
Gamma: 1.0,
|
Gamma: 1.0,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
ElevationTwilight: -6.0,
|
||||||
|
ElevationDaylight: 3.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +156,7 @@ func (m *Manager) GetState() State {
|
|||||||
if m.state == nil {
|
if m.state == nil {
|
||||||
return State{}
|
return State{}
|
||||||
}
|
}
|
||||||
stateCopy := *m.state
|
return *m.state
|
||||||
return stateCopy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
@@ -185,5 +200,8 @@ func stateChanged(old, new *State) bool {
|
|||||||
if old.Config.Enabled != new.Config.Enabled {
|
if old.Config.Enabled != new.Config.Enabled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if old.SunPosition != new.SunPosition {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,6 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params map[string]any `json:"params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResult struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HeadConfig struct {
|
type HeadConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
@@ -42,7 +31,7 @@ type ConfigurationRequest struct {
|
|||||||
Test bool `json:"test"`
|
Test bool `json:"test"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
models.RespondError(conn, req.ID, "wlroutput manager not initialized")
|
models.RespondError(conn, req.ID, "wlroutput manager not initialized")
|
||||||
return
|
return
|
||||||
@@ -62,12 +51,11 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
state := manager.GetState()
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
models.Respond(conn, req.ID, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleApplyConfiguration(conn net.Conn, req Request, manager *Manager, test bool) {
|
func handleApplyConfiguration(conn net.Conn, req models.Request, manager *Manager, test bool) {
|
||||||
headsParam, ok := req.Params["heads"]
|
headsParam, ok := req.Params["heads"]
|
||||||
if !ok {
|
if !ok {
|
||||||
models.RespondError(conn, req.ID, "missing 'heads' parameter")
|
models.RespondError(conn, req.ID, "missing 'heads' parameter")
|
||||||
@@ -95,10 +83,10 @@ func handleApplyConfiguration(conn net.Conn, req Request, manager *Manager, test
|
|||||||
if test {
|
if test {
|
||||||
msg = "configuration test succeeded"
|
msg = "configuration test succeeded"
|
||||||
}
|
}
|
||||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: msg})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
clientID := fmt.Sprintf("client-%p", conn)
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
stateChan := manager.Subscribe(clientID)
|
stateChan := manager.Subscribe(clientID)
|
||||||
defer manager.Unsubscribe(clientID)
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
dms-git (0.6.2+git2264.c5c5ce84) nightly; urgency=medium
|
dms-git (0.6.2+git2419.993f14a3) nightly; urgency=medium
|
||||||
|
|
||||||
* Add VERSION file creation to all distro packages
|
* widgets: make dank icon picker a popup
|
||||||
* Fix Fedora COPR to pass VERSION/COMMIT to make dist
|
* Previous updates included in build
|
||||||
* Fix obs-upload.sh auto-increment to preserve git hash and add ppa suffix
|
|
||||||
* Fix debian/rules to use source at root level (native format)
|
|
||||||
* Remove incorrect dms-git-source subdirectory references
|
|
||||||
* Build dms binary from source for true git version strings
|
|
||||||
* Match Fedora COPR git build behavior
|
|
||||||
* Add golang-go and make as build dependencies
|
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Tue, 03 Dec 2025 01:50:00 +0000
|
-- Avenge Media <AvengeMedia.US@gmail.com> Mon, 09 Dec 2025 14:00:00 +0000
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export GOTOOLCHAIN := local
|
|||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
|
override_dh_installsystemd:
|
||||||
|
dh_installsystemd --name=dms
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
# Create Go cache directories
|
# Create Go cache directories
|
||||||
mkdir -p $(HOME) $(GOCACHE) $(GOMODCACHE)
|
mkdir -p $(HOME) $(GOCACHE) $(GOMODCACHE)
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
|||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
|
override_dh_installsystemd:
|
||||||
|
dh_installsystemd --name=dms
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
||||||
if [ -f dms-distropkg-amd64.gz ]; then \
|
if [ -f dms-distropkg-amd64.gz ]; then \
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ BuildRequires: systemd-rpm-macros
|
|||||||
# Core requirements
|
# Core requirements
|
||||||
Requires: (quickshell-git or quickshell)
|
Requires: (quickshell-git or quickshell)
|
||||||
Requires: accountsservice
|
Requires: accountsservice
|
||||||
Requires: dms-cli
|
Requires: dms-cli = %{epoch}:%{version}-%{release}
|
||||||
Requires: dgop
|
Requires: dgop
|
||||||
|
|
||||||
# Core utilities (Highly recommended for DMS functionality)
|
# Core utilities (Highly recommended for DMS functionality)
|
||||||
@@ -60,41 +60,9 @@ URL: https://github.com/AvengeMedia/DankMaterialShell
|
|||||||
Command-line interface for DankMaterialShell configuration and management.
|
Command-line interface for DankMaterialShell configuration and management.
|
||||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||||
|
|
||||||
%package -n dgop
|
|
||||||
Summary: Stateless CPU/GPU monitor for DankMaterialShell
|
|
||||||
License: MIT
|
|
||||||
URL: https://github.com/AvengeMedia/dgop
|
|
||||||
Provides: dgop
|
|
||||||
|
|
||||||
%description -n dgop
|
|
||||||
DGOP is a stateless system monitoring tool that provides CPU, GPU, memory, and
|
|
||||||
network statistics. Designed for integration with DankMaterialShell but can be
|
|
||||||
used standalone. This package always includes the latest stable dgop release.
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
{{{ git_repo_setup_macro }}}
|
{{{ git_repo_setup_macro }}}
|
||||||
|
|
||||||
# Download and extract DGOP binary for target architecture
|
|
||||||
case "%{_arch}" in
|
|
||||||
x86_64)
|
|
||||||
DGOP_ARCH="amd64"
|
|
||||||
;;
|
|
||||||
aarch64)
|
|
||||||
DGOP_ARCH="arm64"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unsupported architecture: %{_arch}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
wget -O %{_builddir}/dgop.gz "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-${DGOP_ARCH}.gz" || {
|
|
||||||
echo "Failed to download dgop for architecture %{_arch}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
gunzip -c %{_builddir}/dgop.gz > %{_builddir}/dgop
|
|
||||||
chmod +x %{_builddir}/dgop
|
|
||||||
|
|
||||||
%build
|
%build
|
||||||
# Build DMS CLI from source (core/subdirectory)
|
# Build DMS CLI from source (core/subdirectory)
|
||||||
VERSION="%{version}"
|
VERSION="%{version}"
|
||||||
@@ -128,9 +96,6 @@ core/bin/${DMS_BINARY} completion bash > %{buildroot}%{_datadir}/bash-completion
|
|||||||
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
core/bin/${DMS_BINARY} completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||||
core/bin/${DMS_BINARY} completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
core/bin/${DMS_BINARY} completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||||
|
|
||||||
# Install dgop binary
|
|
||||||
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
|
|
||||||
|
|
||||||
# Install systemd user service
|
# Install systemd user service
|
||||||
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||||
|
|
||||||
@@ -150,11 +115,8 @@ rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
|||||||
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||||
|
|
||||||
%posttrans
|
%posttrans
|
||||||
|
# Signal running DMS instances to reload
|
||||||
# Restart DMS for active users after upgrade
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
if [ "$1" -ge 2 ]; then
|
|
||||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
@@ -171,8 +133,5 @@ fi
|
|||||||
%{_datadir}/zsh/site-functions/_dms
|
%{_datadir}/zsh/site-functions/_dms
|
||||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||||
|
|
||||||
%files -n dgop
|
|
||||||
%{_bindir}/dgop
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
{{{ git_repo_changelog }}}
|
{{{ git_repo_changelog }}}
|
||||||
@@ -110,10 +110,8 @@ if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
|||||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
# Signal running DMS instances to reload
|
||||||
if [ "$1" -ge 2 ]; then
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
|
|||||||
@@ -80,10 +80,8 @@ rm -rf %{buildroot}%{_datadir}/quickshell/dms/core
|
|||||||
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||||
|
|
||||||
%posttrans
|
%posttrans
|
||||||
|
# Signal running DMS instances to reload
|
||||||
if [ "$1" -ge 2 ]; then
|
pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
dms-git (0.6.2+git2264.c5c5ce84) questing; urgency=medium
|
dms-git (0.6.2+git2419.993f14a3) questing; urgency=medium
|
||||||
|
|
||||||
* Add VERSION file creation to all distro packages
|
* widgets: make dank icon picker a popup
|
||||||
* Fix Fedora COPR to pass VERSION/COMMIT to make dist
|
* Previous updates included in build
|
||||||
* Fix obs-upload.sh auto-increment to preserve git hash and add ppa suffix
|
|
||||||
* Git snapshot (commit 2264: c5c5ce84)
|
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Tue, 03 Dec 2025 01:50:00 +0000
|
-- Avenge Media <AvengeMedia.US@gmail.com> Mon, 09 Dec 2025 14:00:00 +0000
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export GOTOOLCHAIN := local
|
|||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
|
override_dh_installsystemd:
|
||||||
|
dh_installsystemd --name=dms
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
# Create Go cache directories (sbuild sets HOME to non-existent path)
|
# Create Go cache directories (sbuild sets HOME to non-existent path)
|
||||||
mkdir -p $(HOME) $(GOCACHE) $(GOMODCACHE)
|
mkdir -p $(HOME) $(GOCACHE) $(GOMODCACHE)
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ BASE_VERSION := $(shell echo $(UPSTREAM_VERSION) | sed 's/ppa[0-9]*$$//' | sed '
|
|||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
|
override_dh_installsystemd:
|
||||||
|
dh_installsystemd --name=dms
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
# All files are included in source package (downloaded by build-source.sh)
|
# All files are included in source package (downloaded by build-source.sh)
|
||||||
# Launchpad build environment has no internet access
|
# Launchpad build environment has no internet access
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ const KEY_MAP = {
|
|||||||
16777329: "XF86AudioMute",
|
16777329: "XF86AudioMute",
|
||||||
16842808: "XF86AudioMicMute",
|
16842808: "XF86AudioMicMute",
|
||||||
16777344: "XF86AudioPlay",
|
16777344: "XF86AudioPlay",
|
||||||
16777345: "XF86AudioPause",
|
16777345: "XF86AudioStop",
|
||||||
16777346: "XF86AudioStop",
|
16777346: "XF86AudioPrev",
|
||||||
16777347: "XF86AudioNext",
|
16777347: "XF86AudioNext",
|
||||||
16777348: "XF86AudioPrev",
|
16777348: "XF86AudioPause",
|
||||||
16842792: "XF86AudioRecord",
|
16777349: "XF86AudioMedia",
|
||||||
|
16777350: "XF86AudioRecord",
|
||||||
16842798: "XF86MonBrightnessUp",
|
16842798: "XF86MonBrightnessUp",
|
||||||
16842797: "XF86MonBrightnessDown",
|
16842797: "XF86MonBrightnessDown",
|
||||||
16842800: "XF86KbdBrightnessUp",
|
16842800: "XF86KbdBrightnessUp",
|
||||||
@@ -79,12 +80,49 @@ const KEY_MAP = {
|
|||||||
124: "Backslash",
|
124: "Backslash",
|
||||||
95: "Minus",
|
95: "Minus",
|
||||||
43: "Equal",
|
43: "Equal",
|
||||||
126: "grave"
|
126: "grave",
|
||||||
|
196: "Adiaeresis",
|
||||||
|
214: "Odiaeresis",
|
||||||
|
220: "Udiaeresis",
|
||||||
|
228: "adiaeresis",
|
||||||
|
246: "odiaeresis",
|
||||||
|
252: "udiaeresis",
|
||||||
|
223: "ssharp",
|
||||||
|
201: "Eacute",
|
||||||
|
233: "eacute",
|
||||||
|
200: "Egrave",
|
||||||
|
232: "egrave",
|
||||||
|
202: "Ecircumflex",
|
||||||
|
234: "ecircumflex",
|
||||||
|
203: "Ediaeresis",
|
||||||
|
235: "ediaeresis",
|
||||||
|
192: "Agrave",
|
||||||
|
224: "agrave",
|
||||||
|
194: "Acircumflex",
|
||||||
|
226: "acircumflex",
|
||||||
|
199: "Ccedilla",
|
||||||
|
231: "ccedilla",
|
||||||
|
206: "Icircumflex",
|
||||||
|
238: "icircumflex",
|
||||||
|
207: "Idiaeresis",
|
||||||
|
239: "idiaeresis",
|
||||||
|
212: "Ocircumflex",
|
||||||
|
244: "ocircumflex",
|
||||||
|
217: "Ugrave",
|
||||||
|
249: "ugrave",
|
||||||
|
219: "Ucircumflex",
|
||||||
|
251: "ucircumflex",
|
||||||
|
209: "Ntilde",
|
||||||
|
241: "ntilde",
|
||||||
|
191: "questiondown",
|
||||||
|
161: "exclamdown"
|
||||||
};
|
};
|
||||||
|
|
||||||
function xkbKeyFromQtKey(qk) {
|
function xkbKeyFromQtKey(qk) {
|
||||||
if (qk >= 65 && qk <= 90)
|
if (qk >= 65 && qk <= 90)
|
||||||
return String.fromCharCode(qk);
|
return String.fromCharCode(qk);
|
||||||
|
if (qk >= 97 && qk <= 122)
|
||||||
|
return String.fromCharCode(qk - 32);
|
||||||
if (qk >= 48 && qk <= 57)
|
if (qk >= 48 && qk <= 57)
|
||||||
return String.fromCharCode(qk);
|
return String.fromCharCode(qk);
|
||||||
if (qk >= 16777264 && qk <= 16777298)
|
if (qk >= 16777264 && qk <= 16777298)
|
||||||
@@ -94,16 +132,10 @@ function xkbKeyFromQtKey(qk) {
|
|||||||
|
|
||||||
function modsFromEvent(mods) {
|
function modsFromEvent(mods) {
|
||||||
var result = [];
|
var result = [];
|
||||||
var hasAlt = mods & 0x08000000;
|
if (mods & 0x10000000)
|
||||||
var hasSuper = mods & 0x10000000;
|
result.push("Super");
|
||||||
if (hasAlt && hasSuper) {
|
if (mods & 0x08000000)
|
||||||
result.push("Mod");
|
result.push("Alt");
|
||||||
} else {
|
|
||||||
if (hasSuper)
|
|
||||||
result.push("Super");
|
|
||||||
if (hasAlt)
|
|
||||||
result.push("Alt");
|
|
||||||
}
|
|
||||||
if (mods & 0x04000000)
|
if (mods & 0x04000000)
|
||||||
result.push("Ctrl");
|
result.push("Ctrl");
|
||||||
if (mods & 0x02000000)
|
if (mods & 0x02000000)
|
||||||
|
|||||||
@@ -49,26 +49,26 @@ const DMS_ACTIONS = [
|
|||||||
{ id: "spawn dms ipc call inhibit toggle", label: "Idle Inhibit: Toggle" },
|
{ id: "spawn dms ipc call inhibit toggle", label: "Idle Inhibit: Toggle" },
|
||||||
{ id: "spawn dms ipc call inhibit enable", label: "Idle Inhibit: Enable" },
|
{ id: "spawn dms ipc call inhibit enable", label: "Idle Inhibit: Enable" },
|
||||||
{ id: "spawn dms ipc call inhibit disable", label: "Idle Inhibit: Disable" },
|
{ id: "spawn dms ipc call inhibit disable", label: "Idle Inhibit: Disable" },
|
||||||
{ id: "spawn dms ipc call audio increment", label: "Volume Up" },
|
{ id: "spawn dms ipc call audio increment 5", label: "Volume Up" },
|
||||||
{ id: "spawn dms ipc call audio increment 1", label: "Volume Up (1%)" },
|
{ id: "spawn dms ipc call audio increment 1", label: "Volume Up (1%)" },
|
||||||
{ id: "spawn dms ipc call audio increment 5", label: "Volume Up (5%)" },
|
{ id: "spawn dms ipc call audio increment 5", label: "Volume Up (5%)" },
|
||||||
{ id: "spawn dms ipc call audio increment 10", label: "Volume Up (10%)" },
|
{ id: "spawn dms ipc call audio increment 10", label: "Volume Up (10%)" },
|
||||||
{ id: "spawn dms ipc call audio decrement", label: "Volume Down" },
|
{ id: "spawn dms ipc call audio decrement 5", label: "Volume Down" },
|
||||||
{ id: "spawn dms ipc call audio decrement 1", label: "Volume Down (1%)" },
|
{ id: "spawn dms ipc call audio decrement 1", label: "Volume Down (1%)" },
|
||||||
{ id: "spawn dms ipc call audio decrement 5", label: "Volume Down (5%)" },
|
{ id: "spawn dms ipc call audio decrement 5", label: "Volume Down (5%)" },
|
||||||
{ id: "spawn dms ipc call audio decrement 10", label: "Volume Down (10%)" },
|
{ id: "spawn dms ipc call audio decrement 10", label: "Volume Down (10%)" },
|
||||||
{ id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" },
|
{ id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" },
|
||||||
{ id: "spawn dms ipc call audio micmute", label: "Microphone Mute Toggle" },
|
{ id: "spawn dms ipc call audio micmute", label: "Microphone Mute Toggle" },
|
||||||
{ id: "spawn dms ipc call audio cycleoutput", label: "Audio Output: Cycle" },
|
{ id: "spawn dms ipc call audio cycleoutput", label: "Audio Output: Cycle" },
|
||||||
{ id: "spawn dms ipc call brightness increment", label: "Brightness Up" },
|
{ id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up" },
|
||||||
{ id: "spawn dms ipc call brightness increment 1", label: "Brightness Up (1%)" },
|
{ id: "spawn dms ipc call brightness increment 1 \"\"", label: "Brightness Up (1%)" },
|
||||||
{ id: "spawn dms ipc call brightness increment 5", label: "Brightness Up (5%)" },
|
{ id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up (5%)" },
|
||||||
{ id: "spawn dms ipc call brightness increment 10", label: "Brightness Up (10%)" },
|
{ id: "spawn dms ipc call brightness increment 10 \"\"", label: "Brightness Up (10%)" },
|
||||||
{ id: "spawn dms ipc call brightness decrement", label: "Brightness Down" },
|
{ id: "spawn dms ipc call brightness decrement 5 \"\"", label: "Brightness Down" },
|
||||||
{ id: "spawn dms ipc call brightness decrement 1", label: "Brightness Down (1%)" },
|
{ id: "spawn dms ipc call brightness decrement 1 \"\"", label: "Brightness Down (1%)" },
|
||||||
{ id: "spawn dms ipc call brightness decrement 5", label: "Brightness Down (5%)" },
|
{ id: "spawn dms ipc call brightness decrement 5 \"\"", label: "Brightness Down (5%)" },
|
||||||
{ id: "spawn dms ipc call brightness decrement 10", label: "Brightness Down (10%)" },
|
{ id: "spawn dms ipc call brightness decrement 10 \"\"", label: "Brightness Down (10%)" },
|
||||||
{ id: "spawn dms ipc call brightness toggleExponential", label: "Brightness: Toggle Exponential" },
|
{ id: "spawn dms ipc call brightness toggleExponential \"\"", label: "Brightness: Toggle Exponential" },
|
||||||
{ id: "spawn dms ipc call theme toggle", label: "Theme: Toggle Light/Dark" },
|
{ id: "spawn dms ipc call theme toggle", label: "Theme: Toggle Light/Dark" },
|
||||||
{ id: "spawn dms ipc call theme light", label: "Theme: Light Mode" },
|
{ id: "spawn dms ipc call theme light", label: "Theme: Light Mode" },
|
||||||
{ id: "spawn dms ipc call theme dark", label: "Theme: Dark Mode" },
|
{ id: "spawn dms ipc call theme dark", label: "Theme: Dark Mode" },
|
||||||
@@ -223,19 +223,37 @@ const ACTION_ARGS = {
|
|||||||
const DMS_ACTION_ARGS = {
|
const DMS_ACTION_ARGS = {
|
||||||
"audio increment": {
|
"audio increment": {
|
||||||
base: "spawn dms ipc call audio increment",
|
base: "spawn dms ipc call audio increment",
|
||||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" }]
|
||||||
},
|
},
|
||||||
"audio decrement": {
|
"audio decrement": {
|
||||||
base: "spawn dms ipc call audio decrement",
|
base: "spawn dms ipc call audio decrement",
|
||||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" }]
|
||||||
},
|
},
|
||||||
"brightness increment": {
|
"brightness increment": {
|
||||||
base: "spawn dms ipc call brightness increment",
|
base: "spawn dms ipc call brightness increment",
|
||||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
args: [
|
||||||
|
{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" },
|
||||||
|
{ name: "device", type: "text", label: "Device", placeholder: "leave empty for default", default: "" }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"brightness decrement": {
|
"brightness decrement": {
|
||||||
base: "spawn dms ipc call brightness decrement",
|
base: "spawn dms ipc call brightness decrement",
|
||||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
args: [
|
||||||
|
{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" },
|
||||||
|
{ name: "device", type: "text", label: "Device", placeholder: "leave empty for default", default: "" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"brightness toggleExponential": {
|
||||||
|
base: "spawn dms ipc call brightness toggleExponential",
|
||||||
|
args: [
|
||||||
|
{ name: "device", type: "text", label: "Device", placeholder: "leave empty for default", default: "" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dash toggle": {
|
||||||
|
base: "spawn dms ipc call dash toggle",
|
||||||
|
args: [
|
||||||
|
{ name: "tab", type: "text", label: "Tab", placeholder: "overview, media, wallpaper, weather", default: "" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,6 +261,10 @@ function getActionTypes() {
|
|||||||
return ACTION_TYPES;
|
return ACTION_TYPES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDmsActionArgs() {
|
||||||
|
return DMS_ACTION_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
function getDmsActions(isNiri, isHyprland) {
|
function getDmsActions(isNiri, isHyprland) {
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let i = 0; i < DMS_ACTIONS.length; i++) {
|
for (let i = 0; i < DMS_ACTIONS.length; i++) {
|
||||||
@@ -495,10 +517,48 @@ function parseDmsActionArgs(action) {
|
|||||||
|
|
||||||
for (var key in DMS_ACTION_ARGS) {
|
for (var key in DMS_ACTION_ARGS) {
|
||||||
var config = DMS_ACTION_ARGS[key];
|
var config = DMS_ACTION_ARGS[key];
|
||||||
if (action.startsWith(config.base)) {
|
if (!action.startsWith(config.base))
|
||||||
var rest = action.slice(config.base.length).trim();
|
continue;
|
||||||
return { base: key, args: { amount: rest || "" } };
|
|
||||||
|
var rest = action.slice(config.base.length).trim();
|
||||||
|
var result = { base: key, args: {} };
|
||||||
|
|
||||||
|
if (!rest)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var tokens = [];
|
||||||
|
var current = "";
|
||||||
|
var inQuotes = false;
|
||||||
|
var hadQuotes = false;
|
||||||
|
for (var i = 0; i < rest.length; i++) {
|
||||||
|
var c = rest[i];
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
inQuotes = !inQuotes;
|
||||||
|
hadQuotes = true;
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
if (inQuotes) {
|
||||||
|
current += c;
|
||||||
|
} else if (current || hadQuotes) {
|
||||||
|
tokens.push(current);
|
||||||
|
current = "";
|
||||||
|
hadQuotes = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
current += c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (current || hadQuotes)
|
||||||
|
tokens.push(current);
|
||||||
|
|
||||||
|
for (var j = 0; j < config.args.length && j < tokens.length; j++) {
|
||||||
|
result.args[config.args[j].name] = tokens[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { base: action, args: {} };
|
return { base: action, args: {} };
|
||||||
@@ -509,11 +569,24 @@ function buildDmsAction(baseKey, args) {
|
|||||||
if (!config)
|
if (!config)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
var action = config.base;
|
var parts = [config.base];
|
||||||
if (args && args.amount)
|
|
||||||
action += " " + args.amount;
|
|
||||||
|
|
||||||
return action;
|
for (var i = 0; i < config.args.length; i++) {
|
||||||
|
var argDef = config.args[i];
|
||||||
|
var value = args?.[argDef.name];
|
||||||
|
if (value === undefined || value === null)
|
||||||
|
value = argDef.default ?? "";
|
||||||
|
|
||||||
|
if (argDef.type === "text" && value === "") {
|
||||||
|
parts.push('""');
|
||||||
|
} else if (value !== "") {
|
||||||
|
parts.push(value);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScreenshotOptions() {
|
function getScreenshotOptions() {
|
||||||
|
|||||||
@@ -255,7 +255,6 @@ Singleton {
|
|||||||
property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend
|
property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend
|
||||||
property string batteryProfileName: ""
|
property string batteryProfileName: ""
|
||||||
property bool lockBeforeSuspend: false
|
property bool lockBeforeSuspend: false
|
||||||
property bool preventIdleForMedia: false
|
|
||||||
property bool loginctlLockIntegration: true
|
property bool loginctlLockIntegration: true
|
||||||
property bool fadeToLockEnabled: false
|
property bool fadeToLockEnabled: false
|
||||||
property int fadeToLockGracePeriod: 5
|
property int fadeToLockGracePeriod: 5
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ var SPEC = {
|
|||||||
batterySuspendBehavior: { def: 0 },
|
batterySuspendBehavior: { def: 0 },
|
||||||
batteryProfileName: { def: "" },
|
batteryProfileName: { def: "" },
|
||||||
lockBeforeSuspend: { def: false },
|
lockBeforeSuspend: { def: false },
|
||||||
preventIdleForMedia: { def: false },
|
|
||||||
loginctlLockIntegration: { def: true },
|
loginctlLockIntegration: { def: true },
|
||||||
fadeToLockEnabled: { def: false },
|
fadeToLockEnabled: { def: false },
|
||||||
fadeToLockGracePeriod: { def: 5 },
|
fadeToLockGracePeriod: { def: 5 },
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ DankModal {
|
|||||||
|
|
||||||
for (let i = 0; i < binds.length; i++) {
|
for (let i = 0; i < binds.length; i++) {
|
||||||
const bind = binds[i];
|
const bind = binds[i];
|
||||||
|
if (bind.hideOnOverlay)
|
||||||
|
continue;
|
||||||
if (bind.subcat) {
|
if (bind.subcat) {
|
||||||
hasSubcats = true;
|
hasSubcats = true;
|
||||||
if (!subcats[bind.subcat])
|
if (!subcats[bind.subcat])
|
||||||
@@ -108,6 +110,9 @@ DankModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(subcats).length === 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
processed[cat] = {
|
processed[cat] = {
|
||||||
hasSubcats: hasSubcats,
|
hasSubcats: hasSubcats,
|
||||||
subcats: subcats,
|
subcats: subcats,
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ Rectangle {
|
|||||||
|
|
||||||
function isActiveProfile(profile) {
|
function isActiveProfile(profile) {
|
||||||
if (typeof PowerProfiles === "undefined") {
|
if (typeof PowerProfiles === "undefined") {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
return PowerProfiles.profile === profile
|
return PowerProfiles.profile === profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProfile(profile) {
|
function setProfile(profile) {
|
||||||
if (typeof PowerProfiles === "undefined") {
|
if (typeof PowerProfiles === "undefined") {
|
||||||
ToastService.showError("power-profiles-daemon not available")
|
ToastService.showError("power-profiles-daemon not available");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
PowerProfiles.profile = profile
|
PowerProfiles.profile = profile;
|
||||||
if (PowerProfiles.profile !== profile) {
|
if (PowerProfiles.profile !== profile) {
|
||||||
ToastService.showError("Failed to set power profile")
|
ToastService.showError("Failed to set power profile");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,6 @@ Rectangle {
|
|||||||
Row {
|
Row {
|
||||||
id: headerRow
|
id: headerRow
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 48
|
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -50,10 +49,10 @@ Rectangle {
|
|||||||
size: Theme.iconSizeLarge
|
size: Theme.iconSizeLarge
|
||||||
color: {
|
color: {
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||||
return Theme.error
|
return Theme.error;
|
||||||
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||||
return Theme.primary
|
return Theme.primary;
|
||||||
return Theme.surfaceText
|
return Theme.surfaceText;
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -71,12 +70,12 @@ Rectangle {
|
|||||||
font.pixelSize: Theme.fontSizeXLarge
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
color: {
|
color: {
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
return Theme.error
|
return Theme.error;
|
||||||
}
|
}
|
||||||
if (BatteryService.isCharging) {
|
if (BatteryService.isCharging) {
|
||||||
return Theme.primary
|
return Theme.primary;
|
||||||
}
|
}
|
||||||
return Theme.surfaceText
|
return Theme.surfaceText;
|
||||||
}
|
}
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
}
|
}
|
||||||
@@ -86,12 +85,12 @@ Rectangle {
|
|||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
color: {
|
color: {
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
return Theme.error
|
return Theme.error;
|
||||||
}
|
}
|
||||||
if (BatteryService.isCharging) {
|
if (BatteryService.isCharging) {
|
||||||
return Theme.primary
|
return Theme.primary;
|
||||||
}
|
}
|
||||||
return Theme.surfaceText
|
return Theme.surfaceText;
|
||||||
}
|
}
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -100,12 +99,13 @@ Rectangle {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
if (!BatteryService.batteryAvailable) return "Power profile management available"
|
if (!BatteryService.batteryAvailable)
|
||||||
const time = BatteryService.formatTimeRemaining()
|
return "Power profile management available";
|
||||||
|
const time = BatteryService.formatTimeRemaining();
|
||||||
if (time !== "Unknown") {
|
if (time !== "Unknown") {
|
||||||
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`
|
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`;
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceTextMedium
|
||||||
@@ -145,10 +145,10 @@ Rectangle {
|
|||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
color: {
|
color: {
|
||||||
if (BatteryService.batteryHealth === "N/A") {
|
if (BatteryService.batteryHealth === "N/A") {
|
||||||
return Theme.surfaceText
|
return Theme.surfaceText;
|
||||||
}
|
}
|
||||||
const healthNum = parseInt(BatteryService.batteryHealth)
|
const healthNum = parseInt(BatteryService.batteryHealth);
|
||||||
return healthNum < 80 ? Theme.error : Theme.surfaceText
|
return healthNum < 80 ? Theme.error : Theme.surfaceText;
|
||||||
}
|
}
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
@@ -189,8 +189,9 @@ Rectangle {
|
|||||||
DankButtonGroup {
|
DankButtonGroup {
|
||||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||||
property int currentProfileIndex: {
|
property int currentProfileIndex: {
|
||||||
if (typeof PowerProfiles === "undefined") return 1
|
if (typeof PowerProfiles === "undefined")
|
||||||
return profileModel.findIndex(profile => isActiveProfile(profile))
|
return 1;
|
||||||
|
return profileModel.findIndex(profile => isActiveProfile(profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||||
@@ -198,8 +199,9 @@ Rectangle {
|
|||||||
selectionMode: "single"
|
selectionMode: "single"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
onSelectionChanged: (index, selected) => {
|
onSelectionChanged: (index, selected) => {
|
||||||
if (!selected) return
|
if (!selected)
|
||||||
setProfile(profileModel[index])
|
return;
|
||||||
|
setProfile(profileModel[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -384,260 +384,269 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controls Group
|
Item {
|
||||||
Column {
|
id: seekbarContainer
|
||||||
id: controlsGroup
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingXS
|
anchors.top: songInfo.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: playbackControls.top
|
||||||
anchors.bottomMargin: 0
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
DankSeekbar {
|
Column {
|
||||||
width: parent.width * 0.8
|
|
||||||
height: 20
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
activePlayer: root.activePlayer
|
|
||||||
isSeeking: root.isSeeking
|
|
||||||
onIsSeekingChanged: root.isSeeking = isSeeking
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width * 0.8
|
|
||||||
height: 20
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: {
|
|
||||||
if (!activePlayer)
|
|
||||||
return "0:00";
|
|
||||||
const rawPos = Math.max(0, activePlayer.position || 0);
|
|
||||||
const pos = activePlayer.length ? rawPos % Math.max(1, activePlayer.length) : rawPos;
|
|
||||||
const minutes = Math.floor(pos / 60);
|
|
||||||
const seconds = Math.floor(pos % 60);
|
|
||||||
const timeStr = minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
|
|
||||||
return timeStr;
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: {
|
|
||||||
if (!activePlayer || !activePlayer.length)
|
|
||||||
return "0:00";
|
|
||||||
const dur = Math.max(0, activePlayer.length || 0); // Length is already in seconds
|
|
||||||
const minutes = Math.floor(dur / 60);
|
|
||||||
const seconds = Math.floor(dur % 60);
|
|
||||||
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 50
|
spacing: 2
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.verticalCenterOffset: parent.height * 0.2
|
||||||
|
|
||||||
Row {
|
DankSeekbar {
|
||||||
anchors.centerIn: parent
|
width: parent.width * 0.8
|
||||||
spacing: Theme.spacingM
|
height: 20
|
||||||
height: parent.height
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
activePlayer: root.activePlayer
|
||||||
|
isSeeking: root.isSeeking
|
||||||
|
onIsSeekingChanged: root.isSeeking = isSeeking
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 50
|
width: parent.width * 0.8
|
||||||
height: 50
|
height: 16
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: activePlayer && activePlayer.shuffleSupported
|
text: {
|
||||||
|
if (!activePlayer)
|
||||||
|
return "0:00";
|
||||||
|
const rawPos = Math.max(0, activePlayer.position || 0);
|
||||||
|
const pos = activePlayer.length ? rawPos % Math.max(1, activePlayer.length) : rawPos;
|
||||||
|
const minutes = Math.floor(pos / 60);
|
||||||
|
const seconds = Math.floor(pos % 60);
|
||||||
|
const timeStr = minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
|
||||||
|
return timeStr;
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
StyledText {
|
||||||
width: 40
|
anchors.right: parent.right
|
||||||
height: 40
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: 20
|
text: {
|
||||||
|
if (!activePlayer || !activePlayer.length)
|
||||||
|
return "0:00";
|
||||||
|
const dur = Math.max(0, activePlayer.length || 0);
|
||||||
|
const minutes = Math.floor(dur / 60);
|
||||||
|
const seconds = Math.floor(dur % 60);
|
||||||
|
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: playbackControls
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 50
|
||||||
|
height: 50
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: activePlayer && activePlayer.shuffleSupported
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
radius: 20
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: shuffleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: shuffleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
name: "shuffle"
|
||||||
|
size: 20
|
||||||
|
color: activePlayer && activePlayer.shuffle ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
anchors.centerIn: parent
|
id: shuffleArea
|
||||||
name: "shuffle"
|
anchors.fill: parent
|
||||||
size: 20
|
hoverEnabled: true
|
||||||
color: activePlayer && activePlayer.shuffle ? Theme.primary : Theme.surfaceText
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
onClicked: {
|
||||||
|
if (activePlayer && activePlayer.canControl && activePlayer.shuffleSupported) {
|
||||||
MouseArea {
|
activePlayer.shuffle = !activePlayer.shuffle;
|
||||||
id: shuffleArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (activePlayer && activePlayer.canControl && activePlayer.shuffleSupported) {
|
|
||||||
activePlayer.shuffle = !activePlayer.shuffle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 50
|
width: 50
|
||||||
height: 50
|
height: 50
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 40
|
width: 40
|
||||||
height: 40
|
height: 40
|
||||||
radius: 20
|
radius: 20
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: prevBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: prevBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
|
name: "skip_previous"
|
||||||
|
size: 24
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
anchors.centerIn: parent
|
id: prevBtnArea
|
||||||
name: "skip_previous"
|
anchors.fill: parent
|
||||||
size: 24
|
hoverEnabled: true
|
||||||
color: Theme.surfaceText
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
onClicked: {
|
||||||
|
if (!activePlayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
if (activePlayer.position > 8 && activePlayer.canSeek) {
|
||||||
id: prevBtnArea
|
activePlayer.position = 0;
|
||||||
anchors.fill: parent
|
} else {
|
||||||
hoverEnabled: true
|
activePlayer.previous();
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (!activePlayer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activePlayer.position > 8 && activePlayer.canSeek) {
|
|
||||||
activePlayer.position = 0;
|
|
||||||
} else {
|
|
||||||
activePlayer.previous();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
width: 50
|
||||||
|
height: 50
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
width: 50
|
width: 50
|
||||||
height: 50
|
height: 50
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
radius: 25
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Theme.primary
|
||||||
|
|
||||||
Rectangle {
|
DankIcon {
|
||||||
width: 50
|
|
||||||
height: 50
|
|
||||||
radius: 25
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: Theme.primary
|
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||||
|
size: 28
|
||||||
|
color: Theme.background
|
||||||
|
weight: 500
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
anchors.centerIn: parent
|
anchors.fill: parent
|
||||||
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
hoverEnabled: true
|
||||||
size: 28
|
cursorShape: Qt.PointingHandCursor
|
||||||
color: Theme.background
|
onClicked: activePlayer && activePlayer.togglePlaying()
|
||||||
weight: 500
|
}
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
layer.enabled: true
|
||||||
anchors.fill: parent
|
layer.effect: MultiEffect {
|
||||||
hoverEnabled: true
|
shadowEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
shadowHorizontalOffset: 0
|
||||||
onClicked: activePlayer && activePlayer.togglePlaying()
|
shadowVerticalOffset: 0
|
||||||
}
|
shadowBlur: 1.0
|
||||||
|
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||||
layer.enabled: true
|
shadowOpacity: 0.3
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 0
|
|
||||||
shadowBlur: 1.0
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 50
|
width: 50
|
||||||
height: 50
|
height: 50
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 40
|
width: 40
|
||||||
height: 40
|
height: 40
|
||||||
radius: 20
|
radius: 20
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: nextBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: nextBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
|
name: "skip_next"
|
||||||
|
size: 24
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
anchors.centerIn: parent
|
id: nextBtnArea
|
||||||
name: "skip_next"
|
anchors.fill: parent
|
||||||
size: 24
|
hoverEnabled: true
|
||||||
color: Theme.surfaceText
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
onClicked: activePlayer && activePlayer.next()
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nextBtnArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: activePlayer && activePlayer.next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 50
|
width: 50
|
||||||
height: 50
|
height: 50
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: activePlayer && activePlayer.loopSupported
|
visible: activePlayer && activePlayer.loopSupported
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 40
|
width: 40
|
||||||
height: 40
|
height: 40
|
||||||
radius: 20
|
radius: 20
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: repeatArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: repeatArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
name: {
|
||||||
|
if (!activePlayer)
|
||||||
|
return "repeat";
|
||||||
|
switch (activePlayer.loopState) {
|
||||||
|
case MprisLoopState.Track:
|
||||||
|
return "repeat_one";
|
||||||
|
case MprisLoopState.Playlist:
|
||||||
|
return "repeat";
|
||||||
|
default:
|
||||||
|
return "repeat";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size: 20
|
||||||
|
color: activePlayer && activePlayer.loopState !== MprisLoopState.None ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
anchors.centerIn: parent
|
id: repeatArea
|
||||||
name: {
|
anchors.fill: parent
|
||||||
if (!activePlayer)
|
hoverEnabled: true
|
||||||
return "repeat";
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (activePlayer && activePlayer.canControl && activePlayer.loopSupported) {
|
||||||
switch (activePlayer.loopState) {
|
switch (activePlayer.loopState) {
|
||||||
case MprisLoopState.Track:
|
case MprisLoopState.None:
|
||||||
return "repeat_one";
|
activePlayer.loopState = MprisLoopState.Playlist;
|
||||||
|
break;
|
||||||
case MprisLoopState.Playlist:
|
case MprisLoopState.Playlist:
|
||||||
return "repeat";
|
activePlayer.loopState = MprisLoopState.Track;
|
||||||
default:
|
break;
|
||||||
return "repeat";
|
case MprisLoopState.Track:
|
||||||
}
|
activePlayer.loopState = MprisLoopState.None;
|
||||||
}
|
break;
|
||||||
size: 20
|
|
||||||
color: activePlayer && activePlayer.loopState !== MprisLoopState.None ? Theme.primary : Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: repeatArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (activePlayer && activePlayer.canControl && activePlayer.loopSupported) {
|
|
||||||
switch (activePlayer.loopState) {
|
|
||||||
case MprisLoopState.None:
|
|
||||||
activePlayer.loopState = MprisLoopState.Playlist;
|
|
||||||
break;
|
|
||||||
case MprisLoopState.Playlist:
|
|
||||||
activePlayer.loopState = MprisLoopState.Track;
|
|
||||||
break;
|
|
||||||
case MprisLoopState.Track:
|
|
||||||
activePlayer.loopState = MprisLoopState.None;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,166 +657,166 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
id: playerSelectorButton
|
Rectangle {
|
||||||
width: 40
|
id: playerSelectorButton
|
||||||
height: 40
|
width: 40
|
||||||
radius: 20
|
height: 40
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
radius: 20
|
||||||
y: 185
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
color: playerSelectorArea.containsMouse || playersExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
y: 185
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
color: playerSelectorArea.containsMouse || playersExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||||
border.width: 1
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
z: 100
|
border.width: 1
|
||||||
visible: (allPlayers?.length || 0) >= 1
|
z: 100
|
||||||
|
visible: (allPlayers?.length || 0) >= 1
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
DankIcon {
|
||||||
name: "assistant_device"
|
anchors.centerIn: parent
|
||||||
size: 18
|
name: "assistant_device"
|
||||||
color: Theme.surfaceText
|
size: 18
|
||||||
}
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
MouseArea {
|
|
||||||
id: playerSelectorArea
|
MouseArea {
|
||||||
anchors.fill: parent
|
id: playerSelectorArea
|
||||||
hoverEnabled: true
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
hoverEnabled: true
|
||||||
onClicked: {
|
cursorShape: Qt.PointingHandCursor
|
||||||
if (playersExpanded) {
|
onClicked: {
|
||||||
hideDropdowns();
|
if (playersExpanded) {
|
||||||
return;
|
hideDropdowns();
|
||||||
}
|
return;
|
||||||
hideDropdowns();
|
}
|
||||||
playersExpanded = true;
|
hideDropdowns();
|
||||||
const buttonsOnRight = !isRightEdge;
|
playersExpanded = true;
|
||||||
const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
|
const buttonsOnRight = !isRightEdge;
|
||||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
|
||||||
const screenY = popoutY + contentOffsetY + btnY;
|
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||||
showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
const screenY = popoutY + contentOffsetY + btnY;
|
||||||
}
|
showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||||
onEntered: sharedTooltip.show("Media Players", playerSelectorButton, 0, 0, isRightEdge ? "right" : "left")
|
}
|
||||||
onExited: sharedTooltip.hide()
|
onEntered: sharedTooltip.show("Media Players", playerSelectorButton, 0, 0, isRightEdge ? "right" : "left")
|
||||||
}
|
onExited: sharedTooltip.hide()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
id: volumeButton
|
Rectangle {
|
||||||
width: 40
|
id: volumeButton
|
||||||
height: 40
|
width: 40
|
||||||
radius: 20
|
height: 40
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
radius: 20
|
||||||
y: 130
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
y: 130
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, volumeAvailable ? 0.3 : 0.15)
|
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||||
border.width: 1
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, volumeAvailable ? 0.3 : 0.15)
|
||||||
z: 101
|
border.width: 1
|
||||||
enabled: volumeAvailable
|
z: 101
|
||||||
|
enabled: volumeAvailable
|
||||||
property real previousVolume: 0.0
|
|
||||||
|
property real previousVolume: 0.0
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
DankIcon {
|
||||||
name: getVolumeIcon()
|
anchors.centerIn: parent
|
||||||
size: 18
|
name: getVolumeIcon()
|
||||||
color: volumeAvailable && currentVolume > 0 ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, volumeAvailable ? 1.0 : 0.5)
|
size: 18
|
||||||
}
|
color: volumeAvailable && currentVolume > 0 ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, volumeAvailable ? 1.0 : 0.5)
|
||||||
|
}
|
||||||
MouseArea {
|
|
||||||
id: volumeButtonArea
|
MouseArea {
|
||||||
anchors.fill: parent
|
id: volumeButtonArea
|
||||||
hoverEnabled: true
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
hoverEnabled: true
|
||||||
onEntered: {
|
cursorShape: Qt.PointingHandCursor
|
||||||
if (volumeExpanded)
|
onEntered: {
|
||||||
return;
|
if (volumeExpanded)
|
||||||
hideDropdowns();
|
return;
|
||||||
volumeExpanded = true;
|
hideDropdowns();
|
||||||
const buttonsOnRight = !isRightEdge;
|
volumeExpanded = true;
|
||||||
const btnY = volumeButton.y + volumeButton.height / 2;
|
const buttonsOnRight = !isRightEdge;
|
||||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
const btnY = volumeButton.y + volumeButton.height / 2;
|
||||||
const screenY = popoutY + contentOffsetY + btnY;
|
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||||
showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
const screenY = popoutY + contentOffsetY + btnY;
|
||||||
}
|
showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||||
onExited: {
|
}
|
||||||
if (volumeExpanded)
|
onExited: {
|
||||||
volumeButtonExited();
|
if (volumeExpanded)
|
||||||
}
|
volumeButtonExited();
|
||||||
onClicked: {
|
}
|
||||||
SessionData.suppressOSDTemporarily();
|
onClicked: {
|
||||||
if (currentVolume > 0) {
|
SessionData.suppressOSDTemporarily();
|
||||||
volumeButton.previousVolume = currentVolume;
|
if (currentVolume > 0) {
|
||||||
if (usePlayerVolume) {
|
volumeButton.previousVolume = currentVolume;
|
||||||
activePlayer.volume = 0;
|
if (usePlayerVolume) {
|
||||||
} else if (AudioService.sink?.audio) {
|
activePlayer.volume = 0;
|
||||||
AudioService.sink.audio.volume = 0;
|
} else if (AudioService.sink?.audio) {
|
||||||
}
|
AudioService.sink.audio.volume = 0;
|
||||||
} else {
|
}
|
||||||
const restoreVolume = volumeButton.previousVolume > 0 ? volumeButton.previousVolume : 0.5;
|
} else {
|
||||||
if (usePlayerVolume) {
|
const restoreVolume = volumeButton.previousVolume > 0 ? volumeButton.previousVolume : 0.5;
|
||||||
activePlayer.volume = restoreVolume;
|
if (usePlayerVolume) {
|
||||||
} else if (AudioService.sink?.audio) {
|
activePlayer.volume = restoreVolume;
|
||||||
AudioService.sink.audio.volume = restoreVolume;
|
} else if (AudioService.sink?.audio) {
|
||||||
}
|
AudioService.sink.audio.volume = restoreVolume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onWheel: wheelEvent => {
|
}
|
||||||
SessionData.suppressOSDTemporarily();
|
onWheel: wheelEvent => {
|
||||||
const delta = wheelEvent.angleDelta.y;
|
SessionData.suppressOSDTemporarily();
|
||||||
const current = (currentVolume * 100) || 0;
|
const delta = wheelEvent.angleDelta.y;
|
||||||
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);
|
const current = (currentVolume * 100) || 0;
|
||||||
|
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);
|
||||||
if (usePlayerVolume) {
|
|
||||||
activePlayer.volume = newVolume / 100;
|
if (usePlayerVolume) {
|
||||||
} else if (AudioService.sink?.audio) {
|
activePlayer.volume = newVolume / 100;
|
||||||
AudioService.sink.audio.volume = newVolume / 100;
|
} else if (AudioService.sink?.audio) {
|
||||||
}
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
wheelEvent.accepted = true;
|
}
|
||||||
}
|
wheelEvent.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
id: audioDevicesButton
|
Rectangle {
|
||||||
width: 40
|
id: audioDevicesButton
|
||||||
height: 40
|
width: 40
|
||||||
radius: 20
|
height: 40
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
radius: 20
|
||||||
y: 240
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
color: audioDevicesArea.containsMouse || devicesExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
y: 240
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
color: audioDevicesArea.containsMouse || devicesExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||||
border.width: 1
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
z: 100
|
border.width: 1
|
||||||
|
z: 100
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
DankIcon {
|
||||||
name: devicesExpanded ? "expand_less" : "speaker"
|
anchors.centerIn: parent
|
||||||
size: 18
|
name: devicesExpanded ? "expand_less" : "speaker"
|
||||||
color: Theme.surfaceText
|
size: 18
|
||||||
}
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
MouseArea {
|
|
||||||
id: audioDevicesArea
|
MouseArea {
|
||||||
anchors.fill: parent
|
id: audioDevicesArea
|
||||||
hoverEnabled: true
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
hoverEnabled: true
|
||||||
onClicked: {
|
cursorShape: Qt.PointingHandCursor
|
||||||
if (devicesExpanded) {
|
onClicked: {
|
||||||
hideDropdowns();
|
if (devicesExpanded) {
|
||||||
return;
|
hideDropdowns();
|
||||||
}
|
return;
|
||||||
hideDropdowns();
|
}
|
||||||
devicesExpanded = true;
|
hideDropdowns();
|
||||||
const buttonsOnRight = !isRightEdge;
|
devicesExpanded = true;
|
||||||
const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
|
const buttonsOnRight = !isRightEdge;
|
||||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
|
||||||
const screenY = popoutY + contentOffsetY + btnY;
|
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||||
showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
|
const screenY = popoutY + contentOffsetY + btnY;
|
||||||
}
|
showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
|
||||||
onEntered: sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left")
|
}
|
||||||
onExited: sharedTooltip.hide()
|
onEntered: sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left")
|
||||||
}
|
onExited: sharedTooltip.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,19 @@ import qs.Widgets
|
|||||||
Item {
|
Item {
|
||||||
id: displaysTab
|
id: displaysTab
|
||||||
|
|
||||||
|
function formatGammaTime(isoString) {
|
||||||
|
if (!isoString)
|
||||||
|
return "";
|
||||||
|
try {
|
||||||
|
const date = new Date(isoString);
|
||||||
|
if (isNaN(date.getTime()))
|
||||||
|
return "";
|
||||||
|
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
|
||||||
|
} catch (e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getBarComponentsFromSettings() {
|
function getBarComponentsFromSettings() {
|
||||||
const bars = SettingsData.barConfigs || [];
|
const bars = SettingsData.barConfigs || [];
|
||||||
return bars.map(bar => ({
|
return bars.map(bar => ({
|
||||||
@@ -530,6 +543,233 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.2
|
||||||
|
visible: gammaStatusSection.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: gammaStatusSection
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: DisplayService.nightModeEnabled && DisplayService.gammaCurrentTemp > 0
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: DisplayService.gammaIsDay ? "light_mode" : "dark_mode"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Current Status")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: tempColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: tempColumn
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "device_thermostat"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: DisplayService.gammaCurrentTemp + "K"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Current Temp")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: periodColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: periodColumn
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: DisplayService.gammaIsDay ? "wb_sunny" : "nightlight"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: DisplayService.gammaIsDay ? "#FFA726" : "#7E57C2"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: DisplayService.gammaIsDay ? I18n.tr("Daytime") : I18n.tr("Night")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Current Period")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: SessionData.nightModeAutoMode === "location" && (DisplayService.gammaSunriseTime || DisplayService.gammaSunsetTime)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: sunriseColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
visible: DisplayService.gammaSunriseTime
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: sunriseColumn
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "wb_twilight"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: "#FF7043"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: displaysTab.formatGammaTime(DisplayService.gammaSunriseTime)
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Sunrise")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: sunsetColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
visible: DisplayService.gammaSunsetTime
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: sunsetColumn
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "wb_twilight"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: "#5C6BC0"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: displaysTab.formatGammaTime(DisplayService.gammaSunsetTime)
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Sunset")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: nextChangeRow.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
visible: DisplayService.gammaNextTransition
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: nextChangeRow
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "schedule"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Next Transition")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: displaysTab.formatGammaTime(DisplayService.gammaNextTransition)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
|
||||||
text: I18n.tr("Prevent idle for media")
|
|
||||||
description: I18n.tr("Inhibit idle timeout when audio or video is playing")
|
|
||||||
checked: SettingsData.preventIdleForMedia
|
|
||||||
visible: IdleService.idleMonitorAvailable
|
|
||||||
onToggled: checked => SettingsData.set("preventIdleForMedia", checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
text: I18n.tr("Fade to lock screen")
|
text: I18n.tr("Fade to lock screen")
|
||||||
description: I18n.tr("Gradually fade the screen before locking with a configurable grace period")
|
description: I18n.tr("Gradually fade the screen before locking with a configurable grace period")
|
||||||
@@ -76,6 +68,14 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("fadeToLockEnabled", checked)
|
onToggled: checked => SettingsData.set("fadeToLockEnabled", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Lock before suspend")
|
||||||
|
description: I18n.tr("Automatically lock the screen when the system prepares to suspend")
|
||||||
|
checked: SettingsData.lockBeforeSuspend
|
||||||
|
visible: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
|
||||||
|
onToggled: checked => SettingsData.set("lockBeforeSuspend", checked)
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
id: fadeGracePeriodDropdown
|
id: fadeGracePeriodDropdown
|
||||||
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
|
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
|
||||||
@@ -114,14 +114,14 @@ Item {
|
|||||||
function onCurrentIndexChanged() {
|
function onCurrentIndexChanged() {
|
||||||
const currentProfile = powerCategory.currentIndex === 0 ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
const currentProfile = powerCategory.currentIndex === 0 ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
||||||
const index = powerProfileDropdown.profileValues.indexOf(currentProfile);
|
const index = powerProfileDropdown.profileValues.indexOf(currentProfile);
|
||||||
powerProfileDropdown.currentValue = powerProfileDropdown.profileOptions[index]
|
powerProfileDropdown.currentValue = powerProfileDropdown.profileOptions[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
const currentProfile = powerCategory.currentIndex === 0 ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
const currentProfile = powerCategory.currentIndex === 0 ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
||||||
const index = profileValues.indexOf(currentProfile);
|
const index = profileValues.indexOf(currentProfile);
|
||||||
currentValue = profileOptions[index]
|
currentValue = profileOptions[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Singleton {
|
|||||||
signal extWorkspaceStateUpdate(var data)
|
signal extWorkspaceStateUpdate(var data)
|
||||||
signal wlrOutputStateUpdate(var data)
|
signal wlrOutputStateUpdate(var data)
|
||||||
signal evdevStateUpdate(var data)
|
signal evdevStateUpdate(var data)
|
||||||
|
signal gammaStateUpdate(var data)
|
||||||
signal openUrlRequested(string url)
|
signal openUrlRequested(string url)
|
||||||
signal appPickerRequested(var data)
|
signal appPickerRequested(var data)
|
||||||
|
|
||||||
@@ -267,9 +268,9 @@ Singleton {
|
|||||||
|
|
||||||
function removeSubscription(service) {
|
function removeSubscription(service) {
|
||||||
if (activeSubscriptions.includes("all")) {
|
if (activeSubscriptions.includes("all")) {
|
||||||
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "extworkspace", "browser"]
|
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "extworkspace", "browser"];
|
||||||
const filtered = allServices.filter(s => s !== service)
|
const filtered = allServices.filter(s => s !== service);
|
||||||
subscribe(filtered)
|
subscribe(filtered);
|
||||||
} else {
|
} else {
|
||||||
const filtered = activeSubscriptions.filter(s => s !== service);
|
const filtered = activeSubscriptions.filter(s => s !== service);
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
@@ -289,9 +290,9 @@ Singleton {
|
|||||||
excludeServices = [excludeServices];
|
excludeServices = [excludeServices];
|
||||||
}
|
}
|
||||||
|
|
||||||
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser"]
|
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser"];
|
||||||
const filtered = allServices.filter(s => !excludeServices.includes(s))
|
const filtered = allServices.filter(s => !excludeServices.includes(s));
|
||||||
subscribe(filtered)
|
subscribe(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubscriptionEvent(response) {
|
function handleSubscriptionEvent(response) {
|
||||||
@@ -355,16 +356,18 @@ Singleton {
|
|||||||
if (data.capsLock !== undefined) {
|
if (data.capsLock !== undefined) {
|
||||||
capsLockState = data.capsLock;
|
capsLockState = data.capsLock;
|
||||||
}
|
}
|
||||||
evdevStateUpdate(data)
|
evdevStateUpdate(data);
|
||||||
|
} else if (service === "gamma") {
|
||||||
|
gammaStateUpdate(data);
|
||||||
} else if (service === "browser.open_requested") {
|
} else if (service === "browser.open_requested") {
|
||||||
if (data.target) {
|
if (data.target) {
|
||||||
if (data.requestType === "url" || !data.requestType) {
|
if (data.requestType === "url" || !data.requestType) {
|
||||||
openUrlRequested(data.target)
|
openUrlRequested(data.target);
|
||||||
} else {
|
} else {
|
||||||
appPickerRequested(data)
|
appPickerRequested(data);
|
||||||
}
|
}
|
||||||
} else if (data.url) {
|
} else if (data.url) {
|
||||||
openUrlRequested(data.url)
|
openUrlRequested(data.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,18 @@ Singleton {
|
|||||||
property bool automationAvailable: false
|
property bool automationAvailable: false
|
||||||
property bool gammaControlAvailable: false
|
property bool gammaControlAvailable: false
|
||||||
|
|
||||||
|
property var gammaState: ({})
|
||||||
|
property int gammaCurrentTemp: gammaState?.currentTemp ?? 0
|
||||||
|
property string gammaNextTransition: gammaState?.nextTransition ?? ""
|
||||||
|
property string gammaSunriseTime: gammaState?.sunriseTime ?? ""
|
||||||
|
property string gammaSunsetTime: gammaState?.sunsetTime ?? ""
|
||||||
|
property string gammaDawnTime: gammaState?.dawnTime ?? ""
|
||||||
|
property string gammaNightTime: gammaState?.nightTime ?? ""
|
||||||
|
property bool gammaIsDay: gammaState?.isDay ?? true
|
||||||
|
property real gammaSunPosition: gammaState?.sunPosition ?? 0
|
||||||
|
property int gammaLowTemp: gammaState?.config?.LowTemp ?? 0
|
||||||
|
property int gammaHighTemp: gammaState?.config?.HighTemp ?? 0
|
||||||
|
|
||||||
function markDeviceUserControlled(deviceId) {
|
function markDeviceUserControlled(deviceId) {
|
||||||
const newControlled = Object.assign({}, userControlledDevices);
|
const newControlled = Object.assign({}, userControlledDevices);
|
||||||
newControlled[deviceId] = Date.now();
|
newControlled[deviceId] = Date.now();
|
||||||
@@ -809,6 +821,10 @@ Singleton {
|
|||||||
osdSuppressTimer.restart();
|
osdSuppressTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onGammaStateUpdate(data) {
|
||||||
|
root.gammaState = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session Data Connections
|
// Session Data Connections
|
||||||
|
|||||||
@@ -58,43 +58,12 @@ Singleton {
|
|||||||
property var monitorOffMonitor: null
|
property var monitorOffMonitor: null
|
||||||
property var lockMonitor: null
|
property var lockMonitor: null
|
||||||
property var suspendMonitor: null
|
property var suspendMonitor: null
|
||||||
property var mediaInhibitor: null
|
|
||||||
property var lockComponent: null
|
property var lockComponent: null
|
||||||
|
|
||||||
function wake() {
|
function wake() {
|
||||||
requestMonitorOn();
|
requestMonitorOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMediaInhibitor() {
|
|
||||||
if (!idleInhibitorAvailable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaInhibitor) {
|
|
||||||
mediaInhibitor.destroy();
|
|
||||||
mediaInhibitor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inhibitorString = `
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
IdleInhibitor {
|
|
||||||
active: false
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
mediaInhibitor = Qt.createQmlObject(inhibitorString, root, "IdleService.MediaInhibitor");
|
|
||||||
mediaInhibitor.active = Qt.binding(() => root.mediaPlaying);
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyMediaInhibitor() {
|
|
||||||
if (mediaInhibitor) {
|
|
||||||
mediaInhibitor.destroy();
|
|
||||||
mediaInhibitor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createIdleMonitors() {
|
function createIdleMonitors() {
|
||||||
if (!idleMonitorAvailable) {
|
if (!idleMonitorAvailable) {
|
||||||
console.info("IdleService: IdleMonitor not available, skipping creation");
|
console.info("IdleService: IdleMonitor not available, skipping creation");
|
||||||
@@ -152,10 +121,6 @@ Singleton {
|
|||||||
root.requestSuspend();
|
root.requestSuspend();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (SettingsData.preventIdleForMedia) {
|
|
||||||
createMediaInhibitor();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("IdleService: Error creating IdleMonitors:", e);
|
console.warn("IdleService: Error creating IdleMonitors:", e);
|
||||||
}
|
}
|
||||||
@@ -176,17 +141,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onPreventIdleForMediaChanged() {
|
|
||||||
if (SettingsData.preventIdleForMedia) {
|
|
||||||
createMediaInhibitor();
|
|
||||||
} else {
|
|
||||||
destroyMediaInhibitor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!idleMonitorAvailable) {
|
if (!idleMonitorAvailable) {
|
||||||
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
|
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -10,7 +8,7 @@ Rectangle {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string currentIcon: ""
|
property string currentIcon: ""
|
||||||
property string iconType: "icon" // "icon" or "text"
|
property string iconType: "icon"
|
||||||
|
|
||||||
signal iconSelected(string iconName, string iconType)
|
signal iconSelected(string iconName, string iconType)
|
||||||
|
|
||||||
@@ -18,40 +16,73 @@ Rectangle {
|
|||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
border.color: dropdownLoader.active ? Theme.primary : Theme.outline
|
border.color: iconPopup.visible ? Theme.primary : Theme.outline
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
property var iconCategories: [{
|
property var iconCategories: [
|
||||||
|
{
|
||||||
"name": I18n.tr("Numbers"),
|
"name": I18n.tr("Numbers"),
|
||||||
"icons": ["looks_one", "looks_two", "looks_3", "looks_4", "looks_5", "looks_6", "filter_1", "filter_2", "filter_3", "filter_4", "filter_5", "filter_6", "filter_7", "filter_8", "filter_9", "filter_9_plus", "plus_one", "exposure_plus_1", "exposure_plus_2"]
|
"icons": ["looks_one", "looks_two", "looks_3", "looks_4", "looks_5", "looks_6", "filter_1", "filter_2", "filter_3", "filter_4", "filter_5", "filter_6", "filter_7", "filter_8", "filter_9", "filter_9_plus", "plus_one", "exposure_plus_1", "exposure_plus_2"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Workspace"),
|
"name": I18n.tr("Workspace"),
|
||||||
"icons": ["work", "laptop", "desktop_windows", "folder", "view_module", "dashboard", "apps", "grid_view"]
|
"icons": ["work", "laptop", "desktop_windows", "folder", "view_module", "dashboard", "apps", "grid_view"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Development"),
|
"name": I18n.tr("Development"),
|
||||||
"icons": ["code", "terminal", "bug_report", "build", "engineering", "integration_instructions", "data_object", "schema", "api", "webhook"]
|
"icons": ["code", "terminal", "bug_report", "build", "engineering", "integration_instructions", "data_object", "schema", "api", "webhook"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Communication"),
|
"name": I18n.tr("Communication"),
|
||||||
"icons": ["chat", "mail", "forum", "message", "video_call", "call", "contacts", "group", "notifications", "campaign"]
|
"icons": ["chat", "mail", "forum", "message", "video_call", "call", "contacts", "group", "notifications", "campaign"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Media"),
|
"name": I18n.tr("Media"),
|
||||||
"icons": ["music_note", "headphones", "mic", "videocam", "photo", "movie", "library_music", "album", "radio", "volume_up"]
|
"icons": ["music_note", "headphones", "mic", "videocam", "photo", "movie", "library_music", "album", "radio", "volume_up"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("System"),
|
"name": I18n.tr("System"),
|
||||||
"icons": ["memory", "storage", "developer_board", "monitor", "keyboard", "mouse", "battery_std", "wifi", "bluetooth", "security", "settings"]
|
"icons": ["memory", "storage", "developer_board", "monitor", "keyboard", "mouse", "battery_std", "wifi", "bluetooth", "security", "settings"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Navigation"),
|
"name": I18n.tr("Navigation"),
|
||||||
"icons": ["home", "arrow_forward", "arrow_back", "expand_more", "expand_less", "menu", "close", "search", "filter_list", "sort"]
|
"icons": ["home", "arrow_forward", "arrow_back", "expand_more", "expand_less", "menu", "close", "search", "filter_list", "sort"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Actions"),
|
"name": I18n.tr("Actions"),
|
||||||
"icons": ["add", "remove", "edit", "delete", "save", "download", "upload", "share", "content_copy", "content_paste", "content_cut", "undo", "redo"]
|
"icons": ["add", "remove", "edit", "delete", "save", "download", "upload", "share", "content_copy", "content_paste", "content_cut", "undo", "redo"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Status"),
|
"name": I18n.tr("Status"),
|
||||||
"icons": ["check", "error", "warning", "info", "done", "pending", "schedule", "update", "sync", "offline_bolt"]
|
"icons": ["check", "error", "warning", "info", "done", "pending", "schedule", "update", "sync", "offline_bolt"]
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": I18n.tr("Fun"),
|
"name": I18n.tr("Fun"),
|
||||||
"icons": ["celebration", "cake", "star", "favorite", "pets", "sports_esports", "local_fire_department", "bolt", "auto_awesome", "diamond"]
|
"icons": ["celebration", "cake", "star", "favorite", "pets", "sports_esports", "local_fire_department", "bolt", "auto_awesome", "diamond"]
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (iconPopup.visible) {
|
||||||
|
iconPopup.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pos = root.mapToItem(Overlay.overlay, 0, 0);
|
||||||
|
const popupHeight = 500;
|
||||||
|
const overlayHeight = Overlay.overlay?.height ?? 800;
|
||||||
|
iconPopup.x = pos.x;
|
||||||
|
if (pos.y + root.height + popupHeight + 4 > overlayHeight) {
|
||||||
|
iconPopup.y = pos.y - popupHeight - 4;
|
||||||
|
} else {
|
||||||
|
iconPopup.y = pos.y + root.height + 4;
|
||||||
|
}
|
||||||
|
iconPopup.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -73,18 +104,11 @@ Rectangle {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 160
|
width: 160
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
dropdownLoader.active = !dropdownLoader.active
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: dropdownLoader.active ? "expand_less" : "expand_more"
|
name: iconPopup.visible ? "expand_less" : "expand_more"
|
||||||
size: 16
|
size: 16
|
||||||
color: Theme.outline
|
color: Theme.outline
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -92,185 +116,126 @@ Rectangle {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Popup {
|
||||||
id: dropdownLoader
|
id: iconPopup
|
||||||
active: false
|
|
||||||
asynchronous: true
|
|
||||||
|
|
||||||
sourceComponent: PanelWindow {
|
parent: Overlay.overlay
|
||||||
id: dropdownPopup
|
width: 320
|
||||||
|
height: Math.min(500, dropdownContent.implicitHeight + 32)
|
||||||
|
padding: 0
|
||||||
|
modal: true
|
||||||
|
dim: false
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
visible: true
|
background: Rectangle {
|
||||||
implicitWidth: 320
|
|
||||||
implicitHeight: Math.min(500, dropdownContent.implicitHeight + 32)
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
}
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
|
|
||||||
anchors {
|
contentItem: Rectangle {
|
||||||
top: true
|
color: Theme.surface
|
||||||
left: true
|
radius: Theme.cornerRadius
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top area - above popup
|
layer.enabled: true
|
||||||
MouseArea {
|
layer.effect: MultiEffect {
|
||||||
anchors.left: parent.left
|
shadowEnabled: true
|
||||||
anchors.right: parent.right
|
shadowColor: Theme.shadowStrong
|
||||||
anchors.top: parent.top
|
shadowBlur: 0.8
|
||||||
height: popupContainer.y
|
shadowHorizontalOffset: 0
|
||||||
onClicked: {
|
shadowVerticalOffset: 4
|
||||||
dropdownLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom area - below popup
|
|
||||||
MouseArea {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: popupContainer.bottom
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
onClicked: {
|
|
||||||
dropdownLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left area - left of popup
|
|
||||||
MouseArea {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.top: popupContainer.top
|
|
||||||
anchors.bottom: popupContainer.bottom
|
|
||||||
width: popupContainer.x
|
|
||||||
onClicked: {
|
|
||||||
dropdownLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right area - right of popup
|
|
||||||
MouseArea {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: popupContainer.top
|
|
||||||
anchors.bottom: popupContainer.bottom
|
|
||||||
anchors.left: popupContainer.right
|
|
||||||
onClicked: {
|
|
||||||
dropdownLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: popupContainer
|
width: 24
|
||||||
width: 320
|
height: 24
|
||||||
height: Math.min(500, dropdownContent.implicitHeight + 32)
|
radius: 12
|
||||||
x: Math.max(16, Math.min(root.mapToItem(null, 0, 0).x, parent.width - width - 16))
|
color: closeMouseArea.containsMouse ? Theme.errorHover : "transparent"
|
||||||
y: Math.max(16, Math.min(root.mapToItem(null, 0, root.height + 4).y, parent.height - height - 16))
|
anchors.top: parent.top
|
||||||
radius: Theme.cornerRadius
|
anchors.right: parent.right
|
||||||
color: Theme.surface
|
anchors.topMargin: Theme.spacingS
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
z: 1
|
||||||
|
|
||||||
layer.enabled: true
|
DankIcon {
|
||||||
layer.effect: MultiEffect {
|
name: "close"
|
||||||
shadowEnabled: true
|
size: 16
|
||||||
shadowColor: Theme.shadowStrong
|
color: closeMouseArea.containsMouse ? Theme.error : Theme.outline
|
||||||
shadowBlur: 0.8
|
anchors.centerIn: parent
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close button
|
MouseArea {
|
||||||
Rectangle {
|
id: closeMouseArea
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
radius: 12
|
|
||||||
color: closeMouseArea.containsMouse ? Theme.errorHover : "transparent"
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.topMargin: Theme.spacingS
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
z: 1
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "close"
|
|
||||||
size: 16
|
|
||||||
color: closeMouseArea.containsMouse ? Theme.error : Theme.outline
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
dropdownLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankFlickable {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
hoverEnabled: true
|
||||||
contentHeight: dropdownContent.height
|
cursorShape: Qt.PointingHandCursor
|
||||||
clip: true
|
onClicked: iconPopup.close()
|
||||||
pressDelay: 0
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
DankFlickable {
|
||||||
id: dropdownContent
|
anchors.fill: parent
|
||||||
width: parent.width
|
anchors.margins: Theme.spacingS
|
||||||
spacing: Theme.spacingM
|
contentHeight: dropdownContent.height
|
||||||
|
clip: true
|
||||||
|
pressDelay: 0
|
||||||
|
|
||||||
// Icon categories
|
Column {
|
||||||
Repeater {
|
id: dropdownContent
|
||||||
model: root.iconCategories
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Column {
|
Repeater {
|
||||||
|
model: root.iconCategories
|
||||||
|
|
||||||
|
Column {
|
||||||
|
required property var modelData
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: 4
|
||||||
|
|
||||||
StyledText {
|
Repeater {
|
||||||
text: modelData.name
|
model: modelData.icons
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow {
|
Rectangle {
|
||||||
width: parent.width
|
required property string modelData
|
||||||
spacing: 4
|
width: 36
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: iconMouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
||||||
|
border.color: root.currentIcon === modelData ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
Repeater {
|
DankIcon {
|
||||||
model: modelData.icons
|
name: parent.modelData
|
||||||
|
size: 20
|
||||||
|
color: root.currentIcon === parent.modelData ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
width: 36
|
id: iconMouseArea
|
||||||
height: 36
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
hoverEnabled: true
|
||||||
color: iconMouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
cursorShape: Qt.PointingHandCursor
|
||||||
border.color: root.currentIcon === modelData ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
onClicked: {
|
||||||
border.width: 2
|
root.iconSelected(parent.modelData, "icon");
|
||||||
|
iconPopup.close();
|
||||||
DankIcon {
|
|
||||||
name: modelData
|
|
||||||
size: 20
|
|
||||||
color: root.currentIcon === modelData ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
Behavior on color {
|
||||||
id: iconMouseArea
|
ColorAnimation {
|
||||||
anchors.fill: parent
|
duration: Theme.shortDuration
|
||||||
hoverEnabled: true
|
easing.type: Theme.standardEasing
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
root.iconSelected(modelData, "icon")
|
|
||||||
dropdownLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,8 +249,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setIcon(iconName, type) {
|
function setIcon(iconName, type) {
|
||||||
root.iconType = type
|
root.iconType = type;
|
||||||
root.iconType = "icon"
|
root.iconType = "icon";
|
||||||
root.currentIcon = iconName
|
root.currentIcon = iconName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ Item {
|
|||||||
Loader {
|
Loader {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: contentWindow.visible
|
active: shouldBeVisible || contentWindow.visible
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -880,26 +880,18 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: dmsArgsRow
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
property var dmsArgConfig: {
|
readonly property var argConfig: Actions.getActionArgConfig(root.editAction)
|
||||||
const action = root.editAction;
|
readonly property var parsedArgs: argConfig?.type === "dms" ? Actions.parseDmsActionArgs(root.editAction) : null
|
||||||
if (!action)
|
readonly property var dmsActionArgs: Actions.getDmsActionArgs()
|
||||||
return null;
|
readonly property bool hasAmountArg: parsedArgs?.base ? (dmsActionArgs?.[parsedArgs.base]?.args?.some(a => a.name === "amount") ?? false) : false
|
||||||
if (action.indexOf("audio increment") !== -1 || action.indexOf("audio decrement") !== -1 || action.indexOf("brightness increment") !== -1 || action.indexOf("brightness decrement") !== -1) {
|
readonly property bool hasDeviceArg: parsedArgs?.base ? (dmsActionArgs?.[parsedArgs.base]?.args?.some(a => a.name === "device") ?? false) : false
|
||||||
const parts = action.split(" ");
|
readonly property bool hasTabArg: parsedArgs?.base ? (dmsActionArgs?.[parsedArgs.base]?.args?.some(a => a.name === "tab") ?? false) : false
|
||||||
const lastPart = parts[parts.length - 1];
|
|
||||||
const hasAmount = /^\d+$/.test(lastPart);
|
|
||||||
return {
|
|
||||||
hasAmount: hasAmount,
|
|
||||||
amount: hasAmount ? lastPart : ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
visible: root._actionType === "dms" && dmsArgConfig !== null
|
visible: root._actionType === "dms" && argConfig?.type === "dms"
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Amount")
|
text: I18n.tr("Amount")
|
||||||
@@ -907,26 +899,36 @@ Item {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
Layout.preferredWidth: 60
|
Layout.preferredWidth: 60
|
||||||
|
visible: dmsArgsRow.hasAmountArg
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
|
id: dmsAmountField
|
||||||
Layout.preferredWidth: 80
|
Layout.preferredWidth: 80
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
placeholderText: "5"
|
placeholderText: "5"
|
||||||
text: parent.dmsArgConfig?.amount || ""
|
visible: dmsArgsRow.hasAmountArg
|
||||||
onTextChanged: {
|
|
||||||
if (!parent.dmsArgConfig)
|
Connections {
|
||||||
|
target: dmsArgsRow
|
||||||
|
function onParsedArgsChanged() {
|
||||||
|
const newText = dmsArgsRow.parsedArgs?.args?.amount || "";
|
||||||
|
if (dmsAmountField.text !== newText)
|
||||||
|
dmsAmountField.text = newText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
text = dmsArgsRow.parsedArgs?.args?.amount || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditingFinished: {
|
||||||
|
if (!dmsArgsRow.parsedArgs)
|
||||||
return;
|
return;
|
||||||
const action = root.editAction;
|
const newArgs = Object.assign({}, dmsArgsRow.parsedArgs.args);
|
||||||
const parts = action.split(" ");
|
newArgs.amount = text || "5";
|
||||||
const lastPart = parts[parts.length - 1];
|
|
||||||
const hasOldAmount = /^\d+$/.test(lastPart);
|
|
||||||
if (hasOldAmount)
|
|
||||||
parts.pop();
|
|
||||||
if (text && /^\d+$/.test(text))
|
|
||||||
parts.push(text);
|
|
||||||
root.updateEdit({
|
root.updateEdit({
|
||||||
action: parts.join(" ")
|
action: Actions.buildDmsAction(dmsArgsRow.parsedArgs.base, newArgs)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -935,10 +937,105 @@ Item {
|
|||||||
text: "%"
|
text: "%"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
|
visible: dmsArgsRow.hasAmountArg
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Device")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
Layout.leftMargin: dmsArgsRow.hasAmountArg ? Theme.spacingM : 0
|
||||||
|
Layout.preferredWidth: dmsArgsRow.hasAmountArg ? -1 : 60
|
||||||
|
visible: dmsArgsRow.hasDeviceArg
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: dmsDeviceField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 40
|
||||||
|
placeholderText: I18n.tr("leave empty for default")
|
||||||
|
visible: dmsArgsRow.hasDeviceArg
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: dmsArgsRow
|
||||||
|
function onParsedArgsChanged() {
|
||||||
|
const newText = dmsArgsRow.parsedArgs?.args?.device || "";
|
||||||
|
if (dmsDeviceField.text !== newText)
|
||||||
|
dmsDeviceField.text = newText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
text = dmsArgsRow.parsedArgs?.args?.device || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditingFinished: {
|
||||||
|
if (!dmsArgsRow.parsedArgs)
|
||||||
|
return;
|
||||||
|
const newArgs = Object.assign({}, dmsArgsRow.parsedArgs.args);
|
||||||
|
newArgs.device = text;
|
||||||
|
root.updateEdit({
|
||||||
|
action: Actions.buildDmsAction(dmsArgsRow.parsedArgs.base, newArgs)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
visible: !dmsArgsRow.hasDeviceArg && !dmsArgsRow.hasTabArg
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Tab")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
Layout.preferredWidth: 60
|
||||||
|
visible: dmsArgsRow.hasTabArg
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
id: dmsTabDropdown
|
||||||
|
Layout.fillWidth: true
|
||||||
|
compactMode: true
|
||||||
|
visible: dmsArgsRow.hasTabArg
|
||||||
|
currentValue: {
|
||||||
|
const tab = dmsArgsRow.parsedArgs?.args?.tab || "";
|
||||||
|
switch (tab) {
|
||||||
|
case "media":
|
||||||
|
return I18n.tr("Media");
|
||||||
|
case "wallpaper":
|
||||||
|
return I18n.tr("Wallpaper");
|
||||||
|
case "weather":
|
||||||
|
return I18n.tr("Weather");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Overview");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options: [I18n.tr("Overview"), I18n.tr("Media"), I18n.tr("Wallpaper"), I18n.tr("Weather")]
|
||||||
|
onValueChanged: value => {
|
||||||
|
if (!dmsArgsRow.parsedArgs)
|
||||||
|
return;
|
||||||
|
const newArgs = Object.assign({}, dmsArgsRow.parsedArgs.args);
|
||||||
|
switch (value) {
|
||||||
|
case I18n.tr("Media"):
|
||||||
|
newArgs.tab = "media";
|
||||||
|
break;
|
||||||
|
case I18n.tr("Wallpaper"):
|
||||||
|
newArgs.tab = "wallpaper";
|
||||||
|
break;
|
||||||
|
case I18n.tr("Weather"):
|
||||||
|
newArgs.tab = "weather";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
newArgs.tab = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
root.updateEdit({
|
||||||
|
action: Actions.buildDmsAction(dmsArgsRow.parsedArgs.base, newArgs)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user