mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-28 14:05:21 -04:00
Compare commits
118 Commits
v1.4.3
..
5c92d49873
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c92d49873 | |||
| da47b573be | |||
| 2f04be8778 | |||
| 69178ddfd8 | |||
| a310f6fff0 | |||
| 7474abe286 | |||
| df2ba3a3c6 | |||
| e536456236 | |||
| 8d77122da3 | |||
| fb66effa51 | |||
| 5052e71c31 | |||
| bfc78d16ca | |||
| c425e3562b | |||
| 1f26092aa9 | |||
| 2849bb96f4 | |||
| 7b749f2a4c | |||
| 8803c94ce0 | |||
| f5235c943b | |||
| 59fec889b5 | |||
| f42f04a807 | |||
| 51f6f37925 | |||
| 9651a4ca98 | |||
| 2b7fd36322 | |||
| b8014fd4df | |||
| 07460f6e1f | |||
| f7bf3b2afb | |||
| 056f298cdf | |||
| e83da53162 | |||
| 9f38a47a02 | |||
| 236a4d4a6d | |||
| 0909471510 | |||
| 05eaf59c89 | |||
| 7749613801 | |||
| e3dbaedbb4 | |||
| 9f17ced6de | |||
| de54ef871d | |||
| b0da45d6d0 | |||
| 9b2a46fa92 | |||
| 12099d2db6 | |||
| 84fa75936a | |||
| d78d8121a1 | |||
| a9a3a52872 | |||
| 912e3bdfce | |||
| ee1b25d9e8 | |||
| 20ef5e2c18 | |||
| 6ee419bc52 | |||
| 85b00d3c76 | |||
| bc4ad31d48 | |||
| 71aad8ee32 | |||
| 8bb8231559 | |||
| 3cf9caae89 | |||
| f983c67135 | |||
| f2aef5b93f | |||
| 46d4288969 | |||
| 65516e872f | |||
| 171329246c | |||
| b2bee699e0 | |||
| 95c66b4d67 | |||
| babc8feb2b | |||
| 2f445c546a | |||
| a0283b3e3e | |||
| 61bd156fb0 | |||
| 8ad0cf8e5f | |||
| ecd6d70da6 | |||
| 359617d927 | |||
| 38c286329a | |||
| 401b4095cc | |||
| 06ab1a8ef0 | |||
| 726fb8b015 | |||
| b3b5c7a59f | |||
| d18f934978 | |||
| e67f1f79bc | |||
| e931829411 | |||
| db8ebd606c | |||
| 072a358a94 | |||
| 6ceb1b150c | |||
| a4e03e1877 | |||
| 02b3e4277b | |||
| 37daf801e6 | |||
| 68d9f7eeb2 | |||
| 526e2420ca | |||
| a9cc58fc28 | |||
| 77889ce1c6 | |||
| 549073119e | |||
| 5c5af5795f | |||
| 68e10934e4 | |||
| c67bb1444a | |||
| 07389a152e | |||
| e562e21555 | |||
| 86dfe7dd3f | |||
| ac0a8f3449 | |||
| 8e4a63db67 | |||
| c02c63806f | |||
| 42e5d7f6e9 | |||
| d8cf1af422 | |||
| 9723661c80 | |||
| 81cba7ad97 | |||
| c23f58de40 | |||
| 2cf67ca7da | |||
| 392bd850ea | |||
| 3b2ad9d1bd | |||
| 27b7474180 | |||
| 63948d728e | |||
| d219d3b873 | |||
| 93ab290bc1 | |||
| 7335c5d79a | |||
| 242ead722a | |||
| 8a6d9696a8 | |||
| 896b7ea242 | |||
| 0c7f4c7828 | |||
| 3d35af2a87 | |||
| fed3c36f84 | |||
| 414d81aa40 | |||
| d548803769 | |||
| 1180258394 | |||
| 48a566a24b | |||
| 3bc5d1df81 | |||
| c7222e2e86 |
@@ -16,6 +16,8 @@ require (
|
|||||||
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6
|
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
|
github.com/yeqown/go-qrcode/v2 v2.2.5
|
||||||
|
github.com/yeqown/go-qrcode/writer/standard v1.3.0
|
||||||
github.com/yuin/goldmark v1.7.16
|
github.com/yuin/goldmark v1.7.16
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
go.etcd.io/bbolt v1.4.3
|
go.etcd.io/bbolt v1.4.3
|
||||||
@@ -32,15 +34,19 @@ require (
|
|||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/fogleman/gg v1.3.0 // indirect
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260209124918-37866f83c2d3 // indirect
|
github.com/go-git/go-billy/v6 v6.0.0-20260209124918-37866f83c2d3 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
|
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.48.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.50.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+12
@@ -58,6 +58,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
|
|||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
|
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||||
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
@@ -75,6 +77,8 @@ github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/
|
|||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
@@ -115,6 +119,8 @@ github.com/pilebones/go-udev v0.9.1 h1:uN72M1C1fgzhsVmBGEM8w9RD1JY4iVsPZpr+Z6rb3
|
|||||||
github.com/pilebones/go-udev v0.9.1/go.mod h1:Bgcl07crebF3JSeS4+nuaRvhWFdCeFoBhXXeAp93XNo=
|
github.com/pilebones/go-udev v0.9.1/go.mod h1:Bgcl07crebF3JSeS4+nuaRvhWFdCeFoBhXXeAp93XNo=
|
||||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -142,6 +148,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
github.com/yeqown/go-qrcode/v2 v2.2.5 h1:HCOe2bSjkhZyYoyyNaXNzh4DJZll6inVJQQw+8228Zk=
|
||||||
|
github.com/yeqown/go-qrcode/v2 v2.2.5/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
|
||||||
|
github.com/yeqown/go-qrcode/writer/standard v1.3.0 h1:chdyhEfRtUPgQtuPeaWVGQ/TQx4rE1PqeoW3U+53t34=
|
||||||
|
github.com/yeqown/go-qrcode/writer/standard v1.3.0/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ=
|
||||||
|
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
|
||||||
|
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
|
||||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
|
|||||||
@@ -1062,6 +1062,62 @@ func (_c *MockBackend_GetWiFiNetworkDetails_Call) RunAndReturn(run func(string)
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWiFiQRCodeContent provides a mock function with given fields: ssid
|
||||||
|
func (_m *MockBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
|
||||||
|
ret := _m.Called(ssid)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for GetWiFiQRCodeContent")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
|
||||||
|
return rf(ssid)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||||
|
r0 = rf(ssid)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||||
|
r1 = rf(ssid)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockBackend_GetWiFiQRCodeContent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWiFiQRCodeContent'
|
||||||
|
type MockBackend_GetWiFiQRCodeContent_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWiFiQRCodeContent is a helper method to define mock.On call
|
||||||
|
// - ssid string
|
||||||
|
func (_e *MockBackend_Expecter) GetWiFiQRCodeContent(ssid interface{}) *MockBackend_GetWiFiQRCodeContent_Call {
|
||||||
|
return &MockBackend_GetWiFiQRCodeContent_Call{Call: _e.mock.On("GetWiFiQRCodeContent", ssid)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockBackend_GetWiFiQRCodeContent_Call) Run(run func(ssid string)) *MockBackend_GetWiFiQRCodeContent_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockBackend_GetWiFiQRCodeContent_Call) Return(_a0 string, _a1 error) *MockBackend_GetWiFiQRCodeContent_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockBackend_GetWiFiQRCodeContent_Call) RunAndReturn(run func(string) (string, error)) *MockBackend_GetWiFiQRCodeContent_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// GetWiredConnections provides a mock function with no fields
|
// GetWiredConnections provides a mock function with no fields
|
||||||
func (_m *MockBackend) GetWiredConnections() ([]network.WiredConnection, error) {
|
func (_m *MockBackend) GetWiredConnections() ([]network.WiredConnection, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Backend interface {
|
|||||||
ScanWiFi() error
|
ScanWiFi() error
|
||||||
ScanWiFiDevice(device string) error
|
ScanWiFiDevice(device string) error
|
||||||
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
|
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
|
||||||
|
GetWiFiQRCodeContent(ssid string) (string, error)
|
||||||
GetWiFiDevices() []WiFiDevice
|
GetWiFiDevices() []WiFiDevice
|
||||||
|
|
||||||
ConnectWiFi(req ConnectionRequest) error
|
ConnectWiFi(req ConnectionRequest) error
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ func (b *HybridIwdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkI
|
|||||||
return b.wifi.GetWiFiNetworkDetails(ssid)
|
return b.wifi.GetWiFiNetworkDetails(ssid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *HybridIwdNetworkdBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
|
||||||
|
return b.wifi.GetWiFiQRCodeContent(ssid)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *HybridIwdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
|
func (b *HybridIwdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||||
if err := b.wifi.ConnectWiFi(req); err != nil {
|
if err := b.wifi.ConnectWiFi(req); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func (b *IWDBackend) GetWiredConnections() ([]WiredConnection, error) {
|
func (b *IWDBackend) GetWiredConnections() ([]WiredConnection, error) {
|
||||||
return nil, fmt.Errorf("wired connections not supported by iwd")
|
return nil, fmt.Errorf("wired connections not supported by iwd")
|
||||||
@@ -112,3 +115,19 @@ func (b *IWDBackend) getWiFiDevicesLocked() []WiFiDevice {
|
|||||||
Networks: b.state.WiFiNetworks,
|
Networks: b.state.WiFiNetworks,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *IWDBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
|
||||||
|
path := iwdConfigPath(ssid)
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("no saved iwd config for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
passphrase, err := parseIWDPassphrase(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read passphrase for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatWiFiQRString("WPA", ssid, passphrase), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ func (b *SystemdNetworkdBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInf
|
|||||||
return nil, fmt.Errorf("WiFi details not supported by networkd backend")
|
return nil, fmt.Errorf("WiFi details not supported by networkd backend")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *SystemdNetworkdBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
|
||||||
|
return "", fmt.Errorf("WiFi QR Code not supported by networkd backend")
|
||||||
|
}
|
||||||
|
|
||||||
func (b *SystemdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
|
func (b *SystemdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||||
return fmt.Errorf("WiFi connect not supported by networkd backend")
|
return fmt.Errorf("WiFi connect not supported by networkd backend")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,65 @@ func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfo
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *NetworkManagerBackend) GetWiFiQRCodeContent(ssid string) (string, error) {
|
||||||
|
conn, err := b.findConnection(ssid)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("no saved connection for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connSettings, err := conn.GetSettings()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get settings for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secSettings, ok := connSettings["802-11-wireless-security"]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("network `%s` has no security settings", ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyMgmt, ok := secSettings["key-mgmt"].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("failed to identify security type of network `%s`", ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var securityType string
|
||||||
|
switch keyMgmt {
|
||||||
|
case "none":
|
||||||
|
authAlg, _ := secSettings["auth-alg"].(string)
|
||||||
|
switch authAlg {
|
||||||
|
case "open":
|
||||||
|
securityType = "nopass"
|
||||||
|
default:
|
||||||
|
securityType = "WEP"
|
||||||
|
}
|
||||||
|
case "ieee8021x":
|
||||||
|
securityType = "WEP"
|
||||||
|
default:
|
||||||
|
securityType = "WPA"
|
||||||
|
}
|
||||||
|
|
||||||
|
if securityType != "WPA" {
|
||||||
|
return "", fmt.Errorf("QR code generation only supports WPA connections, `%s` uses %s", ssid, securityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, err := conn.GetSecrets("802-11-wireless-security")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to retrieve connection secrets for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secSecrets, ok := secrets["802-11-wireless-security"]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
psk, ok := secSecrets["psk"].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatWiFiQRString(securityType, ssid, psk), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||||
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
"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"
|
||||||
@@ -40,6 +41,10 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
|||||||
handleSetPreference(conn, req, manager)
|
handleSetPreference(conn, req, manager)
|
||||||
case "network.info":
|
case "network.info":
|
||||||
handleGetNetworkInfo(conn, req, manager)
|
handleGetNetworkInfo(conn, req, manager)
|
||||||
|
case "network.qrcode":
|
||||||
|
handleGetNetworkQRCode(conn, req, manager)
|
||||||
|
case "network.delete-qrcode":
|
||||||
|
handleDeleteQRCode(conn, req, manager)
|
||||||
case "network.ethernet.info":
|
case "network.ethernet.info":
|
||||||
handleGetWiredNetworkInfo(conn, req, manager)
|
handleGetWiredNetworkInfo(conn, req, manager)
|
||||||
case "network.subscribe":
|
case "network.subscribe":
|
||||||
@@ -320,6 +325,42 @@ func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
|
|||||||
models.Respond(conn, req.ID, network)
|
models.Respond(conn, req.ID, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGetNetworkQRCode(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
ssid, err := params.String(req.Params, "ssid")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := manager.GetNetworkQRCode(ssid)
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) {
|
||||||
|
path, err := params.String(req.Params, "path")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValidQRCodePath(path) {
|
||||||
|
models.RespondError(conn, req.ID, "invalid QR code path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "QR code file deleted"})
|
||||||
|
}
|
||||||
|
|
||||||
func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
|
func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
uuid, err := params.String(req.Params, "uuid")
|
uuid, err := params.String(req.Params, "uuid")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/yeqown/go-qrcode/v2"
|
||||||
|
"github.com/yeqown/go-qrcode/writer/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewManager() (*Manager, error) {
|
func NewManager() (*Manager, error) {
|
||||||
@@ -438,6 +440,43 @@ func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, err
|
|||||||
return m.backend.GetWiFiNetworkDetails(ssid)
|
return m.backend.GetWiFiNetworkDetails(ssid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetNetworkQRCode(ssid string) ([2]string, error) {
|
||||||
|
content, err := m.backend.GetWiFiQRCodeContent(ssid)
|
||||||
|
if err != nil {
|
||||||
|
return [2]string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
qrc, err := qrcode.New(content)
|
||||||
|
if err != nil {
|
||||||
|
return [2]string{}, fmt.Errorf("failed to create QR code for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathThemed, pathNormal := qrCodePaths(ssid)
|
||||||
|
|
||||||
|
wThemed, err := standard.New(
|
||||||
|
pathThemed,
|
||||||
|
standard.WithBuiltinImageEncoder(standard.PNG_FORMAT),
|
||||||
|
standard.WithBgTransparent(),
|
||||||
|
standard.WithFgColorRGBHex("#ffffff"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
|
||||||
|
}
|
||||||
|
if err := qrc.Save(wThemed); err != nil {
|
||||||
|
return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wNormal, err := standard.New(pathNormal, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
|
||||||
|
if err != nil {
|
||||||
|
return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
|
||||||
|
}
|
||||||
|
if err := qrc.Save(wNormal); err != nil {
|
||||||
|
return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [2]string{pathThemed, pathNormal}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) ToggleWiFi() error {
|
func (m *Manager) ToggleWiFi() error {
|
||||||
enabled, err := m.backend.GetWiFiEnabled()
|
enabled, err := m.backend.GetWiFiEnabled()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const qrCodeTmpPrefix = "/tmp/dank-wifi-qrcode-"
|
||||||
|
|
||||||
|
func FormatWiFiQRString(securityType, ssid, password string) string {
|
||||||
|
return fmt.Sprintf("WIFI:T:%s;S:%s;P:%s;;", securityType, ssid, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func qrCodePaths(ssid string) (themed, normal string) {
|
||||||
|
safe := sanitizeSSIDForPath(ssid)
|
||||||
|
themed = fmt.Sprintf("%s%s-themed.png", qrCodeTmpPrefix, safe)
|
||||||
|
normal = fmt.Sprintf("%s%s-normal.png", qrCodeTmpPrefix, safe)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidQRCodePath(path string) bool {
|
||||||
|
clean := filepath.Clean(path)
|
||||||
|
return strings.HasPrefix(clean, qrCodeTmpPrefix) && strings.HasSuffix(clean, ".png")
|
||||||
|
}
|
||||||
|
|
||||||
|
var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
||||||
|
|
||||||
|
func sanitizeSSIDForPath(ssid string) string {
|
||||||
|
return safePathChar.ReplaceAllString(ssid, "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
var iwdVerbatimSSID = regexp.MustCompile(`^[a-zA-Z0-9 _-]+$`)
|
||||||
|
|
||||||
|
func iwdConfigPath(ssid string) string {
|
||||||
|
switch {
|
||||||
|
case iwdVerbatimSSID.MatchString(ssid):
|
||||||
|
return fmt.Sprintf("/var/lib/iwd/%s.psk", ssid)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("/var/lib/iwd/=%x.psk", []byte(ssid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIWDPassphrase(data string) (string, error) {
|
||||||
|
inSecurity := false
|
||||||
|
for _, line := range strings.Split(data, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
switch {
|
||||||
|
case line == "[Security]":
|
||||||
|
inSecurity = true
|
||||||
|
case strings.HasPrefix(line, "["):
|
||||||
|
inSecurity = false
|
||||||
|
case inSecurity && strings.HasPrefix(line, "Passphrase="):
|
||||||
|
return strings.TrimPrefix(line, "Passphrase="), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no passphrase found in iwd config")
|
||||||
|
}
|
||||||
@@ -27,12 +27,12 @@ override_dh_auto_build:
|
|||||||
# Verify core directory exists (native package format has source at root)
|
# Verify core directory exists (native package format has source at root)
|
||||||
test -d core || (echo "ERROR: core directory not found!" && exit 1)
|
test -d core || (echo "ERROR: core directory not found!" && exit 1)
|
||||||
|
|
||||||
# Patch go.mod to use Go 1.24 base version (Debian 13 has 1.23.x, may vary)
|
# Pin go.mod and vendor/modules.txt to the installed Go toolchain version
|
||||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod
|
GO_INSTALLED=$$(go version | grep -oP 'go\K[0-9]+\.[0-9]+'); \
|
||||||
|
sed -i "s/^go [0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$$/go $${GO_INSTALLED}/" core/go.mod; \
|
||||||
|
sed -i "s/^\(## explicit; go \)[0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$$/\1$${GO_INSTALLED}/" core/vendor/modules.txt
|
||||||
|
|
||||||
# Build dms-cli from source using vendored dependencies
|
# Build dms-cli (single shell to preserve variables; arch: Debian amd64/arm64 -> Makefile amd64/arm64)
|
||||||
# Extract version info and build in single shell to preserve variables
|
|
||||||
# Architecture mapping: Debian amd64/arm64 -> Makefile amd64/arm64
|
|
||||||
VERSION="$(UPSTREAM_VERSION)"; \
|
VERSION="$(UPSTREAM_VERSION)"; \
|
||||||
COMMIT=$$(echo "$(UPSTREAM_VERSION)" | grep -oP '(?<=git)[0-9]+\.[a-f0-9]+' | cut -d. -f2 | head -c8 || echo "unknown"); \
|
COMMIT=$$(echo "$(UPSTREAM_VERSION)" | grep -oP '(?<=git)[0-9]+\.[a-f0-9]+' | cut -d. -f2 | head -c8 || echo "unknown"); \
|
||||||
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
%global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
%global version {{{ git_repo_version }}}
|
%global version {{{ git_repo_version }}}
|
||||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||||
|
%global go_toolchain_version 1.25.7
|
||||||
|
|
||||||
Name: dms
|
Name: dms
|
||||||
Epoch: 2
|
Epoch: 2
|
||||||
@@ -14,12 +15,12 @@ License: MIT
|
|||||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
VCS: {{{ git_repo_vcs }}}
|
VCS: {{{ git_repo_vcs }}}
|
||||||
Source0: {{{ git_repo_pack }}}
|
Source0: {{{ git_repo_pack }}}
|
||||||
|
Source1: https://go.dev/dl/go%{go_toolchain_version}.linux-amd64.tar.gz
|
||||||
|
Source2: https://go.dev/dl/go%{go_toolchain_version}.linux-arm64.tar.gz
|
||||||
|
|
||||||
BuildRequires: git-core
|
BuildRequires: git-core
|
||||||
BuildRequires: gzip
|
BuildRequires: gzip
|
||||||
BuildRequires: golang >= 1.24
|
|
||||||
BuildRequires: make
|
BuildRequires: make
|
||||||
BuildRequires: wget
|
|
||||||
BuildRequires: systemd-rpm-macros
|
BuildRequires: systemd-rpm-macros
|
||||||
|
|
||||||
# Core requirements
|
# Core requirements
|
||||||
@@ -28,7 +29,7 @@ Requires: accountsservice
|
|||||||
Requires: dms-cli = %{epoch}:%{version}-%{release}
|
Requires: dms-cli = %{epoch}:%{version}-%{release}
|
||||||
Requires: dgop
|
Requires: dgop
|
||||||
|
|
||||||
# Core utilities (Highly recommended for DMS functionality)
|
# Core utilities (Recommended for DMS functionality)
|
||||||
Recommends: cava
|
Recommends: cava
|
||||||
Recommends: danksearch
|
Recommends: danksearch
|
||||||
Recommends: matugen
|
Recommends: matugen
|
||||||
@@ -66,6 +67,28 @@ Provides native DBus bindings, NetworkManager integration, and system utilities.
|
|||||||
VERSION="%{version}"
|
VERSION="%{version}"
|
||||||
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
|
COMMIT=$(echo "%{version}" | grep -oP '[a-f0-9]{7,}' | head -n1 || echo "unknown")
|
||||||
|
|
||||||
|
# Use pinned bundled Go toolchain (deterministic across chroots)
|
||||||
|
case "%{_arch}" in
|
||||||
|
x86_64)
|
||||||
|
GO_TARBALL="%{_sourcedir}/go%{go_toolchain_version}.linux-amd64.tar.gz"
|
||||||
|
;;
|
||||||
|
aarch64)
|
||||||
|
GO_TARBALL="%{_sourcedir}/go%{go_toolchain_version}.linux-arm64.tar.gz"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported architecture for bundled Go: %{_arch}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
rm -rf .go
|
||||||
|
tar -xzf "$GO_TARBALL"
|
||||||
|
mv go .go
|
||||||
|
export GOROOT="$PWD/.go"
|
||||||
|
export PATH="$GOROOT/bin:$PATH"
|
||||||
|
export GOTOOLCHAIN=local
|
||||||
|
go version
|
||||||
|
|
||||||
cd core
|
cd core
|
||||||
make dist VERSION="$VERSION" COMMIT="$COMMIT"
|
make dist VERSION="$VERSION" COMMIT="$COMMIT"
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ mkdir -p $HOME $GOCACHE $GOMODCACHE
|
|||||||
# OBS has no network access, so use local toolchain only
|
# OBS has no network access, so use local toolchain only
|
||||||
export GOTOOLCHAIN=local
|
export GOTOOLCHAIN=local
|
||||||
|
|
||||||
# Patch go.mod to use base Go version (e.g., go 1.24 instead of go 1.24.6)
|
# Pin go.mod and vendor/modules.txt to the installed Go toolchain version
|
||||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod
|
GO_INSTALLED=$(go version | grep -oP 'go\K[0-9]+\.[0-9]+')
|
||||||
|
sed -i "s/^go [0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$/go ${GO_INSTALLED}/" core/go.mod
|
||||||
|
sed -i "s/^\(## explicit; go \)[0-9]\+\.[0-9]\+\(\.[0-9]*\)\?$/\1${GO_INSTALLED}/" core/vendor/modules.txt
|
||||||
|
|
||||||
# Extract version info for embedding in binary
|
# Extract version info for embedding in binary
|
||||||
VERSION="%{version}"
|
VERSION="%{version}"
|
||||||
|
|||||||
@@ -182,6 +182,7 @@
|
|||||||
with pkgs;
|
with pkgs;
|
||||||
[
|
[
|
||||||
go_1_25
|
go_1_25
|
||||||
|
go-mockery_2
|
||||||
gopls
|
gopls
|
||||||
delve
|
delve
|
||||||
go-tools
|
go-tools
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
Saffron Bloom
|
The Wolverine
|
||||||
|
|||||||
+35
-25
@@ -152,35 +152,21 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property string _barLayoutStateJson: {
|
|
||||||
const configs = SettingsData.barConfigs;
|
|
||||||
const mapped = configs.map(c => ({
|
|
||||||
id: c.id,
|
|
||||||
position: c.position,
|
|
||||||
autoHide: c.autoHide,
|
|
||||||
visible: c.visible
|
|
||||||
})).sort((a, b) => {
|
|
||||||
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
|
|
||||||
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
|
|
||||||
if (aVertical !== bVertical) {
|
|
||||||
return aVertical - bVertical;
|
|
||||||
}
|
|
||||||
return String(a.id).localeCompare(String(b.id));
|
|
||||||
});
|
|
||||||
return JSON.stringify(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
on_BarLayoutStateJsonChanged: {
|
|
||||||
if (typeof dockRecreateDebounce !== "undefined") {
|
|
||||||
dockRecreateDebounce.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: dankBarRepeater
|
id: dankBarRepeater
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
id: barRepeaterModel
|
id: barRepeaterModel
|
||||||
values: JSON.parse(root._barLayoutStateJson)
|
values: {
|
||||||
|
const configs = SettingsData.barConfigs;
|
||||||
|
return configs.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
position: c.position
|
||||||
|
})).sort((a, b) => {
|
||||||
|
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
|
||||||
|
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
|
||||||
|
return aVertical - bVertical;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
|
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
|
||||||
@@ -227,6 +213,13 @@ Item {
|
|||||||
PolkitService.polkitAvailable;
|
PolkitService.polkitAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
dockRecreateDebounce.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: dockLoader
|
id: dockLoader
|
||||||
active: root.dockEnabled
|
active: root.dockEnabled
|
||||||
@@ -365,6 +358,23 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
id: wifiQRCodeModalLoader
|
||||||
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.wifiQRCodeModalLoader = wifiQRCodeModalLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
WifiQRCodeModal {
|
||||||
|
id: wifiQRCodeModalItem
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.wifiQRCodeModal = wifiQRCodeModalItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: polkitAuthModalLoader
|
id: polkitAuthModalLoader
|
||||||
active: false
|
active: false
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ Item {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
property string fileSearchType: "all"
|
||||||
|
property string fileSearchExt: ""
|
||||||
|
property string fileSearchFolder: ""
|
||||||
|
property string fileSearchSort: "score"
|
||||||
|
|
||||||
property string pluginFilter: ""
|
property string pluginFilter: ""
|
||||||
property string activePluginName: ""
|
property string activePluginName: ""
|
||||||
property var activePluginCategories: []
|
property var activePluginCategories: []
|
||||||
@@ -346,6 +351,10 @@ Item {
|
|||||||
previousSearchMode = "all";
|
previousSearchMode = "all";
|
||||||
autoSwitchedToFiles = false;
|
autoSwitchedToFiles = false;
|
||||||
isFileSearching = false;
|
isFileSearching = false;
|
||||||
|
fileSearchType = "all";
|
||||||
|
fileSearchExt = "";
|
||||||
|
fileSearchFolder = "";
|
||||||
|
fileSearchSort = "score";
|
||||||
sections = [];
|
sections = [];
|
||||||
flatModel = [];
|
flatModel = [];
|
||||||
selectedFlatIndex = 0;
|
selectedFlatIndex = 0;
|
||||||
@@ -399,6 +408,34 @@ Item {
|
|||||||
performSearch();
|
performSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setFileSearchType(type) {
|
||||||
|
if (fileSearchType === type)
|
||||||
|
return;
|
||||||
|
fileSearchType = type;
|
||||||
|
performFileSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFileSearchExt(ext) {
|
||||||
|
if (fileSearchExt === ext)
|
||||||
|
return;
|
||||||
|
fileSearchExt = ext;
|
||||||
|
performFileSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFileSearchFolder(folder) {
|
||||||
|
if (fileSearchFolder === folder)
|
||||||
|
return;
|
||||||
|
fileSearchFolder = folder;
|
||||||
|
performFileSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFileSearchSort(sort) {
|
||||||
|
if (fileSearchSort === sort)
|
||||||
|
return;
|
||||||
|
fileSearchSort = sort;
|
||||||
|
performFileSearch();
|
||||||
|
}
|
||||||
|
|
||||||
function clearPluginFilter() {
|
function clearPluginFilter() {
|
||||||
if (pluginFilter) {
|
if (pluginFilter) {
|
||||||
pluginFilter = "";
|
pluginFilter = "";
|
||||||
@@ -827,10 +864,20 @@ Item {
|
|||||||
var params = {
|
var params = {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
fuzzy: true,
|
fuzzy: true,
|
||||||
sort: "score",
|
sort: fileSearchSort || "score",
|
||||||
desc: true
|
desc: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (DSearchService.supportsTypeFilter) {
|
||||||
|
params.type = (fileSearchType && fileSearchType !== "all") ? fileSearchType : "all";
|
||||||
|
}
|
||||||
|
if (fileSearchExt) {
|
||||||
|
params.ext = fileSearchExt;
|
||||||
|
}
|
||||||
|
if (fileSearchFolder) {
|
||||||
|
params.folder = fileSearchFolder;
|
||||||
|
}
|
||||||
|
|
||||||
DSearchService.search(fileQuery, params, function (response) {
|
DSearchService.search(fileQuery, params, function (response) {
|
||||||
isFileSearching = false;
|
isFileSearching = false;
|
||||||
if (response.error)
|
if (response.error)
|
||||||
@@ -840,34 +887,73 @@ Item {
|
|||||||
|
|
||||||
for (var i = 0; i < hits.length; i++) {
|
for (var i = 0; i < hits.length; i++) {
|
||||||
var hit = hits[i];
|
var hit = hits[i];
|
||||||
|
var docTypes = hit.locations?.doc_type;
|
||||||
|
var isDir = docTypes ? !!docTypes["dir"] : false;
|
||||||
fileItems.push(transformFileResult({
|
fileItems.push(transformFileResult({
|
||||||
path: hit.id || "",
|
path: hit.id || "",
|
||||||
score: hit.score || 0
|
score: hit.score || 0,
|
||||||
|
is_dir: isDir
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileSection = {
|
var fileSections = [];
|
||||||
id: "files",
|
var showType = fileSearchType || "all";
|
||||||
title: I18n.tr("Files"),
|
|
||||||
icon: "folder",
|
if (showType === "all" && DSearchService.supportsTypeFilter) {
|
||||||
priority: 4,
|
var onlyFiles = [];
|
||||||
items: fileItems,
|
var onlyDirs = [];
|
||||||
collapsed: collapsedSections["files"] || false,
|
for (var j = 0; j < fileItems.length; j++) {
|
||||||
flatStartIndex: 0
|
if (fileItems[j].data?.is_dir)
|
||||||
};
|
onlyDirs.push(fileItems[j]);
|
||||||
|
else
|
||||||
|
onlyFiles.push(fileItems[j]);
|
||||||
|
}
|
||||||
|
if (onlyFiles.length > 0) {
|
||||||
|
fileSections.push({
|
||||||
|
id: "files",
|
||||||
|
title: I18n.tr("Files"),
|
||||||
|
icon: "insert_drive_file",
|
||||||
|
priority: 4,
|
||||||
|
items: onlyFiles,
|
||||||
|
collapsed: collapsedSections["files"] || false,
|
||||||
|
flatStartIndex: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (onlyDirs.length > 0) {
|
||||||
|
fileSections.push({
|
||||||
|
id: "folders",
|
||||||
|
title: I18n.tr("Folders"),
|
||||||
|
icon: "folder",
|
||||||
|
priority: 4.1,
|
||||||
|
items: onlyDirs,
|
||||||
|
collapsed: collapsedSections["folders"] || false,
|
||||||
|
flatStartIndex: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var filesIcon = showType === "dir" ? "folder" : showType === "file" ? "insert_drive_file" : "folder";
|
||||||
|
var filesTitle = showType === "dir" ? I18n.tr("Folders") : I18n.tr("Files");
|
||||||
|
if (fileItems.length > 0) {
|
||||||
|
fileSections.push({
|
||||||
|
id: "files",
|
||||||
|
title: filesTitle,
|
||||||
|
icon: filesIcon,
|
||||||
|
priority: 4,
|
||||||
|
items: fileItems,
|
||||||
|
collapsed: collapsedSections["files"] || false,
|
||||||
|
flatStartIndex: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var newSections;
|
var newSections;
|
||||||
if (searchMode === "files") {
|
if (searchMode === "files") {
|
||||||
newSections = fileItems.length > 0 ? [fileSection] : [];
|
newSections = fileSections;
|
||||||
} else {
|
} else {
|
||||||
var existingNonFile = sections.filter(function (s) {
|
var existingNonFile = sections.filter(function (s) {
|
||||||
return s.id !== "files";
|
return s.id !== "files" && s.id !== "folders";
|
||||||
});
|
});
|
||||||
if (fileItems.length > 0) {
|
newSections = existingNonFile.concat(fileSections);
|
||||||
newSections = existingNonFile.concat([fileSection]);
|
|
||||||
} else {
|
|
||||||
newSections = existingNonFile;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
newSections.sort(function (a, b) {
|
newSections.sort(function (a, b) {
|
||||||
return a.priority - b.priority;
|
return a.priority - b.priority;
|
||||||
@@ -913,7 +999,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function transformFileResult(file) {
|
function transformFileResult(file) {
|
||||||
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"));
|
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"), I18n.tr("Open in terminal"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectTrigger(query) {
|
function detectTrigger(query) {
|
||||||
@@ -1581,6 +1667,9 @@ Item {
|
|||||||
case "copy_path":
|
case "copy_path":
|
||||||
copyToClipboard(item.data.path);
|
copyToClipboard(item.data.path);
|
||||||
break;
|
break;
|
||||||
|
case "open_terminal":
|
||||||
|
openTerminal(item.data.path);
|
||||||
|
break;
|
||||||
case "copy":
|
case "copy":
|
||||||
copyToClipboard(item.name);
|
copyToClipboard(item.name);
|
||||||
break;
|
break;
|
||||||
@@ -1662,6 +1751,16 @@ Item {
|
|||||||
Qt.openUrlExternally("file://" + folder);
|
Qt.openUrlExternally("file://" + folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openTerminal(path) {
|
||||||
|
if (!path)
|
||||||
|
return;
|
||||||
|
var terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||||
|
Quickshell.execDetached({
|
||||||
|
command: [terminal],
|
||||||
|
workingDirectory: path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function copyToClipboard(text) {
|
function copyToClipboard(text) {
|
||||||
if (!text)
|
if (!text)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ Item {
|
|||||||
spotlightContent.controller.activePluginId = "";
|
spotlightContent.controller.activePluginId = "";
|
||||||
spotlightContent.controller.activePluginName = "";
|
spotlightContent.controller.activePluginName = "";
|
||||||
spotlightContent.controller.pluginFilter = "";
|
spotlightContent.controller.pluginFilter = "";
|
||||||
|
spotlightContent.controller.fileSearchType = "all";
|
||||||
|
spotlightContent.controller.fileSearchExt = "";
|
||||||
|
spotlightContent.controller.fileSearchFolder = "";
|
||||||
|
spotlightContent.controller.fileSearchSort = "score";
|
||||||
spotlightContent.controller.collapsedSections = {};
|
spotlightContent.controller.collapsedSections = {};
|
||||||
spotlightContent.controller.selectedFlatIndex = 0;
|
spotlightContent.controller.selectedFlatIndex = 0;
|
||||||
spotlightContent.controller.selectedItem = null;
|
spotlightContent.controller.selectedItem = null;
|
||||||
|
|||||||
@@ -116,31 +116,43 @@ function transformBuiltInLauncherItem(item, pluginId, openLabel) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel) {
|
function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel, openTerminalLabel) {
|
||||||
var filename = file.path ? file.path.split("/").pop() : "";
|
var filename = file.path ? file.path.split("/").pop() : "";
|
||||||
var dirname = file.path ? file.path.substring(0, file.path.lastIndexOf("/")) : "";
|
var dirname = file.path ? file.path.substring(0, file.path.lastIndexOf("/")) : "";
|
||||||
|
var isDir = file.is_dir || false;
|
||||||
|
|
||||||
|
var actions = [];
|
||||||
|
if (isDir) {
|
||||||
|
if (openTerminalLabel) {
|
||||||
|
actions.push({
|
||||||
|
name: openTerminalLabel,
|
||||||
|
icon: "terminal",
|
||||||
|
action: "open_terminal"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actions.push({
|
||||||
|
name: openFolderLabel,
|
||||||
|
icon: "folder_open",
|
||||||
|
action: "open_folder"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
actions.push({
|
||||||
|
name: copyPathLabel,
|
||||||
|
icon: "content_copy",
|
||||||
|
action: "copy_path"
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: file.path || "",
|
id: file.path || "",
|
||||||
type: "file",
|
type: "file",
|
||||||
name: filename,
|
name: filename,
|
||||||
subtitle: dirname,
|
subtitle: dirname,
|
||||||
icon: Utils.getFileIcon(filename),
|
icon: isDir ? "folder" : Utils.getFileIcon(filename),
|
||||||
iconType: "material",
|
iconType: "material",
|
||||||
section: "files",
|
section: "files",
|
||||||
data: file,
|
data: file,
|
||||||
actions: [
|
actions: actions,
|
||||||
{
|
|
||||||
name: openFolderLabel,
|
|
||||||
icon: "folder_open",
|
|
||||||
action: "open_folder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: copyPathLabel,
|
|
||||||
icon: "content_copy",
|
|
||||||
action: "copy_path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
primaryAction: {
|
primaryAction: {
|
||||||
name: openLabel,
|
name: openLabel,
|
||||||
icon: "open_in_new",
|
icon: "open_in_new",
|
||||||
|
|||||||
@@ -549,8 +549,151 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: fileFilterRow
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - searchField.height - categoryRow.height - actionPanel.height - Theme.spacingXS * (categoryRow.visible ? 3 : 2)
|
height: showFileFilters ? fileFilterContent.height : 0
|
||||||
|
visible: showFileFilters
|
||||||
|
|
||||||
|
readonly property bool showFileFilters: controller.searchMode === "files"
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: fileFilterContent
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: typeChips
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
visible: DSearchService.supportsTypeFilter
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [
|
||||||
|
{
|
||||||
|
id: "all",
|
||||||
|
label: I18n.tr("All"),
|
||||||
|
icon: "search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "file",
|
||||||
|
label: I18n.tr("Files"),
|
||||||
|
icon: "insert_drive_file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dir",
|
||||||
|
label: I18n.tr("Folders"),
|
||||||
|
icon: "folder"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: chipContent.width + Theme.spacingM * 2
|
||||||
|
height: sortDropdown.height
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: controller.fileSearchType === modelData.id || chipArea.containsMouse ? Theme.primaryContainer : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: chipContent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: modelData.icon
|
||||||
|
size: 14
|
||||||
|
color: controller.fileSearchType === modelData.id ? Theme.primary : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelData.label
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: controller.fileSearchType === modelData.id ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: chipArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: controller.setFileSearchType(modelData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: 20
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: Theme.outlineMedium
|
||||||
|
visible: typeChips.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
id: sortDropdown
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Math.min(130, parent.width / 3)
|
||||||
|
compactMode: true
|
||||||
|
dropdownWidth: 130
|
||||||
|
popupWidth: 150
|
||||||
|
maxPopupHeight: 200
|
||||||
|
currentValue: {
|
||||||
|
switch (controller.fileSearchSort) {
|
||||||
|
case "score":
|
||||||
|
return I18n.tr("Score");
|
||||||
|
case "name":
|
||||||
|
return I18n.tr("Name");
|
||||||
|
case "modified":
|
||||||
|
return I18n.tr("Modified");
|
||||||
|
case "size":
|
||||||
|
return I18n.tr("Size");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Score");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options: [I18n.tr("Score"), I18n.tr("Name"), I18n.tr("Modified"), I18n.tr("Size")]
|
||||||
|
|
||||||
|
onValueChanged: value => {
|
||||||
|
var sortMap = {};
|
||||||
|
sortMap[I18n.tr("Score")] = "score";
|
||||||
|
sortMap[I18n.tr("Name")] = "name";
|
||||||
|
sortMap[I18n.tr("Modified")] = "modified";
|
||||||
|
sortMap[I18n.tr("Size")] = "size";
|
||||||
|
controller.setFileSearchSort(sortMap[value] || "score");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: extFilterField
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Math.min(100, parent.width / 4)
|
||||||
|
height: sortDropdown.height
|
||||||
|
placeholderText: I18n.tr("ext")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
showClearButton: text.length > 0
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
controller.setFileSearchExt(text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
|
||||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
opacity: root.parentModal?.isClosing ? 0 : 1
|
||||||
|
|
||||||
ResultsList {
|
ResultsList {
|
||||||
@@ -586,6 +729,9 @@ FocusScope {
|
|||||||
function onSearchQueryRequested(query) {
|
function onSearchQueryRequested(query) {
|
||||||
searchField.text = query;
|
searchField.text = query;
|
||||||
}
|
}
|
||||||
|
function onModeChanged() {
|
||||||
|
extFilterField.text = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ Rectangle {
|
|||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
clip: true
|
||||||
visible: (root.item?.subtitle ?? "").length > 0
|
visible: (root.item?.subtitle ?? "").length > 0
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
@@ -181,7 +182,7 @@ Rectangle {
|
|||||||
case "plugin":
|
case "plugin":
|
||||||
return I18n.tr("Plugin");
|
return I18n.tr("Plugin");
|
||||||
case "file":
|
case "file":
|
||||||
return I18n.tr("File");
|
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -435,7 +435,15 @@ Item {
|
|||||||
var mode = root.controller?.searchMode ?? "all";
|
var mode = root.controller?.searchMode ?? "all";
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "files":
|
case "files":
|
||||||
return "folder_open";
|
var fileType = root.controller?.fileSearchType ?? "all";
|
||||||
|
switch (fileType) {
|
||||||
|
case "dir":
|
||||||
|
return "folder_open";
|
||||||
|
case "file":
|
||||||
|
return "insert_drive_file";
|
||||||
|
default:
|
||||||
|
return "folder_open";
|
||||||
|
}
|
||||||
case "plugins":
|
case "plugins":
|
||||||
return "extension";
|
return "extension";
|
||||||
case "apps":
|
case "apps":
|
||||||
@@ -465,7 +473,15 @@ Item {
|
|||||||
return I18n.tr("Type to search files");
|
return I18n.tr("Type to search files");
|
||||||
if (root.controller.searchQuery.length < 2)
|
if (root.controller.searchQuery.length < 2)
|
||||||
return I18n.tr("Type at least 2 characters");
|
return I18n.tr("Type at least 2 characters");
|
||||||
return I18n.tr("No files found");
|
var fileType = root.controller?.fileSearchType ?? "all";
|
||||||
|
switch (fileType) {
|
||||||
|
case "dir":
|
||||||
|
return I18n.tr("No folders found");
|
||||||
|
case "file":
|
||||||
|
return I18n.tr("No files found");
|
||||||
|
default:
|
||||||
|
return I18n.tr("No results found");
|
||||||
|
}
|
||||||
case "plugins":
|
case "plugins":
|
||||||
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
|
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
|
||||||
case "apps":
|
case "apps":
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Effects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Modals.Common
|
||||||
|
import qs.Modals.FileBrowser
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankModal {
|
||||||
|
id: root
|
||||||
|
visible: false
|
||||||
|
layerNamespace: "dms:wifi-qrcode"
|
||||||
|
|
||||||
|
property bool disablePopupTransparency: true
|
||||||
|
property string wifiSSID: ""
|
||||||
|
property string themedQrCodePath: ""
|
||||||
|
property string normalQrCodePath: ""
|
||||||
|
modalWidth: 420
|
||||||
|
modalHeight: 480
|
||||||
|
onBackgroundClicked: hide()
|
||||||
|
onOpened: {
|
||||||
|
Qt.callLater(() => {
|
||||||
|
modalFocusScope.forceActiveFocus();
|
||||||
|
contentLoader.item.wifiSSID = wifiSSID;
|
||||||
|
contentLoader.item.themedQrCodePath = themedQrCodePath;
|
||||||
|
contentLoader.item.saveBrowserLoader = saveBrowserLoader;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(ssid) {
|
||||||
|
wifiSSID = ssid;
|
||||||
|
fetchNetworkQRCode(ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
if (themedQrCodePath !== "") {
|
||||||
|
deleteQRCodeFile(themedQrCodePath);
|
||||||
|
}
|
||||||
|
if (normalQrCodePath !== "") {
|
||||||
|
deleteQRCodeFile(normalQrCodePath);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchNetworkQRCode(ssid) {
|
||||||
|
// TODO: Add loading UI?
|
||||||
|
|
||||||
|
DMSService.sendRequest("network.qrcode", {
|
||||||
|
ssid: ssid
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
ToastService.showError("Failed to fetch network QR code: ", JSON.stringify(response.error));
|
||||||
|
} else if (response.result) {
|
||||||
|
themedQrCodePath = response.result[0];
|
||||||
|
normalQrCodePath = response.result[1];
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteQRCodeFile(path) {
|
||||||
|
DMSService.sendRequest("network.delete-qrcode", {
|
||||||
|
path: path
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
ToastService.showError(`Failed to remove QR code at ${path}: `, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
id: saveBrowserLoader
|
||||||
|
active: false
|
||||||
|
|
||||||
|
FileBrowserSurfaceModal {
|
||||||
|
id: saveBrowser
|
||||||
|
|
||||||
|
browserTitle: I18n.tr("Save QR Code")
|
||||||
|
browserIcon: "qr_code"
|
||||||
|
browserType: "default"
|
||||||
|
fileExtensions: ["*.png"]
|
||||||
|
allowStacking: true
|
||||||
|
saveMode: true
|
||||||
|
defaultFileName: `${root.wifiSSID ?? "wifi-qrcode"}.png`
|
||||||
|
onFileSelected: path => {
|
||||||
|
const cleanPath = decodeURI(path.toString().replace(/^file:\/\//, ''));
|
||||||
|
const fileName = cleanPath.split('/').pop();
|
||||||
|
const fileUrl = "file://" + cleanPath;
|
||||||
|
|
||||||
|
copyQrCodeProcess.exec(["cp", root.normalQrCodePath, cleanPath, "-f"])
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: copyQrCodeProcess
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
saveBrowser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
Item {
|
||||||
|
id: theItem
|
||||||
|
property alias themedQrCodePath: qrCodeImg.source
|
||||||
|
property var saveBrowserLoader: null
|
||||||
|
property string wifiSSID: ""
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: modalTitle
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("WiFi QR code for ") + theItem.wifiSSID
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Bold
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "save"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
onClicked: {
|
||||||
|
saveBrowserLoader.active = true;
|
||||||
|
if (saveBrowserLoader.item) {
|
||||||
|
saveBrowserLoader.item.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
onClicked: root.hide()
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: qrCodeImg
|
||||||
|
height: parent.height - parent.spacing - modalTitle.height
|
||||||
|
width: height
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
source: qrCodeImg
|
||||||
|
anchors.fill: source
|
||||||
|
colorization: 1.0
|
||||||
|
colorizationColor: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -651,6 +651,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: pinButton
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS
|
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -711,6 +712,19 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: qrCodeButton
|
||||||
|
visible: modelData.secured && modelData.saved
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: optionsButton.width + pinWifiRow.width + 3 * Theme.spacingM + Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
iconName: "qr_code"
|
||||||
|
buttonSize: 28
|
||||||
|
onClicked: {
|
||||||
|
PopoutService.showWifiQRCodeModal(modelData.ssid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankRipple {
|
DankRipple {
|
||||||
id: wifiRipple
|
id: wifiRipple
|
||||||
cornerRadius: parent.radius
|
cornerRadius: parent.radius
|
||||||
@@ -719,7 +733,7 @@ Rectangle {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
id: networkMouseArea
|
id: networkMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS + pinWifiRow.width + Theme.spacingS * 4
|
anchors.rightMargin: optionsButton.width + pinWifiRow.width + (qrCodeButton.visible ? qrCodeButton.width : 0) + Theme.spacingS * 5 + Theme.spacingM
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y)
|
||||||
|
|||||||
@@ -21,82 +21,73 @@ Item {
|
|||||||
property alias centerWidgetsModel: centerWidgetsModel
|
property alias centerWidgetsModel: centerWidgetsModel
|
||||||
property alias rightWidgetsModel: rightWidgetsModel
|
property alias rightWidgetsModel: rightWidgetsModel
|
||||||
|
|
||||||
property string _leftWidgetsJson: {
|
|
||||||
root.barConfig;
|
|
||||||
const leftWidgets = root.barConfig?.leftWidgets || [];
|
|
||||||
const mapped = leftWidgets.map((w, index) => {
|
|
||||||
if (typeof w === "string") {
|
|
||||||
return {
|
|
||||||
widgetId: w,
|
|
||||||
id: w + "_" + index,
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const obj = Object.assign({}, w);
|
|
||||||
obj.widgetId = w.id || w.widgetId;
|
|
||||||
obj.id = (w.id || w.widgetId) + "_" + index;
|
|
||||||
obj.enabled = w.enabled !== false;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return JSON.stringify(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
property string _centerWidgetsJson: {
|
|
||||||
root.barConfig;
|
|
||||||
const centerWidgets = root.barConfig?.centerWidgets || [];
|
|
||||||
const mapped = centerWidgets.map((w, index) => {
|
|
||||||
if (typeof w === "string") {
|
|
||||||
return {
|
|
||||||
widgetId: w,
|
|
||||||
id: w + "_" + index,
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const obj = Object.assign({}, w);
|
|
||||||
obj.widgetId = w.id || w.widgetId;
|
|
||||||
obj.id = (w.id || w.widgetId) + "_" + index;
|
|
||||||
obj.enabled = w.enabled !== false;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return JSON.stringify(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
property string _rightWidgetsJson: {
|
|
||||||
root.barConfig;
|
|
||||||
const rightWidgets = root.barConfig?.rightWidgets || [];
|
|
||||||
const mapped = rightWidgets.map((w, index) => {
|
|
||||||
if (typeof w === "string") {
|
|
||||||
return {
|
|
||||||
widgetId: w,
|
|
||||||
id: w + "_" + index,
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const obj = Object.assign({}, w);
|
|
||||||
obj.widgetId = w.id || w.widgetId;
|
|
||||||
obj.id = (w.id || w.widgetId) + "_" + index;
|
|
||||||
obj.enabled = w.enabled !== false;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return JSON.stringify(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptModel {
|
ScriptModel {
|
||||||
id: leftWidgetsModel
|
id: leftWidgetsModel
|
||||||
values: JSON.parse(root._leftWidgetsJson)
|
values: {
|
||||||
|
root.barConfig;
|
||||||
|
const leftWidgets = root.barConfig?.leftWidgets || [];
|
||||||
|
return leftWidgets.map((w, index) => {
|
||||||
|
if (typeof w === "string") {
|
||||||
|
return {
|
||||||
|
widgetId: w,
|
||||||
|
id: w + "_" + index,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const obj = Object.assign({}, w);
|
||||||
|
obj.widgetId = w.id || w.widgetId;
|
||||||
|
obj.id = (w.id || w.widgetId) + "_" + index;
|
||||||
|
obj.enabled = w.enabled !== false;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptModel {
|
ScriptModel {
|
||||||
id: centerWidgetsModel
|
id: centerWidgetsModel
|
||||||
values: JSON.parse(root._centerWidgetsJson)
|
values: {
|
||||||
|
root.barConfig;
|
||||||
|
const centerWidgets = root.barConfig?.centerWidgets || [];
|
||||||
|
return centerWidgets.map((w, index) => {
|
||||||
|
if (typeof w === "string") {
|
||||||
|
return {
|
||||||
|
widgetId: w,
|
||||||
|
id: w + "_" + index,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const obj = Object.assign({}, w);
|
||||||
|
obj.widgetId = w.id || w.widgetId;
|
||||||
|
obj.id = (w.id || w.widgetId) + "_" + index;
|
||||||
|
obj.enabled = w.enabled !== false;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptModel {
|
ScriptModel {
|
||||||
id: rightWidgetsModel
|
id: rightWidgetsModel
|
||||||
values: JSON.parse(root._rightWidgetsJson)
|
values: {
|
||||||
|
root.barConfig;
|
||||||
|
const rightWidgets = root.barConfig?.rightWidgets || [];
|
||||||
|
return rightWidgets.map((w, index) => {
|
||||||
|
if (typeof w === "string") {
|
||||||
|
return {
|
||||||
|
widgetId: w,
|
||||||
|
id: w + "_" + index,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const obj = Object.assign({}, w);
|
||||||
|
obj.widgetId = w.id || w.widgetId;
|
||||||
|
obj.id = (w.id || w.widgetId) + "_" + index;
|
||||||
|
obj.enabled = w.enabled !== false;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerControlCenterOnFocusedScreen() {
|
function triggerControlCenterOnFocusedScreen() {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ BasePill {
|
|||||||
id: textBox
|
id: textBox
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
implicitWidth: root.minimumWidth ? Math.max(cpuBaseline.width, cpuCurrent.width) : cpuCurrent.width
|
implicitWidth: root.minimumWidth ? Math.max(cpuBaseline.width, cpuText.paintedWidth) : cpuText.paintedWidth
|
||||||
implicitHeight: cpuText.implicitHeight
|
implicitHeight: cpuText.implicitHeight
|
||||||
|
|
||||||
width: implicitWidth
|
width: implicitWidth
|
||||||
@@ -105,12 +105,6 @@ BasePill {
|
|||||||
text: "88%"
|
text: "88%"
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledTextMetrics {
|
|
||||||
id: cpuCurrent
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
||||||
text: cpuText.text
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: cpuText
|
id: cpuText
|
||||||
text: {
|
text: {
|
||||||
|
|||||||
@@ -51,34 +51,9 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isBarActive(c) {
|
|
||||||
if (!c.enabled) return false;
|
|
||||||
const prefs = c.screenPreferences || ["all"];
|
|
||||||
if (prefs.length > 0) return true;
|
|
||||||
return (c.showOnLastDisplay ?? true) && Quickshell.screens.length === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifyHorizontalBarChange() {
|
function notifyHorizontalBarChange() {
|
||||||
const configs = SettingsData.barConfigs;
|
if (selectedBarIsVertical)
|
||||||
if (configs.length < 2)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const hasHorizontal = configs.some(c => {
|
|
||||||
if (!_isBarActive(c)) return false;
|
|
||||||
const p = c.position ?? SettingsData.Position.Top;
|
|
||||||
return p === SettingsData.Position.Top || p === SettingsData.Position.Bottom;
|
|
||||||
});
|
|
||||||
if (!hasHorizontal)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const hasVertical = configs.some(c => {
|
|
||||||
if (!_isBarActive(c)) return false;
|
|
||||||
const p = c.position ?? SettingsData.Position.Top;
|
|
||||||
return p === SettingsData.Position.Left || p === SettingsData.Position.Right;
|
|
||||||
});
|
|
||||||
if (!hasVertical)
|
|
||||||
return;
|
|
||||||
|
|
||||||
horizontalBarChangeDebounce.restart();
|
horizontalBarChangeDebounce.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +147,6 @@ Item {
|
|||||||
SettingsData.updateBarConfig(barId, {
|
SettingsData.updateBarConfig(barId, {
|
||||||
screenPreferences: prefs
|
screenPreferences: prefs
|
||||||
});
|
});
|
||||||
notifyHorizontalBarChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBarShowOnLastDisplay(barId) {
|
function getBarShowOnLastDisplay(barId) {
|
||||||
@@ -184,8 +158,6 @@ Item {
|
|||||||
SettingsData.updateBarConfig(barId, {
|
SettingsData.updateBarConfig(barId, {
|
||||||
showOnLastDisplay: value
|
showOnLastDisplay: value
|
||||||
});
|
});
|
||||||
if (Quickshell.screens.length === 1)
|
|
||||||
notifyHorizontalBarChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
@@ -567,10 +539,13 @@ Item {
|
|||||||
newPos = SettingsData.Position.Right;
|
newPos = SettingsData.Position.Right;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
const wasVertical = selectedBarIsVertical;
|
||||||
SettingsData.updateBarConfig(selectedBarId, {
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
position: newPos
|
position: newPos
|
||||||
});
|
});
|
||||||
notifyHorizontalBarChange();
|
const isVertical = newPos === SettingsData.Position.Left || newPos === SettingsData.Position.Right;
|
||||||
|
if (wasVertical !== isVertical || !isVertical)
|
||||||
|
notifyHorizontalBarChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -948,9 +923,9 @@ Item {
|
|||||||
value: Math.round((selectedBarConfig?.fontScale ?? 1.0) * 100)
|
value: Math.round((selectedBarConfig?.fontScale ?? 1.0) * 100)
|
||||||
unit: "%"
|
unit: "%"
|
||||||
defaultValue: 100
|
defaultValue: 100
|
||||||
onSliderValueChanged: newValue => {
|
onSliderDragFinished: finalValue => {
|
||||||
SettingsData.updateBarConfig(selectedBarId, {
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
fontScale: newValue / 100
|
fontScale: finalValue / 100
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,9 +948,9 @@ Item {
|
|||||||
value: Math.round((selectedBarConfig?.iconScale ?? 1.0) * 100)
|
value: Math.round((selectedBarConfig?.iconScale ?? 1.0) * 100)
|
||||||
unit: "%"
|
unit: "%"
|
||||||
defaultValue: 100
|
defaultValue: 100
|
||||||
onSliderValueChanged: newValue => {
|
onSliderDragFinished: finalValue => {
|
||||||
SettingsData.updateBarConfig(selectedBarId, {
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
iconScale: newValue / 100
|
iconScale: finalValue / 100
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1281,6 +1281,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "qr_code"
|
||||||
|
buttonSize: 28
|
||||||
|
visible: modelData.secured && modelData.saved
|
||||||
|
onClicked: {
|
||||||
|
PopoutService.showWifiQRCodeModal(modelData.ssid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
iconName: isPinned ? "push_pin" : "push_pin"
|
iconName: isPinned ? "push_pin" : "push_pin"
|
||||||
buttonSize: 28
|
buttonSize: 28
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ Singleton {
|
|||||||
function registerWidget(widgetId, screenName, widgetRef) {
|
function registerWidget(widgetId, screenName, widgetRef) {
|
||||||
if (!widgetId || !screenName || !widgetRef)
|
if (!widgetId || !screenName || !widgetRef)
|
||||||
return;
|
return;
|
||||||
if (typeof widgetRegistry !== "object" || widgetRegistry === null)
|
|
||||||
widgetRegistry = ({});
|
|
||||||
|
|
||||||
if (!widgetRegistry[widgetId])
|
if (!widgetRegistry[widgetId])
|
||||||
widgetRegistry[widgetId] = {};
|
widgetRegistry[widgetId] = {};
|
||||||
@@ -31,8 +29,6 @@ Singleton {
|
|||||||
function unregisterWidget(widgetId, screenName) {
|
function unregisterWidget(widgetId, screenName) {
|
||||||
if (!widgetId || !screenName)
|
if (!widgetId || !screenName)
|
||||||
return;
|
return;
|
||||||
if (typeof widgetRegistry !== "object" || widgetRegistry === null)
|
|
||||||
return;
|
|
||||||
if (!widgetRegistry[widgetId])
|
if (!widgetRegistry[widgetId])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property string currentVersion: "1.4"
|
readonly property string currentVersion: "1.4"
|
||||||
readonly property bool changelogEnabled: true
|
readonly property bool changelogEnabled: false
|
||||||
|
|
||||||
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
|
||||||
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion
|
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtCore
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
@@ -13,6 +11,9 @@ Singleton {
|
|||||||
|
|
||||||
property bool dsearchAvailable: false
|
property bool dsearchAvailable: false
|
||||||
property int searchIdCounter: 0
|
property int searchIdCounter: 0
|
||||||
|
property int indexVersion: 0
|
||||||
|
property bool supportsTypeFilter: false
|
||||||
|
property bool versionChecked: false
|
||||||
|
|
||||||
signal searchResultsReceived(var results)
|
signal searchResultsReceived(var results)
|
||||||
signal statsReceived(var stats)
|
signal statsReceived(var stats)
|
||||||
@@ -26,118 +27,157 @@ Singleton {
|
|||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: line => {
|
onRead: line => {
|
||||||
if (line && line.trim().length > 0) {
|
if (line && line.trim().length > 0) {
|
||||||
root.dsearchAvailable = true
|
root.dsearchAvailable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.dsearchAvailable = false
|
root.dsearchAvailable = false;
|
||||||
|
} else {
|
||||||
|
root._checkVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _checkVersion() {
|
||||||
|
Proc.runCommand("dsearch-version", ["dsearch", "version", "--json"], (stdout, exitCode) => {
|
||||||
|
root.versionChecked = true;
|
||||||
|
if (exitCode !== 0)
|
||||||
|
return;
|
||||||
|
const response = JSON.parse(stdout);
|
||||||
|
root.indexVersion = response.index_schema || 0;
|
||||||
|
root.supportsTypeFilter = root.indexVersion >= 2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function ping(callback) {
|
function ping(callback) {
|
||||||
if (!dsearchAvailable) {
|
if (!dsearchAvailable) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "error": "dsearch not available" })
|
callback({
|
||||||
|
"error": "dsearch not available"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Proc.runCommand("dsearch-ping", ["dsearch", "ping", "--json"], (stdout, exitCode) => {
|
Proc.runCommand("dsearch-ping", ["dsearch", "ping", "--json"], (stdout, exitCode) => {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
try {
|
try {
|
||||||
const response = JSON.parse(stdout)
|
const response = JSON.parse(stdout);
|
||||||
callback({ "result": response })
|
callback({
|
||||||
|
"result": response
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback({ "error": "failed to parse ping response" })
|
callback({
|
||||||
|
"error": "failed to parse ping response"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
callback({ "error": "ping failed" })
|
callback({
|
||||||
|
"error": "ping failed"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function search(query, params, callback) {
|
function search(query, params, callback) {
|
||||||
if (!query || query.length === 0) {
|
if (!query || query.length === 0) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "error": "query is required" })
|
callback({
|
||||||
|
"error": "query is required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dsearchAvailable) {
|
if (!dsearchAvailable) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "error": "dsearch not available" })
|
callback({
|
||||||
|
"error": "dsearch not available"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = ["dsearch", "search", query, "--json"]
|
const args = ["dsearch", "search", query, "--json"];
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
if (params.limit !== undefined) {
|
if (params.limit !== undefined) {
|
||||||
args.push("-n", String(params.limit))
|
args.push("-n", String(params.limit));
|
||||||
|
}
|
||||||
|
if (params.type) {
|
||||||
|
args.push("-t", params.type);
|
||||||
}
|
}
|
||||||
if (params.ext) {
|
if (params.ext) {
|
||||||
args.push("-e", params.ext)
|
args.push("-e", params.ext);
|
||||||
|
}
|
||||||
|
if (params.folder) {
|
||||||
|
args.push("--folder", params.folder);
|
||||||
}
|
}
|
||||||
if (params.field) {
|
if (params.field) {
|
||||||
args.push("-f", params.field)
|
args.push("-f", params.field);
|
||||||
}
|
}
|
||||||
if (params.fuzzy) {
|
if (params.fuzzy) {
|
||||||
args.push("--fuzzy")
|
args.push("--fuzzy");
|
||||||
}
|
}
|
||||||
if (params.sort) {
|
if (params.sort) {
|
||||||
args.push("--sort", params.sort)
|
args.push("--sort", params.sort);
|
||||||
}
|
}
|
||||||
if (params.desc !== undefined) {
|
if (params.desc !== undefined) {
|
||||||
args.push("--desc=" + (params.desc ? "true" : "false"))
|
args.push("--desc=" + (params.desc ? "true" : "false"));
|
||||||
}
|
}
|
||||||
if (params.minSize !== undefined) {
|
if (params.minSize !== undefined) {
|
||||||
args.push("--min-size", String(params.minSize))
|
args.push("--min-size", String(params.minSize));
|
||||||
}
|
}
|
||||||
if (params.maxSize !== undefined) {
|
if (params.maxSize !== undefined) {
|
||||||
args.push("--max-size", String(params.maxSize))
|
args.push("--max-size", String(params.maxSize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Proc.runCommand("dsearch-search", args, (stdout, exitCode) => {
|
Proc.runCommand("dsearch-search", args, (stdout, exitCode) => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
try {
|
try {
|
||||||
const response = JSON.parse(stdout)
|
const response = JSON.parse(stdout);
|
||||||
searchResultsReceived(response)
|
searchResultsReceived(response);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "result": response })
|
callback({
|
||||||
|
"result": response
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = "failed to parse search response"
|
const error = "failed to parse search response";
|
||||||
errorOccurred(error)
|
errorOccurred(error);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "error": error })
|
callback({
|
||||||
|
"error": error
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (exitCode === 124) {
|
} else if (exitCode === 124) {
|
||||||
const error = "search timed out"
|
const error = "search timed out";
|
||||||
errorOccurred(error)
|
errorOccurred(error);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "error": error })
|
callback({
|
||||||
|
"error": error
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const error = "search failed"
|
const error = "search failed";
|
||||||
errorOccurred(error)
|
errorOccurred(error);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({ "error": error })
|
callback({
|
||||||
|
"error": error
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 100, 5000)
|
}, 100, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rediscover() {
|
function rediscover() {
|
||||||
checkProcess.running = true
|
checkProcess.running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,10 +158,7 @@ Singleton {
|
|||||||
continue;
|
continue;
|
||||||
const urg = typeof item.urgency === "number" ? item.urgency : 1;
|
const urg = typeof item.urgency === "number" ? item.urgency : 1;
|
||||||
const body = item.body || "";
|
const body = item.body || "";
|
||||||
let htmlBody = item.htmlBody || _resolveHtmlBody(body);
|
const htmlBody = item.htmlBody || _resolveHtmlBody(body);
|
||||||
if (htmlBody) {
|
|
||||||
htmlBody = htmlBody.replace(/<img\b[^>]*>/gi, "");
|
|
||||||
}
|
|
||||||
loaded.push({
|
loaded.push({
|
||||||
id: item.id || "",
|
id: item.id || "",
|
||||||
summary: item.summary || "",
|
summary: item.summary || "",
|
||||||
@@ -723,8 +720,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
required property Notification notification
|
required property Notification notification
|
||||||
readonly property string summary: (notification?.summary ?? "").replace(/<img\b[^>]*>/gi, "")
|
readonly property string summary: notification?.summary ?? ""
|
||||||
readonly property string body: (notification?.body ?? "").replace(/<img\b[^>]*>/gi, "")
|
readonly property string body: notification?.body ?? ""
|
||||||
readonly property string htmlBody: root._resolveHtmlBody(body)
|
readonly property string htmlBody: root._resolveHtmlBody(body)
|
||||||
readonly property string appIcon: notification?.appIcon ?? ""
|
readonly property string appIcon: notification?.appIcon ?? ""
|
||||||
readonly property string appName: {
|
readonly property string appName: {
|
||||||
@@ -981,34 +978,22 @@ Singleton {
|
|||||||
function _resolveHtmlBody(body) {
|
function _resolveHtmlBody(body) {
|
||||||
if (!body)
|
if (!body)
|
||||||
return "";
|
return "";
|
||||||
|
if (/<\/?[a-z][\s\S]*>/i.test(body))
|
||||||
|
return body;
|
||||||
|
|
||||||
let result = body;
|
// Decode percent-encoded URLs (e.g. https%3A%2F%2F → https://)
|
||||||
|
body = body.replace(/\bhttps?%3A%2F%2F[^\s]+/gi, match => {
|
||||||
|
try { return decodeURIComponent(match); }
|
||||||
|
catch (e) { return match; }
|
||||||
|
});
|
||||||
|
|
||||||
if (/<\/?[a-z][\s\S]*>/i.test(body)) {
|
if (/&(#\d+|#x[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]+);/.test(body)) {
|
||||||
result = body;
|
const decoded = _decodeEntities(body);
|
||||||
} else {
|
if (/<\/?[a-z][\s\S]*>/i.test(decoded))
|
||||||
// Decode percent-encoded URLs (e.g. https%3A%2F%2F → https://)
|
return decoded;
|
||||||
let processed = body.replace(/\bhttps?%3A%2F%2F[^\s]+/gi, match => {
|
return Markdown2Html.markdownToHtml(decoded);
|
||||||
try {
|
|
||||||
return decodeURIComponent(match);
|
|
||||||
} catch (e) {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (/&(#\d+|#x[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]+);/.test(processed)) {
|
|
||||||
const decoded = _decodeEntities(processed);
|
|
||||||
if (/<\/?[a-z][\s\S]*>/i.test(decoded))
|
|
||||||
result = decoded;
|
|
||||||
else
|
|
||||||
result = Markdown2Html.markdownToHtml(decoded);
|
|
||||||
} else {
|
|
||||||
result = Markdown2Html.markdownToHtml(processed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return Markdown2Html.markdownToHtml(body);
|
||||||
// Strip out image tags to prevent IP tracking
|
|
||||||
return result.replace(/<img\b[^>]*>/gi, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGroupKey(wrapper) {
|
function getGroupKey(wrapper) {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ Singleton {
|
|||||||
property var notificationModal: null
|
property var notificationModal: null
|
||||||
property var wifiPasswordModal: null
|
property var wifiPasswordModal: null
|
||||||
property var wifiPasswordModalLoader: null
|
property var wifiPasswordModalLoader: null
|
||||||
|
property var wifiQRCodeModal: null
|
||||||
|
property var wifiQRCodeModalLoader: null
|
||||||
property var polkitAuthModal: null
|
property var polkitAuthModal: null
|
||||||
property var polkitAuthModalLoader: null
|
property var polkitAuthModalLoader: null
|
||||||
property var bluetoothPairingModal: null
|
property var bluetoothPairingModal: null
|
||||||
@@ -661,6 +663,13 @@ Singleton {
|
|||||||
wifiPasswordModal.show(ssid);
|
wifiPasswordModal.show(ssid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showWifiQRCodeModal(ssid) {
|
||||||
|
if (wifiQRCodeModalLoader)
|
||||||
|
wifiQRCodeModalLoader.active = true;
|
||||||
|
if (wifiQRCodeModal)
|
||||||
|
wifiQRCodeModal.show(ssid);
|
||||||
|
}
|
||||||
|
|
||||||
function showHiddenNetworkModal() {
|
function showHiddenNetworkModal() {
|
||||||
if (wifiPasswordModalLoader)
|
if (wifiPasswordModalLoader)
|
||||||
wifiPasswordModalLoader.active = true;
|
wifiPasswordModalLoader.active = true;
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
v1.4.3
|
v1.5-beta
|
||||||
|
|||||||
Reference in New Issue
Block a user