mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-12 23:32:50 -04:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc27253cbf | |||
| 0672b711f3 | |||
| ed9ee6e347 | |||
| 7ad23ad4a2 | |||
| 8a83f03cc1 | |||
| 0be9ac4097 | |||
| ba5be6b516 | |||
| c4aea6d326 | |||
| 858c6407a9 | |||
| c4313395b5 | |||
| a32aec3d59 | |||
| 696bcfe8fa | |||
| 2f3a253c6a | |||
| e41fbe0188 | |||
| ef9d28597b | |||
| 6f3c4c89ab | |||
| 60c577a61e | |||
| f3276c3039 | |||
| 37a843323d | |||
| 95c780ca8c | |||
| d60d5b154a | |||
| 0435a805c7 | |||
| f406a977e0 | |||
| 18db1e1ecb | |||
| 6bd1beb719 | |||
| 1293aecbca | |||
| 8a10c2e112 | |||
| c21d777269 | |||
| d864094f48 | |||
| deaac3fdf0 | |||
| b7062fe40c | |||
| 64d5e99b9d | |||
| f9d8a7d22b | |||
| 52fcd3ad98 | |||
| 9d1e0ee29b | |||
| de62f48f50 | |||
| f47b19274c | |||
| bb7f7083b9 | |||
| cd580090dc | |||
| ddb74b598d | |||
| 29571fc3aa |
@@ -0,0 +1,23 @@
|
|||||||
|
name: Check nix flake
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
paths:
|
||||||
|
- "flake.*"
|
||||||
|
- "distro/nix/**"
|
||||||
|
jobs:
|
||||||
|
check-flake:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v31
|
||||||
|
|
||||||
|
- name: Check the flake
|
||||||
|
run: nix flake check
|
||||||
@@ -127,7 +127,7 @@ dms plugins search # Browse plugin registry
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- **Website:** [danklinux.com](https://danklinux.com)
|
- **Website:** [danklinux.com](https://danklinux.com)
|
||||||
- **Docs:** [danklinux.com/docs](https://danklinux.com/docs)
|
- **Docs:** [danklinux.com/docs](https://danklinux.com/docs/)
|
||||||
- **Theming:** [Application themes](https://danklinux.com/docs/dankmaterialshell/application-themes) | [Custom themes](https://danklinux.com/docs/dankmaterialshell/custom-themes)
|
- **Theming:** [Application themes](https://danklinux.com/docs/dankmaterialshell/application-themes) | [Custom themes](https://danklinux.com/docs/dankmaterialshell/custom-themes)
|
||||||
- **Plugins:** [Development guide](https://danklinux.com/docs/dankmaterialshell/plugins-overview)
|
- **Plugins:** [Development guide](https://danklinux.com/docs/dankmaterialshell/plugins-overview)
|
||||||
- **Support:** [Ko-fi](https://ko-fi.com/avengemediallc)
|
- **Support:** [Ko-fi](https://ko-fi.com/avengemediallc)
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import (
|
|||||||
var Version = "dev"
|
var Version = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: dankinstall must not be run as root")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
fileLogger, err := log.NewFileLogger()
|
fileLogger, err := log.NewFileLogger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Failed to create log file: %v\n", err)
|
fmt.Printf("Warning: Failed to create log file: %v\n", err)
|
||||||
|
|||||||
@@ -295,7 +295,14 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
|
|
||||||
data := buf.Data()
|
data := buf.Data()
|
||||||
rgb := make([]byte, dstW*dstH*3)
|
rgb := make([]byte, dstW*dstH*3)
|
||||||
swapRB := pixelFormat == uint32(screenshot.FormatARGB8888) || pixelFormat == uint32(screenshot.FormatXRGB8888) || pixelFormat == 0
|
|
||||||
|
var swapRB bool
|
||||||
|
switch pixelFormat {
|
||||||
|
case uint32(screenshot.FormatABGR8888), uint32(screenshot.FormatXBGR8888):
|
||||||
|
swapRB = false
|
||||||
|
default:
|
||||||
|
swapRB = true
|
||||||
|
}
|
||||||
|
|
||||||
for y := 0; y < dstH; y++ {
|
for y := 0; y < dstH; y++ {
|
||||||
srcY := int(float64(y) / scale)
|
srcY := int(float64(y) / scale)
|
||||||
@@ -309,16 +316,17 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
}
|
}
|
||||||
si := srcY*buf.Stride + srcX*4
|
si := srcY*buf.Stride + srcX*4
|
||||||
di := (y*dstW + x) * 3
|
di := (y*dstW + x) * 3
|
||||||
if si+2 < len(data) {
|
if si+3 >= len(data) {
|
||||||
if swapRB {
|
continue
|
||||||
rgb[di+0] = data[si+2]
|
}
|
||||||
rgb[di+1] = data[si+1]
|
if swapRB {
|
||||||
rgb[di+2] = data[si+0]
|
rgb[di+0] = data[si+2]
|
||||||
} else {
|
rgb[di+1] = data[si+1]
|
||||||
rgb[di+0] = data[si+0]
|
rgb[di+2] = data[si+0]
|
||||||
rgb[di+1] = data[si+1]
|
} else {
|
||||||
rgb[di+2] = data[si+2]
|
rgb[di+0] = data[si+0]
|
||||||
}
|
rgb[di+1] = data[si+1]
|
||||||
|
rgb[di+2] = data[si+2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,7 +378,37 @@ func runScreenshotList(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range outputs {
|
for _, o := range outputs {
|
||||||
fmt.Printf("%s: %dx%d+%d+%d (scale: %d)\n",
|
scaleStr := fmt.Sprintf("%.2f", o.FractionalScale)
|
||||||
o.Name, o.Width, o.Height, o.X, o.Y, o.Scale)
|
if o.FractionalScale == float64(int(o.FractionalScale)) {
|
||||||
|
scaleStr = fmt.Sprintf("%d", int(o.FractionalScale))
|
||||||
|
}
|
||||||
|
|
||||||
|
transformStr := transformName(o.Transform)
|
||||||
|
|
||||||
|
fmt.Printf("%s: %dx%d+%d+%d scale=%s transform=%s\n",
|
||||||
|
o.Name, o.Width, o.Height, o.X, o.Y, scaleStr, transformStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformName(t int32) string {
|
||||||
|
switch t {
|
||||||
|
case 0:
|
||||||
|
return "normal"
|
||||||
|
case 1:
|
||||||
|
return "90"
|
||||||
|
case 2:
|
||||||
|
return "180"
|
||||||
|
case 3:
|
||||||
|
return "270"
|
||||||
|
case 4:
|
||||||
|
return "flipped"
|
||||||
|
case 5:
|
||||||
|
return "flipped-90"
|
||||||
|
case 6:
|
||||||
|
return "flipped-180"
|
||||||
|
case 7:
|
||||||
|
return "flipped-270"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ func runSetup() error {
|
|||||||
|
|
||||||
wm, wmSelected := promptCompositor()
|
wm, wmSelected := promptCompositor()
|
||||||
terminal, terminalSelected := promptTerminal()
|
terminal, terminalSelected := promptTerminal()
|
||||||
|
useSystemd := promptSystemd()
|
||||||
|
|
||||||
if !wmSelected && !terminalSelected {
|
if !wmSelected && !terminalSelected {
|
||||||
fmt.Println("No configurations selected. Exiting.")
|
fmt.Println("No configurations selected. Exiting.")
|
||||||
@@ -67,14 +68,14 @@ func runSetup() error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if wmSelected && terminalSelected {
|
if wmSelected && terminalSelected {
|
||||||
results, err = deployer.DeployConfigurationsWithTerminal(ctx, wm, terminal)
|
results, err = deployer.DeployConfigurationsWithSystemd(ctx, wm, terminal, useSystemd)
|
||||||
} else if wmSelected {
|
} else if wmSelected {
|
||||||
results, err = deployer.DeployConfigurationsWithTerminal(ctx, wm, deps.TerminalGhostty)
|
results, err = deployer.DeployConfigurationsWithSystemd(ctx, wm, deps.TerminalGhostty, useSystemd)
|
||||||
if len(results) > 1 {
|
if len(results) > 1 {
|
||||||
results = results[:1]
|
results = results[:1]
|
||||||
}
|
}
|
||||||
} else if terminalSelected {
|
} else if terminalSelected {
|
||||||
results, err = deployer.DeployConfigurationsWithTerminal(ctx, deps.WindowManagerNiri, terminal)
|
results, err = deployer.DeployConfigurationsWithSystemd(ctx, deps.WindowManagerNiri, terminal, useSystemd)
|
||||||
if len(results) > 0 && results[0].ConfigType == "Niri" {
|
if len(results) > 0 && results[0].ConfigType == "Niri" {
|
||||||
results = results[1:]
|
results = results[1:]
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,19 @@ func promptTerminal() (deps.Terminal, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func promptSystemd() bool {
|
||||||
|
fmt.Println("\nUse systemd for session management?")
|
||||||
|
fmt.Println("1) Yes (recommended for most distros)")
|
||||||
|
fmt.Println("2) No (standalone, no systemd integration)")
|
||||||
|
|
||||||
|
var response string
|
||||||
|
fmt.Print("\nChoice (1-2): ")
|
||||||
|
fmt.Scanln(&response)
|
||||||
|
response = strings.TrimSpace(response)
|
||||||
|
|
||||||
|
return response != "2"
|
||||||
|
}
|
||||||
|
|
||||||
func checkExistingConfigs(wm deps.WindowManager, wmSelected bool, terminal deps.Terminal, terminalSelected bool) bool {
|
func checkExistingConfigs(wm deps.WindowManager, wmSelected bool, terminal deps.Terminal, terminalSelected bool) bool {
|
||||||
homeDir := os.Getenv("HOME")
|
homeDir := os.Getenv("HOME")
|
||||||
willBackup := false
|
willBackup := false
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type Output struct {
|
|||||||
height int32
|
height int32
|
||||||
scale int32
|
scale int32
|
||||||
fractionalScale float64
|
fractionalScale float64
|
||||||
|
transform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayerSurface struct {
|
type LayerSurface struct {
|
||||||
@@ -276,6 +277,7 @@ func (p *Picker) setupOutputHandlers(name uint32, output *client.Output) {
|
|||||||
if o, ok := p.outputs[name]; ok {
|
if o, ok := p.outputs[name]; ok {
|
||||||
o.x = e.X
|
o.x = e.X
|
||||||
o.y = e.Y
|
o.y = e.Y
|
||||||
|
o.transform = int32(e.Transform)
|
||||||
}
|
}
|
||||||
p.outputsMu.Unlock()
|
p.outputsMu.Unlock()
|
||||||
})
|
})
|
||||||
@@ -485,8 +487,19 @@ func (p *Picker) captureForSurface(ls *LayerSurface) {
|
|||||||
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
||||||
ls.state.OnScreencopyReady()
|
ls.state.OnScreencopyReady()
|
||||||
|
|
||||||
logicalW, _ := ls.state.LogicalSize()
|
|
||||||
screenBuf := ls.state.ScreenBuffer()
|
screenBuf := ls.state.ScreenBuffer()
|
||||||
|
if screenBuf != nil && ls.output.transform != TransformNormal {
|
||||||
|
invTransform := InverseTransform(ls.output.transform)
|
||||||
|
transformed, err := screenBuf.ApplyTransform(invTransform)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("apply transform failed", "err", err)
|
||||||
|
} else if transformed != screenBuf {
|
||||||
|
ls.state.ReplaceScreenBuffer(transformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logicalW, _ := ls.state.LogicalSize()
|
||||||
|
screenBuf = ls.state.ScreenBuffer()
|
||||||
if logicalW > 0 && screenBuf != nil {
|
if logicalW > 0 && screenBuf != nil {
|
||||||
ls.output.fractionalScale = float64(screenBuf.Width) / float64(logicalW)
|
ls.output.fractionalScale = float64(screenBuf.Width) / float64(logicalW)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,25 @@ import "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/shm"
|
|||||||
|
|
||||||
type ShmBuffer = shm.Buffer
|
type ShmBuffer = shm.Buffer
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransformNormal = shm.TransformNormal
|
||||||
|
Transform90 = shm.Transform90
|
||||||
|
Transform180 = shm.Transform180
|
||||||
|
Transform270 = shm.Transform270
|
||||||
|
TransformFlipped = shm.TransformFlipped
|
||||||
|
TransformFlipped90 = shm.TransformFlipped90
|
||||||
|
TransformFlipped180 = shm.TransformFlipped180
|
||||||
|
TransformFlipped270 = shm.TransformFlipped270
|
||||||
|
)
|
||||||
|
|
||||||
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
||||||
return shm.CreateBuffer(width, height, stride)
|
return shm.CreateBuffer(width, height, stride)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InverseTransform(transform int32) int32 {
|
||||||
|
return shm.InverseTransform(transform)
|
||||||
|
}
|
||||||
|
|
||||||
func GetPixelColor(buf *ShmBuffer, x, y int) Color {
|
func GetPixelColor(buf *ShmBuffer, x, y int) Color {
|
||||||
return GetPixelColorWithFormat(buf, x, y, FormatARGB8888)
|
return GetPixelColorWithFormat(buf, x, y, FormatARGB8888)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package colorpicker
|
package colorpicker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -15,6 +16,8 @@ const (
|
|||||||
FormatXRGB8888 = shm.FormatXRGB8888
|
FormatXRGB8888 = shm.FormatXRGB8888
|
||||||
FormatABGR8888 = shm.FormatABGR8888
|
FormatABGR8888 = shm.FormatABGR8888
|
||||||
FormatXBGR8888 = shm.FormatXBGR8888
|
FormatXBGR8888 = shm.FormatXBGR8888
|
||||||
|
FormatRGB888 = shm.FormatRGB888
|
||||||
|
FormatBGR888 = shm.FormatBGR888
|
||||||
)
|
)
|
||||||
|
|
||||||
type SurfaceState struct {
|
type SurfaceState struct {
|
||||||
@@ -79,6 +82,11 @@ func (s *SurfaceState) OnScreencopyBuffer(format PixelFormat, width, height, str
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
bpp := format.BytesPerPixel()
|
||||||
|
if stride < width*bpp {
|
||||||
|
return fmt.Errorf("invalid stride %d for width %d (bpp=%d)", stride, width, bpp)
|
||||||
|
}
|
||||||
|
|
||||||
if s.screenBuf != nil {
|
if s.screenBuf != nil {
|
||||||
s.screenBuf.Close()
|
s.screenBuf.Close()
|
||||||
s.screenBuf = nil
|
s.screenBuf = nil
|
||||||
@@ -90,6 +98,7 @@ func (s *SurfaceState) OnScreencopyBuffer(format PixelFormat, width, height, str
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.screenBuf = buf
|
s.screenBuf = buf
|
||||||
|
s.screenBuf.Format = format
|
||||||
s.screenFormat = format
|
s.screenFormat = format
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -106,6 +115,20 @@ func (s *SurfaceState) ScreenFormat() PixelFormat {
|
|||||||
return s.screenFormat
|
return s.screenFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SurfaceState) ReplaceScreenBuffer(newBuf *ShmBuffer) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.screenBuf != nil {
|
||||||
|
s.screenBuf.Close()
|
||||||
|
}
|
||||||
|
s.screenBuf = newBuf
|
||||||
|
s.screenFormat = newBuf.Format
|
||||||
|
|
||||||
|
s.recomputeScale()
|
||||||
|
s.ensureRenderBuffers()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SurfaceState) OnScreencopyFlags(flags uint32) {
|
func (s *SurfaceState) OnScreencopyFlags(flags uint32) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.yInverted = (flags & 1) != 0
|
s.yInverted = (flags & 1) != 0
|
||||||
@@ -120,6 +143,15 @@ func (s *SurfaceState) OnScreencopyReady() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.screenFormat.Is24Bit() {
|
||||||
|
converted, newFormat, err := s.screenBuf.ConvertTo32Bit(s.screenFormat)
|
||||||
|
if err == nil && converted != s.screenBuf {
|
||||||
|
s.screenBuf.Close()
|
||||||
|
s.screenBuf = converted
|
||||||
|
s.screenFormat = newFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.recomputeScale()
|
s.recomputeScale()
|
||||||
s.ensureRenderBuffers()
|
s.ensureRenderBuffers()
|
||||||
s.readyForDisplay = true
|
s.readyForDisplay = true
|
||||||
@@ -279,10 +311,10 @@ func (s *SurfaceState) Redraw() *ShmBuffer {
|
|||||||
drawMagnifierWithInversion(
|
drawMagnifierWithInversion(
|
||||||
dst.Data(), dst.Stride, dst.Width, dst.Height,
|
dst.Data(), dst.Stride, dst.Width, dst.Height,
|
||||||
s.screenBuf.Data(), s.screenBuf.Stride, s.screenBuf.Width, s.screenBuf.Height,
|
s.screenBuf.Data(), s.screenBuf.Stride, s.screenBuf.Width, s.screenBuf.Height,
|
||||||
px, py, picked, s.yInverted,
|
px, py, picked, s.yInverted, s.screenFormat,
|
||||||
)
|
)
|
||||||
|
|
||||||
drawColorPreview(dst.Data(), dst.Stride, dst.Width, dst.Height, px, py, picked, s.displayFormat, s.lowercase)
|
drawColorPreview(dst.Data(), dst.Stride, dst.Width, dst.Height, px, py, picked, s.displayFormat, s.lowercase, s.screenFormat)
|
||||||
|
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
@@ -390,6 +422,7 @@ func drawMagnifierWithInversion(
|
|||||||
cx, cy int,
|
cx, cy int,
|
||||||
borderColor Color,
|
borderColor Color,
|
||||||
yInverted bool,
|
yInverted bool,
|
||||||
|
format PixelFormat,
|
||||||
) {
|
) {
|
||||||
if dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0 {
|
if dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0 {
|
||||||
return
|
return
|
||||||
@@ -407,6 +440,14 @@ func drawMagnifierWithInversion(
|
|||||||
innerRadius := float64(outerRadius - borderThickness)
|
innerRadius := float64(outerRadius - borderThickness)
|
||||||
outerRadiusF := float64(outerRadius)
|
outerRadiusF := float64(outerRadius)
|
||||||
|
|
||||||
|
var rOff, bOff int
|
||||||
|
switch format {
|
||||||
|
case FormatABGR8888, FormatXBGR8888:
|
||||||
|
rOff, bOff = 0, 2
|
||||||
|
default:
|
||||||
|
rOff, bOff = 2, 0
|
||||||
|
}
|
||||||
|
|
||||||
for dy := -outerRadius - 2; dy <= outerRadius+2; dy++ {
|
for dy := -outerRadius - 2; dy <= outerRadius+2; dy++ {
|
||||||
y := cy + dy
|
y := cy + dy
|
||||||
if y < 0 || y >= dstH {
|
if y < 0 || y >= dstH {
|
||||||
@@ -431,9 +472,9 @@ func drawMagnifierWithInversion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bgColor := Color{
|
bgColor := Color{
|
||||||
B: dst[dstOff+0],
|
R: dst[dstOff+rOff],
|
||||||
G: dst[dstOff+1],
|
G: dst[dstOff+1],
|
||||||
R: dst[dstOff+2],
|
B: dst[dstOff+bOff],
|
||||||
A: dst[dstOff+3],
|
A: dst[dstOff+3],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +503,7 @@ func drawMagnifierWithInversion(
|
|||||||
}
|
}
|
||||||
srcOff := sy*srcStride + sx*4
|
srcOff := sy*srcStride + sx*4
|
||||||
if srcOff+4 <= len(src) {
|
if srcOff+4 <= len(src) {
|
||||||
magColor := Color{B: src[srcOff+0], G: src[srcOff+1], R: src[srcOff+2], A: 255}
|
magColor := Color{R: src[srcOff+rOff], G: src[srcOff+1], B: src[srcOff+bOff], A: 255}
|
||||||
finalColor = blendColors(magColor, borderColor, alpha)
|
finalColor = blendColors(magColor, borderColor, alpha)
|
||||||
} else {
|
} else {
|
||||||
finalColor = borderColor
|
finalColor = borderColor
|
||||||
@@ -483,24 +524,25 @@ func drawMagnifierWithInversion(
|
|||||||
}
|
}
|
||||||
srcOff := sy*srcStride + sx*4
|
srcOff := sy*srcStride + sx*4
|
||||||
if srcOff+4 <= len(src) {
|
if srcOff+4 <= len(src) {
|
||||||
finalColor = Color{B: src[srcOff+0], G: src[srcOff+1], R: src[srcOff+2], A: 255}
|
finalColor = Color{R: src[srcOff+rOff], G: src[srcOff+1], B: src[srcOff+bOff], A: 255}
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dst[dstOff+0] = finalColor.B
|
dst[dstOff+rOff] = finalColor.R
|
||||||
dst[dstOff+1] = finalColor.G
|
dst[dstOff+1] = finalColor.G
|
||||||
dst[dstOff+2] = finalColor.R
|
dst[dstOff+bOff] = finalColor.B
|
||||||
dst[dstOff+3] = 255
|
dst[dstOff+3] = 255
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawMagnifierCrosshair(dst, dstStride, dstW, dstH, cx, cy, int(innerRadius), crossThickness, crossInnerRadius)
|
drawMagnifierCrosshair(dst, dstStride, dstW, dstH, cx, cy, int(innerRadius), crossThickness, crossInnerRadius, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawMagnifierCrosshair(
|
func drawMagnifierCrosshair(
|
||||||
data []byte, stride, width, height, cx, cy, radius, thickness, innerRadius int,
|
data []byte, stride, width, height, cx, cy, radius, thickness, innerRadius int,
|
||||||
|
format PixelFormat,
|
||||||
) {
|
) {
|
||||||
if width <= 0 || height <= 0 {
|
if width <= 0 || height <= 0 {
|
||||||
return
|
return
|
||||||
@@ -998,7 +1040,7 @@ var fontGlyphs = map[rune][fontH]uint8{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Color, format OutputFormat, lowercase bool) {
|
func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Color, format OutputFormat, lowercase bool, pixelFormat PixelFormat) {
|
||||||
text := formatColorForPreview(c, format, lowercase)
|
text := formatColorForPreview(c, format, lowercase)
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return
|
return
|
||||||
@@ -1033,9 +1075,8 @@ func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Colo
|
|||||||
y = height - boxH
|
y = height - boxH
|
||||||
}
|
}
|
||||||
|
|
||||||
drawFilledRect(data, stride, width, height, x, y, boxW, boxH, c)
|
drawFilledRect(data, stride, width, height, x, y, boxW, boxH, c, pixelFormat)
|
||||||
|
|
||||||
// Use contrasting text color based on luminance
|
|
||||||
lum := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
|
lum := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
|
||||||
var fg Color
|
var fg Color
|
||||||
if lum > 128 {
|
if lum > 128 {
|
||||||
@@ -1043,7 +1084,7 @@ func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Colo
|
|||||||
} else {
|
} else {
|
||||||
fg = Color{R: 255, G: 255, B: 255, A: 255}
|
fg = Color{R: 255, G: 255, B: 255, A: 255}
|
||||||
}
|
}
|
||||||
drawText(data, stride, width, height, x+paddingX, y+paddingY, text, fg)
|
drawText(data, stride, width, height, x+paddingX, y+paddingY, text, fg, pixelFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatColorForPreview(c Color, format OutputFormat, lowercase bool) string {
|
func formatColorForPreview(c Color, format OutputFormat, lowercase bool) string {
|
||||||
@@ -1064,7 +1105,7 @@ func formatColorForPreview(c Color, format OutputFormat, lowercase bool) string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawFilledRect(data []byte, stride, width, height, x, y, w, h int, col Color) {
|
func drawFilledRect(data []byte, stride, width, height, x, y, w, h int, col Color, format PixelFormat) {
|
||||||
if w <= 0 || h <= 0 {
|
if w <= 0 || h <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1073,6 +1114,14 @@ func drawFilledRect(data []byte, stride, width, height, x, y, w, h int, col Colo
|
|||||||
x = clamp(x, 0, width)
|
x = clamp(x, 0, width)
|
||||||
y = clamp(y, 0, height)
|
y = clamp(y, 0, height)
|
||||||
|
|
||||||
|
var rOff, bOff int
|
||||||
|
switch format {
|
||||||
|
case FormatABGR8888, FormatXBGR8888:
|
||||||
|
rOff, bOff = 0, 2
|
||||||
|
default:
|
||||||
|
rOff, bOff = 2, 0
|
||||||
|
}
|
||||||
|
|
||||||
for yy := y; yy < yEnd; yy++ {
|
for yy := y; yy < yEnd; yy++ {
|
||||||
rowOff := yy * stride
|
rowOff := yy * stride
|
||||||
for xx := x; xx < xEnd; xx++ {
|
for xx := x; xx < xEnd; xx++ {
|
||||||
@@ -1080,26 +1129,34 @@ func drawFilledRect(data []byte, stride, width, height, x, y, w, h int, col Colo
|
|||||||
if off+4 > len(data) {
|
if off+4 > len(data) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
data[off+0] = col.B
|
data[off+rOff] = col.R
|
||||||
data[off+1] = col.G
|
data[off+1] = col.G
|
||||||
data[off+2] = col.R
|
data[off+bOff] = col.B
|
||||||
data[off+3] = 255
|
data[off+3] = 255
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawText(data []byte, stride, width, height, x, y int, text string, col Color) {
|
func drawText(data []byte, stride, width, height, x, y int, text string, col Color, format PixelFormat) {
|
||||||
for i, r := range text {
|
for i, r := range text {
|
||||||
drawGlyph(data, stride, width, height, x+i*(fontW+2), y, r, col)
|
drawGlyph(data, stride, width, height, x+i*(fontW+2), y, r, col, format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawGlyph(data []byte, stride, width, height, x, y int, r rune, col Color) {
|
func drawGlyph(data []byte, stride, width, height, x, y int, r rune, col Color, format PixelFormat) {
|
||||||
g, ok := fontGlyphs[r]
|
g, ok := fontGlyphs[r]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rOff, bOff int
|
||||||
|
switch format {
|
||||||
|
case FormatABGR8888, FormatXBGR8888:
|
||||||
|
rOff, bOff = 0, 2
|
||||||
|
default:
|
||||||
|
rOff, bOff = 2, 0
|
||||||
|
}
|
||||||
|
|
||||||
for row := 0; row < fontH; row++ {
|
for row := 0; row < fontH; row++ {
|
||||||
yy := y + row
|
yy := y + row
|
||||||
if yy < 0 || yy >= height {
|
if yy < 0 || yy >= height {
|
||||||
@@ -1123,9 +1180,9 @@ func drawGlyph(data []byte, stride, width, height, x, y int, r rune, col Color)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
data[off+0] = col.B
|
data[off+rOff] = col.R
|
||||||
data[off+1] = col.G
|
data[off+1] = col.G
|
||||||
data[off+2] = col.R
|
data[off+bOff] = col.B
|
||||||
data[off+3] = 255
|
data[off+3] = 255
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,20 @@ func (cd *ConfigDeployer) DeployConfigurationsWithTerminal(ctx context.Context,
|
|||||||
return cd.DeployConfigurationsSelective(ctx, wm, terminal, nil, nil)
|
return cd.DeployConfigurationsSelective(ctx, wm, terminal, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeployConfigurationsWithSystemd deploys configurations with systemd option
|
||||||
|
func (cd *ConfigDeployer) DeployConfigurationsWithSystemd(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, useSystemd bool) ([]DeploymentResult, error) {
|
||||||
|
return cd.deployConfigurationsInternal(ctx, wm, terminal, nil, nil, nil, useSystemd)
|
||||||
|
}
|
||||||
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurationsSelective(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool) ([]DeploymentResult, error) {
|
func (cd *ConfigDeployer) DeployConfigurationsSelective(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool) ([]DeploymentResult, error) {
|
||||||
return cd.DeployConfigurationsSelectiveWithReinstalls(ctx, wm, terminal, installedDeps, replaceConfigs, nil)
|
return cd.DeployConfigurationsSelectiveWithReinstalls(ctx, wm, terminal, installedDeps, replaceConfigs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool) ([]DeploymentResult, error) {
|
func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool) ([]DeploymentResult, error) {
|
||||||
|
return cd.deployConfigurationsInternal(ctx, wm, terminal, installedDeps, replaceConfigs, reinstallItems, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ConfigDeployer) deployConfigurationsInternal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool, useSystemd bool) ([]DeploymentResult, error) {
|
||||||
var results []DeploymentResult
|
var results []DeploymentResult
|
||||||
|
|
||||||
shouldReplaceConfig := func(configType string) bool {
|
shouldReplaceConfig := func(configType string) bool {
|
||||||
@@ -64,7 +73,7 @@ func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx contex
|
|||||||
switch wm {
|
switch wm {
|
||||||
case deps.WindowManagerNiri:
|
case deps.WindowManagerNiri:
|
||||||
if shouldReplaceConfig("Niri") {
|
if shouldReplaceConfig("Niri") {
|
||||||
result, err := cd.deployNiriConfig(terminal)
|
result, err := cd.deployNiriConfig(terminal, useSystemd)
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return results, fmt.Errorf("failed to deploy Niri config: %w", err)
|
return results, fmt.Errorf("failed to deploy Niri config: %w", err)
|
||||||
@@ -72,7 +81,7 @@ func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx contex
|
|||||||
}
|
}
|
||||||
case deps.WindowManagerHyprland:
|
case deps.WindowManagerHyprland:
|
||||||
if shouldReplaceConfig("Hyprland") {
|
if shouldReplaceConfig("Hyprland") {
|
||||||
result, err := cd.deployHyprlandConfig(terminal)
|
result, err := cd.deployHyprlandConfig(terminal, useSystemd)
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return results, fmt.Errorf("failed to deploy Hyprland config: %w", err)
|
return results, fmt.Errorf("failed to deploy Hyprland config: %w", err)
|
||||||
@@ -110,7 +119,7 @@ func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx contex
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal) (DeploymentResult, error) {
|
func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
|
||||||
result := DeploymentResult{
|
result := DeploymentResult{
|
||||||
ConfigType: "Niri",
|
ConfigType: "Niri",
|
||||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "niri", "config.kdl"),
|
Path: filepath.Join(os.Getenv("HOME"), ".config", "niri", "config.kdl"),
|
||||||
@@ -148,12 +157,6 @@ func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal) (DeploymentRe
|
|||||||
cd.log(fmt.Sprintf("Backed up existing config to %s", result.BackupPath))
|
cd.log(fmt.Sprintf("Backed up existing config to %s", result.BackupPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
polkitPath, err := cd.detectPolkitAgent()
|
|
||||||
if err != nil {
|
|
||||||
cd.log(fmt.Sprintf("Warning: Could not detect polkit agent: %v", err))
|
|
||||||
polkitPath = "/usr/lib/mate-polkit/polkit-mate-authentication-agent-1"
|
|
||||||
}
|
|
||||||
|
|
||||||
var terminalCommand string
|
var terminalCommand string
|
||||||
switch terminal {
|
switch terminal {
|
||||||
case deps.TerminalGhostty:
|
case deps.TerminalGhostty:
|
||||||
@@ -166,8 +169,11 @@ func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal) (DeploymentRe
|
|||||||
terminalCommand = "ghostty"
|
terminalCommand = "ghostty"
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig := strings.ReplaceAll(NiriConfig, "{{POLKIT_AGENT_PATH}}", polkitPath)
|
newConfig := strings.ReplaceAll(NiriConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
|
||||||
newConfig = strings.ReplaceAll(newConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
|
|
||||||
|
if !useSystemd {
|
||||||
|
newConfig = cd.transformNiriConfigForNonSystemd(newConfig, terminalCommand)
|
||||||
|
}
|
||||||
|
|
||||||
if existingConfig != "" {
|
if existingConfig != "" {
|
||||||
mergedConfig, err := cd.mergeNiriOutputSections(newConfig, existingConfig)
|
mergedConfig, err := cd.mergeNiriOutputSections(newConfig, existingConfig)
|
||||||
@@ -404,41 +410,6 @@ func (cd *ConfigDeployer) deployAlacrittyConfig() ([]DeploymentResult, error) {
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectPolkitAgent tries to find the polkit authentication agent on the system
|
|
||||||
// Prioritizes mate-polkit paths since that's what we install
|
|
||||||
func (cd *ConfigDeployer) detectPolkitAgent() (string, error) {
|
|
||||||
// Prioritize mate-polkit paths first
|
|
||||||
matePaths := []string{
|
|
||||||
"/usr/libexec/polkit-mate-authentication-agent-1", // Fedora path
|
|
||||||
"/usr/lib/mate-polkit/polkit-mate-authentication-agent-1",
|
|
||||||
"/usr/libexec/mate-polkit/polkit-mate-authentication-agent-1",
|
|
||||||
"/usr/lib/polkit-mate/polkit-mate-authentication-agent-1",
|
|
||||||
"/usr/lib/x86_64-linux-gnu/mate-polkit/polkit-mate-authentication-agent-1",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range matePaths {
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
cd.log(fmt.Sprintf("Found mate-polkit agent at: %s", path))
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to other polkit agents if mate-polkit is not found
|
|
||||||
fallbackPaths := []string{
|
|
||||||
"/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1",
|
|
||||||
"/usr/libexec/polkit-gnome-authentication-agent-1",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range fallbackPaths {
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
cd.log(fmt.Sprintf("Found fallback polkit agent at: %s", path))
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("no polkit agent found in common locations")
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeNiriOutputSections extracts output sections from existing config and merges them into the new config
|
// mergeNiriOutputSections extracts output sections from existing config and merges them into the new config
|
||||||
func (cd *ConfigDeployer) mergeNiriOutputSections(newConfig, existingConfig string) (string, error) {
|
func (cd *ConfigDeployer) mergeNiriOutputSections(newConfig, existingConfig string) (string, error) {
|
||||||
// Regular expression to match output sections (including commented ones)
|
// Regular expression to match output sections (including commented ones)
|
||||||
@@ -482,7 +453,7 @@ func (cd *ConfigDeployer) mergeNiriOutputSections(newConfig, existingConfig stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deployHyprlandConfig handles Hyprland configuration deployment with backup and merging
|
// deployHyprlandConfig handles Hyprland configuration deployment with backup and merging
|
||||||
func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal) (DeploymentResult, error) {
|
func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
|
||||||
result := DeploymentResult{
|
result := DeploymentResult{
|
||||||
ConfigType: "Hyprland",
|
ConfigType: "Hyprland",
|
||||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "hypr", "hyprland.conf"),
|
Path: filepath.Join(os.Getenv("HOME"), ".config", "hypr", "hyprland.conf"),
|
||||||
@@ -514,14 +485,6 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal) (Deployme
|
|||||||
cd.log(fmt.Sprintf("Backed up existing config to %s", result.BackupPath))
|
cd.log(fmt.Sprintf("Backed up existing config to %s", result.BackupPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect polkit agent path
|
|
||||||
polkitPath, err := cd.detectPolkitAgent()
|
|
||||||
if err != nil {
|
|
||||||
cd.log(fmt.Sprintf("Warning: Could not detect polkit agent: %v", err))
|
|
||||||
polkitPath = "/usr/lib/mate-polkit/polkit-mate-authentication-agent-1" // fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine terminal command based on choice
|
|
||||||
var terminalCommand string
|
var terminalCommand string
|
||||||
switch terminal {
|
switch terminal {
|
||||||
case deps.TerminalGhostty:
|
case deps.TerminalGhostty:
|
||||||
@@ -531,13 +494,15 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal) (Deployme
|
|||||||
case deps.TerminalAlacritty:
|
case deps.TerminalAlacritty:
|
||||||
terminalCommand = "alacritty"
|
terminalCommand = "alacritty"
|
||||||
default:
|
default:
|
||||||
terminalCommand = "ghostty" // fallback to ghostty
|
terminalCommand = "ghostty"
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig := strings.ReplaceAll(HyprlandConfig, "{{POLKIT_AGENT_PATH}}", polkitPath)
|
newConfig := strings.ReplaceAll(HyprlandConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
|
||||||
newConfig = strings.ReplaceAll(newConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
|
|
||||||
|
if !useSystemd {
|
||||||
|
newConfig = cd.transformHyprlandConfigForNonSystemd(newConfig, terminalCommand)
|
||||||
|
}
|
||||||
|
|
||||||
// If there was an existing config, merge the monitor sections
|
|
||||||
if existingConfig != "" {
|
if existingConfig != "" {
|
||||||
mergedConfig, err := cd.mergeHyprlandMonitorSections(newConfig, existingConfig)
|
mergedConfig, err := cd.mergeHyprlandMonitorSections(newConfig, existingConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -560,24 +525,16 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal) (Deployme
|
|||||||
|
|
||||||
// mergeHyprlandMonitorSections extracts monitor sections from existing config and merges them into the new config
|
// mergeHyprlandMonitorSections extracts monitor sections from existing config and merges them into the new config
|
||||||
func (cd *ConfigDeployer) mergeHyprlandMonitorSections(newConfig, existingConfig string) (string, error) {
|
func (cd *ConfigDeployer) mergeHyprlandMonitorSections(newConfig, existingConfig string) (string, error) {
|
||||||
// Regular expression to match monitor lines (including commented ones)
|
|
||||||
// Matches: monitor = NAME, RESOLUTION, POSITION, SCALE, etc.
|
|
||||||
// Also matches commented versions: # monitor = ...
|
|
||||||
monitorRegex := regexp.MustCompile(`(?m)^#?\s*monitor\s*=.*$`)
|
monitorRegex := regexp.MustCompile(`(?m)^#?\s*monitor\s*=.*$`)
|
||||||
|
|
||||||
// Find all monitor lines in the existing config
|
|
||||||
existingMonitors := monitorRegex.FindAllString(existingConfig, -1)
|
existingMonitors := monitorRegex.FindAllString(existingConfig, -1)
|
||||||
|
|
||||||
if len(existingMonitors) == 0 {
|
if len(existingMonitors) == 0 {
|
||||||
// No monitor sections to merge
|
|
||||||
return newConfig, nil
|
return newConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the example monitor line from the new config
|
|
||||||
exampleMonitorRegex := regexp.MustCompile(`(?m)^# monitor = eDP-2.*$`)
|
exampleMonitorRegex := regexp.MustCompile(`(?m)^# monitor = eDP-2.*$`)
|
||||||
mergedConfig := exampleMonitorRegex.ReplaceAllString(newConfig, "")
|
mergedConfig := exampleMonitorRegex.ReplaceAllString(newConfig, "")
|
||||||
|
|
||||||
// Find where to insert the monitor sections (after the MONITOR CONFIG header)
|
|
||||||
monitorHeaderRegex := regexp.MustCompile(`(?m)^# MONITOR CONFIG\n# ==================$`)
|
monitorHeaderRegex := regexp.MustCompile(`(?m)^# MONITOR CONFIG\n# ==================$`)
|
||||||
headerMatch := monitorHeaderRegex.FindStringIndex(mergedConfig)
|
headerMatch := monitorHeaderRegex.FindStringIndex(mergedConfig)
|
||||||
|
|
||||||
@@ -585,8 +542,7 @@ func (cd *ConfigDeployer) mergeHyprlandMonitorSections(newConfig, existingConfig
|
|||||||
return "", fmt.Errorf("could not find MONITOR CONFIG section")
|
return "", fmt.Errorf("could not find MONITOR CONFIG section")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert after the header
|
insertPos := headerMatch[1] + 1
|
||||||
insertPos := headerMatch[1] + 1 // +1 for the newline
|
|
||||||
|
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
builder.WriteString(mergedConfig[:insertPos])
|
builder.WriteString(mergedConfig[:insertPos])
|
||||||
@@ -601,3 +557,69 @@ func (cd *ConfigDeployer) mergeHyprlandMonitorSections(newConfig, existingConfig
|
|||||||
|
|
||||||
return builder.String(), nil
|
return builder.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cd *ConfigDeployer) transformHyprlandConfigForNonSystemd(config, terminalCommand string) string {
|
||||||
|
lines := strings.Split(config, "\n")
|
||||||
|
var result []string
|
||||||
|
startupSectionFound := false
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(trimmed, "exec-once = dbus-update-activation-environment") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(trimmed, "exec-once = systemctl --user start") {
|
||||||
|
startupSectionFound = true
|
||||||
|
result = append(result, "exec-once = dms run")
|
||||||
|
result = append(result, "env = QT_QPA_PLATFORM,wayland")
|
||||||
|
result = append(result, "env = ELECTRON_OZONE_PLATFORM_HINT,auto")
|
||||||
|
result = append(result, "env = QT_QPA_PLATFORMTHEME,gtk3")
|
||||||
|
result = append(result, "env = QT_QPA_PLATFORMTHEME_QT6,gtk3")
|
||||||
|
result = append(result, fmt.Sprintf("env = TERMINAL,%s", terminalCommand))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !startupSectionFound {
|
||||||
|
for i, line := range result {
|
||||||
|
if strings.Contains(line, "STARTUP APPS") {
|
||||||
|
insertLines := []string{
|
||||||
|
"exec-once = dms run",
|
||||||
|
"env = QT_QPA_PLATFORM,wayland",
|
||||||
|
"env = ELECTRON_OZONE_PLATFORM_HINT,auto",
|
||||||
|
"env = QT_QPA_PLATFORMTHEME,gtk3",
|
||||||
|
"env = QT_QPA_PLATFORMTHEME_QT6,gtk3",
|
||||||
|
fmt.Sprintf("env = TERMINAL,%s", terminalCommand),
|
||||||
|
}
|
||||||
|
result = append(result[:i+2], append(insertLines, result[i+2:]...)...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(result, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ConfigDeployer) transformNiriConfigForNonSystemd(config, terminalCommand string) string {
|
||||||
|
envVars := fmt.Sprintf(`environment {
|
||||||
|
XDG_CURRENT_DESKTOP "niri"
|
||||||
|
QT_QPA_PLATFORM "wayland"
|
||||||
|
ELECTRON_OZONE_PLATFORM_HINT "auto"
|
||||||
|
QT_QPA_PLATFORMTHEME "gtk3"
|
||||||
|
QT_QPA_PLATFORMTHEME_QT6 "gtk3"
|
||||||
|
TERMINAL "%s"
|
||||||
|
}`, terminalCommand)
|
||||||
|
|
||||||
|
config = regexp.MustCompile(`environment \{[^}]*\}`).ReplaceAllString(config, envVars)
|
||||||
|
|
||||||
|
spawnDms := `spawn-at-startup "dms" "run"`
|
||||||
|
if !strings.Contains(config, spawnDms) {
|
||||||
|
config = strings.Replace(config,
|
||||||
|
`spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &"`,
|
||||||
|
`spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &"`+"\n"+spawnDms,
|
||||||
|
1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package config
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
@@ -11,23 +10,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDetectPolkitAgent(t *testing.T) {
|
|
||||||
cd := &ConfigDeployer{}
|
|
||||||
|
|
||||||
// This test depends on the system having a polkit agent installed
|
|
||||||
// We'll just test that the function doesn't crash and returns some path or error
|
|
||||||
path, err := cd.detectPolkitAgent()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// If no polkit agent is found, that's okay for testing
|
|
||||||
assert.Contains(t, err.Error(), "no polkit agent found")
|
|
||||||
} else {
|
|
||||||
// If found, it should be a valid path
|
|
||||||
assert.NotEmpty(t, path)
|
|
||||||
assert.True(t, strings.Contains(path, "polkit"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeNiriOutputSections(t *testing.T) {
|
func TestMergeNiriOutputSections(t *testing.T) {
|
||||||
cd := &ConfigDeployer{}
|
cd := &ConfigDeployer{}
|
||||||
|
|
||||||
@@ -272,17 +254,6 @@ func getGhosttyPath() string {
|
|||||||
return filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config")
|
return filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolkitPathInjection(t *testing.T) {
|
|
||||||
|
|
||||||
testConfig := `spawn-at-startup "{{POLKIT_AGENT_PATH}}"
|
|
||||||
other content`
|
|
||||||
|
|
||||||
result := strings.Replace(testConfig, "{{POLKIT_AGENT_PATH}}", "/test/polkit/path", 1)
|
|
||||||
|
|
||||||
assert.Contains(t, result, `spawn-at-startup "/test/polkit/path"`)
|
|
||||||
assert.NotContains(t, result, "{{POLKIT_AGENT_PATH}}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeHyprlandMonitorSections(t *testing.T) {
|
func TestMergeHyprlandMonitorSections(t *testing.T) {
|
||||||
cd := &ConfigDeployer{}
|
cd := &ConfigDeployer{}
|
||||||
|
|
||||||
@@ -424,7 +395,7 @@ func TestHyprlandConfigDeployment(t *testing.T) {
|
|||||||
cd := NewConfigDeployer(logChan)
|
cd := NewConfigDeployer(logChan)
|
||||||
|
|
||||||
t.Run("deploy hyprland config to empty directory", func(t *testing.T) {
|
t.Run("deploy hyprland config to empty directory", func(t *testing.T) {
|
||||||
result, err := cd.deployHyprlandConfig(deps.TerminalGhostty)
|
result, err := cd.deployHyprlandConfig(deps.TerminalGhostty, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "Hyprland", result.ConfigType)
|
assert.Equal(t, "Hyprland", result.ConfigType)
|
||||||
@@ -435,7 +406,7 @@ func TestHyprlandConfigDeployment(t *testing.T) {
|
|||||||
content, err := os.ReadFile(result.Path)
|
content, err := os.ReadFile(result.Path)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, string(content), "# MONITOR CONFIG")
|
assert.Contains(t, string(content), "# MONITOR CONFIG")
|
||||||
assert.Contains(t, string(content), "bind = $mod, T, exec, $TERMINAL")
|
assert.Contains(t, string(content), "bind = $mod, T, exec, ghostty")
|
||||||
assert.Contains(t, string(content), "exec-once = ")
|
assert.Contains(t, string(content), "exec-once = ")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -454,7 +425,7 @@ general {
|
|||||||
err = os.WriteFile(hyprPath, []byte(existingContent), 0644)
|
err = os.WriteFile(hyprPath, []byte(existingContent), 0644)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result, err := cd.deployHyprlandConfig(deps.TerminalKitty)
|
result, err := cd.deployHyprlandConfig(deps.TerminalKitty, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "Hyprland", result.ConfigType)
|
assert.Equal(t, "Hyprland", result.ConfigType)
|
||||||
@@ -471,7 +442,7 @@ general {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, string(newContent), "monitor = DP-1, 1920x1080@144")
|
assert.Contains(t, string(newContent), "monitor = DP-1, 1920x1080@144")
|
||||||
assert.Contains(t, string(newContent), "monitor = HDMI-A-1, 3840x2160@60")
|
assert.Contains(t, string(newContent), "monitor = HDMI-A-1, 3840x2160@60")
|
||||||
assert.Contains(t, string(newContent), "bind = $mod, T, exec, $TERMINAL")
|
assert.Contains(t, string(newContent), "bind = $mod, T, exec, kitty")
|
||||||
assert.NotContains(t, string(newContent), "monitor = eDP-2")
|
assert.NotContains(t, string(newContent), "monitor = eDP-2")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -479,7 +450,6 @@ general {
|
|||||||
func TestNiriConfigStructure(t *testing.T) {
|
func TestNiriConfigStructure(t *testing.T) {
|
||||||
assert.Contains(t, NiriConfig, "input {")
|
assert.Contains(t, NiriConfig, "input {")
|
||||||
assert.Contains(t, NiriConfig, "layout {")
|
assert.Contains(t, NiriConfig, "layout {")
|
||||||
assert.Contains(t, NiriConfig, "{{POLKIT_AGENT_PATH}}")
|
|
||||||
|
|
||||||
assert.Contains(t, NiriBindsConfig, "binds {")
|
assert.Contains(t, NiriBindsConfig, "binds {")
|
||||||
assert.Contains(t, NiriBindsConfig, `spawn "{{TERMINAL_COMMAND}}"`)
|
assert.Contains(t, NiriBindsConfig, `spawn "{{TERMINAL_COMMAND}}"`)
|
||||||
@@ -490,11 +460,9 @@ func TestHyprlandConfigStructure(t *testing.T) {
|
|||||||
assert.Contains(t, HyprlandConfig, "# STARTUP APPS")
|
assert.Contains(t, HyprlandConfig, "# STARTUP APPS")
|
||||||
assert.Contains(t, HyprlandConfig, "# INPUT CONFIG")
|
assert.Contains(t, HyprlandConfig, "# INPUT CONFIG")
|
||||||
assert.Contains(t, HyprlandConfig, "# KEYBINDINGS")
|
assert.Contains(t, HyprlandConfig, "# KEYBINDINGS")
|
||||||
assert.Contains(t, HyprlandConfig, "{{POLKIT_AGENT_PATH}}")
|
assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec, {{TERMINAL_COMMAND}}")
|
||||||
assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec, $TERMINAL")
|
|
||||||
assert.Contains(t, HyprlandConfig, "bind = $mod, space, exec, dms ipc call spotlight toggle")
|
assert.Contains(t, HyprlandConfig, "bind = $mod, space, exec, dms ipc call spotlight toggle")
|
||||||
assert.Contains(t, HyprlandConfig, "windowrule {")
|
assert.Contains(t, HyprlandConfig, "windowrulev2 = noborder, class:^(com\\.mitchellh\\.ghostty)$")
|
||||||
assert.Contains(t, HyprlandConfig, "match:class = ^(com\\.mitchellh\\.ghostty)$")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGhosttyConfigStructure(t *testing.T) {
|
func TestGhosttyConfigStructure(t *testing.T) {
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ monitor = , preferred,auto,auto
|
|||||||
# ==================
|
# ==================
|
||||||
# STARTUP APPS
|
# STARTUP APPS
|
||||||
# ==================
|
# ==================
|
||||||
|
exec-once = dbus-update-activation-environment --systemd --all
|
||||||
|
exec-once = systemctl --user start hyprland-session.target
|
||||||
exec-once = bash -c "wl-paste --watch cliphist store &"
|
exec-once = bash -c "wl-paste --watch cliphist store &"
|
||||||
exec-once = {{POLKIT_AGENT_PATH}}
|
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
# INPUT CONFIG
|
# INPUT CONFIG
|
||||||
@@ -90,132 +91,36 @@ misc {
|
|||||||
# ==================
|
# ==================
|
||||||
# WINDOW RULES
|
# WINDOW RULES
|
||||||
# ==================
|
# ==================
|
||||||
windowrule {
|
windowrulev2 = tile, class:^(org\.wezfurlong\.wezterm)$
|
||||||
name = windowrule-1
|
|
||||||
tile = on
|
|
||||||
match:class = ^(org\.wezfurlong\.wezterm)$
|
|
||||||
border_size = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
windowrulev2 = rounding 12, class:^(org\.gnome\.)
|
||||||
|
windowrulev2 = noborder, class:^(org\.gnome\.)
|
||||||
|
|
||||||
windowrule {
|
windowrulev2 = tile, class:^(gnome-control-center)$
|
||||||
name = windowrule-2
|
windowrulev2 = tile, class:^(pavucontrol)$
|
||||||
rounding = 12
|
windowrulev2 = tile, class:^(nm-connection-editor)$
|
||||||
match:class = ^(org\.gnome\.)
|
|
||||||
border_size = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
windowrulev2 = float, class:^(gnome-calculator)$
|
||||||
|
windowrulev2 = float, class:^(galculator)$
|
||||||
|
windowrulev2 = float, class:^(blueman-manager)$
|
||||||
|
windowrulev2 = float, class:^(org\.gnome\.Nautilus)$
|
||||||
|
windowrulev2 = float, class:^(steam)$
|
||||||
|
windowrulev2 = float, class:^(xdg-desktop-portal)$
|
||||||
|
|
||||||
|
windowrulev2 = noborder, class:^(org\.wezfurlong\.wezterm)$
|
||||||
|
windowrulev2 = noborder, class:^(Alacritty)$
|
||||||
|
windowrulev2 = noborder, class:^(zen)$
|
||||||
|
windowrulev2 = noborder, class:^(com\.mitchellh\.ghostty)$
|
||||||
|
windowrulev2 = noborder, class:^(kitty)$
|
||||||
|
|
||||||
windowrule {
|
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
||||||
name = windowrule-3
|
windowrulev2 = float, class:^(zoom)$
|
||||||
tile = on
|
|
||||||
match:class = ^(gnome-control-center)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
# DMS windows floating by default
|
||||||
name = windowrule-4
|
windowrulev2 = float, class:^(org.quickshell)$
|
||||||
tile = on
|
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
||||||
match:class = ^(pavucontrol)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
layerrule = noanim, ^(quickshell)$
|
||||||
name = windowrule-5
|
|
||||||
tile = on
|
|
||||||
match:class = ^(nm-connection-editor)$
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-6
|
|
||||||
float = on
|
|
||||||
match:class = ^(gnome-calculator)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-7
|
|
||||||
float = on
|
|
||||||
match:class = ^(galculator)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-8
|
|
||||||
float = on
|
|
||||||
match:class = ^(blueman-manager)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-9
|
|
||||||
float = on
|
|
||||||
match:class = ^(org\.gnome\.Nautilus)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-10
|
|
||||||
float = on
|
|
||||||
match:class = ^(steam)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-11
|
|
||||||
float = on
|
|
||||||
match:class = ^(xdg-desktop-portal)$
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-12
|
|
||||||
border_size = 0
|
|
||||||
match:class = ^(Alacritty)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-13
|
|
||||||
border_size = 0
|
|
||||||
match:class = ^(zen)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-14
|
|
||||||
border_size = 0
|
|
||||||
match:class = ^(com\.mitchellh\.ghostty)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-15
|
|
||||||
border_size = 0
|
|
||||||
match:class = ^(kitty)$
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-16
|
|
||||||
float = on
|
|
||||||
match:class = ^(firefox)$
|
|
||||||
match:title = ^(Picture-in-Picture)$
|
|
||||||
}
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-17
|
|
||||||
float = on
|
|
||||||
match:class = ^(zoom)$
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
windowrule {
|
|
||||||
name = windowrule-18
|
|
||||||
opacity = 0.9 0.9
|
|
||||||
match:float = 0
|
|
||||||
match:focus = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
layerrule {
|
|
||||||
name = layerrule-1
|
|
||||||
no_anim = on
|
|
||||||
match:namespace = ^(quickshell)$
|
|
||||||
}
|
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
# KEYBINDINGS
|
# KEYBINDINGS
|
||||||
@@ -223,7 +128,7 @@ layerrule {
|
|||||||
$mod = SUPER
|
$mod = SUPER
|
||||||
|
|
||||||
# === Application Launchers ===
|
# === Application Launchers ===
|
||||||
bind = $mod, T, exec, $TERMINAL
|
bind = $mod, T, exec, {{TERMINAL_COMMAND}}
|
||||||
bind = $mod, space, exec, dms ipc call spotlight toggle
|
bind = $mod, space, exec, dms ipc call spotlight toggle
|
||||||
bind = $mod, V, exec, dms ipc call clipboard toggle
|
bind = $mod, V, exec, dms ipc call clipboard toggle
|
||||||
bind = $mod, M, exec, dms ipc call processlist focusOrToggle
|
bind = $mod, M, exec, dms ipc call processlist focusOrToggle
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ input {
|
|||||||
// https://github.com/YaLTeR/niri/wiki/Configuration:-Layout
|
// https://github.com/YaLTeR/niri/wiki/Configuration:-Layout
|
||||||
layout {
|
layout {
|
||||||
// Set gaps around windows in logical pixels.
|
// Set gaps around windows in logical pixels.
|
||||||
gaps 5
|
|
||||||
background-color "transparent"
|
background-color "transparent"
|
||||||
// When to center a column when changing focus, options are:
|
// When to center a column when changing focus, options are:
|
||||||
// - "never", default behavior, focusing an off-screen column will keep at the left
|
// - "never", default behavior, focusing an off-screen column will keep at the left
|
||||||
@@ -87,11 +86,6 @@ layout {
|
|||||||
inactive-color "#d0d0d0" // Light gray
|
inactive-color "#d0d0d0" // Light gray
|
||||||
urgent-color "#cc4444" // Softer red
|
urgent-color "#cc4444" // Softer red
|
||||||
}
|
}
|
||||||
focus-ring {
|
|
||||||
width 2
|
|
||||||
active-color "#808080" // Medium gray
|
|
||||||
inactive-color "#505050" // Dark gray
|
|
||||||
}
|
|
||||||
shadow {
|
shadow {
|
||||||
softness 30
|
softness 30
|
||||||
spread 5
|
spread 5
|
||||||
@@ -116,7 +110,6 @@ overview {
|
|||||||
// See the binds section below for more spawn examples.
|
// See the binds section below for more spawn examples.
|
||||||
// This line starts waybar, a commonly used bar for Wayland compositors.
|
// This line starts waybar, a commonly used bar for Wayland compositors.
|
||||||
spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &"
|
spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &"
|
||||||
spawn-at-startup "{{POLKIT_AGENT_PATH}}"
|
|
||||||
environment {
|
environment {
|
||||||
XDG_CURRENT_DESKTOP "niri"
|
XDG_CURRENT_DESKTOP "niri"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,13 +182,11 @@ func (a *ArchDistribution) getQuickshellMapping(variant deps.PackageVariant) Pac
|
|||||||
if forceQuickshellGit || variant == deps.VariantGit {
|
if forceQuickshellGit || variant == deps.VariantGit {
|
||||||
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
|
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
|
||||||
}
|
}
|
||||||
return PackageMapping{Name: "quickshell", Repository: RepoTypeSystem}
|
// ! TODO - for now we're only forcing quickshell-git on ARCH, as other distros use DL repos which pin a newer quickshell
|
||||||
|
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) getHyprlandMapping(variant deps.PackageVariant) PackageMapping {
|
func (a *ArchDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
|
||||||
if variant == deps.VariantGit {
|
|
||||||
return PackageMapping{Name: "hyprland-git", Repository: RepoTypeAUR}
|
|
||||||
}
|
|
||||||
return PackageMapping{Name: "hyprland", Repository: RepoTypeSystem}
|
return PackageMapping{Name: "hyprland", Repository: RepoTypeSystem}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +360,10 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
|
|||||||
a.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
a.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := a.WriteWindowManagerConfig(wm); err != nil {
|
||||||
|
a.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
if err := a.EnableDMSService(ctx); err != nil {
|
if err := a.EnableDMSService(ctx); err != nil {
|
||||||
a.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
a.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -611,6 +611,41 @@ func (b *BaseDistribution) EnableDMSService(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseDistribution) WriteWindowManagerConfig(wm deps.WindowManager) error {
|
||||||
|
if wm == deps.WindowManagerHyprland {
|
||||||
|
if err := b.WriteHyprlandSessionTarget(); err != nil {
|
||||||
|
return fmt.Errorf("failed to write hyprland session target: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseDistribution) WriteHyprlandSessionTarget() error {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get home directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetDir := filepath.Join(homeDir, ".config", "systemd", "user")
|
||||||
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create systemd user directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(targetDir, "hyprland-session.target")
|
||||||
|
content := `[Unit]
|
||||||
|
Description=Hyprland Session Target
|
||||||
|
Requires=graphical-session.target
|
||||||
|
After=graphical-session.target
|
||||||
|
`
|
||||||
|
|
||||||
|
if err := os.WriteFile(targetPath, []byte(content), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write hyprland-session.target: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.log(fmt.Sprintf("Wrote hyprland-session.target to %s", targetPath))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// installDMSBinary installs the DMS binary from GitHub releases
|
// installDMSBinary installs the DMS binary from GitHub releases
|
||||||
func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
b.log("Installing/updating DMS binary...")
|
b.log("Installing/updating DMS binary...")
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
|
|
||||||
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
|
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
|
||||||
if err := checkCmd.Run(); err != nil {
|
if err := checkCmd.Run(); err != nil {
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y build-essential")
|
cmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential")
|
||||||
if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
|
if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
|
||||||
return fmt.Errorf("failed to install build-essential: %w", err)
|
return fmt.Errorf("failed to install build-essential: %w", err)
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
}
|
}
|
||||||
|
|
||||||
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
||||||
"apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||||
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||||
return fmt.Errorf("failed to install development tools: %w", err)
|
return fmt.Errorf("failed to install development tools: %w", err)
|
||||||
}
|
}
|
||||||
@@ -338,6 +338,10 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
d.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
d.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := d.WriteWindowManagerConfig(wm); err != nil {
|
||||||
|
d.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
if err := d.EnableDMSService(ctx); err != nil {
|
if err := d.EnableDMSService(ctx); err != nil {
|
||||||
d.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
d.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
@@ -449,7 +453,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL),
|
CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
keyCmd := fmt.Sprintf("curl -fsSL %s/Release.key | gpg --dearmor -o %s", baseURL, keyringPath)
|
keyCmd := fmt.Sprintf("bash -c 'rm -f %s && curl -fsSL %s/Release.key | gpg --batch --dearmor -o %s'", keyringPath, baseURL, keyringPath)
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd)
|
cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd)
|
||||||
if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
|
if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
|
||||||
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
||||||
@@ -467,7 +471,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRepoCmd := ExecSudoCommand(ctx, sudoPassword,
|
addRepoCmd := ExecSudoCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("echo '%s' | tee %s", repoLine, listFile))
|
fmt.Sprintf("bash -c \"echo '%s' | tee %s\"", repoLine, listFile))
|
||||||
if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||||
return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
|
return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
|
||||||
}
|
}
|
||||||
@@ -502,7 +506,7 @@ func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []
|
|||||||
|
|
||||||
d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
args := []string{"apt-get", "install", "-y"}
|
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
||||||
args = append(args, packages...)
|
args = append(args, packages...)
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
@@ -612,7 +616,7 @@ func (d *DebianDistribution) installRust(ctx context.Context, sudoPassword strin
|
|||||||
CommandInfo: "sudo apt-get install rustup",
|
CommandInfo: "sudo apt-get install rustup",
|
||||||
}
|
}
|
||||||
|
|
||||||
rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y rustup")
|
rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y rustup")
|
||||||
if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
||||||
return fmt.Errorf("failed to install rustup: %w", err)
|
return fmt.Errorf("failed to install rustup: %w", err)
|
||||||
}
|
}
|
||||||
@@ -651,7 +655,7 @@ func (d *DebianDistribution) installGo(ctx context.Context, sudoPassword string,
|
|||||||
CommandInfo: "sudo apt-get install golang-go",
|
CommandInfo: "sudo apt-get install golang-go",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y golang-go")
|
installCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y golang-go")
|
||||||
return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90)
|
return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,10 +166,7 @@ func (f *FedoraDistribution) getDmsMapping(variant deps.PackageVariant) PackageM
|
|||||||
return PackageMapping{Name: "dms", Repository: RepoTypeCOPR, RepoURL: "avengemedia/dms"}
|
return PackageMapping{Name: "dms", Repository: RepoTypeCOPR, RepoURL: "avengemedia/dms"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) getHyprlandMapping(variant deps.PackageVariant) PackageMapping {
|
func (f *FedoraDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
|
||||||
if variant == deps.VariantGit {
|
|
||||||
return PackageMapping{Name: "hyprland-git", Repository: RepoTypeCOPR, RepoURL: "solopasha/hyprland"}
|
|
||||||
}
|
|
||||||
return PackageMapping{Name: "hyprland", Repository: RepoTypeCOPR, RepoURL: "solopasha/hyprland"}
|
return PackageMapping{Name: "hyprland", Repository: RepoTypeCOPR, RepoURL: "solopasha/hyprland"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +174,7 @@ func (f *FedoraDistribution) getNiriMapping(variant deps.PackageVariant) Package
|
|||||||
if variant == deps.VariantGit {
|
if variant == deps.VariantGit {
|
||||||
return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri-git"}
|
return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri-git"}
|
||||||
}
|
}
|
||||||
return PackageMapping{Name: "niri", Repository: RepoTypeSystem}
|
return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency {
|
func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||||
@@ -362,6 +359,10 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
f.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
f.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := f.WriteWindowManagerConfig(wm); err != nil {
|
||||||
|
f.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
if err := f.EnableDMSService(ctx); err != nil {
|
if err := f.EnableDMSService(ctx); err != nil {
|
||||||
f.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
f.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ func (g *GentooDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
dependencies = append(dependencies, g.detectWindowManager(wm))
|
dependencies = append(dependencies, g.detectWindowManager(wm))
|
||||||
dependencies = append(dependencies, g.detectQuickshell())
|
dependencies = append(dependencies, g.detectQuickshell())
|
||||||
dependencies = append(dependencies, g.detectXDGPortal())
|
dependencies = append(dependencies, g.detectXDGPortal())
|
||||||
dependencies = append(dependencies, g.detectPolkitAgent())
|
|
||||||
dependencies = append(dependencies, g.detectAccountsService())
|
dependencies = append(dependencies, g.detectAccountsService())
|
||||||
|
|
||||||
if wm == deps.WindowManagerHyprland {
|
if wm == deps.WindowManagerHyprland {
|
||||||
@@ -127,20 +126,6 @@ func (g *GentooDistribution) detectXDGPortal() deps.Dependency {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) detectPolkitAgent() deps.Dependency {
|
|
||||||
status := deps.StatusMissing
|
|
||||||
if g.packageInstalled("mate-extra/mate-polkit") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "mate-polkit",
|
|
||||||
Status: status,
|
|
||||||
Description: "PolicyKit authentication agent",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GentooDistribution) detectXwaylandSatellite() deps.Dependency {
|
func (g *GentooDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
status := deps.StatusMissing
|
||||||
if g.packageInstalled("gui-apps/xwayland-satellite") {
|
if g.packageInstalled("gui-apps/xwayland-satellite") {
|
||||||
@@ -187,7 +172,6 @@ func (g *GentooDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
|
|||||||
"alacritty": {Name: "x11-terms/alacritty", Repository: RepoTypeSystem, UseFlags: "X wayland"},
|
"alacritty": {Name: "x11-terms/alacritty", Repository: RepoTypeSystem, UseFlags: "X wayland"},
|
||||||
"wl-clipboard": {Name: "gui-apps/wl-clipboard", Repository: RepoTypeSystem},
|
"wl-clipboard": {Name: "gui-apps/wl-clipboard", Repository: RepoTypeSystem},
|
||||||
"xdg-desktop-portal-gtk": {Name: "sys-apps/xdg-desktop-portal-gtk", Repository: RepoTypeSystem, UseFlags: "wayland X"},
|
"xdg-desktop-portal-gtk": {Name: "sys-apps/xdg-desktop-portal-gtk", Repository: RepoTypeSystem, UseFlags: "wayland X"},
|
||||||
"mate-polkit": {Name: "mate-extra/mate-polkit", Repository: RepoTypeSystem},
|
|
||||||
"accountsservice": {Name: "sys-apps/accountsservice", Repository: RepoTypeSystem},
|
"accountsservice": {Name: "sys-apps/accountsservice", Repository: RepoTypeSystem},
|
||||||
|
|
||||||
"qtbase": {Name: "dev-qt/qtbase", Repository: RepoTypeSystem, UseFlags: "wayland opengl vulkan widgets"},
|
"qtbase": {Name: "dev-qt/qtbase", Repository: RepoTypeSystem, UseFlags: "wayland opengl vulkan widgets"},
|
||||||
@@ -223,12 +207,8 @@ func (g *GentooDistribution) getDmsMapping(_ deps.PackageVariant) PackageMapping
|
|||||||
return PackageMapping{Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"}
|
return PackageMapping{Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) getHyprlandMapping(variant deps.PackageVariant) PackageMapping {
|
func (g *GentooDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
|
||||||
archKeyword := g.getArchKeyword()
|
return PackageMapping{Name: "gui-wm/hyprland", Repository: RepoTypeSystem, UseFlags: "X", AcceptKeywords: g.getArchKeyword()}
|
||||||
if variant == deps.VariantGit {
|
|
||||||
return PackageMapping{Name: "gui-wm/hyprland", Repository: RepoTypeGURU, UseFlags: "X", AcceptKeywords: archKeyword}
|
|
||||||
}
|
|
||||||
return PackageMapping{Name: "gui-wm/hyprland", Repository: RepoTypeSystem, UseFlags: "X", AcceptKeywords: archKeyword}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) getNiriMapping(_ deps.PackageVariant) PackageMapping {
|
func (g *GentooDistribution) getNiriMapping(_ deps.PackageVariant) PackageMapping {
|
||||||
@@ -456,6 +436,10 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
g.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
g.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := g.WriteWindowManagerConfig(wm); err != nil {
|
||||||
|
g.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
if err := g.EnableDMSService(ctx); err != nil {
|
if err := g.EnableDMSService(ctx); err != nil {
|
||||||
g.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
g.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -377,6 +377,10 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
|||||||
o.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
o.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := o.WriteWindowManagerConfig(wm); err != nil {
|
||||||
|
o.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
if err := o.EnableDMSService(ctx); err != nil {
|
if err := o.EnableDMSService(ctx); err != nil {
|
||||||
o.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
o.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
@@ -472,7 +476,7 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
|
|||||||
cmd := ExecSudoCommand(ctx, sudoPassword,
|
cmd := ExecSudoCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("zypper addrepo -f %s", repoURL))
|
fmt.Sprintf("zypper addrepo -f %s", repoURL))
|
||||||
if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||||
return fmt.Errorf("failed to enable OBS repo %s: %w", pkg.RepoURL, err)
|
o.log(fmt.Sprintf("OBS repo %s add failed (may already exist): %v", pkg.RepoURL, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledRepos[pkg.RepoURL] = true
|
enabledRepos[pkg.RepoURL] = true
|
||||||
|
|||||||
@@ -357,6 +357,10 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
|
|||||||
u.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
u.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := u.WriteWindowManagerConfig(wm); err != nil {
|
||||||
|
u.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
if err := u.EnableDMSService(ctx); err != nil {
|
if err := u.EnableDMSService(ctx); err != nil {
|
||||||
u.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
u.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||||
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
)
|
)
|
||||||
@@ -89,12 +90,15 @@ func SetCompositorDWL() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WindowGeometry struct {
|
type WindowGeometry struct {
|
||||||
X int32
|
X int32
|
||||||
Y int32
|
Y int32
|
||||||
Width int32
|
Width int32
|
||||||
Height int32
|
Height int32
|
||||||
Output string
|
Output string
|
||||||
Scale float64
|
Scale float64
|
||||||
|
OutputX int32
|
||||||
|
OutputY int32
|
||||||
|
OutputTransform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActiveWindow() (*WindowGeometry, error) {
|
func GetActiveWindow() (*WindowGeometry, error) {
|
||||||
@@ -382,6 +386,92 @@ func GetFocusedMonitor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type outputInfo struct {
|
||||||
|
x, y int32
|
||||||
|
transform int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOutputInfo(outputName string) (*outputInfo, bool) {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputManager *wlr_output_management.ZwlrOutputManagerV1
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName {
|
||||||
|
mgr := wlr_output_management.NewZwlrOutputManagerV1(ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, mgr); err == nil {
|
||||||
|
outputManager = mgr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputManager == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
type headState struct {
|
||||||
|
name string
|
||||||
|
x, y int32
|
||||||
|
transform int32
|
||||||
|
}
|
||||||
|
heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState)
|
||||||
|
done := false
|
||||||
|
|
||||||
|
outputManager.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
|
||||||
|
state := &headState{}
|
||||||
|
heads[e.Head] = state
|
||||||
|
e.Head.SetNameHandler(func(ne wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
||||||
|
state.name = ne.Name
|
||||||
|
})
|
||||||
|
e.Head.SetPositionHandler(func(pe wlr_output_management.ZwlrOutputHeadV1PositionEvent) {
|
||||||
|
state.x = pe.X
|
||||||
|
state.y = pe.Y
|
||||||
|
})
|
||||||
|
e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
|
||||||
|
state.transform = te.Transform
|
||||||
|
})
|
||||||
|
})
|
||||||
|
outputManager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
|
||||||
|
done = true
|
||||||
|
})
|
||||||
|
|
||||||
|
for !done {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range heads {
|
||||||
|
if state.name == outputName {
|
||||||
|
return &outputInfo{
|
||||||
|
x: state.x,
|
||||||
|
y: state.y,
|
||||||
|
transform: state.transform,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
func getDWLActiveWindow() (*WindowGeometry, error) {
|
func getDWLActiveWindow() (*WindowGeometry, error) {
|
||||||
display, err := client.Connect("")
|
display, err := client.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -509,14 +599,23 @@ func getDWLActiveWindow() (*WindowGeometry, error) {
|
|||||||
if scale <= 0 {
|
if scale <= 0 {
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
}
|
}
|
||||||
return &WindowGeometry{
|
|
||||||
|
geom := &WindowGeometry{
|
||||||
X: state.x,
|
X: state.x,
|
||||||
Y: state.y,
|
Y: state.y,
|
||||||
Width: state.w,
|
Width: state.w,
|
||||||
Height: state.h,
|
Height: state.h,
|
||||||
Output: state.name,
|
Output: state.name,
|
||||||
Scale: scale,
|
Scale: scale,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if info, ok := getOutputInfo(state.name); ok {
|
||||||
|
geom.OutputX = info.x
|
||||||
|
geom.OutputY = info.y
|
||||||
|
geom.OutputTransform = info.transform
|
||||||
|
}
|
||||||
|
|
||||||
|
return geom, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no active output found")
|
return nil, fmt.Errorf("no active output found")
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ func BufferToImageWithFormat(buf *ShmBuffer, format uint32) *image.RGBA {
|
|||||||
img := image.NewRGBA(image.Rect(0, 0, buf.Width, buf.Height))
|
img := image.NewRGBA(image.Rect(0, 0, buf.Width, buf.Height))
|
||||||
data := buf.Data()
|
data := buf.Data()
|
||||||
|
|
||||||
swapRB := format == uint32(FormatARGB8888) || format == uint32(FormatXRGB8888) || format == 0
|
var swapRB bool
|
||||||
|
switch format {
|
||||||
|
case uint32(FormatABGR8888), uint32(FormatXBGR8888):
|
||||||
|
swapRB = false
|
||||||
|
default:
|
||||||
|
swapRB = true
|
||||||
|
}
|
||||||
|
|
||||||
for y := 0; y < buf.Height; y++ {
|
for y := 0; y < buf.Height; y++ {
|
||||||
srcOff := y * buf.Stride
|
srcOff := y * buf.Stride
|
||||||
|
|||||||
@@ -380,19 +380,24 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var capturedBuf *ShmBuffer
|
||||||
|
|
||||||
|
var capturedFormat PixelFormat
|
||||||
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
||||||
|
capturedFormat = PixelFormat(e.Format)
|
||||||
|
bpp := capturedFormat.BytesPerPixel()
|
||||||
|
if int(e.Stride) < int(e.Width)*bpp {
|
||||||
|
log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp)
|
||||||
|
return
|
||||||
|
}
|
||||||
buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("create screen buffer failed", "err", err)
|
log.Error("create screen buffer failed", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if withCursor {
|
capturedBuf = buf
|
||||||
pc.screenBuf = buf
|
buf.Format = capturedFormat
|
||||||
pc.format = e.Format
|
|
||||||
} else {
|
|
||||||
pc.screenBufNoCursor = buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
|
pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -421,6 +426,47 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture,
|
|||||||
|
|
||||||
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
||||||
frame.Destroy()
|
frame.Destroy()
|
||||||
|
|
||||||
|
if capturedBuf == nil {
|
||||||
|
onReady()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if capturedFormat.Is24Bit() {
|
||||||
|
converted, newFormat, err := capturedBuf.ConvertTo32Bit(capturedFormat)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("convert 24-bit to 32-bit failed", "err", err)
|
||||||
|
} else if converted != capturedBuf {
|
||||||
|
capturedBuf.Close()
|
||||||
|
capturedBuf = converted
|
||||||
|
capturedFormat = newFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.format = uint32(capturedFormat)
|
||||||
|
|
||||||
|
if pc.yInverted {
|
||||||
|
capturedBuf.FlipVertical()
|
||||||
|
pc.yInverted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.transform != TransformNormal {
|
||||||
|
invTransform := InverseTransform(output.transform)
|
||||||
|
transformed, err := capturedBuf.ApplyTransform(invTransform)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("apply transform failed", "err", err)
|
||||||
|
} else if transformed != capturedBuf {
|
||||||
|
capturedBuf.Close()
|
||||||
|
capturedBuf = transformed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if withCursor {
|
||||||
|
pc.screenBuf = capturedBuf
|
||||||
|
} else {
|
||||||
|
pc.screenBufNoCursor = capturedBuf
|
||||||
|
}
|
||||||
|
|
||||||
onReady()
|
onReady()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -150,51 +150,33 @@ func (s *Screenshoter) captureWindow() (*CaptureResult, error) {
|
|||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
return s.captureAndCrop(output, region)
|
return s.captureAndCrop(output, region)
|
||||||
case CompositorDWL:
|
case CompositorDWL:
|
||||||
return s.captureDWLWindow(output, region, geom.Scale)
|
return s.captureDWLWindow(output, region, geom)
|
||||||
default:
|
default:
|
||||||
return s.captureRegionOnOutput(output, region)
|
return s.captureRegionOnOutput(output, region)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, dwlScale float64) (*CaptureResult, error) {
|
func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) {
|
||||||
result, err := s.captureWholeOutput(output)
|
result, err := s.captureWholeOutput(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scale := dwlScale
|
scale := geom.Scale
|
||||||
if scale <= 0 {
|
if scale <= 0 || scale == 1.0 {
|
||||||
scale = float64(result.Buffer.Width) / float64(output.width)
|
if output.fractionalScale > 1.0 {
|
||||||
|
scale = output.fractionalScale
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if scale <= 0 {
|
if scale <= 0 {
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
localX := int(float64(region.X) * scale)
|
localX := int(float64(region.X-geom.OutputX) * scale)
|
||||||
localY := int(float64(region.Y) * scale)
|
localY := int(float64(region.Y-geom.OutputY) * scale)
|
||||||
if localX >= result.Buffer.Width {
|
|
||||||
localX = localX % result.Buffer.Width
|
|
||||||
}
|
|
||||||
if localY >= result.Buffer.Height {
|
|
||||||
localY = localY % result.Buffer.Height
|
|
||||||
}
|
|
||||||
|
|
||||||
w := int(float64(region.Width) * scale)
|
w := int(float64(region.Width) * scale)
|
||||||
h := int(float64(region.Height) * scale)
|
h := int(float64(region.Height) * scale)
|
||||||
|
|
||||||
if localY+h > result.Buffer.Height && h <= result.Buffer.Height {
|
|
||||||
localY = result.Buffer.Height - h
|
|
||||||
if localY < 0 {
|
|
||||||
localY = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if localX+w > result.Buffer.Width && w <= result.Buffer.Width {
|
|
||||||
localX = result.Buffer.Width - w
|
|
||||||
if localX < 0 {
|
|
||||||
localX = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if localX < 0 {
|
if localX < 0 {
|
||||||
w += localX
|
w += localX
|
||||||
localX = 0
|
localX = 0
|
||||||
@@ -342,13 +324,18 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
|
|||||||
|
|
||||||
outX, outY := output.x, output.y
|
outX, outY := output.x, output.y
|
||||||
scale := float64(output.scale)
|
scale := float64(output.scale)
|
||||||
if DetectCompositor() == CompositorHyprland {
|
switch DetectCompositor() {
|
||||||
|
case CompositorHyprland:
|
||||||
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
|
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
|
||||||
outX, outY = hx, hy
|
outX, outY = hx, hy
|
||||||
}
|
}
|
||||||
if s := GetHyprlandMonitorScale(output.name); s > 0 {
|
if s := GetHyprlandMonitorScale(output.name); s > 0 {
|
||||||
scale = s
|
scale = s
|
||||||
}
|
}
|
||||||
|
case CompositorDWL:
|
||||||
|
if info, ok := getOutputInfo(output.name); ok {
|
||||||
|
outX, outY = info.x, info.y
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if scale <= 0 {
|
if scale <= 0 {
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
@@ -476,13 +463,42 @@ func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult
|
|||||||
return nil, fmt.Errorf("capture output: %w", err)
|
return nil, fmt.Errorf("capture output: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.processFrame(frame, Region{
|
result, err := s.processFrame(frame, Region{
|
||||||
X: output.x,
|
X: output.x,
|
||||||
Y: output.y,
|
Y: output.y,
|
||||||
Width: output.width,
|
Width: output.width,
|
||||||
Height: output.height,
|
Height: output.height,
|
||||||
Output: output.name,
|
Output: output.name,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.YInverted {
|
||||||
|
result.Buffer.FlipVertical()
|
||||||
|
result.YInverted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.transform == TransformNormal {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
invTransform := InverseTransform(output.transform)
|
||||||
|
transformed, err := result.Buffer.ApplyTransform(invTransform)
|
||||||
|
if err != nil {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("apply transform: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transformed != result.Buffer {
|
||||||
|
result.Buffer.Close()
|
||||||
|
result.Buffer = transformed
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Region.Width = int32(transformed.Width)
|
||||||
|
result.Region.Height = int32(transformed.Height)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
@@ -563,6 +579,10 @@ func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*Ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
|
if output.transform != TransformNormal {
|
||||||
|
return s.captureRegionOnTransformedOutput(output, region)
|
||||||
|
}
|
||||||
|
|
||||||
scale := output.fractionalScale
|
scale := output.fractionalScale
|
||||||
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
||||||
scale = GetHyprlandMonitorScale(output.name)
|
scale = GetHyprlandMonitorScale(output.name)
|
||||||
@@ -617,6 +637,76 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
|||||||
return s.processFrame(frame, region)
|
return s.processFrame(frame, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
|
result, err := s.captureWholeOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := output.fractionalScale
|
||||||
|
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
||||||
|
scale = GetHyprlandMonitorScale(output.name)
|
||||||
|
}
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = float64(output.scale)
|
||||||
|
}
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
localX := int(float64(region.X-output.x) * scale)
|
||||||
|
localY := int(float64(region.Y-output.y) * scale)
|
||||||
|
w := int(float64(region.Width) * scale)
|
||||||
|
h := int(float64(region.Height) * scale)
|
||||||
|
|
||||||
|
if localX < 0 {
|
||||||
|
w += localX
|
||||||
|
localX = 0
|
||||||
|
}
|
||||||
|
if localY < 0 {
|
||||||
|
h += localY
|
||||||
|
localY = 0
|
||||||
|
}
|
||||||
|
if localX+w > result.Buffer.Width {
|
||||||
|
w = result.Buffer.Width - localX
|
||||||
|
}
|
||||||
|
if localY+h > result.Buffer.Height {
|
||||||
|
h = result.Buffer.Height - localY
|
||||||
|
}
|
||||||
|
|
||||||
|
if w <= 0 || h <= 0 {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("region not visible on output")
|
||||||
|
}
|
||||||
|
|
||||||
|
cropped, err := CreateShmBuffer(w, h, w*4)
|
||||||
|
if err != nil {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("create crop buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcData := result.Buffer.Data()
|
||||||
|
dstData := cropped.Data()
|
||||||
|
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
srcOff := (localY+y)*result.Buffer.Stride + localX*4
|
||||||
|
dstOff := y * cropped.Stride
|
||||||
|
if srcOff+w*4 <= len(srcData) && dstOff+w*4 <= len(dstData) {
|
||||||
|
copy(dstData[dstOff:dstOff+w*4], srcData[srcOff:srcOff+w*4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Buffer.Close()
|
||||||
|
cropped.Format = PixelFormat(result.Format)
|
||||||
|
|
||||||
|
return &CaptureResult{
|
||||||
|
Buffer: cropped,
|
||||||
|
Region: region,
|
||||||
|
YInverted: false,
|
||||||
|
Format: result.Format,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, region Region) (*CaptureResult, error) {
|
func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, region Region) (*CaptureResult, error) {
|
||||||
var buf *ShmBuffer
|
var buf *ShmBuffer
|
||||||
var pool *client.ShmPool
|
var pool *client.ShmPool
|
||||||
@@ -627,13 +717,18 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
|
|||||||
failed := false
|
failed := false
|
||||||
|
|
||||||
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
||||||
|
format = PixelFormat(e.Format)
|
||||||
|
bpp := format.BytesPerPixel()
|
||||||
|
if int(e.Stride) < int(e.Width)*bpp {
|
||||||
|
log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp)
|
||||||
|
return
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
buf, err = CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
buf, err = CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to create buffer", "err", err)
|
log.Error("failed to create buffer", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
format = PixelFormat(e.Format)
|
|
||||||
buf.Format = format
|
buf.Format = format
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -696,6 +791,19 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
|
|||||||
return nil, fmt.Errorf("frame capture failed")
|
return nil, fmt.Errorf("frame capture failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if format.Is24Bit() {
|
||||||
|
converted, newFormat, err := buf.ConvertTo32Bit(format)
|
||||||
|
if err != nil {
|
||||||
|
buf.Close()
|
||||||
|
return nil, fmt.Errorf("convert 24-bit to 32-bit: %w", err)
|
||||||
|
}
|
||||||
|
if converted != buf {
|
||||||
|
buf.Close()
|
||||||
|
buf = converted
|
||||||
|
}
|
||||||
|
format = newFormat
|
||||||
|
}
|
||||||
|
|
||||||
return &CaptureResult{
|
return &CaptureResult{
|
||||||
Buffer: buf,
|
Buffer: buf,
|
||||||
Region: region,
|
Region: region,
|
||||||
@@ -924,16 +1032,32 @@ func ListOutputs() ([]Output, error) {
|
|||||||
sc.outputsMu.Lock()
|
sc.outputsMu.Lock()
|
||||||
defer sc.outputsMu.Unlock()
|
defer sc.outputsMu.Unlock()
|
||||||
|
|
||||||
|
compositor := DetectCompositor()
|
||||||
result := make([]Output, 0, len(sc.outputs))
|
result := make([]Output, 0, len(sc.outputs))
|
||||||
for _, o := range sc.outputs {
|
for _, o := range sc.outputs {
|
||||||
result = append(result, Output{
|
out := Output{
|
||||||
Name: o.name,
|
Name: o.name,
|
||||||
X: o.x,
|
X: o.x,
|
||||||
Y: o.y,
|
Y: o.y,
|
||||||
Width: o.width,
|
Width: o.width,
|
||||||
Height: o.height,
|
Height: o.height,
|
||||||
Scale: o.scale,
|
Scale: o.scale,
|
||||||
})
|
FractionalScale: o.fractionalScale,
|
||||||
|
Transform: o.transform,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch compositor {
|
||||||
|
case CompositorHyprland:
|
||||||
|
if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok {
|
||||||
|
out.X, out.Y = hx, hy
|
||||||
|
out.Width, out.Height = hw, hh
|
||||||
|
}
|
||||||
|
if s := GetHyprlandMonitorScale(o.name); s > 0 {
|
||||||
|
out.FractionalScale = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, out)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,19 @@ const (
|
|||||||
FormatXRGB8888 = shm.FormatXRGB8888
|
FormatXRGB8888 = shm.FormatXRGB8888
|
||||||
FormatABGR8888 = shm.FormatABGR8888
|
FormatABGR8888 = shm.FormatABGR8888
|
||||||
FormatXBGR8888 = shm.FormatXBGR8888
|
FormatXBGR8888 = shm.FormatXBGR8888
|
||||||
|
FormatRGB888 = shm.FormatRGB888
|
||||||
|
FormatBGR888 = shm.FormatBGR888
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransformNormal = shm.TransformNormal
|
||||||
|
Transform90 = shm.Transform90
|
||||||
|
Transform180 = shm.Transform180
|
||||||
|
Transform270 = shm.Transform270
|
||||||
|
TransformFlipped = shm.TransformFlipped
|
||||||
|
TransformFlipped90 = shm.TransformFlipped90
|
||||||
|
TransformFlipped180 = shm.TransformFlipped180
|
||||||
|
TransformFlipped270 = shm.TransformFlipped270
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShmBuffer = shm.Buffer
|
type ShmBuffer = shm.Buffer
|
||||||
@@ -16,3 +29,7 @@ type ShmBuffer = shm.Buffer
|
|||||||
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
||||||
return shm.CreateBuffer(width, height, stride)
|
return shm.CreateBuffer(width, height, stride)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InverseTransform(transform int32) int32 {
|
||||||
|
return shm.InverseTransform(transform)
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ func (r Region) IsEmpty() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
Name string
|
Name string
|
||||||
X, Y int32
|
X, Y int32
|
||||||
Width int32
|
Width int32
|
||||||
Height int32
|
Height int32
|
||||||
Scale int32
|
Scale int32
|
||||||
|
FractionalScale float64
|
||||||
|
Transform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|||||||
@@ -306,6 +306,15 @@ func (m *Manager) readAndUpdateCapsLockState(deviceIndex int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ledStates) == 0 {
|
||||||
|
log.Debug("No LED state available (empty map)")
|
||||||
|
|
||||||
|
// This means the device either:
|
||||||
|
// - doesn't support LED reporting at all, or
|
||||||
|
// - the kernel returned an empty state
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
capsLockState := ledStates[ledCapslockKey]
|
capsLockState := ledStates[ledCapslockKey]
|
||||||
m.updateCapsLockStateDirect(capsLockState)
|
m.updateCapsLockStateDirect(capsLockState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -562,53 +562,62 @@ func (m *Manager) transitionWorker() {
|
|||||||
case <-m.stopChan:
|
case <-m.stopChan:
|
||||||
return
|
return
|
||||||
case targetTemp := <-m.transitionChan:
|
case targetTemp := <-m.transitionChan:
|
||||||
m.transitionMutex.Lock()
|
m.runTransition(targetTemp, steps, stepDur)
|
||||||
currentTemp := m.currentTemp
|
}
|
||||||
m.targetTemp = targetTemp
|
}
|
||||||
m.transitionMutex.Unlock()
|
}
|
||||||
|
|
||||||
if currentTemp == targetTemp {
|
func (m *Manager) runTransition(targetTemp int, steps int, stepDur time.Duration) {
|
||||||
continue
|
for {
|
||||||
|
m.transitionMutex.Lock()
|
||||||
|
currentTemp := m.currentTemp
|
||||||
|
m.targetTemp = targetTemp
|
||||||
|
m.transitionMutex.Unlock()
|
||||||
|
|
||||||
|
if currentTemp == targetTemp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Starting smooth transition: %dK -> %dK over %v", currentTemp, targetTemp, stepDur*time.Duration(steps))
|
||||||
|
|
||||||
|
redirected := false
|
||||||
|
for i := 0; i <= steps; i++ {
|
||||||
|
select {
|
||||||
|
case newTarget := <-m.transitionChan:
|
||||||
|
m.transitionMutex.Lock()
|
||||||
|
m.targetTemp = newTarget
|
||||||
|
m.transitionMutex.Unlock()
|
||||||
|
if newTarget != targetTemp {
|
||||||
|
log.Debugf("Transition %dK -> %dK redirected to %dK", currentTemp, targetTemp, newTarget)
|
||||||
|
targetTemp = newTarget
|
||||||
|
redirected = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Starting smooth transition: %dK -> %dK over %v", currentTemp, targetTemp, dur)
|
if redirected {
|
||||||
|
break
|
||||||
stepLoop:
|
|
||||||
for i := 0; i <= steps; i++ {
|
|
||||||
select {
|
|
||||||
case newTarget := <-m.transitionChan:
|
|
||||||
m.transitionMutex.Lock()
|
|
||||||
m.targetTemp = newTarget
|
|
||||||
m.transitionMutex.Unlock()
|
|
||||||
log.Debugf("Transition %dK -> %dK aborted (newer transition started)", currentTemp, targetTemp)
|
|
||||||
break stepLoop
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
m.transitionMutex.RLock()
|
|
||||||
if m.targetTemp != targetTemp {
|
|
||||||
m.transitionMutex.RUnlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.transitionMutex.RUnlock()
|
|
||||||
|
|
||||||
progress := float64(i) / float64(steps)
|
|
||||||
temp := currentTemp + int(float64(targetTemp-currentTemp)*progress)
|
|
||||||
|
|
||||||
m.post(func() { m.applyNowOnActor(temp) })
|
|
||||||
|
|
||||||
if i < steps {
|
|
||||||
time.Sleep(stepDur)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.transitionMutex.RLock()
|
m.transitionMutex.RLock()
|
||||||
finalTarget := m.targetTemp
|
currentTarget := m.targetTemp
|
||||||
m.transitionMutex.RUnlock()
|
m.transitionMutex.RUnlock()
|
||||||
|
|
||||||
if finalTarget == targetTemp {
|
if currentTarget != targetTemp {
|
||||||
log.Debugf("Transition complete: now at %dK", targetTemp)
|
targetTemp = currentTarget
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress := float64(i) / float64(steps)
|
||||||
|
temp := currentTemp + int(float64(targetTemp-currentTemp)*progress)
|
||||||
|
m.post(func() { m.applyNowOnActor(temp) })
|
||||||
|
|
||||||
|
if i == steps {
|
||||||
|
log.Debugf("Transition complete: now at %dK", targetTemp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(stepDur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1206,11 +1215,18 @@ func (m *Manager) SetGamma(gamma float64) error {
|
|||||||
|
|
||||||
func (m *Manager) SetEnabled(enabled bool) {
|
func (m *Manager) SetEnabled(enabled bool) {
|
||||||
m.configMutex.Lock()
|
m.configMutex.Lock()
|
||||||
|
wasEnabled := m.config.Enabled
|
||||||
m.config.Enabled = enabled
|
m.config.Enabled = enabled
|
||||||
|
highTemp := m.config.HighTemp
|
||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
if !m.controlsInitialized {
|
if !m.controlsInitialized {
|
||||||
|
m.transitionMutex.Lock()
|
||||||
|
m.currentTemp = highTemp
|
||||||
|
m.targetTemp = highTemp
|
||||||
|
m.transitionMutex.Unlock()
|
||||||
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
log.Info("Creating gamma controls")
|
log.Info("Creating gamma controls")
|
||||||
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
|
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
|
||||||
@@ -1221,9 +1237,10 @@ func (m *Manager) SetEnabled(enabled bool) {
|
|||||||
log.Errorf("Failed to create gamma controls: %v", err)
|
log.Errorf("Failed to create gamma controls: %v", err)
|
||||||
} else {
|
} else {
|
||||||
m.controlsInitialized = true
|
m.controlsInitialized = true
|
||||||
|
m.triggerUpdate()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else if !wasEnabled {
|
||||||
m.triggerUpdate()
|
m.triggerUpdate()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
@@ -118,6 +120,59 @@ func (m Model) viewInstallingPackages() string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dmsPackageName(distroID string, dependencies []deps.Dependency) string {
|
||||||
|
config, ok := distros.Registry[distroID]
|
||||||
|
if !ok {
|
||||||
|
return "dms"
|
||||||
|
}
|
||||||
|
|
||||||
|
var isGit bool
|
||||||
|
for _, dep := range dependencies {
|
||||||
|
if dep.Name == "dms (DankMaterialShell)" {
|
||||||
|
isGit = dep.Variant == deps.VariantGit
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch config.Family {
|
||||||
|
case distros.FamilyArch:
|
||||||
|
if isGit {
|
||||||
|
return "dms-shell-git"
|
||||||
|
}
|
||||||
|
return "dms-shell-bin"
|
||||||
|
case distros.FamilyFedora, distros.FamilyUbuntu, distros.FamilyDebian, distros.FamilySUSE:
|
||||||
|
if isGit {
|
||||||
|
return "dms-git"
|
||||||
|
}
|
||||||
|
return "dms"
|
||||||
|
default:
|
||||||
|
return "dms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstallCommand(distroID string, dependencies []deps.Dependency) string {
|
||||||
|
config, ok := distros.Registry[distroID]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if config.Family == distros.FamilyGentoo {
|
||||||
|
return "rm -rf ~/.config/quickshell/dms && sudo rm /usr/local/bin/dms"
|
||||||
|
}
|
||||||
|
pkg := dmsPackageName(distroID, dependencies)
|
||||||
|
switch config.Family {
|
||||||
|
case distros.FamilyArch:
|
||||||
|
return "sudo pacman -Rs " + pkg
|
||||||
|
case distros.FamilyFedora:
|
||||||
|
return "sudo dnf remove " + pkg
|
||||||
|
case distros.FamilyUbuntu, distros.FamilyDebian:
|
||||||
|
return "sudo apt remove " + pkg
|
||||||
|
case distros.FamilySUSE:
|
||||||
|
return "sudo zypper remove " + pkg
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m Model) viewInstallComplete() string {
|
func (m Model) viewInstallComplete() string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
|
|
||||||
@@ -132,7 +187,6 @@ func (m Model) viewInstallComplete() string {
|
|||||||
b.WriteString(success)
|
b.WriteString(success)
|
||||||
b.WriteString("\n\n")
|
b.WriteString("\n\n")
|
||||||
|
|
||||||
// Show what was accomplished
|
|
||||||
accomplishments := []string{
|
accomplishments := []string{
|
||||||
"• Window manager and dependencies installed",
|
"• Window manager and dependencies installed",
|
||||||
"• Terminal and development tools configured",
|
"• Terminal and development tools configured",
|
||||||
@@ -146,8 +200,26 @@ func (m Model) viewInstallComplete() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
info := m.styles.Normal.Render("Your system is ready! Log out and log back in to start using\nyour new desktop environment.\nIf you do not have a greeter, login with \"niri-session\" or \"Hyprland\" \n\nPress Enter to exit.")
|
info := m.styles.Normal.Render("Your system is ready! Log out and log back in to start using\nyour new desktop environment.\nIf you do not have a greeter, login with \"niri-session\" or \"Hyprland\"")
|
||||||
b.WriteString(info)
|
b.WriteString(info)
|
||||||
|
b.WriteString("\n\n")
|
||||||
|
|
||||||
|
theme := TerminalTheme()
|
||||||
|
cmdStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(theme.Accent))
|
||||||
|
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(theme.Subtle))
|
||||||
|
|
||||||
|
b.WriteString(labelStyle.Render("Troubleshooting:") + "\n")
|
||||||
|
b.WriteString(labelStyle.Render(" Disable autostart: ") + cmdStyle.Render("systemctl --user disable dms") + "\n")
|
||||||
|
b.WriteString(labelStyle.Render(" View logs: ") + cmdStyle.Render("journalctl --user -u dms") + "\n")
|
||||||
|
|
||||||
|
if m.osInfo != nil {
|
||||||
|
if cmd := uninstallCommand(m.osInfo.Distribution.ID, m.dependencies); cmd != "" {
|
||||||
|
b.WriteString(labelStyle.Render(" Uninstall: ") + cmdStyle.Render(cmd) + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString(m.styles.Normal.Render("Press Enter to exit."))
|
||||||
|
|
||||||
if m.logFilePath != "" {
|
if m.logFilePath != "" {
|
||||||
b.WriteString("\n\n")
|
b.WriteString("\n\n")
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (m Model) viewWelcome() string {
|
|||||||
subtitle := lipgloss.NewStyle().
|
subtitle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color(theme.Subtle)).
|
Foreground(lipgloss.Color(theme.Subtle)).
|
||||||
Italic(true).
|
Italic(true).
|
||||||
Render("Quickstart for a Dank™ Desktop")
|
Render("Quickstart for a Dank Desktop")
|
||||||
|
|
||||||
b.WriteString(decorator)
|
b.WriteString(decorator)
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
|
|||||||
@@ -13,8 +13,23 @@ const (
|
|||||||
FormatXRGB8888 PixelFormat = 1
|
FormatXRGB8888 PixelFormat = 1
|
||||||
FormatABGR8888 PixelFormat = 0x34324241
|
FormatABGR8888 PixelFormat = 0x34324241
|
||||||
FormatXBGR8888 PixelFormat = 0x34324258
|
FormatXBGR8888 PixelFormat = 0x34324258
|
||||||
|
FormatRGB888 PixelFormat = 0x34324752
|
||||||
|
FormatBGR888 PixelFormat = 0x34324742
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (f PixelFormat) BytesPerPixel() int {
|
||||||
|
switch f {
|
||||||
|
case FormatRGB888, FormatBGR888:
|
||||||
|
return 3
|
||||||
|
default:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f PixelFormat) Is24Bit() bool {
|
||||||
|
return f == FormatRGB888 || f == FormatBGR888
|
||||||
|
}
|
||||||
|
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
fd int
|
fd int
|
||||||
data []byte
|
data []byte
|
||||||
@@ -78,6 +93,52 @@ func (b *Buffer) Close() error {
|
|||||||
return firstErr
|
return firstErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ConvertTo32Bit(srcFormat PixelFormat) (*Buffer, PixelFormat, error) {
|
||||||
|
if !srcFormat.Is24Bit() {
|
||||||
|
return b, srcFormat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dstFormat := FormatXRGB8888
|
||||||
|
dstStride := b.Width * 4
|
||||||
|
|
||||||
|
dst, err := CreateBuffer(b.Width, b.Height, dstStride)
|
||||||
|
if err != nil {
|
||||||
|
return nil, srcFormat, err
|
||||||
|
}
|
||||||
|
dst.Format = dstFormat
|
||||||
|
|
||||||
|
srcData := b.data
|
||||||
|
dstData := dst.data
|
||||||
|
|
||||||
|
// DRM format names are counterintuitive on little-endian:
|
||||||
|
// RGB888 memory layout: B, G, R (name is logical order, not memory)
|
||||||
|
// BGR888 memory layout: R, G, B
|
||||||
|
isBGRMemory := srcFormat == FormatRGB888
|
||||||
|
|
||||||
|
for y := 0; y < b.Height; y++ {
|
||||||
|
srcRow := y * b.Stride
|
||||||
|
dstRow := y * dstStride
|
||||||
|
for x := 0; x < b.Width; x++ {
|
||||||
|
si := srcRow + x*3
|
||||||
|
di := dstRow + x*4
|
||||||
|
if isBGRMemory {
|
||||||
|
// RGB888: src memory is B,G,R -> dst XRGB8888 memory B,G,R,X
|
||||||
|
dstData[di+0] = srcData[si+0]
|
||||||
|
dstData[di+1] = srcData[si+1]
|
||||||
|
dstData[di+2] = srcData[si+2]
|
||||||
|
} else {
|
||||||
|
// BGR888: src memory is R,G,B -> dst XRGB8888 memory B,G,R,X
|
||||||
|
dstData[di+0] = srcData[si+2]
|
||||||
|
dstData[di+1] = srcData[si+1]
|
||||||
|
dstData[di+2] = srcData[si+0]
|
||||||
|
}
|
||||||
|
dstData[di+3] = 0xFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst, dstFormat, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Buffer) GetPixelRGBA(x, y int) (r, g, b2, a uint8) {
|
func (b *Buffer) GetPixelRGBA(x, y int) (r, g, b2, a uint8) {
|
||||||
if x < 0 || x >= b.Width || y < 0 || y >= b.Height {
|
if x < 0 || x >= b.Width || y < 0 || y >= b.Height {
|
||||||
return
|
return
|
||||||
@@ -88,7 +149,12 @@ func (b *Buffer) GetPixelRGBA(x, y int) (r, g, b2, a uint8) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.data[off+2], b.data[off+1], b.data[off], b.data[off+3]
|
switch b.Format {
|
||||||
|
case FormatXBGR8888, FormatABGR8888:
|
||||||
|
return b.data[off], b.data[off+1], b.data[off+2], 0xFF
|
||||||
|
default:
|
||||||
|
return b.data[off+2], b.data[off+1], b.data[off], 0xFF
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) GetPixelBGRA(x, y int) (b2, g, r, a uint8) {
|
func (b *Buffer) GetPixelBGRA(x, y int) (b2, g, r, a uint8) {
|
||||||
@@ -137,3 +203,96 @@ func (b *Buffer) Clear() {
|
|||||||
func (b *Buffer) CopyFrom(src *Buffer) {
|
func (b *Buffer) CopyFrom(src *Buffer) {
|
||||||
copy(b.data, src.data)
|
copy(b.data, src.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransformNormal = 0
|
||||||
|
Transform90 = 1
|
||||||
|
Transform180 = 2
|
||||||
|
Transform270 = 3
|
||||||
|
TransformFlipped = 4
|
||||||
|
TransformFlipped90 = 5
|
||||||
|
TransformFlipped180 = 6
|
||||||
|
TransformFlipped270 = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Buffer) ApplyTransform(transform int32) (*Buffer, error) {
|
||||||
|
if transform == TransformNormal {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var newW, newH int
|
||||||
|
switch transform {
|
||||||
|
case Transform90, Transform270, TransformFlipped90, TransformFlipped270:
|
||||||
|
newW, newH = b.Height, b.Width
|
||||||
|
default:
|
||||||
|
newW, newH = b.Width, b.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
newBuf, err := CreateBuffer(newW, newH, newW*4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newBuf.Format = b.Format
|
||||||
|
|
||||||
|
srcData := b.data
|
||||||
|
dstData := newBuf.data
|
||||||
|
|
||||||
|
for sy := 0; sy < b.Height; sy++ {
|
||||||
|
for sx := 0; sx < b.Width; sx++ {
|
||||||
|
var dx, dy int
|
||||||
|
|
||||||
|
switch transform {
|
||||||
|
case Transform90: // 90° CCW
|
||||||
|
dx = sy
|
||||||
|
dy = b.Width - 1 - sx
|
||||||
|
case Transform180:
|
||||||
|
dx = b.Width - 1 - sx
|
||||||
|
dy = b.Height - 1 - sy
|
||||||
|
case Transform270: // 270° CCW = 90° CW
|
||||||
|
dx = b.Height - 1 - sy
|
||||||
|
dy = sx
|
||||||
|
case TransformFlipped:
|
||||||
|
dx = b.Width - 1 - sx
|
||||||
|
dy = sy
|
||||||
|
case TransformFlipped90:
|
||||||
|
dx = sy
|
||||||
|
dy = sx
|
||||||
|
case TransformFlipped180:
|
||||||
|
dx = sx
|
||||||
|
dy = b.Height - 1 - sy
|
||||||
|
case TransformFlipped270:
|
||||||
|
dx = b.Height - 1 - sy
|
||||||
|
dy = b.Width - 1 - sx
|
||||||
|
default:
|
||||||
|
dx, dy = sx, sy
|
||||||
|
}
|
||||||
|
|
||||||
|
si := sy*b.Stride + sx*4
|
||||||
|
di := dy*newBuf.Stride + dx*4
|
||||||
|
|
||||||
|
if si+3 < len(srcData) && di+3 < len(dstData) {
|
||||||
|
dstData[di+0] = srcData[si+0]
|
||||||
|
dstData[di+1] = srcData[si+1]
|
||||||
|
dstData[di+2] = srcData[si+2]
|
||||||
|
dstData[di+3] = srcData[si+3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InverseTransform(transform int32) int32 {
|
||||||
|
switch transform {
|
||||||
|
case Transform90:
|
||||||
|
return Transform270
|
||||||
|
case Transform270:
|
||||||
|
return Transform90
|
||||||
|
case TransformFlipped90:
|
||||||
|
return TransformFlipped270
|
||||||
|
case TransformFlipped270:
|
||||||
|
return TransformFlipped90
|
||||||
|
default:
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+16
-6
@@ -10,13 +10,14 @@
|
|||||||
|
|
||||||
user = config.services.greetd.settings.default_session.user;
|
user = config.services.greetd.settings.default_session.user;
|
||||||
|
|
||||||
|
cacheDir = "/var/lib/dms-greeter";
|
||||||
greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
|
greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
|
||||||
export PATH=$PATH:${lib.makeBinPath [cfg.quickshell.package config.programs.${cfg.compositor.name}.package]}
|
export PATH=$PATH:${lib.makeBinPath [cfg.quickshell.package config.programs.${cfg.compositor.name}.package]}
|
||||||
${lib.escapeShellArgs ([
|
${lib.escapeShellArgs ([
|
||||||
"sh"
|
"sh"
|
||||||
"${../../quickshell/Modules/Greetd/assets/dms-greeter}"
|
"${../../quickshell/Modules/Greetd/assets/dms-greeter}"
|
||||||
"--cache-dir"
|
"--cache-dir"
|
||||||
"/var/lib/dmsgreeter"
|
cacheDir
|
||||||
"--command"
|
"--command"
|
||||||
cfg.compositor.name
|
cfg.compositor.name
|
||||||
"-p"
|
"-p"
|
||||||
@@ -27,6 +28,8 @@
|
|||||||
"${pkgs.writeText "dmsgreeter-compositor-config" cfg.compositor.customConfig}"
|
"${pkgs.writeText "dmsgreeter-compositor-config" cfg.compositor.customConfig}"
|
||||||
])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
|
])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
jq = lib.getExe pkgs.jq;
|
||||||
in {
|
in {
|
||||||
imports = let
|
imports = let
|
||||||
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
|
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
|
||||||
@@ -93,17 +96,17 @@ in {
|
|||||||
material-symbols
|
material-symbols
|
||||||
];
|
];
|
||||||
systemd.tmpfiles.settings."10-dmsgreeter" = {
|
systemd.tmpfiles.settings."10-dmsgreeter" = {
|
||||||
"/var/lib/dmsgreeter".d = {
|
${cacheDir}.d = {
|
||||||
user = user;
|
user = user;
|
||||||
group =
|
group =
|
||||||
if config.users.users.${user}.group != ""
|
if config.users.users.${user}.group != ""
|
||||||
then config.users.users.${user}.group
|
then config.users.users.${user}.group
|
||||||
else "greeter";
|
else "greeter";
|
||||||
mode = "0755";
|
mode = "0750";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
systemd.services.greetd.preStart = ''
|
systemd.services.greetd.preStart = ''
|
||||||
cd /var/lib/dmsgreeter
|
cd ${cacheDir}
|
||||||
${lib.concatStringsSep "\n" (lib.map (f: ''
|
${lib.concatStringsSep "\n" (lib.map (f: ''
|
||||||
if [ -f "${f}" ]; then
|
if [ -f "${f}" ]; then
|
||||||
cp "${f}" .
|
cp "${f}" .
|
||||||
@@ -112,9 +115,16 @@ in {
|
|||||||
cfg.configFiles)}
|
cfg.configFiles)}
|
||||||
|
|
||||||
if [ -f session.json ]; then
|
if [ -f session.json ]; then
|
||||||
if cp "$(${lib.getExe pkgs.jq} -r '.wallpaperPath' session.json)" wallpaper.jpg; then
|
if cp "$(${jq} -r '.wallpaperPath' session.json)" wallpaper.jpg; then
|
||||||
mv session.json session.orig.json
|
mv session.json session.orig.json
|
||||||
${lib.getExe pkgs.jq} '.wallpaperPath = "/var/lib/dmsgreeter/wallpaper.jpg"' session.orig.json > session.json
|
${jq} '.wallpaperPath = "${cacheDir}/wallpaper.jpg"' session.orig.json > session.json
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f settings.json ]; then
|
||||||
|
if cp "$(${jq} -r '.customThemeFile' settings.json)" custom-theme.json; then
|
||||||
|
mv settings.json settings.orig.json
|
||||||
|
${jq} '.customThemeFile = "${cacheDir}/custom-theme.json"' settings.orig.json > settings.json
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,9 @@
|
|||||||
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
||||||
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
||||||
|
|
||||||
|
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
|
||||||
|
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
|
||||||
|
|
||||||
installShellCompletion --cmd dms \
|
installShellCompletion --cmd dms \
|
||||||
--bash <($out/bin/dms completion bash) \
|
--bash <($out/bin/dms completion bash) \
|
||||||
--fish <($out/bin/dms completion fish) \
|
--fish <($out/bin/dms completion fish) \
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
The Dark Knight
|
||||||
@@ -53,7 +53,7 @@ Singleton {
|
|||||||
if (appId === "home assistant desktop")
|
if (appId === "home assistant desktop")
|
||||||
return "homeassistant-desktop";
|
return "homeassistant-desktop";
|
||||||
if (appId.includes("com.transmissionbt.transmission"))
|
if (appId.includes("com.transmissionbt.transmission"))
|
||||||
return "transmission-gtk";
|
return "transmission";
|
||||||
return appId;
|
return appId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,21 +128,13 @@ Singleton {
|
|||||||
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
|
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (currentTheme !== "custom") {
|
||||||
let primaryColor;
|
const darkTheme = StockThemes.getThemeByName(currentTheme, false);
|
||||||
let matugenType;
|
const lightTheme = StockThemes.getThemeByName(currentTheme, true);
|
||||||
if (currentTheme === "custom") {
|
if (darkTheme && darkTheme.primary) {
|
||||||
if (customThemeData && customThemeData.primary) {
|
const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme);
|
||||||
primaryColor = customThemeData.primary;
|
const themeData = isLight ? lightTheme : darkTheme;
|
||||||
matugenType = customThemeData.matugen_type;
|
setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
primaryColor = currentThemeData.primary;
|
|
||||||
matugenType = currentThemeData.matugen_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (primaryColor) {
|
|
||||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|||||||
+72
-37
@@ -108,13 +108,14 @@ Item {
|
|||||||
id: barRepeaterModel
|
id: barRepeaterModel
|
||||||
values: {
|
values: {
|
||||||
const configs = SettingsData.barConfigs;
|
const configs = SettingsData.barConfigs;
|
||||||
return configs
|
return configs.map(c => ({
|
||||||
.map(c => ({ id: c.id, position: c.position }))
|
id: c.id,
|
||||||
.sort((a, b) => {
|
position: c.position
|
||||||
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
|
})).sort((a, b) => {
|
||||||
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
|
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
|
||||||
return aVertical - bVertical;
|
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
|
||||||
});
|
return aVertical - bVertical;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +143,34 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool dockEnabled: false
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: dockRecreateDebounce
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
root.dockEnabled = false;
|
||||||
|
Qt.callLater(() => {
|
||||||
|
root.dockEnabled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
dockRecreateDebounce.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
dockRecreateDebounce.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: dockLoader
|
id: dockLoader
|
||||||
active: true
|
active: root.dockEnabled
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
|
|
||||||
property var currentPosition: SettingsData.dockPosition
|
property var currentPosition: SettingsData.dockPosition
|
||||||
@@ -440,62 +466,71 @@ Item {
|
|||||||
title: I18n.tr("Open with...")
|
title: I18n.tr("Open with...")
|
||||||
|
|
||||||
function shellEscape(str) {
|
function shellEscape(str) {
|
||||||
return "'" + str.replace(/'/g, "'\\''") + "'"
|
return "'" + str.replace(/'/g, "'\\''") + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
onApplicationSelected: (app, filePath) => {
|
onApplicationSelected: (app, filePath) => {
|
||||||
if (!app) return
|
if (!app)
|
||||||
|
return;
|
||||||
|
let cmd = app.exec || "";
|
||||||
|
const escapedPath = shellEscape(filePath);
|
||||||
|
const escapedUri = shellEscape("file://" + filePath);
|
||||||
|
|
||||||
let cmd = app.exec || ""
|
let hasField = false;
|
||||||
const escapedPath = shellEscape(filePath)
|
if (cmd.includes("%f")) {
|
||||||
const escapedUri = shellEscape("file://" + filePath)
|
cmd = cmd.replace("%f", escapedPath);
|
||||||
|
hasField = true;
|
||||||
let hasField = false
|
} else if (cmd.includes("%F")) {
|
||||||
if (cmd.includes("%f")) { cmd = cmd.replace("%f", escapedPath); hasField = true }
|
cmd = cmd.replace("%F", escapedPath);
|
||||||
else if (cmd.includes("%F")) { cmd = cmd.replace("%F", escapedPath); hasField = true }
|
hasField = true;
|
||||||
else if (cmd.includes("%u")) { cmd = cmd.replace("%u", escapedUri); hasField = true }
|
} else if (cmd.includes("%u")) {
|
||||||
else if (cmd.includes("%U")) { cmd = cmd.replace("%U", escapedUri); hasField = true }
|
cmd = cmd.replace("%u", escapedUri);
|
||||||
|
hasField = true;
|
||||||
cmd = cmd.replace(/%[ikc]/g, "")
|
} else if (cmd.includes("%U")) {
|
||||||
|
cmd = cmd.replace("%U", escapedUri);
|
||||||
if (!hasField) {
|
hasField = true;
|
||||||
cmd += " " + escapedPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("FilePicker: Launching", cmd)
|
cmd = cmd.replace(/%[ikc]/g, "");
|
||||||
|
|
||||||
|
if (!hasField) {
|
||||||
|
cmd += " " + escapedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("FilePicker: Launching", cmd);
|
||||||
|
|
||||||
Quickshell.execDetached({
|
Quickshell.execDetached({
|
||||||
command: ["sh", "-c", cmd]
|
command: ["sh", "-c", cmd]
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: DMSService
|
target: DMSService
|
||||||
function onOpenUrlRequested(url) {
|
function onOpenUrlRequested(url) {
|
||||||
browserPickerModal.url = url
|
browserPickerModal.url = url;
|
||||||
browserPickerModal.open()
|
browserPickerModal.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAppPickerRequested(data) {
|
function onAppPickerRequested(data) {
|
||||||
console.log("DMSShell: App picker requested with data:", JSON.stringify(data))
|
console.log("DMSShell: App picker requested with data:", JSON.stringify(data));
|
||||||
|
|
||||||
if (!data || !data.target) {
|
if (!data || !data.target) {
|
||||||
console.warn("DMSShell: Invalid app picker request data")
|
console.warn("DMSShell: Invalid app picker request data");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filePickerModal.targetData = data.target
|
filePickerModal.targetData = data.target;
|
||||||
filePickerModal.targetDataLabel = data.requestType || "file"
|
filePickerModal.targetDataLabel = data.requestType || "file";
|
||||||
|
|
||||||
if (data.categories && data.categories.length > 0) {
|
if (data.categories && data.categories.length > 0) {
|
||||||
filePickerModal.categoryFilter = data.categories
|
filePickerModal.categoryFilter = data.categories;
|
||||||
} else {
|
} else {
|
||||||
filePickerModal.categoryFilter = []
|
filePickerModal.categoryFilter = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
filePickerModal.usageHistoryKey = "filePickerUsageHistory"
|
filePickerModal.usageHistoryKey = "filePickerUsageHistory";
|
||||||
filePickerModal.open()
|
filePickerModal.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -332,18 +332,16 @@ Item {
|
|||||||
if (!provider)
|
if (!provider)
|
||||||
return "ERROR: No provider specified";
|
return "ERROR: No provider specified";
|
||||||
|
|
||||||
KeybindsService.currentProvider = provider;
|
KeybindsService.loadCheatsheet(provider);
|
||||||
KeybindsService.loadBinds();
|
|
||||||
root.hyprKeybindsModalLoader.active = true;
|
root.hyprKeybindsModalLoader.active = true;
|
||||||
|
|
||||||
if (!root.hyprKeybindsModalLoader.item)
|
if (!root.hyprKeybindsModalLoader.item)
|
||||||
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
|
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
|
||||||
|
|
||||||
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
|
if (root.hyprKeybindsModalLoader.item.shouldBeVisible)
|
||||||
root.hyprKeybindsModalLoader.item.close();
|
root.hyprKeybindsModalLoader.item.close();
|
||||||
} else {
|
else
|
||||||
root.hyprKeybindsModalLoader.item.open();
|
root.hyprKeybindsModalLoader.item.open();
|
||||||
}
|
|
||||||
return `KEYBINDS_TOGGLE_SUCCESS: ${provider}`;
|
return `KEYBINDS_TOGGLE_SUCCESS: ${provider}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,18 +349,16 @@ Item {
|
|||||||
if (!provider)
|
if (!provider)
|
||||||
return "ERROR: No provider specified";
|
return "ERROR: No provider specified";
|
||||||
|
|
||||||
KeybindsService.currentProvider = provider;
|
KeybindsService.loadCheatsheet(provider);
|
||||||
KeybindsService.loadBinds();
|
|
||||||
root.hyprKeybindsModalLoader.active = true;
|
root.hyprKeybindsModalLoader.active = true;
|
||||||
|
|
||||||
if (!root.hyprKeybindsModalLoader.item)
|
if (!root.hyprKeybindsModalLoader.item)
|
||||||
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
|
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
|
||||||
|
|
||||||
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
|
if (root.hyprKeybindsModalLoader.item.shouldBeVisible)
|
||||||
root.hyprKeybindsModalLoader.item.close();
|
root.hyprKeybindsModalLoader.item.close();
|
||||||
} else {
|
else
|
||||||
root.hyprKeybindsModalLoader.item.open();
|
root.hyprKeybindsModalLoader.item.open();
|
||||||
}
|
|
||||||
return `KEYBINDS_TOGGLE_SUCCESS: ${provider} (${path})`;
|
return `KEYBINDS_TOGGLE_SUCCESS: ${provider} (${path})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,8 +366,7 @@ Item {
|
|||||||
if (!provider)
|
if (!provider)
|
||||||
return "ERROR: No provider specified";
|
return "ERROR: No provider specified";
|
||||||
|
|
||||||
KeybindsService.currentProvider = provider;
|
KeybindsService.loadCheatsheet(provider);
|
||||||
KeybindsService.loadBinds();
|
|
||||||
root.hyprKeybindsModalLoader.active = true;
|
root.hyprKeybindsModalLoader.active = true;
|
||||||
|
|
||||||
if (!root.hyprKeybindsModalLoader.item)
|
if (!root.hyprKeybindsModalLoader.item)
|
||||||
@@ -385,8 +380,7 @@ Item {
|
|||||||
if (!provider)
|
if (!provider)
|
||||||
return "ERROR: No provider specified";
|
return "ERROR: No provider specified";
|
||||||
|
|
||||||
KeybindsService.currentProvider = provider;
|
KeybindsService.loadCheatsheet(provider);
|
||||||
KeybindsService.loadBinds();
|
|
||||||
root.hyprKeybindsModalLoader.active = true;
|
root.hyprKeybindsModalLoader.active = true;
|
||||||
|
|
||||||
if (!root.hyprKeybindsModalLoader.item)
|
if (!root.hyprKeybindsModalLoader.item)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ FocusScope {
|
|||||||
property bool pathInputHasFocus: false
|
property bool pathInputHasFocus: false
|
||||||
property int actualGridColumns: 5
|
property int actualGridColumns: 5
|
||||||
property bool _initialized: false
|
property bool _initialized: false
|
||||||
|
property bool closeOnEscape: true
|
||||||
|
|
||||||
signal fileSelected(string path)
|
signal fileSelected(string path)
|
||||||
signal closeRequested
|
signal closeRequested
|
||||||
@@ -298,7 +299,7 @@ FocusScope {
|
|||||||
property int gridColumns: viewMode === "list" ? 1 : Math.max(1, actualGridColumns)
|
property int gridColumns: viewMode === "list" ? 1 : Math.max(1, actualGridColumns)
|
||||||
|
|
||||||
function handleKey(event) {
|
function handleKey(event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape && root.closeOnEscape) {
|
||||||
closeRequested();
|
closeRequested();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(500, 400)
|
minimumSize: Qt.size(500, 400)
|
||||||
implicitWidth: 800
|
implicitWidth: 800
|
||||||
implicitHeight: 600
|
implicitHeight: 600
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
@@ -59,6 +59,7 @@ FloatingWindow {
|
|||||||
id: content
|
id: content
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
|
closeOnEscape: false
|
||||||
|
|
||||||
browserTitle: fileBrowserModal.browserTitle
|
browserTitle: fileBrowserModal.browserTitle
|
||||||
browserIcon: fileBrowserModal.browserIcon
|
browserIcon: fileBrowserModal.browserIcon
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ DankModal {
|
|||||||
modalWidth: _maxW
|
modalWidth: _maxW
|
||||||
modalHeight: _maxH
|
modalHeight: _maxH
|
||||||
onBackgroundClicked: close()
|
onBackgroundClicked: close()
|
||||||
onOpened: () => Qt.callLater(() => modalFocusScope.forceActiveFocus())
|
onOpened: {
|
||||||
|
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||||
|
if (!Object.keys(KeybindsService.cheatsheet).length && KeybindsService.cheatsheetAvailable)
|
||||||
|
KeybindsService.loadCheatsheet();
|
||||||
|
}
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [root.contentWindow]
|
windows: [root.contentWindow]
|
||||||
@@ -66,7 +70,7 @@ DankModal {
|
|||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: KeybindsService.keybinds.title || "Keybinds"
|
text: KeybindsService.cheatsheet.title || "Keybinds"
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
@@ -82,7 +86,7 @@ DankModal {
|
|||||||
|
|
||||||
Component.onCompleted: root.activeFlickable = mainFlickable
|
Component.onCompleted: root.activeFlickable = mainFlickable
|
||||||
|
|
||||||
property var rawBinds: KeybindsService.keybinds.binds || {}
|
property var rawBinds: KeybindsService.cheatsheet.binds || {}
|
||||||
property var categories: {
|
property var categories: {
|
||||||
const processed = {};
|
const processed = {};
|
||||||
for (const cat in rawBinds) {
|
for (const cat in rawBinds) {
|
||||||
@@ -114,12 +118,36 @@ DankModal {
|
|||||||
}
|
}
|
||||||
property var categoryKeys: Object.keys(categories)
|
property var categoryKeys: Object.keys(categories)
|
||||||
|
|
||||||
|
function estimateCategoryHeight(catName) {
|
||||||
|
const catData = categories[catName];
|
||||||
|
if (!catData)
|
||||||
|
return 0;
|
||||||
|
let bindCount = 0;
|
||||||
|
for (const key of catData.subcatKeys) {
|
||||||
|
bindCount += catData.subcats[key]?.length || 0;
|
||||||
|
if (key !== "_root")
|
||||||
|
bindCount += 1;
|
||||||
|
}
|
||||||
|
return 40 + bindCount * 28;
|
||||||
|
}
|
||||||
|
|
||||||
function distributeCategories(cols) {
|
function distributeCategories(cols) {
|
||||||
const columns = [];
|
const columns = [];
|
||||||
for (let i = 0; i < cols; i++)
|
const heights = [];
|
||||||
|
for (let i = 0; i < cols; i++) {
|
||||||
columns.push([]);
|
columns.push([]);
|
||||||
for (let i = 0; i < categoryKeys.length; i++)
|
heights.push(0);
|
||||||
columns[i % cols].push(categoryKeys[i]);
|
}
|
||||||
|
const sorted = [...categoryKeys].sort((a, b) => estimateCategoryHeight(b) - estimateCategoryHeight(a));
|
||||||
|
for (const cat of sorted) {
|
||||||
|
let minIdx = 0;
|
||||||
|
for (let i = 1; i < cols; i++) {
|
||||||
|
if (heights[i] < heights[minIdx])
|
||||||
|
minIdx = i;
|
||||||
|
}
|
||||||
|
columns[minIdx].push(cat);
|
||||||
|
heights[minIdx] += estimateCategoryHeight(cat);
|
||||||
|
}
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +165,7 @@ DankModal {
|
|||||||
Column {
|
Column {
|
||||||
id: masonryColumn
|
id: masonryColumn
|
||||||
width: (rowLayout.width - rowLayout.spacing * (rowLayout.numColumns - 1)) / rowLayout.numColumns
|
width: (rowLayout.width - rowLayout.spacing * (rowLayout.numColumns - 1)) / rowLayout.numColumns
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: rowLayout.columnCategories[index] || []
|
model: rowLayout.columnCategories[index] || []
|
||||||
@@ -199,37 +227,37 @@ DankModal {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: parent.parent.subcatBinds
|
model: parent.parent.subcatBinds
|
||||||
|
|
||||||
Row {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
height: 24
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
width: Math.min(140, parent.width * 0.42)
|
id: keyBadge
|
||||||
|
width: Math.min(keyText.implicitWidth + 12, 160)
|
||||||
height: 22
|
height: 22
|
||||||
radius: 4
|
radius: 4
|
||||||
opacity: 0.9
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
id: keyText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.margins: 2
|
|
||||||
width: parent.width - 4
|
|
||||||
color: Theme.secondary
|
color: Theme.secondary
|
||||||
text: modelData.key || ""
|
text: modelData.key || ""
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
isMonospace: true
|
isMonospace: true
|
||||||
elide: Text.ElideRight
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
width: parent.width - 150
|
anchors.left: parent.left
|
||||||
text: modelData.desc || ""
|
anchors.leftMargin: 170
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelData.desc || modelData.action || ""
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
opacity: 0.9
|
opacity: 0.9
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ DankModal {
|
|||||||
|
|
||||||
modalWidth: 500
|
modalWidth: 500
|
||||||
modalHeight: 700
|
modalHeight: 700
|
||||||
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
visible: false
|
visible: false
|
||||||
onBackgroundClicked: hide()
|
onBackgroundClicked: hide()
|
||||||
onOpened: () => {
|
onOpened: () => {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ FloatingWindow {
|
|||||||
title: I18n.tr("Authentication")
|
title: I18n.tr("Authentication")
|
||||||
minimumSize: Qt.size(420, calculatedHeight)
|
minimumSize: Qt.size(420, calculatedHeight)
|
||||||
maximumSize: Qt.size(420, calculatedHeight)
|
maximumSize: Qt.size(420, calculatedHeight)
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(650, 400)
|
minimumSize: Qt.size(650, 400)
|
||||||
implicitWidth: 900
|
implicitWidth: 900
|
||||||
implicitHeight: 680
|
implicitHeight: 680
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
@@ -112,12 +112,6 @@ FloatingWindow {
|
|||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
hide();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_1:
|
case Qt.Key_1:
|
||||||
currentTab = 0;
|
currentTab = 0;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(500, 400)
|
minimumSize: Qt.size(500, 400)
|
||||||
implicitWidth: 800
|
implicitWidth: 800
|
||||||
implicitHeight: 940
|
implicitHeight: 940
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onIsCompactModeChanged: {
|
onIsCompactModeChanged: {
|
||||||
@@ -139,11 +139,6 @@ FloatingWindow {
|
|||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
hide();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Down || (event.key === Qt.Key_Tab && !event.modifiers)) {
|
if (event.key === Qt.Key_Down || (event.key === Qt.Key_Tab && !event.modifiers)) {
|
||||||
sidebar.navigateNext();
|
sidebar.navigateNext();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ FloatingWindow {
|
|||||||
title: isVpnPrompt ? I18n.tr("VPN Password") : I18n.tr("Wi-Fi Password")
|
title: isVpnPrompt ? I18n.tr("VPN Password") : I18n.tr("Wi-Fi Password")
|
||||||
minimumSize: Qt.size(420, calculatedHeight)
|
minimumSize: Qt.size(420, calculatedHeight)
|
||||||
maximumSize: Qt.size(420, calculatedHeight)
|
maximumSize: Qt.size(420, calculatedHeight)
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ DankPopout {
|
|||||||
|
|
||||||
property alias searchField: searchField
|
property alias searchField: searchField
|
||||||
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
smooth: true
|
smooth: true
|
||||||
|
|||||||
@@ -113,11 +113,7 @@ DankPopout {
|
|||||||
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
|
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
|
||||||
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
||||||
|
|
||||||
color: {
|
color: "transparent"
|
||||||
const transparency = Theme.popupTransparency;
|
|
||||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1);
|
|
||||||
return Qt.rgba(surface.r, surface.g, surface.b, transparency);
|
|
||||||
}
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 0
|
border.width: 0
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.UPower
|
import Quickshell.Services.UPower
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -26,13 +23,12 @@ DankPopout {
|
|||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
popupWidth: 400
|
popupWidth: 400
|
||||||
@@ -48,7 +44,7 @@ DankPopout {
|
|||||||
id: batteryContent
|
id: batteryContent
|
||||||
|
|
||||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Theme.outlineMedium
|
border.color: Theme.outlineMedium
|
||||||
border.width: 0
|
border.width: 0
|
||||||
@@ -59,9 +55,8 @@ DankPopout {
|
|||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
forceActiveFocus();
|
forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Keys.onPressed: function(event) {
|
Keys.onPressed: function (event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
root.close();
|
root.close();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
@@ -71,11 +66,10 @@ DankPopout {
|
|||||||
Connections {
|
Connections {
|
||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
Qt.callLater(function() {
|
Qt.callLater(function () {
|
||||||
batteryContent.forceActiveFocus();
|
batteryContent.forceActiveFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: root
|
target: root
|
||||||
@@ -246,7 +240,8 @@ DankPopout {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
if (!BatteryService.batteryAvailable) return "Power profile management available"
|
if (!BatteryService.batteryAvailable)
|
||||||
|
return "Power profile management available";
|
||||||
const time = BatteryService.formatTimeRemaining();
|
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}`;
|
||||||
@@ -470,14 +465,14 @@ DankPopout {
|
|||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
|
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
|
||||||
return "N/A"
|
return "N/A";
|
||||||
return `${Math.round(modelData.healthPercentage)}%`
|
return `${Math.round(modelData.healthPercentage)}%`;
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: {
|
color: {
|
||||||
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
|
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
|
||||||
return Theme.surfaceText
|
return Theme.surfaceText;
|
||||||
return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText
|
return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText;
|
||||||
}
|
}
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
@@ -526,10 +521,7 @@ DankPopout {
|
|||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: modelData.state === UPowerDeviceState.Charging
|
text: modelData.state === UPowerDeviceState.Charging ? I18n.tr("To Full") : modelData.state === UPowerDeviceState.Discharging ? I18n.tr("Left") : ""
|
||||||
? I18n.tr("To Full")
|
|
||||||
: modelData.state === UPowerDeviceState.Discharging
|
|
||||||
? I18n.tr("Left") : ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceTextMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
@@ -538,17 +530,14 @@ DankPopout {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
const time = modelData.state === UPowerDeviceState.Charging
|
const time = modelData.state === UPowerDeviceState.Charging ? modelData.timeToFull : modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0 ? (3600 * modelData.energy) / BatteryService.changeRate : 0;
|
||||||
? modelData.timeToFull
|
|
||||||
: modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0
|
|
||||||
? (3600 * modelData.energy) / BatteryService.changeRate : 0
|
|
||||||
|
|
||||||
if (!time || time <= 0 || time > 86400)
|
if (!time || time <= 0 || time > 86400)
|
||||||
return "N/A"
|
return "N/A";
|
||||||
|
|
||||||
const hours = Math.floor(time / 3600)
|
const hours = Math.floor(time / 3600);
|
||||||
const minutes = Math.floor((time % 3600) / 60)
|
const minutes = Math.floor((time % 3600) / 60);
|
||||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`
|
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -566,8 +555,9 @@ DankPopout {
|
|||||||
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 => root.isActiveProfile(profile))
|
return 1;
|
||||||
|
return profileModel.findIndex(profile => root.isActiveProfile(profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||||
@@ -575,8 +565,9 @@ DankPopout {
|
|||||||
selectionMode: "single"
|
selectionMode: "single"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
onSelectionChanged: (index, selected) => {
|
onSelectionChanged: (index, selected) => {
|
||||||
if (!selected) return
|
if (!selected)
|
||||||
root.setProfile(profileModel[index])
|
return;
|
||||||
|
root.setProfile(profileModel[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,5 +625,4 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -15,31 +11,31 @@ DankPopout {
|
|||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
|
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
|
||||||
triggerX = x
|
triggerX = x;
|
||||||
triggerY = y
|
triggerY = y;
|
||||||
triggerWidth = width
|
triggerWidth = width;
|
||||||
triggerSection = section
|
triggerSection = section;
|
||||||
root.screen = screen
|
root.screen = screen;
|
||||||
|
|
||||||
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4)
|
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
||||||
storedBarSpacing = barSpacing !== undefined ? barSpacing : 4
|
storedBarSpacing = barSpacing !== undefined ? barSpacing : 4;
|
||||||
storedBarConfig = barConfig
|
storedBarConfig = barConfig;
|
||||||
|
|
||||||
const pos = barPosition !== undefined ? barPosition : 0
|
const pos = barPosition !== undefined ? barPosition : 0;
|
||||||
const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0
|
const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0;
|
||||||
|
|
||||||
setBarContext(pos, bottomGap)
|
setBarContext(pos, bottomGap);
|
||||||
|
|
||||||
updateOutputState()
|
updateOutputState();
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenChanged: updateOutputState()
|
onScreenChanged: updateOutputState()
|
||||||
|
|
||||||
function updateOutputState() {
|
function updateOutputState() {
|
||||||
if (screen && DwlService.dwlAvailable) {
|
if (screen && DwlService.dwlAvailable) {
|
||||||
outputState = DwlService.getOutputState(screen.name)
|
outputState = DwlService.getOutputState(screen.name);
|
||||||
} else {
|
} else {
|
||||||
outputState = null
|
outputState = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,56 +43,56 @@ DankPopout {
|
|||||||
property string currentLayoutSymbol: outputState?.layoutSymbol || ""
|
property string currentLayoutSymbol: outputState?.layoutSymbol || ""
|
||||||
|
|
||||||
readonly property var layoutNames: ({
|
readonly property var layoutNames: ({
|
||||||
"CT": I18n.tr("Center Tiling"),
|
"CT": I18n.tr("Center Tiling"),
|
||||||
"G": I18n.tr("Grid"),
|
"G": I18n.tr("Grid"),
|
||||||
"K": I18n.tr("Deck"),
|
"K": I18n.tr("Deck"),
|
||||||
"M": I18n.tr("Monocle"),
|
"M": I18n.tr("Monocle"),
|
||||||
"RT": I18n.tr("Right Tiling"),
|
"RT": I18n.tr("Right Tiling"),
|
||||||
"S": I18n.tr("Scrolling"),
|
"S": I18n.tr("Scrolling"),
|
||||||
"T": I18n.tr("Tiling"),
|
"T": I18n.tr("Tiling"),
|
||||||
"VG": I18n.tr("Vertical Grid"),
|
"VG": I18n.tr("Vertical Grid"),
|
||||||
"VK": I18n.tr("Vertical Deck"),
|
"VK": I18n.tr("Vertical Deck"),
|
||||||
"VS": I18n.tr("Vertical Scrolling"),
|
"VS": I18n.tr("Vertical Scrolling"),
|
||||||
"VT": I18n.tr("Vertical Tiling")
|
"VT": I18n.tr("Vertical Tiling")
|
||||||
})
|
})
|
||||||
|
|
||||||
readonly property var layoutIcons: ({
|
readonly property var layoutIcons: ({
|
||||||
"CT": "view_compact",
|
"CT": "view_compact",
|
||||||
"G": "grid_view",
|
"G": "grid_view",
|
||||||
"K": "layers",
|
"K": "layers",
|
||||||
"M": "fullscreen",
|
"M": "fullscreen",
|
||||||
"RT": "view_sidebar",
|
"RT": "view_sidebar",
|
||||||
"S": "view_carousel",
|
"S": "view_carousel",
|
||||||
"T": "view_quilt",
|
"T": "view_quilt",
|
||||||
"VG": "grid_on",
|
"VG": "grid_on",
|
||||||
"VK": "view_day",
|
"VK": "view_day",
|
||||||
"VS": "scrollable_header",
|
"VS": "scrollable_header",
|
||||||
"VT": "clarify"
|
"VT": "clarify"
|
||||||
})
|
})
|
||||||
|
|
||||||
function getLayoutName(symbol) {
|
function getLayoutName(symbol) {
|
||||||
return layoutNames[symbol] || symbol
|
return layoutNames[symbol] || symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLayoutIcon(symbol) {
|
function getLayoutIcon(symbol) {
|
||||||
return layoutIcons[symbol] || "view_quilt"
|
return layoutIcons[symbol] || "view_quilt";
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: DwlService
|
target: DwlService
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
updateOutputState()
|
updateOutputState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onShouldBeVisibleChanged: {
|
onShouldBeVisibleChanged: {
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
updateOutputState()
|
updateOutputState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
updateOutputState()
|
updateOutputState();
|
||||||
}
|
}
|
||||||
|
|
||||||
popupWidth: 300
|
popupWidth: 300
|
||||||
@@ -111,7 +107,7 @@ DankPopout {
|
|||||||
id: layoutContent
|
id: layoutContent
|
||||||
|
|
||||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Theme.outlineMedium
|
border.color: Theme.outlineMedium
|
||||||
border.width: 0
|
border.width: 0
|
||||||
@@ -121,14 +117,14 @@ DankPopout {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
forceActiveFocus()
|
forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
root.close()
|
root.close();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +133,8 @@ DankPopout {
|
|||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
layoutContent.forceActiveFocus()
|
layoutContent.forceActiveFocus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +208,7 @@ DankPopout {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: {
|
onPressed: {
|
||||||
root.close()
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,14 +278,14 @@ DankPopout {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: {
|
onPressed: {
|
||||||
if (!root.triggerScreen) {
|
if (!root.triggerScreen) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (!DwlService.dwlAvailable) {
|
if (!DwlService.dwlAvailable) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DwlService.setLayout(root.triggerScreen.name, index)
|
DwlService.setLayout(root.triggerScreen.name, index);
|
||||||
root.close()
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ DankPopout {
|
|||||||
id: content
|
id: content
|
||||||
|
|
||||||
implicitHeight: contentColumn.height + Theme.spacingL * 2
|
implicitHeight: contentColumn.height + Theme.spacingL * 2
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Theme.outlineMedium
|
border.color: Theme.outlineMedium
|
||||||
border.width: 0
|
border.width: 0
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ DankPopout {
|
|||||||
id: mainContainer
|
id: mainContainer
|
||||||
|
|
||||||
implicitHeight: contentColumn.height + Theme.spacingM * 2
|
implicitHeight: contentColumn.height + Theme.spacingM * 2
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
@@ -358,6 +358,8 @@ DankPopout {
|
|||||||
popoutWidth: root.alignedWidth
|
popoutWidth: root.alignedWidth
|
||||||
popoutHeight: root.alignedHeight
|
popoutHeight: root.alignedHeight
|
||||||
contentOffsetY: Theme.spacingM + 48 + Theme.spacingS + Theme.spacingXS
|
contentOffsetY: Theme.spacingM + 48 + Theme.spacingS + Theme.spacingXS
|
||||||
|
section: root.triggerSection
|
||||||
|
barPosition: root.effectiveBarPosition
|
||||||
Component.onCompleted: root.__mediaTabRef = this
|
Component.onCompleted: root.__mediaTabRef = this
|
||||||
onShowVolumeDropdown: (pos, screen, rightEdge, player, players) => {
|
onShowVolumeDropdown: (pos, screen, rightEdge, player, players) => {
|
||||||
root.__showVolumeDropdown(pos, rightEdge, player, players);
|
root.__showVolumeDropdown(pos, rightEdge, player, players);
|
||||||
@@ -382,7 +384,7 @@ DankPopout {
|
|||||||
active: true
|
active: true
|
||||||
tabBarItem: tabBar
|
tabBarItem: tabBar
|
||||||
keyForwardTarget: mainContainer
|
keyForwardTarget: mainContainer
|
||||||
targetScreen: root.triggerScreen
|
targetScreen: root.screen
|
||||||
parentPopout: root
|
parentPopout: root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import QtQuick.Effects
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -19,6 +18,8 @@ Item {
|
|||||||
property real popoutWidth: 0
|
property real popoutWidth: 0
|
||||||
property real popoutHeight: 0
|
property real popoutHeight: 0
|
||||||
property real contentOffsetY: 0
|
property real contentOffsetY: 0
|
||||||
|
property string section: ""
|
||||||
|
property int barPosition: SettingsData.Position.Top
|
||||||
|
|
||||||
signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
||||||
signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge)
|
signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge)
|
||||||
@@ -40,7 +41,13 @@ Item {
|
|||||||
id: sharedTooltip
|
id: sharedTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool isRightEdge: (SettingsData.barConfigs[0]?.position ?? SettingsData.Position.Top) === SettingsData.Position.Right
|
readonly property bool isRightEdge: {
|
||||||
|
if (barPosition === SettingsData.Position.Right)
|
||||||
|
return true;
|
||||||
|
if (barPosition === SettingsData.Position.Left)
|
||||||
|
return false;
|
||||||
|
return section === "right";
|
||||||
|
}
|
||||||
readonly property bool __isChromeBrowser: {
|
readonly property bool __isChromeBrowser: {
|
||||||
if (!activePlayer?.identity)
|
if (!activePlayer?.identity)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Greetd
|
import Quickshell.Services.Greetd
|
||||||
import Quickshell.Services.Pam
|
import Quickshell.Services.Pam
|
||||||
@@ -35,19 +36,40 @@ Item {
|
|||||||
randomFact = Facts.getRandomFact()
|
randomFact = Facts.getRandomFact()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool weatherInitialized: false
|
||||||
|
|
||||||
|
function initWeatherService() {
|
||||||
|
if (weatherInitialized)
|
||||||
|
return
|
||||||
|
if (!GreetdSettings.settingsLoaded)
|
||||||
|
return
|
||||||
|
if (!GreetdSettings.weatherEnabled)
|
||||||
|
return
|
||||||
|
|
||||||
|
weatherInitialized = true
|
||||||
|
WeatherService.addRef()
|
||||||
|
WeatherService.forceRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: GreetdSettings
|
||||||
|
function onSettingsLoadedChanged() {
|
||||||
|
if (GreetdSettings.settingsLoaded)
|
||||||
|
initWeatherService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
pickRandomFact()
|
pickRandomFact()
|
||||||
WeatherService.addRef()
|
initWeatherService()
|
||||||
|
|
||||||
if (isPrimaryScreen) {
|
if (isPrimaryScreen) {
|
||||||
sessionListProc.running = true
|
sessionListProc.running = true
|
||||||
applyLastSuccessfulUser()
|
applyLastSuccessfulUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CompositorService.isHyprland) {
|
if (CompositorService.isHyprland)
|
||||||
updateHyprlandLayout()
|
updateHyprlandLayout()
|
||||||
hyprlandLayoutUpdateTimer.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLastSuccessfulUser() {
|
function applyLastSuccessfulUser() {
|
||||||
@@ -61,10 +83,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
WeatherService.removeRef()
|
if (weatherInitialized)
|
||||||
if (CompositorService.isHyprland) {
|
WeatherService.removeRef()
|
||||||
hyprlandLayoutUpdateTimer.stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHyprlandLayout() {
|
function updateHyprlandLayout() {
|
||||||
@@ -106,14 +126,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Connections {
|
||||||
id: hyprlandLayoutUpdateTimer
|
target: CompositorService.isHyprland ? Hyprland : null
|
||||||
interval: 1000
|
enabled: CompositorService.isHyprland
|
||||||
running: false
|
|
||||||
repeat: true
|
|
||||||
onTriggered: updateHyprlandLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function onRawEvent(event) {
|
||||||
|
if (event.name === "activelayout")
|
||||||
|
updateHyprlandLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: GreetdMemory
|
target: GreetdMemory
|
||||||
@@ -750,13 +771,13 @@ Item {
|
|||||||
visible: {
|
visible: {
|
||||||
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
|
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
|
||||||
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
|
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
|
||||||
return keyboardVisible && WeatherService.weather.available
|
return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
spacing: 6
|
spacing: 6
|
||||||
visible: WeatherService.weather.available
|
visible: GreetdSettings.weatherEnabled && WeatherService.weather.available
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -780,7 +801,7 @@ Item {
|
|||||||
height: 24
|
height: 24
|
||||||
color: Qt.rgba(255, 255, 255, 0.2)
|
color: Qt.rgba(255, 255, 255, 0.2)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: WeatherService.weather.available && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio) || BatteryService.batteryAvailable)
|
visible: GreetdSettings.weatherEnabled && WeatherService.weather.available && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio) || BatteryService.batteryAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
|||||||
@@ -1110,7 +1110,7 @@ Item {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: "vpn_lock"
|
name: "vpn_lock"
|
||||||
size: Theme.iconSize - 2
|
size: Theme.iconSize - 2
|
||||||
color: NetworkService.vpnConnected ? Theme.primary : Qt.rgba(255, 255, 255, 0.5)
|
color: "white"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: NetworkService.vpnAvailable && NetworkService.vpnConnected
|
visible: NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ DankPopout {
|
|||||||
return Math.max(300, Math.min(baseHeight, maxHeight));
|
return Math.max(300, Math.min(baseHeight, maxHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 0
|
border.width: 0
|
||||||
@@ -138,7 +138,7 @@ DankPopout {
|
|||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
root.close();
|
notificationHistoryVisible = false;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if (externalKeyboardController) {
|
} else if (externalKeyboardController) {
|
||||||
externalKeyboardController.handleKey(event);
|
externalKeyboardController.handleKey(event);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ DankPopout {
|
|||||||
id: popoutContainer
|
id: popoutContainer
|
||||||
|
|
||||||
implicitHeight: popoutColumn.implicitHeight + Theme.spacingL * 2
|
implicitHeight: popoutColumn.implicitHeight + Theme.spacingL * 2
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.width: 0
|
border.width: 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
@@ -34,14 +33,14 @@ DankPopout {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
forceActiveFocus()
|
forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
root.close()
|
root.close();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +49,8 @@ DankPopout {
|
|||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
popoutContainer.forceActiveFocus()
|
popoutContainer.forceActiveFocus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,12 +69,12 @@ DankPopout {
|
|||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item && "closePopout" in item) {
|
if (item && "closePopout" in item) {
|
||||||
item.closePopout = function() {
|
item.closePopout = function () {
|
||||||
root.close()
|
root.close();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (item) {
|
if (item) {
|
||||||
root.contentHeight = Qt.binding(() => item.implicitHeight + Theme.spacingS * 2)
|
root.contentHeight = Qt.binding(() => item.implicitHeight + Theme.spacingS * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Effects
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modules.ProcessList
|
import qs.Modules.ProcessList
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -57,7 +51,7 @@ DankPopout {
|
|||||||
id: processListContent
|
id: processListContent
|
||||||
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 0
|
border.width: 0
|
||||||
clip: true
|
clip: true
|
||||||
@@ -70,7 +64,7 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
processContextMenu.parent = processListContent;
|
processContextMenu.parent = processListContent;
|
||||||
}
|
}
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
processListPopout.close();
|
processListPopout.close();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
@@ -108,7 +102,6 @@ DankPopout {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - Theme.spacingM * 2
|
width: parent.width - Theme.spacingM * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -124,13 +117,8 @@ DankPopout {
|
|||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
contextMenu: processContextMenu
|
contextMenu: processContextMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import QtQuick.Effects
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modules.Settings.Widgets
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: aboutTab
|
id: aboutTab
|
||||||
@@ -200,6 +199,16 @@ Item {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: SystemUpdateService.shellCodename.length > 0
|
||||||
|
text: `"${SystemUpdateService.shellCodename}"`
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.italic: true
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: resourceButtonsRow
|
id: resourceButtonsRow
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(450, 400)
|
minimumSize: Qt.size(450, 400)
|
||||||
implicitWidth: 600
|
implicitWidth: 600
|
||||||
implicitHeight: 650
|
implicitHeight: 650
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
@@ -537,7 +537,7 @@ FloatingWindow {
|
|||||||
title: I18n.tr("Third-Party Plugin Warning")
|
title: I18n.tr("Third-Party Plugin Warning")
|
||||||
implicitWidth: 500
|
implicitWidth: 500
|
||||||
implicitHeight: 350
|
implicitHeight: 350
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(400, 350)
|
minimumSize: Qt.size(400, 350)
|
||||||
implicitWidth: 500
|
implicitWidth: 500
|
||||||
implicitHeight: 550
|
implicitHeight: 550
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ DankPopout {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: updaterPanel
|
id: updaterPanel
|
||||||
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
smooth: true
|
smooth: true
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ Variants {
|
|||||||
readonly property bool transitioning: transitionAnimation.running
|
readonly property bool transitioning: transitionAnimation.running
|
||||||
property bool effectActive: false
|
property bool effectActive: false
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
|
property string pendingWallpaper: ""
|
||||||
|
|
||||||
function getFillMode(modeName) {
|
function getFillMode(modeName) {
|
||||||
switch (modeName) {
|
switch (modeName) {
|
||||||
@@ -162,12 +163,10 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
if (!newPath || newPath.startsWith("#"))
|
if (!newPath || newPath.startsWith("#"))
|
||||||
return;
|
return;
|
||||||
if (root.transitioning) {
|
|
||||||
transitionAnimation.stop();
|
if (root.transitioning || root.effectActive) {
|
||||||
root.transitionProgress = 0;
|
root.pendingWallpaper = newPath;
|
||||||
root.effectActive = false;
|
return;
|
||||||
currentWallpaper.source = nextWallpaper.source;
|
|
||||||
nextWallpaper.source = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentWallpaper.source) {
|
if (!currentWallpaper.source) {
|
||||||
@@ -488,24 +487,28 @@ Variants {
|
|||||||
currentWallpaper.source = nextWallpaper.source;
|
currentWallpaper.source = nextWallpaper.source;
|
||||||
}
|
}
|
||||||
root.useNextForEffect = false;
|
root.useNextForEffect = false;
|
||||||
Qt.callLater(() => {
|
nextWallpaper.source = "";
|
||||||
nextWallpaper.source = "";
|
root.transitionProgress = 0.0;
|
||||||
|
currentWallpaper.layer.enabled = false;
|
||||||
|
nextWallpaper.layer.enabled = false;
|
||||||
|
currentWallpaper.cache = true;
|
||||||
|
nextWallpaper.cache = false;
|
||||||
|
root.effectActive = false;
|
||||||
|
|
||||||
|
if (root.pendingWallpaper) {
|
||||||
|
var pending = root.pendingWallpaper;
|
||||||
|
root.pendingWallpaper = "";
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.effectActive = false;
|
root.changeWallpaper(pending, true);
|
||||||
currentWallpaper.layer.enabled = false;
|
|
||||||
nextWallpaper.layer.enabled = false;
|
|
||||||
currentWallpaper.cache = true;
|
|
||||||
nextWallpaper.cache = false;
|
|
||||||
root.transitionProgress = 0.0;
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: effectLoader.active ? effectLoader.item : (root.actualTransitionType === "none" ? currentWallpaper : null)
|
source: effectLoader.active ? effectLoader.item : currentWallpaper
|
||||||
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && source !== null
|
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
|
||||||
blurEnabled: true
|
blurEnabled: true
|
||||||
blur: 0.8
|
blur: 0.8
|
||||||
blurMax: 75
|
blurMax: 75
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ Singleton {
|
|||||||
|
|
||||||
property bool available: CompositorService.isNiri && shortcutInhibitorAvailable
|
property bool available: CompositorService.isNiri && shortcutInhibitorAvailable
|
||||||
property string currentProvider: "niri"
|
property string currentProvider: "niri"
|
||||||
|
|
||||||
|
readonly property string cheatsheetProvider: {
|
||||||
|
if (CompositorService.isNiri)
|
||||||
|
return "niri";
|
||||||
|
if (CompositorService.isHyprland)
|
||||||
|
return "hyprland";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
property bool cheatsheetAvailable: cheatsheetProvider !== ""
|
||||||
|
property bool cheatsheetLoading: false
|
||||||
|
property var cheatsheet: ({})
|
||||||
|
|
||||||
property bool loading: false
|
property bool loading: false
|
||||||
property bool saving: false
|
property bool saving: false
|
||||||
property bool fixing: false
|
property bool fixing: false
|
||||||
@@ -58,17 +70,14 @@ Singleton {
|
|||||||
signal bindSaveCompleted(bool success)
|
signal bindSaveCompleted(bool success)
|
||||||
signal bindRemoved(string key)
|
signal bindRemoved(string key)
|
||||||
signal dmsBindsFixed
|
signal dmsBindsFixed
|
||||||
|
signal cheatsheetLoaded
|
||||||
Component.onCompleted: {
|
|
||||||
if (available)
|
|
||||||
Qt.callLater(loadBinds);
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: CompositorService
|
target: CompositorService
|
||||||
function onCompositorChanged() {
|
function onCompositorChanged() {
|
||||||
if (CompositorService.isNiri)
|
if (!CompositorService.isNiri)
|
||||||
Qt.callLater(root.loadBinds);
|
return;
|
||||||
|
Qt.callLater(root.loadBinds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +89,31 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: cheatsheetProcess
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
root.cheatsheet = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[KeybindsService] Failed to parse cheatsheet:", e);
|
||||||
|
root.cheatsheet = {};
|
||||||
|
}
|
||||||
|
root.cheatsheetLoading = false;
|
||||||
|
root.cheatsheetLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0)
|
||||||
|
return;
|
||||||
|
console.warn("[KeybindsService] Cheatsheet load failed with code:", exitCode);
|
||||||
|
root.cheatsheetLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: loadProcess
|
id: loadProcess
|
||||||
running: false
|
running: false
|
||||||
@@ -199,6 +233,17 @@ Singleton {
|
|||||||
loadBinds(true);
|
loadBinds(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadCheatsheet(provider) {
|
||||||
|
if (cheatsheetProcess.running)
|
||||||
|
return;
|
||||||
|
const target = provider || cheatsheetProvider;
|
||||||
|
if (!target)
|
||||||
|
return;
|
||||||
|
cheatsheetLoading = true;
|
||||||
|
cheatsheetProcess.command = ["dms", "keybinds", "show", target];
|
||||||
|
cheatsheetProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
function loadBinds(showLoading) {
|
function loadBinds(showLoading) {
|
||||||
if (loadProcess.running || !available)
|
if (loadProcess.running || !available)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -20,6 +19,7 @@ Singleton {
|
|||||||
property string distribution: ""
|
property string distribution: ""
|
||||||
property bool distributionSupported: false
|
property bool distributionSupported: false
|
||||||
property string shellVersion: ""
|
property string shellVersion: ""
|
||||||
|
property string shellCodename: ""
|
||||||
|
|
||||||
readonly property var archBasedUCSettings: {
|
readonly property var archBasedUCSettings: {
|
||||||
"listUpdatesSettings": {
|
"listUpdatesSettings": {
|
||||||
@@ -34,7 +34,7 @@ Singleton {
|
|||||||
"currentVersion": match[2],
|
"currentVersion": match[2],
|
||||||
"newVersion": match[3],
|
"newVersion": match[3],
|
||||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ Singleton {
|
|||||||
"currentVersion": match[2],
|
"currentVersion": match[2],
|
||||||
"newVersion": match[3],
|
"newVersion": match[3],
|
||||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ Singleton {
|
|||||||
"currentVersion": "",
|
"currentVersion": "",
|
||||||
"newVersion": match[2],
|
"newVersion": match[2],
|
||||||
"description": `${match[1]} → ${match[2]}`
|
"description": `${match[1]} → ${match[2]}`
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,40 +100,49 @@ Singleton {
|
|||||||
command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"]
|
command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"]
|
||||||
running: true
|
running: true
|
||||||
|
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
distribution = stdout.text.trim().toLowerCase()
|
distribution = stdout.text.trim().toLowerCase();
|
||||||
distributionSupported = supportedDistributions.includes(distribution)
|
distributionSupported = supportedDistributions.includes(distribution);
|
||||||
|
|
||||||
if (distributionSupported) {
|
if (distributionSupported) {
|
||||||
updateFinderDetection.running = true
|
updateFinderDetection.running = true;
|
||||||
pkgManagerDetection.running = true
|
pkgManagerDetection.running = true;
|
||||||
checkForUpdates()
|
checkForUpdates();
|
||||||
} else {
|
} else {
|
||||||
console.warn("SystemUpdate: Unsupported distribution:", distribution)
|
console.warn("SystemUpdate: Unsupported distribution:", distribution);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("SystemUpdate: Failed to detect distribution")
|
console.warn("SystemUpdate: Failed to detect distribution");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: StdioCollector {}
|
stdout: StdioCollector {}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
versionDetection.running = true
|
versionDetection.running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: versionDetection
|
id: versionDetection
|
||||||
command: [
|
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`]
|
||||||
"sh", "-c",
|
|
||||||
`cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`
|
|
||||||
]
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
shellVersion = text.trim()
|
shellVersion = text.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: codenameDetection
|
||||||
|
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
|
||||||
|
running: true
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
shellCodename = text.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,12 +151,12 @@ Singleton {
|
|||||||
id: updateFinderDetection
|
id: updateFinderDetection
|
||||||
command: ["sh", "-c", "which checkupdates"]
|
command: ["sh", "-c", "which checkupdates"]
|
||||||
|
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
const exeFound = stdout.text.trim()
|
const exeFound = stdout.text.trim();
|
||||||
updChecker = exeFound.split('/').pop()
|
updChecker = exeFound.split('/').pop();
|
||||||
} else {
|
} else {
|
||||||
console.warn("SystemUpdate: No update checker found. Will use package manager.")
|
console.warn("SystemUpdate: No update checker found. Will use package manager.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,12 +167,12 @@ Singleton {
|
|||||||
id: pkgManagerDetection
|
id: pkgManagerDetection
|
||||||
command: ["sh", "-c", "which paru || which yay || which dnf"]
|
command: ["sh", "-c", "which paru || which yay || which dnf"]
|
||||||
|
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
const exeFound = stdout.text.trim()
|
const exeFound = stdout.text.trim();
|
||||||
pkgManager = exeFound.split('/').pop()
|
pkgManager = exeFound.split('/').pop();
|
||||||
} else {
|
} else {
|
||||||
console.warn("SystemUpdate: No package manager found")
|
console.warn("SystemUpdate: No package manager found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,19 +182,17 @@ Singleton {
|
|||||||
Process {
|
Process {
|
||||||
id: updateChecker
|
id: updateChecker
|
||||||
|
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
isChecking = false
|
isChecking = false;
|
||||||
const correctExitCodes = updChecker.length > 0 ?
|
const correctExitCodes = updChecker.length > 0 ? [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.correctExitCodes) : [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.correctExitCodes);
|
||||||
[updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.correctExitCodes) :
|
|
||||||
[pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.correctExitCodes)
|
|
||||||
if (correctExitCodes.includes(exitCode)) {
|
if (correctExitCodes.includes(exitCode)) {
|
||||||
parseUpdates(stdout.text)
|
parseUpdates(stdout.text);
|
||||||
hasError = false
|
hasError = false;
|
||||||
errorMessage = ""
|
errorMessage = "";
|
||||||
} else {
|
} else {
|
||||||
hasError = true
|
hasError = true;
|
||||||
errorMessage = "Failed to check for updates"
|
errorMessage = "Failed to check for updates";
|
||||||
console.warn("SystemUpdate: Update check failed with code:", exitCode)
|
console.warn("SystemUpdate: Update check failed with code:", exitCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,67 +201,67 @@ Singleton {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: updater
|
id: updater
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
checkForUpdates()
|
checkForUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForUpdates() {
|
function checkForUpdates() {
|
||||||
if (!distributionSupported || (!pkgManager && !updChecker) || isChecking) return
|
if (!distributionSupported || (!pkgManager && !updChecker) || isChecking)
|
||||||
|
return;
|
||||||
isChecking = true
|
isChecking = true;
|
||||||
hasError = false
|
hasError = false;
|
||||||
if (updChecker.length > 0) {
|
if (updChecker.length > 0) {
|
||||||
updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params)
|
updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params);
|
||||||
} else {
|
} else {
|
||||||
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params)
|
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params);
|
||||||
}
|
}
|
||||||
updateChecker.running = true
|
updateChecker.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseUpdates(output) {
|
function parseUpdates(output) {
|
||||||
const lines = output.trim().split('\n').filter(line => line.trim())
|
const lines = output.trim().split('\n').filter(line => line.trim());
|
||||||
const updates = []
|
const updates = [];
|
||||||
|
|
||||||
const regex = packageManagerParams[pkgManager].parserSettings.lineRegex
|
const regex = packageManagerParams[pkgManager].parserSettings.lineRegex;
|
||||||
const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer
|
const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer;
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const match = line.match(regex)
|
const match = line.match(regex);
|
||||||
if (match) {
|
if (match) {
|
||||||
updates.push(entryProducer(match))
|
updates.push(entryProducer(match));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
availableUpdates = updates
|
availableUpdates = updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runUpdates() {
|
function runUpdates() {
|
||||||
if (!distributionSupported || !pkgManager || updateCount === 0) return
|
if (!distributionSupported || !pkgManager || updateCount === 0)
|
||||||
|
return;
|
||||||
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
const terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||||
|
|
||||||
if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
|
if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
|
||||||
const updateCommand = `${SettingsData.updaterCustomCommand} && echo "Updates complete! Press Enter to close..." && read`
|
const updateCommand = `${SettingsData.updaterCustomCommand} && echo "Updates complete! Press Enter to close..." && read`;
|
||||||
const termClass = SettingsData.updaterTerminalAdditionalParams
|
const termClass = SettingsData.updaterTerminalAdditionalParams;
|
||||||
|
|
||||||
var finalCommand = [terminal]
|
var finalCommand = [terminal];
|
||||||
if (termClass.length > 0) {
|
if (termClass.length > 0) {
|
||||||
finalCommand = finalCommand.concat(termClass.split(" "))
|
finalCommand = finalCommand.concat(termClass.split(" "));
|
||||||
}
|
}
|
||||||
finalCommand.push("-e")
|
finalCommand.push("-e");
|
||||||
finalCommand.push("sh")
|
finalCommand.push("sh");
|
||||||
finalCommand.push("-c")
|
finalCommand.push("-c");
|
||||||
finalCommand.push(updateCommand)
|
finalCommand.push(updateCommand);
|
||||||
updater.command = finalCommand
|
updater.command = finalCommand;
|
||||||
} else {
|
} else {
|
||||||
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ")
|
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ");
|
||||||
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : ""
|
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : "";
|
||||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`
|
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`;
|
||||||
|
|
||||||
updater.command = [terminal, "-e", "sh", "-c", updateCommand]
|
updater.command = [terminal, "-e", "sh", "-c", updateCommand];
|
||||||
}
|
}
|
||||||
updater.running = true
|
updater.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -269,16 +276,16 @@ Singleton {
|
|||||||
|
|
||||||
function updatestatus(): string {
|
function updatestatus(): string {
|
||||||
if (root.isChecking) {
|
if (root.isChecking) {
|
||||||
return "ERROR: already checking"
|
return "ERROR: already checking";
|
||||||
}
|
}
|
||||||
if (!distributionSupported) {
|
if (!distributionSupported) {
|
||||||
return "ERROR: distribution not supported"
|
return "ERROR: distribution not supported";
|
||||||
}
|
}
|
||||||
if (!pkgManager && !updChecker) {
|
if (!pkgManager && !updChecker) {
|
||||||
return "ERROR: update checker not available"
|
return "ERROR: update checker not available";
|
||||||
}
|
}
|
||||||
root.checkForUpdates()
|
root.checkForUpdates();
|
||||||
return "SUCCESS: Now checking..."
|
return "SUCCESS: Now checking...";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Modules.Greetd
|
||||||
import "../Common/suncalc.js" as SunCalc
|
import "../Common/suncalc.js" as SunCalc
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
@@ -509,27 +510,29 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateLocation() {
|
function updateLocation() {
|
||||||
if (SettingsData.useAutoLocation) {
|
const useAuto = SessionData.isGreeterMode ? GreetdSettings.useAutoLocation : SettingsData.useAutoLocation;
|
||||||
|
const coords = SessionData.isGreeterMode ? GreetdSettings.weatherCoordinates : SettingsData.weatherCoordinates;
|
||||||
|
const cityName = SessionData.isGreeterMode ? GreetdSettings.weatherLocation : SettingsData.weatherLocation;
|
||||||
|
|
||||||
|
if (useAuto) {
|
||||||
getLocationFromIP();
|
getLocationFromIP();
|
||||||
} else {
|
return;
|
||||||
const coords = SettingsData.weatherCoordinates;
|
}
|
||||||
if (coords) {
|
|
||||||
const parts = coords.split(",");
|
if (coords) {
|
||||||
if (parts.length === 2) {
|
const parts = coords.split(",");
|
||||||
const lat = parseFloat(parts[0]);
|
if (parts.length === 2) {
|
||||||
const lon = parseFloat(parts[1]);
|
const lat = parseFloat(parts[0]);
|
||||||
if (!isNaN(lat) && !isNaN(lon)) {
|
const lon = parseFloat(parts[1]);
|
||||||
getLocationFromCoords(lat, lon);
|
if (!isNaN(lat) && !isNaN(lon)) {
|
||||||
return;
|
getLocationFromCoords(lat, lon);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cityName = SettingsData.weatherLocation;
|
|
||||||
if (cityName) {
|
|
||||||
getLocationFromCity(cityName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cityName)
|
||||||
|
getLocationFromCity(cityName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLocationFromCoords(lat, lon) {
|
function getLocationFromCoords(lat, lon) {
|
||||||
@@ -867,7 +870,7 @@ Singleton {
|
|||||||
Timer {
|
Timer {
|
||||||
id: updateTimer
|
id: updateTimer
|
||||||
interval: nextInterval()
|
interval: nextInterval()
|
||||||
running: root.refCount > 0 && SettingsData.weatherEnabled
|
running: root.refCount > 0 && SettingsData.weatherEnabled && !SessionData.isGreeterMode
|
||||||
repeat: true
|
repeat: true
|
||||||
triggeredOnStart: true
|
triggeredOnStart: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
|
|||||||
@@ -46,11 +46,12 @@ Flickable {
|
|||||||
lastWheelTime = currentTime;
|
lastWheelTime = currentTime;
|
||||||
|
|
||||||
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
||||||
const hasAngle = event.angleDelta && event.angleDelta.y !== 0;
|
|
||||||
const deltaY = event.angleDelta.y;
|
const deltaY = event.angleDelta.y;
|
||||||
const isMouseWheel = !hasPixel && hasAngle;
|
const isTraditionalMouse = !hasPixel && Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
|
||||||
|
const isHighDpiMouse = !hasPixel && !isTraditionalMouse && deltaY !== 0;
|
||||||
|
const isTouchpad = hasPixel;
|
||||||
|
|
||||||
if (isMouseWheel) {
|
if (isTraditionalMouse) {
|
||||||
sessionUsedMouseWheel = true;
|
sessionUsedMouseWheel = true;
|
||||||
momentumTimer.stop();
|
momentumTimer.stop();
|
||||||
flickable.isMomentumActive = false;
|
flickable.isMomentumActive = false;
|
||||||
@@ -58,7 +59,7 @@ Flickable {
|
|||||||
momentum = 0;
|
momentum = 0;
|
||||||
flickable.momentumVelocity = 0;
|
flickable.momentumVelocity = 0;
|
||||||
|
|
||||||
const lines = Math.floor(Math.abs(deltaY) / 120);
|
const lines = Math.round(Math.abs(deltaY) / 120);
|
||||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * flickable.mouseWheelSpeed;
|
const scrollAmount = (deltaY > 0 ? -lines : lines) * flickable.mouseWheelSpeed;
|
||||||
let newY = flickable.contentY + scrollAmount;
|
let newY = flickable.contentY + scrollAmount;
|
||||||
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY));
|
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY));
|
||||||
@@ -68,17 +69,29 @@ Flickable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flickable.contentY = newY;
|
flickable.contentY = newY;
|
||||||
} else {
|
} else if (isHighDpiMouse) {
|
||||||
|
sessionUsedMouseWheel = true;
|
||||||
|
momentumTimer.stop();
|
||||||
|
flickable.isMomentumActive = false;
|
||||||
|
velocitySamples = [];
|
||||||
|
momentum = 0;
|
||||||
|
flickable.momentumVelocity = 0;
|
||||||
|
|
||||||
|
let delta = deltaY / 8 * touchpadSpeed;
|
||||||
|
let newY = flickable.contentY - delta;
|
||||||
|
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY));
|
||||||
|
|
||||||
|
if (flickable.flicking) {
|
||||||
|
flickable.cancelFlick();
|
||||||
|
}
|
||||||
|
|
||||||
|
flickable.contentY = newY;
|
||||||
|
} else if (isTouchpad) {
|
||||||
sessionUsedMouseWheel = false;
|
sessionUsedMouseWheel = false;
|
||||||
momentumTimer.stop();
|
momentumTimer.stop();
|
||||||
flickable.isMomentumActive = false;
|
flickable.isMomentumActive = false;
|
||||||
|
|
||||||
let delta = 0;
|
let delta = event.pixelDelta.y * touchpadSpeed;
|
||||||
if (event.pixelDelta.y !== 0) {
|
|
||||||
delta = event.pixelDelta.y * touchpadSpeed;
|
|
||||||
} else {
|
|
||||||
delta = event.angleDelta.y / 8 * touchpadSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
velocitySamples.push({
|
velocitySamples.push({
|
||||||
"delta": delta,
|
"delta": delta,
|
||||||
@@ -94,7 +107,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
if (timeDelta < 50) {
|
||||||
momentum = momentum * momentumRetention + delta * 0.15;
|
momentum = momentum * momentumRetention + delta * 0.15;
|
||||||
delta += momentum;
|
delta += momentum;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,11 +50,12 @@ GridView {
|
|||||||
lastWheelTime = currentTime;
|
lastWheelTime = currentTime;
|
||||||
|
|
||||||
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
||||||
const hasAngle = event.angleDelta && event.angleDelta.y !== 0;
|
|
||||||
const deltaY = event.angleDelta.y;
|
const deltaY = event.angleDelta.y;
|
||||||
const isMouseWheel = !hasPixel && hasAngle;
|
const isTraditionalMouse = !hasPixel && Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
|
||||||
|
const isHighDpiMouse = !hasPixel && !isTraditionalMouse && deltaY !== 0;
|
||||||
|
const isTouchpad = hasPixel;
|
||||||
|
|
||||||
if (isMouseWheel) {
|
if (isTraditionalMouse) {
|
||||||
sessionUsedMouseWheel = true;
|
sessionUsedMouseWheel = true;
|
||||||
momentumTimer.stop();
|
momentumTimer.stop();
|
||||||
isMomentumActive = false;
|
isMomentumActive = false;
|
||||||
@@ -62,7 +63,7 @@ GridView {
|
|||||||
momentum = 0;
|
momentum = 0;
|
||||||
momentumVelocity = 0;
|
momentumVelocity = 0;
|
||||||
|
|
||||||
const lines = Math.floor(Math.abs(deltaY) / 120);
|
const lines = Math.round(Math.abs(deltaY) / 120);
|
||||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * cellHeight * 0.35;
|
const scrollAmount = (deltaY > 0 ? -lines : lines) * cellHeight * 0.35;
|
||||||
let newY = contentY + scrollAmount;
|
let newY = contentY + scrollAmount;
|
||||||
newY = Math.max(0, Math.min(contentHeight - height, newY));
|
newY = Math.max(0, Math.min(contentHeight - height, newY));
|
||||||
@@ -72,12 +73,29 @@ GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentY = newY;
|
contentY = newY;
|
||||||
} else {
|
} else if (isHighDpiMouse) {
|
||||||
|
sessionUsedMouseWheel = true;
|
||||||
|
momentumTimer.stop();
|
||||||
|
isMomentumActive = false;
|
||||||
|
velocitySamples = [];
|
||||||
|
momentum = 0;
|
||||||
|
momentumVelocity = 0;
|
||||||
|
|
||||||
|
let delta = deltaY / 120 * cellHeight * 1.2;
|
||||||
|
let newY = contentY - delta;
|
||||||
|
newY = Math.max(0, Math.min(contentHeight - height, newY));
|
||||||
|
|
||||||
|
if (flicking) {
|
||||||
|
cancelFlick();
|
||||||
|
}
|
||||||
|
|
||||||
|
contentY = newY;
|
||||||
|
} else if (isTouchpad) {
|
||||||
sessionUsedMouseWheel = false;
|
sessionUsedMouseWheel = false;
|
||||||
momentumTimer.stop();
|
momentumTimer.stop();
|
||||||
isMomentumActive = false;
|
isMomentumActive = false;
|
||||||
|
|
||||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * touchpadSpeed : event.angleDelta.y / 120 * cellHeight * 1.2;
|
let delta = event.pixelDelta.y * touchpadSpeed;
|
||||||
|
|
||||||
velocitySamples.push({
|
velocitySamples.push({
|
||||||
"delta": delta,
|
"delta": delta,
|
||||||
@@ -93,7 +111,7 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
if (timeDelta < 50) {
|
||||||
momentum = momentum * momentumRetention + delta * 0.15;
|
momentum = momentum * momentumRetention + delta * 0.15;
|
||||||
delta += momentum;
|
delta += momentum;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -69,11 +69,12 @@ ListView {
|
|||||||
lastWheelTime = currentTime;
|
lastWheelTime = currentTime;
|
||||||
|
|
||||||
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
||||||
const hasAngle = event.angleDelta && event.angleDelta.y !== 0;
|
|
||||||
const deltaY = event.angleDelta.y;
|
const deltaY = event.angleDelta.y;
|
||||||
const isMouseWheel = !hasPixel && hasAngle;
|
const isTraditionalMouse = !hasPixel && Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
|
||||||
|
const isHighDpiMouse = !hasPixel && !isTraditionalMouse && deltaY !== 0;
|
||||||
|
const isTouchpad = hasPixel;
|
||||||
|
|
||||||
if (isMouseWheel) {
|
if (isTraditionalMouse) {
|
||||||
sessionUsedMouseWheel = true;
|
sessionUsedMouseWheel = true;
|
||||||
momentumTimer.stop();
|
momentumTimer.stop();
|
||||||
isMomentumActive = false;
|
isMomentumActive = false;
|
||||||
@@ -81,7 +82,7 @@ ListView {
|
|||||||
momentum = 0;
|
momentum = 0;
|
||||||
momentumVelocity = 0;
|
momentumVelocity = 0;
|
||||||
|
|
||||||
const lines = Math.floor(Math.abs(deltaY) / 120);
|
const lines = Math.round(Math.abs(deltaY) / 120);
|
||||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * mouseWheelSpeed;
|
const scrollAmount = (deltaY > 0 ? -lines : lines) * mouseWheelSpeed;
|
||||||
let newY = listView.contentY + scrollAmount;
|
let newY = listView.contentY + scrollAmount;
|
||||||
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY);
|
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY);
|
||||||
@@ -93,17 +94,31 @@ ListView {
|
|||||||
|
|
||||||
listView.contentY = newY;
|
listView.contentY = newY;
|
||||||
savedY = newY;
|
savedY = newY;
|
||||||
} else {
|
} else if (isHighDpiMouse) {
|
||||||
|
sessionUsedMouseWheel = true;
|
||||||
|
momentumTimer.stop();
|
||||||
|
isMomentumActive = false;
|
||||||
|
velocitySamples = [];
|
||||||
|
momentum = 0;
|
||||||
|
momentumVelocity = 0;
|
||||||
|
|
||||||
|
let delta = deltaY / 8 * touchpadSpeed;
|
||||||
|
let newY = listView.contentY - delta;
|
||||||
|
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY);
|
||||||
|
newY = Math.max(listView.originY, Math.min(maxY, newY));
|
||||||
|
|
||||||
|
if (listView.flicking) {
|
||||||
|
listView.cancelFlick();
|
||||||
|
}
|
||||||
|
|
||||||
|
listView.contentY = newY;
|
||||||
|
savedY = newY;
|
||||||
|
} else if (isTouchpad) {
|
||||||
sessionUsedMouseWheel = false;
|
sessionUsedMouseWheel = false;
|
||||||
momentumTimer.stop();
|
momentumTimer.stop();
|
||||||
isMomentumActive = false;
|
isMomentumActive = false;
|
||||||
|
|
||||||
let delta = 0;
|
let delta = event.pixelDelta.y * touchpadSpeed;
|
||||||
if (event.pixelDelta.y !== 0) {
|
|
||||||
delta = event.pixelDelta.y * touchpadSpeed;
|
|
||||||
} else {
|
|
||||||
delta = event.angleDelta.y / 8 * touchpadSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
velocitySamples.push({
|
velocitySamples.push({
|
||||||
"delta": delta,
|
"delta": delta,
|
||||||
@@ -119,7 +134,7 @@ ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
if (timeDelta < 50) {
|
||||||
momentum = momentum * 0.92 + delta * 0.15;
|
momentum = momentum * 0.92 + delta * 0.15;
|
||||||
delta += momentum;
|
delta += momentum;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -396,7 +396,6 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
id: bgShadowLayer
|
id: bgShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: contentWrapper.popupSurfaceAlpha >= 0.95
|
|
||||||
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||||
layer.smooth: false
|
layer.smooth: false
|
||||||
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr))
|
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr))
|
||||||
@@ -421,6 +420,7 @@ Item {
|
|||||||
DankRectangle {
|
DankRectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user