1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 08:22:51 -05:00

feat: add scroll compositor support (#959)

* added scroll support

* import QuickShell.i3

* update scroll provider registration logic

* improve scroll support for workspace switcher

* update title for scroll keybinds

* add scroll to dms-greeter

* fix: formatting & sway keybind provider

* readme update

---------

Co-authored-by: bbedward <bbedward@gmail.com>
This commit is contained in:
Varshit
2025-12-09 21:57:46 +01:00
committed by GitHub
parent aeacf109eb
commit f94011cf05
18 changed files with 298 additions and 169 deletions

View File

@@ -5,21 +5,21 @@
<img src="assets/danklogo.svg" alt="DankMaterialShell" width="200"> <img src="assets/danklogo.svg" alt="DankMaterialShell" width="200">
</a> </a>
### A modern desktop shell for Wayland ### A modern desktop shell for Wayland
Built with [Quickshell](https://quickshell.org/) and [Go](https://go.dev/) Built with [Quickshell](https://quickshell.org/) and [Go](https://go.dev/)
[![Documentation](https://img.shields.io/badge/docs-danklinux.com-9ccbfb?style=for-the-badge&labelColor=101418)](https://danklinux.com/docs) [![Documentation](https://img.shields.io/badge/docs-danklinux.com-9ccbfb?style=for-the-badge&labelColor=101418)](https://danklinux.com/docs)
[![GitHub stars](https://img.shields.io/github/stars/AvengeMedia/DankMaterialShell?style=for-the-badge&labelColor=101418&color=ffd700)](https://github.com/AvengeMedia/DankMaterialShell/stargazers) [![GitHub stars](https://img.shields.io/github/stars/AvengeMedia/DankMaterialShell?style=for-the-badge&labelColor=101418&color=ffd700)](https://github.com/AvengeMedia/DankMaterialShell/stargazers)
[![GitHub License](https://img.shields.io/github/license/AvengeMedia/DankMaterialShell?style=for-the-badge&labelColor=101418&color=b9c8da)](https://github.com/AvengeMedia/DankMaterialShell/blob/master/LICENSE) [![GitHub License](https://img.shields.io/github/license/AvengeMedia/DankMaterialShell?style=for-the-badge&labelColor=101418&color=b9c8da)](https://github.com/AvengeMedia/DankMaterialShell/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/v/release/AvengeMedia/DankMaterialShell?style=for-the-badge&labelColor=101418&color=9ccbfb)](https://github.com/AvengeMedia/DankMaterialShell/releases) [![GitHub release](https://img.shields.io/github/v/release/AvengeMedia/DankMaterialShell?style=for-the-badge&labelColor=101418&color=9ccbfb)](https://github.com/AvengeMedia/DankMaterialShell/releases)
[![AUR version](https://img.shields.io/aur/version/dms-shell-bin?style=for-the-badge&labelColor=101418&color=9ccbfb)](https://aur.archlinux.org/packages/dms-shell-bin) [![AUR version](https://img.shields.io/aur/version/dms-shell-bin?style=for-the-badge&labelColor=101418&color=9ccbfb)](https://aur.archlinux.org/packages/dms-shell-bin)
[![AUR version (git)](https://img.shields.io/aur/version/dms-shell-git?style=for-the-badge&labelColor=101418&color=9ccbfb&label=AUR%20(git))](https://aur.archlinux.org/packages/dms-shell-git) [![AUR version (git)](<https://img.shields.io/aur/version/dms-shell-git?style=for-the-badge&labelColor=101418&color=9ccbfb&label=AUR%20(git)>)](https://aur.archlinux.org/packages/dms-shell-git)
[![Ko-Fi donate](https://img.shields.io/badge/donate-kofi?style=for-the-badge&logo=ko-fi&logoColor=ffffff&label=ko-fi&labelColor=101418&color=f16061&link=https%3A%2F%2Fko-fi.com%2Fdanklinux)](https://ko-fi.com/danklinux) [![Ko-Fi donate](https://img.shields.io/badge/donate-kofi?style=for-the-badge&logo=ko-fi&logoColor=ffffff&label=ko-fi&labelColor=101418&color=f16061&link=https%3A%2F%2Fko-fi.com%2Fdanklinux)](https://ko-fi.com/danklinux)
</div> </div>
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop. DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), [Scroll](https://github.com/dawsers/scroll), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
## Repository Structure ## Repository Structure
@@ -105,7 +105,7 @@ Extend functionality with the [plugin registry](https://plugins.danklinux.com).
## Supported Compositors ## Supported Compositors
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), and [labwc](https://labwc.github.io/) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features. Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [labwc](https://labwc.github.io/), and [Scroll](https://github.com/dawsers/scroll) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
[Compositor configuration guide](https://danklinux.com/docs/dankmaterialshell/compositors) [Compositor configuration guide](https://danklinux.com/docs/dankmaterialshell/compositors)
@@ -143,6 +143,7 @@ See component-specific documentation:
### Building from Source ### Building from Source
**Core + Dankinstall:** **Core + Dankinstall:**
```bash ```bash
cd core cd core
make # Build dms CLI make # Build dms CLI
@@ -150,11 +151,13 @@ make dankinstall # Build installer
``` ```
**Shell:** **Shell:**
```bash ```bash
quickshell -p quickshell/ quickshell -p quickshell/
``` ```
**NixOS:** **NixOS:**
```nix ```nix
{ {
inputs.dms.url = "github:AvengeMedia/DankMaterialShell"; inputs.dms.url = "github:AvengeMedia/DankMaterialShell";

View File

@@ -89,6 +89,11 @@ func initializeProviders() {
log.Warnf("Failed to register MangoWC provider: %v", err) log.Warnf("Failed to register MangoWC provider: %v", err)
} }
scrollProvider := providers.NewSwayProvider("$HOME/.config/scroll")
if err := registry.Register(scrollProvider); err != nil {
log.Warnf("Failed to register Scroll provider: %v", err)
}
swayProvider := providers.NewSwayProvider("$HOME/.config/sway") swayProvider := providers.NewSwayProvider("$HOME/.config/sway")
if err := registry.Register(swayProvider); err != nil { if err := registry.Register(swayProvider); err != nil {
log.Warnf("Failed to register Sway provider: %v", err) log.Warnf("Failed to register Sway provider: %v", err)
@@ -125,6 +130,8 @@ func makeProviderWithPath(name, path string) keybinds.Provider {
return providers.NewMangoWCProvider(path) return providers.NewMangoWCProvider(path)
case "sway": case "sway":
return providers.NewSwayProvider(path) return providers.NewSwayProvider(path)
case "scroll":
return providers.NewSwayProvider(path)
case "niri": case "niri":
return providers.NewNiriProvider(path) return providers.NewNiriProvider(path)
default: default:

View File

@@ -2,6 +2,7 @@ package providers
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
@@ -9,18 +10,38 @@ import (
type SwayProvider struct { type SwayProvider struct {
configPath string configPath string
isScroll bool
} }
func NewSwayProvider(configPath string) *SwayProvider { func NewSwayProvider(configPath string) *SwayProvider {
isScroll := false
_, ok := os.LookupEnv("SCROLLSOCK")
if ok {
isScroll = true
}
if configPath == "" { if configPath == "" {
configPath = "$HOME/.config/sway" configPath = "$HOME/.config/sway"
} }
if isScroll && configPath == "" {
configPath = "$HOME/.config/scroll"
}
return &SwayProvider{ return &SwayProvider{
configPath: configPath, configPath: configPath,
isScroll: isScroll,
} }
} }
func (s *SwayProvider) Name() string { func (s *SwayProvider) Name() string {
if s != nil && s.isScroll {
return "scroll"
}
if s == nil {
_, ok := os.LookupEnv("SCROLLSOCK")
if ok {
return "scroll"
}
}
return "sway" return "sway"
} }
@@ -33,8 +54,13 @@ func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
categorizedBinds := make(map[string][]keybinds.Keybind) categorizedBinds := make(map[string][]keybinds.Keybind)
s.convertSection(section, "", categorizedBinds) s.convertSection(section, "", categorizedBinds)
cheatSheetTitle := "Sway Keybinds"
if s != nil && s.isScroll {
cheatSheetTitle = "Scroll Keybinds"
}
return &keybinds.CheatSheet{ return &keybinds.CheatSheet{
Title: "Sway Keybinds", Title: cheatSheetTitle,
Provider: s.Name(), Provider: s.Name(),
Binds: categorizedBinds, Binds: categorizedBinds,
}, nil }, nil

View File

@@ -20,6 +20,7 @@ const (
CompositorSway CompositorSway
CompositorNiri CompositorNiri
CompositorDWL CompositorDWL
CompositorScroll
) )
var detectedCompositor Compositor = -1 var detectedCompositor Compositor = -1
@@ -32,6 +33,7 @@ func DetectCompositor() Compositor {
hyprlandSig := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") hyprlandSig := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE")
niriSocket := os.Getenv("NIRI_SOCKET") niriSocket := os.Getenv("NIRI_SOCKET")
swaySocket := os.Getenv("SWAYSOCK") swaySocket := os.Getenv("SWAYSOCK")
scrollSocket := os.Getenv("SCROLLSOCK")
switch { switch {
case niriSocket != "": case niriSocket != "":
@@ -39,6 +41,12 @@ func DetectCompositor() Compositor {
detectedCompositor = CompositorNiri detectedCompositor = CompositorNiri
return detectedCompositor return detectedCompositor
} }
case scrollSocket != "":
if _, err := os.Stat(scrollSocket); err == nil {
detectedCompositor = CompositorScroll
return detectedCompositor
}
case swaySocket != "": case swaySocket != "":
if _, err := os.Stat(swaySocket); err == nil { if _, err := os.Stat(swaySocket); err == nil {
detectedCompositor = CompositorSway detectedCompositor = CompositorSway
@@ -233,6 +241,25 @@ func getSwayFocusedMonitor() string {
return "" return ""
} }
func getScrollFocusedMonitor() string {
output, err := exec.Command("scrollmsg", "-t", "get_workspaces").Output()
if err != nil {
return ""
}
var workspaces []swayWorkspace
if err := json.Unmarshal(output, &workspaces); err != nil {
return ""
}
for _, ws := range workspaces {
if ws.Focused {
return ws.Output
}
}
return ""
}
type niriWorkspace struct { type niriWorkspace struct {
Output string `json:"output"` Output string `json:"output"`
IsFocused bool `json:"is_focused"` IsFocused bool `json:"is_focused"`
@@ -378,6 +405,8 @@ func GetFocusedMonitor() string {
return getHyprlandFocusedMonitor() return getHyprlandFocusedMonitor()
case CompositorSway: case CompositorSway:
return getSwayFocusedMonitor() return getSwayFocusedMonitor()
case CompositorScroll:
return getScrollFocusedMonitor()
case CompositorNiri: case CompositorNiri:
return getNiriFocusedMonitor() return getNiriFocusedMonitor()
case CompositorDWL: case CompositorDWL:

View File

@@ -38,7 +38,7 @@ in {
options.programs.dankMaterialShell.greeter = { options.programs.dankMaterialShell.greeter = {
enable = lib.mkEnableOption "DankMaterialShell greeter"; enable = lib.mkEnableOption "DankMaterialShell greeter";
compositor.name = lib.mkOption { compositor.name = lib.mkOption {
type = types.enum ["niri" "hyprland" "sway"]; type = types.enum ["niri" "hyprland" "sway" "scroll"];
description = "Compositor to run greeter in"; description = "Compositor to run greeter in";
}; };
compositor.customConfig = lib.mkOption { compositor.customConfig = lib.mkOption {

View File

@@ -96,7 +96,7 @@ Item {
focusedScreenName = Hyprland.focusedWorkspace.monitor.name; focusedScreenName = Hyprland.focusedWorkspace.monitor.name;
} else if (CompositorService.isNiri && NiriService.currentOutput) { } else if (CompositorService.isNiri && NiriService.currentOutput) {
focusedScreenName = NiriService.currentOutput; focusedScreenName = NiriService.currentOutput;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
focusedScreenName = focusedWs?.monitor?.name || ""; focusedScreenName = focusedWs?.monitor?.name || "";
} }
@@ -123,7 +123,7 @@ Item {
focusedScreenName = Hyprland.focusedWorkspace.monitor.name; focusedScreenName = Hyprland.focusedWorkspace.monitor.name;
} else if (CompositorService.isNiri && NiriService.currentOutput) { } else if (CompositorService.isNiri && NiriService.currentOutput) {
focusedScreenName = NiriService.currentOutput; focusedScreenName = NiriService.currentOutput;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
focusedScreenName = focusedWs?.monitor?.name || ""; focusedScreenName = focusedWs?.monitor?.name || "";
} }

View File

@@ -82,7 +82,7 @@ Item {
}, (_, i) => i); }, (_, i) => i);
} }
return DwlService.getVisibleTags(barWindow.screenName); return DwlService.getVisibleTags(barWindow.screenName);
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
const workspaces = I3.workspaces?.values || []; const workspaces = I3.workspaces?.values || [];
if (workspaces.length === 0) if (workspaces.length === 0)
return [ return [
@@ -124,7 +124,7 @@ Item {
return 0; return 0;
const activeTags = DwlService.getActiveTags(barWindow.screenName); const activeTags = DwlService.getActiveTags(barWindow.screenName);
return activeTags.length > 0 ? activeTags[0] : 0; return activeTags.length > 0 ? activeTags[0] : 0;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) { if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs ? focusedWs.num : 1; return focusedWs ? focusedWs.num : 1;
@@ -169,7 +169,7 @@ Item {
if (nextIndex !== validIndex) { if (nextIndex !== validIndex) {
DwlService.switchToTag(barWindow.screenName, realWorkspaces[nextIndex]); DwlService.switchToTag(barWindow.screenName, realWorkspaces[nextIndex]);
} }
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
const currentWs = getCurrentWorkspace(); const currentWs = getCurrentWorkspace();
const currentIndex = realWorkspaces.findIndex(ws => ws.num === currentWs); const currentIndex = realWorkspaces.findIndex(ws => ws.num === currentWs);
const validIndex = currentIndex === -1 ? 0 : currentIndex; const validIndex = currentIndex === -1 ? 0 : currentIndex;
@@ -534,7 +534,7 @@ Item {
section: topBarContent.getWidgetSection(parent) section: topBarContent.getWidgetSection(parent)
parentScreen: barWindow.screen parentScreen: barWindow.screen
onClicked: { onClicked: {
clipboardHistoryModalPopup.toggle() clipboardHistoryModalPopup.toggle();
} }
} }
} }
@@ -550,9 +550,9 @@ Item {
parentScreen: barWindow.screen parentScreen: barWindow.screen
onClicked: { onClicked: {
if (powerMenuModalLoader) { if (powerMenuModalLoader) {
powerMenuModalLoader.active = true powerMenuModalLoader.active = true;
if (powerMenuModalLoader.item) { if (powerMenuModalLoader.item) {
powerMenuModalLoader.item.openCentered() powerMenuModalLoader.item.openCentered();
} }
} }
} }

View File

@@ -1,6 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.Plugins import qs.Modules.Plugins
@@ -56,7 +55,7 @@ BasePill {
} }
IconImage { IconImage {
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isLabwc) visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isLabwc)
anchors.centerIn: parent anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset) width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset) height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
@@ -64,17 +63,19 @@ BasePill {
asynchronous: true asynchronous: true
source: { source: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
return "file://" + Theme.shellDir + "/assets/niri.svg" return "file://" + Theme.shellDir + "/assets/niri.svg";
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return "file://" + Theme.shellDir + "/assets/hyprland.svg" return "file://" + Theme.shellDir + "/assets/hyprland.svg";
} else if (CompositorService.isDwl) { } else if (CompositorService.isDwl) {
return "file://" + Theme.shellDir + "/assets/mango.png" return "file://" + Theme.shellDir + "/assets/mango.png";
} else if (CompositorService.isSway) { } else if (CompositorService.isSway) {
return "file://" + Theme.shellDir + "/assets/sway.svg" return "file://" + Theme.shellDir + "/assets/sway.svg";
} else if (CompositorService.isScroll) {
return "file://" + Theme.shellDir + "/assets/sway.svg";
} else if (CompositorService.isLabwc) { } else if (CompositorService.isLabwc) {
return "file://" + Theme.shellDir + "/assets/labwc.png" return "file://" + Theme.shellDir + "/assets/labwc.png";
} }
return "" return "";
} }
layer.enabled: Theme.effectiveLogoColor !== "" layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -82,10 +83,10 @@ BasePill {
colorization: 1 colorization: 1
colorizationColor: Theme.effectiveLogoColor colorizationColor: Theme.effectiveLogoColor
brightness: { brightness: {
SettingsData.launcherLogoBrightness SettingsData.launcherLogoBrightness;
} }
contrast: { contrast: {
SettingsData.launcherLogoContrast SettingsData.launcherLogoContrast;
} }
} }
} }
@@ -112,9 +113,9 @@ BasePill {
onRightClicked: { onRightClicked: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.toggleOverview() NiriService.toggleOverview();
} else if (root.hyprlandOverviewLoader?.item) { } else if (root.hyprlandOverviewLoader?.item) {
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
} }
} }
} }

View File

@@ -23,7 +23,7 @@ Item {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName); return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
} }
readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && ExtWorkspaceService.extWorkspaceAvailable) readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && ExtWorkspaceService.extWorkspaceAvailable)
Connections { Connections {
target: DesktopEntries target: DesktopEntries
@@ -45,6 +45,7 @@ Item {
const activeTags = getDwlActiveTags(); const activeTags = getDwlActiveTags();
return activeTags.length > 0 ? activeTags[0] : -1; return activeTags.length > 0 ? activeTags[0] : -1;
case "sway": case "sway":
case "scroll":
return getSwayActiveWorkspace(); return getSwayActiveWorkspace();
default: default:
return 1; return 1;
@@ -74,6 +75,7 @@ Item {
baseList = getDwlTags(); baseList = getDwlTags();
break; break;
case "sway": case "sway":
case "scroll":
baseList = getSwayWorkspaces(); baseList = getSwayWorkspaces();
break; break;
default: default:
@@ -192,7 +194,7 @@ Item {
return []; return [];
} }
targetWorkspaceId = ws.tag; targetWorkspaceId = ws.tag;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
targetWorkspaceId = ws.num !== undefined ? ws.num : ws; targetWorkspaceId = ws.num !== undefined ? ws.num : ws;
} else { } else {
return []; return [];
@@ -204,7 +206,7 @@ Item {
let isActiveWs = false; let isActiveWs = false;
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
isActiveWs = NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active); isActiveWs = NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active);
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false; isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
} else if (CompositorService.isDwl) { } else if (CompositorService.isDwl) {
@@ -225,7 +227,7 @@ Item {
let winWs = null; let winWs = null;
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
winWs = w.workspace_id; winWs = w.workspace_id;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
winWs = w.workspace?.num; winWs = w.workspace?.num;
} else { } else {
const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []); const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
@@ -283,7 +285,7 @@ Item {
placeholder = { placeholder = {
"tag": -1 "tag": -1
}; };
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
placeholder = { placeholder = {
"num": -1 "num": -1
}; };
@@ -453,7 +455,7 @@ Item {
return ws && ws.id !== -1; return ws && ws.id !== -1;
if (CompositorService.isDwl) if (CompositorService.isDwl)
return ws && ws.tag !== -1; return ws && ws.tag !== -1;
if (CompositorService.isSway) if (CompositorService.isSway || CompositorService.isScroll)
return ws && ws.num !== -1; return ws && ws.num !== -1;
return ws !== -1; return ws !== -1;
}); });
@@ -521,7 +523,7 @@ Item {
} }
DwlService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag); DwlService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
const realWorkspaces = getRealWorkspaces(); const realWorkspaces = getRealWorkspaces();
if (realWorkspaces.length < 2) { if (realWorkspaces.length < 2) {
return; return;
@@ -549,7 +551,7 @@ Item {
isPlaceholder = modelData?.id === -1; isPlaceholder = modelData?.id === -1;
} else if (CompositorService.isDwl) { } else if (CompositorService.isDwl) {
isPlaceholder = modelData?.tag === -1; isPlaceholder = modelData?.tag === -1;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
isPlaceholder = modelData?.num === -1; isPlaceholder = modelData?.num === -1;
} else { } else {
isPlaceholder = modelData === -1; isPlaceholder = modelData === -1;
@@ -564,12 +566,12 @@ Item {
return modelData?.id || ""; return modelData?.id || "";
if (CompositorService.isDwl) if (CompositorService.isDwl)
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : ""; return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
if (CompositorService.isSway) if (CompositorService.isSway || CompositorService.isScroll)
return modelData?.num || ""; return modelData?.num || "";
return modelData - 1; return modelData - 1;
} }
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0 readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces) readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
@@ -675,7 +677,7 @@ Item {
return !!(modelData && modelData.id === root.currentWorkspace); return !!(modelData && modelData.id === root.currentWorkspace);
if (CompositorService.isDwl) if (CompositorService.isDwl)
return !!(modelData && root.dwlActiveTags.includes(modelData.tag)); return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
if (CompositorService.isSway) if (CompositorService.isSway || CompositorService.isScroll)
return !!(modelData && modelData.num === root.currentWorkspace); return !!(modelData && modelData.num === root.currentWorkspace);
return modelData === root.currentWorkspace; return modelData === root.currentWorkspace;
} }
@@ -686,7 +688,7 @@ Item {
return !!(modelData && modelData.id === -1); return !!(modelData && modelData.id === -1);
if (CompositorService.isDwl) if (CompositorService.isDwl)
return !!(modelData && modelData.tag === -1); return !!(modelData && modelData.tag === -1);
if (CompositorService.isSway) if (CompositorService.isSway || CompositorService.isScroll)
return !!(modelData && modelData.num === -1); return !!(modelData && modelData.num === -1);
return modelData === -1; return modelData === -1;
} }
@@ -703,7 +705,7 @@ Item {
return loadedIsUrgent; return loadedIsUrgent;
if (CompositorService.isDwl) if (CompositorService.isDwl)
return modelData?.state === 2; return modelData?.state === 2;
if (CompositorService.isSway) if (CompositorService.isSway || CompositorService.isScroll)
return loadedIsUrgent; return loadedIsUrgent;
return false; return false;
} }
@@ -767,7 +769,7 @@ Item {
console.log("Calling switchToTag"); console.log("Calling switchToTag");
DwlService.switchToTag(root.screenName, modelData.tag); DwlService.switchToTag(root.screenName, modelData.tag);
} }
} else if (CompositorService.isSway && modelData?.num) { } else if ((CompositorService.isSway || CompositorService.isScroll) && modelData?.num) {
try { try {
I3.dispatch(`workspace number ${modelData.num}`); I3.dispatch(`workspace number ${modelData.num}`);
} catch (_) {} } catch (_) {}
@@ -797,7 +799,7 @@ Item {
wsData = modelData; wsData = modelData;
} else if (CompositorService.isDwl) { } else if (CompositorService.isDwl) {
wsData = modelData; wsData = modelData;
} else if (CompositorService.isSway) { } else if (CompositorService.isSway || CompositorService.isScroll) {
wsData = modelData; wsData = modelData;
} }
delegateRoot.loadedWorkspaceData = wsData; delegateRoot.loadedWorkspaceData = wsData;
@@ -811,7 +813,7 @@ Item {
delegateRoot.loadedHasIcon = icData !== null; delegateRoot.loadedHasIcon = icData !== null;
if (SettingsData.showWorkspaceApps) { if (SettingsData.showWorkspaceApps) {
if (CompositorService.isDwl || CompositorService.isSway) { if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll) {
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData); delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
} else { } else {
delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData)); delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
@@ -1192,7 +1194,7 @@ Item {
} }
Connections { Connections {
target: I3.workspaces target: I3.workspaces
enabled: CompositorService.isSway enabled: (CompositorService.isSway || CompositorService.isScroll)
function onValuesChanged() { function onValuesChanged() {
delegateRoot.updateAllData(); delegateRoot.updateAllData();
} }

View File

@@ -67,6 +67,8 @@ Card {
return "on MangoWC"; return "on MangoWC";
if (CompositorService.isSway) if (CompositorService.isSway)
return "on Sway"; return "on Sway";
if (CompositorService.isScroll)
return "on Scroll";
return ""; return "";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -16,7 +16,7 @@ dms-greeter - DankMaterialShell greeter launcher
Usage: dms-greeter --command COMPOSITOR [OPTIONS] Usage: dms-greeter --command COMPOSITOR [OPTIONS]
Required: Required:
--command COMPOSITOR Compositor to use (niri, hyprland, sway, or mangowc) --command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll or mangowc)
Options: Options:
-C, --config PATH Custom compositor config file -C, --config PATH Custom compositor config file
@@ -30,6 +30,7 @@ Examples:
dms-greeter --command niri dms-greeter --command niri
dms-greeter --command hyprland -C /etc/greetd/custom-hypr.conf dms-greeter --command hyprland -C /etc/greetd/custom-hypr.conf
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms
dms-greeter --command niri --cache-dir /tmp/dmsgreeter dms-greeter --command niri --cache-dir /tmp/dmsgreeter
dms-greeter --command mangowc dms-greeter --command mangowc
EOF EOF
@@ -207,6 +208,25 @@ SWAY_EOF
exec sway -c "$COMPOSITOR_CONFIG" exec sway -c "$COMPOSITOR_CONFIG"
;; ;;
scroll)
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << SCROLL_EOF
exec "$QS_CMD; scrollmsg exit"
SCROLL_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << SCROLL_EOF
exec "$QS_CMD; scrollmsg exit"
SCROLL_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec scroll -c "$COMPOSITOR_CONFIG"
;;
mangowc) mangowc)
if [[ -n "$COMPOSITOR_CONFIG" ]]; then if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit" exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit"

View File

@@ -10,6 +10,7 @@ Item {
property bool isHyprland: CompositorService.isHyprland property bool isHyprland: CompositorService.isHyprland
property bool isNiri: CompositorService.isNiri property bool isNiri: CompositorService.isNiri
property bool isSway: CompositorService.isSway property bool isSway: CompositorService.isSway
property bool isScroll: CompositorService.isScroll
property bool isDwl: CompositorService.isDwl property bool isDwl: CompositorService.isDwl
property bool isLabwc: CompositorService.isLabwc property bool isLabwc: CompositorService.isLabwc
@@ -18,6 +19,8 @@ Item {
return "hyprland"; return "hyprland";
if (isSway) if (isSway)
return "sway"; return "sway";
if (isScroll)
return "scroll";
if (isDwl) if (isDwl)
return "mangowc"; return "mangowc";
if (isLabwc) if (isLabwc)
@@ -30,6 +33,8 @@ Item {
return "/assets/hyprland.svg"; return "/assets/hyprland.svg";
if (isSway) if (isSway)
return "/assets/sway.svg"; return "/assets/sway.svg";
if (isScroll)
return "/assets/sway.svg";
if (isDwl) if (isDwl)
return "/assets/mango.png"; return "/assets/mango.png";
if (isLabwc) if (isLabwc)
@@ -42,6 +47,8 @@ Item {
return "https://hypr.land"; return "https://hypr.land";
if (isSway) if (isSway)
return "https://swaywm.org"; return "https://swaywm.org";
if (isScroll)
return "https://github.com/dawsers/scroll";
if (isDwl) if (isDwl)
return "https://github.com/DreamMaoMao/mangowc"; return "https://github.com/DreamMaoMao/mangowc";
if (isLabwc) if (isLabwc)
@@ -54,6 +61,8 @@ Item {
return "Hyprland Website"; return "Hyprland Website";
if (isSway) if (isSway)
return "Sway Website"; return "Sway Website";
if (isScroll)
return "Scroll Github";
if (isDwl) if (isDwl)
return "mangowc GitHub"; return "mangowc GitHub";
if (isLabwc) if (isLabwc)
@@ -86,9 +95,9 @@ Item {
property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc" property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc"
property string ircTooltip: "LabWC IRC Channel" property string ircTooltip: "LabWC IRC Channel"
property bool showMatrix: isNiri && !isHyprland && !isSway && !isDwl && !isLabwc property bool showMatrix: isNiri && !isHyprland && !isSway && !isScroll && !isDwl && !isLabwc
property bool showCompositorDiscord: isHyprland || isDwl property bool showCompositorDiscord: isHyprland || isDwl
property bool showReddit: isNiri && !isHyprland && !isSway && !isDwl && !isLabwc property bool showReddit: isNiri && !isHyprland && !isSway && !isScroll && !isDwl && !isLabwc
property bool showIrc: isLabwc property bool showIrc: isLabwc
DankFlickable { DankFlickable {

View File

@@ -63,6 +63,8 @@ Item {
modes.push("mango"); modes.push("mango");
} else if (CompositorService.isSway) { } else if (CompositorService.isSway) {
modes.push("Sway"); modes.push("Sway");
} else if (CompositorService.isScroll) {
modes.push("Scroll");
} else { } else {
modes.push(I18n.tr("Compositor")); modes.push(I18n.tr("Compositor"));
} }

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
@@ -65,7 +66,7 @@ Singleton {
return Hyprland.focusedWorkspace.monitor.name; return Hyprland.focusedWorkspace.monitor.name;
if (CompositorService.isNiri && NiriService.currentOutput) if (CompositorService.isNiri && NiriService.currentOutput)
return NiriService.currentOutput; return NiriService.currentOutput;
if (CompositorService.isSway) { if (CompositorService.isSway || CompositorService.isScroll) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs?.monitor?.name || ""; return focusedWs?.monitor?.name || "";
} }

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.I3
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import qs.Common import qs.Common
@@ -14,6 +15,7 @@ Singleton {
property bool isNiri: false property bool isNiri: false
property bool isDwl: false property bool isDwl: false
property bool isSway: false property bool isSway: false
property bool isScroll: false
property bool isLabwc: false property bool isLabwc: false
property string compositor: "unknown" property string compositor: "unknown"
readonly property bool useHyprlandFocusGrab: isHyprland && Quickshell.env("DMS_HYPRLAND_EXCLUSIVE_FOCUS") !== "1" readonly property bool useHyprlandFocusGrab: isHyprland && Quickshell.env("DMS_HYPRLAND_EXCLUSIVE_FOCUS") !== "1"
@@ -21,6 +23,7 @@ Singleton {
readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
readonly property string niriSocket: Quickshell.env("NIRI_SOCKET") readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
readonly property string swaySocket: Quickshell.env("SWAYSOCK") readonly property string swaySocket: Quickshell.env("SWAYSOCK")
readonly property string scrollSocket: Quickshell.env("SWAYSOCK")
readonly property string labwcPid: Quickshell.env("LABWC_PID") readonly property string labwcPid: Quickshell.env("LABWC_PID")
property bool useNiriSorting: isNiri && NiriService property bool useNiriSorting: isNiri && NiriService
@@ -71,7 +74,7 @@ Singleton {
screenName = Hyprland.focusedWorkspace.monitor.name; screenName = Hyprland.focusedWorkspace.monitor.name;
else if (isNiri && NiriService.currentOutput) else if (isNiri && NiriService.currentOutput)
screenName = NiriService.currentOutput; screenName = NiriService.currentOutput;
else if (isSway) { else if (isSway || isScroll) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
screenName = focusedWs?.monitor?.name || ""; screenName = focusedWs?.monitor?.name || "";
} else if (isDwl && DwlService.activeOutput) } else if (isDwl && DwlService.activeOutput)
@@ -398,11 +401,12 @@ Singleton {
} }
function detectCompositor() { function detectCompositor() {
if (hyprlandSignature && hyprlandSignature.length > 0 && !niriSocket && !swaySocket && !labwcPid) { if (hyprlandSignature && hyprlandSignature.length > 0 && !niriSocket && !swaySocket && !scrollSocket && !labwcPid) {
isHyprland = true; isHyprland = true;
isNiri = false; isNiri = false;
isDwl = false; isDwl = false;
isSway = false; isSway = false;
isScroll = false;
isLabwc = false; isLabwc = false;
compositor = "hyprland"; compositor = "hyprland";
console.info("CompositorService: Detected Hyprland"); console.info("CompositorService: Detected Hyprland");
@@ -416,6 +420,7 @@ Singleton {
isHyprland = false; isHyprland = false;
isDwl = false; isDwl = false;
isSway = false; isSway = false;
isScroll = false;
isLabwc = false; isLabwc = false;
compositor = "niri"; compositor = "niri";
console.info("CompositorService: Detected Niri with socket:", niriSocket); console.info("CompositorService: Detected Niri with socket:", niriSocket);
@@ -425,13 +430,14 @@ Singleton {
return; return;
} }
if (swaySocket && swaySocket.length > 0) { if (swaySocket && swaySocket.length > 0 && !scrollSocket && scrollSocket.length == 0) {
Proc.runCommand("swaySocketCheck", ["test", "-S", swaySocket], (output, exitCode) => { Proc.runCommand("swaySocketCheck", ["test", "-S", swaySocket], (output, exitCode) => {
if (exitCode === 0) { if (exitCode === 0) {
isNiri = false; isNiri = false;
isHyprland = false; isHyprland = false;
isDwl = false; isDwl = false;
isSway = true; isSway = true;
isScroll = false;
isLabwc = false; isLabwc = false;
compositor = "sway"; compositor = "sway";
console.info("CompositorService: Detected Sway with socket:", swaySocket); console.info("CompositorService: Detected Sway with socket:", swaySocket);
@@ -440,11 +446,28 @@ Singleton {
return; return;
} }
if (scrollSocket && scrollSocket.length > 0) {
Proc.runCommand("scrollSocketCheck", ["test", "-S", scrollSocket], (output, exitCode) => {
if (exitCode === 0) {
isNiri = false;
isHyprland = false;
isDwl = false;
isSway = false;
isScroll = true;
isLabwc = false;
compositor = "scroll";
console.info("CompositorService: Detected Scroll with socket:", scrollSocket);
}
}, 0);
return;
}
if (labwcPid && labwcPid.length > 0) { if (labwcPid && labwcPid.length > 0) {
isHyprland = false; isHyprland = false;
isNiri = false; isNiri = false;
isDwl = false; isDwl = false;
isSway = false; isSway = false;
isScroll = false;
isLabwc = true; isLabwc = true;
compositor = "labwc"; compositor = "labwc";
console.info("CompositorService: Detected LabWC with PID:", labwcPid); console.info("CompositorService: Detected LabWC with PID:", labwcPid);
@@ -458,6 +481,7 @@ Singleton {
isNiri = false; isNiri = false;
isDwl = false; isDwl = false;
isSway = false; isSway = false;
isScroll = false;
isLabwc = false; isLabwc = false;
compositor = "unknown"; compositor = "unknown";
console.warn("CompositorService: No compositor detected"); console.warn("CompositorService: No compositor detected");
@@ -479,6 +503,7 @@ Singleton {
isNiri = false; isNiri = false;
isDwl = true; isDwl = true;
isSway = false; isSway = false;
isScroll = false;
isLabwc = false; isLabwc = false;
compositor = "dwl"; compositor = "dwl";
console.info("CompositorService: Detected DWL via DMS capability"); console.info("CompositorService: Detected DWL via DMS capability");
@@ -492,7 +517,7 @@ Singleton {
return Hyprland.dispatch("dpms off"); return Hyprland.dispatch("dpms off");
if (isDwl) if (isDwl)
return _dwlPowerOffMonitors(); return _dwlPowerOffMonitors();
if (isSway) { if (isSway || isScroll) {
try { try {
I3.dispatch("output * dpms off"); I3.dispatch("output * dpms off");
} catch (_) {} } catch (_) {}
@@ -511,7 +536,7 @@ Singleton {
return Hyprland.dispatch("dpms on"); return Hyprland.dispatch("dpms on");
if (isDwl) if (isDwl)
return _dwlPowerOnMonitors(); return _dwlPowerOnMonitors();
if (isSway) { if (isSway || isScroll) {
try { try {
I3.dispatch("output * dpms on"); I3.dispatch("output * dpms on");
} catch (_) {} } catch (_) {}

View File

@@ -15,81 +15,82 @@ Singleton {
property string activeOutput: "" property string activeOutput: ""
property var outputScales: ({}) property var outputScales: ({})
property string currentKeyboardLayout: { property string currentKeyboardLayout: {
if (!outputs || !activeOutput) return "" if (!outputs || !activeOutput)
const output = outputs[activeOutput] return "";
return (output && output.kbLayout) || "" const output = outputs[activeOutput];
return (output && output.kbLayout) || "";
} }
signal stateChanged() signal stateChanged
Connections { Connections {
target: DMSService target: DMSService
function onCapabilitiesReceived() { function onCapabilitiesReceived() {
checkCapabilities() checkCapabilities();
} }
function onConnectionStateChanged() { function onConnectionStateChanged() {
if (DMSService.isConnected) { if (DMSService.isConnected) {
checkCapabilities() checkCapabilities();
} else { } else {
dwlAvailable = false dwlAvailable = false;
} }
} }
function onDwlStateUpdate(data) { function onDwlStateUpdate(data) {
if (dwlAvailable) { if (dwlAvailable) {
handleStateUpdate(data) handleStateUpdate(data);
} }
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (DMSService.dmsAvailable) { if (DMSService.dmsAvailable) {
checkCapabilities() checkCapabilities();
} }
if (dwlAvailable) { if (dwlAvailable) {
refreshOutputScales() refreshOutputScales();
} }
} }
function checkCapabilities() { function checkCapabilities() {
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) { if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
dwlAvailable = false dwlAvailable = false;
return return;
} }
const hasDwl = DMSService.capabilities.includes("dwl") const hasDwl = DMSService.capabilities.includes("dwl");
if (hasDwl && !dwlAvailable) { if (hasDwl && !dwlAvailable) {
dwlAvailable = true dwlAvailable = true;
console.info("DwlService: DWL capability detected") console.info("DwlService: DWL capability detected");
requestState() requestState();
refreshOutputScales() refreshOutputScales();
} else if (!hasDwl) { } else if (!hasDwl) {
dwlAvailable = false dwlAvailable = false;
} }
} }
function requestState() { function requestState() {
if (!DMSService.isConnected || !dwlAvailable) { if (!DMSService.isConnected || !dwlAvailable) {
return return;
} }
DMSService.sendRequest("dwl.getState", null, response => { DMSService.sendRequest("dwl.getState", null, response => {
if (response.result) { if (response.result) {
handleStateUpdate(response.result) handleStateUpdate(response.result);
} }
}) });
} }
function handleStateUpdate(state) { function handleStateUpdate(state) {
outputs = state.outputs || {} outputs = state.outputs || {};
tagCount = state.tagCount || 9 tagCount = state.tagCount || 9;
layouts = state.layouts || [] layouts = state.layouts || [];
activeOutput = state.activeOutput || "" activeOutput = state.activeOutput || "";
stateChanged() stateChanged();
} }
function setTags(outputName, tagmask, toggleTagset) { function setTags(outputName, tagmask, toggleTagset) {
if (!DMSService.isConnected || !dwlAvailable) { if (!DMSService.isConnected || !dwlAvailable) {
return return;
} }
DMSService.sendRequest("dwl.setTags", { DMSService.sendRequest("dwl.setTags", {
@@ -98,14 +99,14 @@ Singleton {
"toggleTagset": toggleTagset "toggleTagset": toggleTagset
}, response => { }, response => {
if (response.error) { if (response.error) {
console.warn("DwlService: setTags error:", response.error) console.warn("DwlService: setTags error:", response.error);
} }
}) });
} }
function setClientTags(outputName, andTags, xorTags) { function setClientTags(outputName, andTags, xorTags) {
if (!DMSService.isConnected || !dwlAvailable) { if (!DMSService.isConnected || !dwlAvailable) {
return return;
} }
DMSService.sendRequest("dwl.setClientTags", { DMSService.sendRequest("dwl.setClientTags", {
@@ -114,14 +115,14 @@ Singleton {
"xorTags": xorTags "xorTags": xorTags
}, response => { }, response => {
if (response.error) { if (response.error) {
console.warn("DwlService: setClientTags error:", response.error) console.warn("DwlService: setClientTags error:", response.error);
} }
}) });
} }
function setLayout(outputName, index) { function setLayout(outputName, index) {
if (!DMSService.isConnected || !dwlAvailable) { if (!DMSService.isConnected || !dwlAvailable) {
return return;
} }
DMSService.sendRequest("dwl.setLayout", { DMSService.sendRequest("dwl.setLayout", {
@@ -129,77 +130,77 @@ Singleton {
"index": index "index": index
}, response => { }, response => {
if (response.error) { if (response.error) {
console.warn("DwlService: setLayout error:", response.error) console.warn("DwlService: setLayout error:", response.error);
} }
}) });
} }
function getOutputState(outputName) { function getOutputState(outputName) {
if (!outputs || !outputs[outputName]) { if (!outputs || !outputs[outputName]) {
return null return null;
} }
return outputs[outputName] return outputs[outputName];
} }
function getActiveTags(outputName) { function getActiveTags(outputName) {
const output = getOutputState(outputName) const output = getOutputState(outputName);
if (!output || !output.tags) { if (!output || !output.tags) {
return [] return [];
} }
return output.tags.filter(tag => tag.state === 1).map(tag => tag.tag) return output.tags.filter(tag => tag.state === 1).map(tag => tag.tag);
} }
function getTagsWithClients(outputName) { function getTagsWithClients(outputName) {
const output = getOutputState(outputName) const output = getOutputState(outputName);
if (!output || !output.tags) { if (!output || !output.tags) {
return [] return [];
} }
return output.tags.filter(tag => tag.clients > 0).map(tag => tag.tag) return output.tags.filter(tag => tag.clients > 0).map(tag => tag.tag);
} }
function getUrgentTags(outputName) { function getUrgentTags(outputName) {
const output = getOutputState(outputName) const output = getOutputState(outputName);
if (!output || !output.tags) { if (!output || !output.tags) {
return [] return [];
} }
return output.tags.filter(tag => tag.state === 2).map(tag => tag.tag) return output.tags.filter(tag => tag.state === 2).map(tag => tag.tag);
} }
function switchToTag(outputName, tagIndex) { function switchToTag(outputName, tagIndex) {
const tagmask = 1 << tagIndex const tagmask = 1 << tagIndex;
setTags(outputName, tagmask, 0) setTags(outputName, tagmask, 0);
} }
function toggleTag(outputName, tagIndex) { function toggleTag(outputName, tagIndex) {
const output = getOutputState(outputName) const output = getOutputState(outputName);
if (!output || !output.tags) { if (!output || !output.tags) {
console.log("toggleTag: no output or tags for", outputName) console.log("toggleTag: no output or tags for", outputName);
return return;
} }
let currentMask = 0 let currentMask = 0;
output.tags.forEach(tag => { output.tags.forEach(tag => {
if (tag.state === 1) { if (tag.state === 1) {
currentMask |= (1 << tag.tag) currentMask |= (1 << tag.tag);
} }
}) });
const clickedMask = 1 << tagIndex const clickedMask = 1 << tagIndex;
const newMask = currentMask ^ clickedMask const newMask = currentMask ^ clickedMask;
console.log("toggleTag:", outputName, "tag:", tagIndex, "currentMask:", currentMask.toString(2), "clickedMask:", clickedMask.toString(2), "newMask:", newMask.toString(2)) console.log("toggleTag:", outputName, "tag:", tagIndex, "currentMask:", currentMask.toString(2), "clickedMask:", clickedMask.toString(2), "newMask:", newMask.toString(2));
if (newMask === 0) { if (newMask === 0) {
console.log("toggleTag: newMask is 0, switching to tag", tagIndex) console.log("toggleTag: newMask is 0, switching to tag", tagIndex);
setTags(outputName, 1 << tagIndex, 0) setTags(outputName, 1 << tagIndex, 0);
} else { } else {
console.log("toggleTag: setting combined mask", newMask) console.log("toggleTag: setting combined mask", newMask);
setTags(outputName, newMask, 0) setTags(outputName, newMask, 0);
} }
} }
function quit() { function quit() {
Quickshell.execDetached(["mmsg", "-d", "quit"]) Quickshell.execDetached(["mmsg", "-d", "quit"]);
} }
Process { Process {
@@ -210,55 +211,56 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
try { try {
const newScales = {} const newScales = {};
const lines = text.trim().split('\n') const lines = text.trim().split('\n');
for (const line of lines) { for (const line of lines) {
const parts = line.trim().split(/\s+/) const parts = line.trim().split(/\s+/);
if (parts.length >= 3 && parts[1] === "scale_factor") { if (parts.length >= 3 && parts[1] === "scale_factor") {
const outputName = parts[0] const outputName = parts[0];
const scale = parseFloat(parts[2]) const scale = parseFloat(parts[2]);
if (!isNaN(scale)) { if (!isNaN(scale)) {
newScales[outputName] = scale newScales[outputName] = scale;
} }
} }
} }
outputScales = newScales outputScales = newScales;
} catch (e) { } catch (e) {
console.warn("DwlService: Failed to parse mmsg output:", e) console.warn("DwlService: Failed to parse mmsg output:", e);
} }
} }
} }
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("DwlService: mmsg failed with exit code:", exitCode) console.warn("DwlService: mmsg failed with exit code:", exitCode);
} }
} }
} }
function refreshOutputScales() { function refreshOutputScales() {
if (!dwlAvailable) return if (!dwlAvailable)
scaleQueryProcess.running = true return;
scaleQueryProcess.running = true;
} }
function getOutputScale(outputName) { function getOutputScale(outputName) {
return outputScales[outputName] return outputScales[outputName];
} }
function getVisibleTags(outputName) { function getVisibleTags(outputName) {
const output = getOutputState(outputName) const output = getOutputState(outputName);
if (!output || !output.tags) { if (!output || !output.tags) {
return [] return [];
} }
const visibleTags = new Set() const visibleTags = new Set();
output.tags.forEach(tag => { output.tags.forEach(tag => {
if (tag.state === 1 || tag.clients > 0) { if (tag.state === 1 || tag.clients > 0) {
visibleTags.add(tag.tag) visibleTags.add(tag.tag);
} }
}) });
return Array.from(visibleTags).sort((a, b) => a - b) return Array.from(visibleTags).sort((a, b) => a - b);
} }
} }

View File

@@ -47,7 +47,7 @@ Singleton {
const hasExtWorkspace = DMSService.capabilities.includes("extworkspace") const hasExtWorkspace = DMSService.capabilities.includes("extworkspace")
if (hasExtWorkspace && !extWorkspaceAvailable) { if (hasExtWorkspace && !extWorkspaceAvailable) {
if (typeof CompositorService !== "undefined") { if (typeof CompositorService !== "undefined") {
const useExtWorkspace = DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway) const useExtWorkspace = DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll)
if (!useExtWorkspace) { if (!useExtWorkspace) {
console.info("ExtWorkspaceService: ext-workspace available but compositor has native support") console.info("ExtWorkspaceService: ext-workspace available but compositor has native support")
extWorkspaceAvailable = false extWorkspaceAvailable = false

View File

@@ -110,9 +110,9 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode === 0) { if (exitCode === 0) {
nvidiaCommand = "prime-run" nvidiaCommand = "prime-run";
} else { } else {
detectNvidiaOffloadProcess.running = true detectNvidiaOffloadProcess.running = true;
} }
} }
} }
@@ -124,7 +124,7 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode === 0) { if (exitCode === 0) {
nvidiaCommand = "nvidia-offload" nvidiaCommand = "nvidia-offload";
} }
} }
} }
@@ -243,7 +243,7 @@ Singleton {
return; return;
} }
if (CompositorService.isSway) { if (CompositorService.isSway || CompositorService.isScroll) {
try { try {
I3.dispatch("exit"); I3.dispatch("exit");
} catch (_) {} } catch (_) {}