1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 16:02:51 -05:00

Compare commits

...

137 Commits

Author SHA1 Message Date
bbedward
2ed6c33c83 missing import 2025-11-19 19:14:47 -05:00
bbedward
7ad532ed17 dankinstall: add ultramarine 2025-11-19 18:53:41 -05:00
bbedward
92fe8c5b14 hyprland: restore focus grab to tray menus 2025-11-19 17:24:14 -05:00
bbedward
8e95572589 modals: move HyprFocusGrab out of common Modal 2025-11-19 17:16:51 -05:00
bbedward
62da862a66 modal: round textureSize pixels 2025-11-19 14:36:08 -05:00
bbedward
993e34f548 dankinstall: weakdeps for niri/system 2025-11-19 09:35:22 -05:00
github-actions[bot]
e39465aece chore: bump version to v0.6.2 2025-11-19 13:54:50 +00:00
bbedward
8fd616b680 osd: suppression fix from cc 2025-11-19 08:52:37 -05:00
bbedward
cc054b27de filebrowser: fix auto closing from ddash 2025-11-19 08:33:07 -05:00
github-actions[bot]
dfdaa82245 chore: bump version to v0.6.1 2025-11-19 03:16:35 +00:00
bbedward
99a307e0ad dankbar: hot fix color moda & systm tray item positions 2025-11-18 22:13:06 -05:00
github-actions[bot]
5ddea836a1 chore: bump version to v0.6.0 2025-11-18 23:52:39 +00:00
bbedward
208d92aa06 launcher: re-create grid on open 2025-11-18 18:50:42 -05:00
bbedward
6ef9ddd4f3 hyprland: fix right click overview 2025-11-18 17:53:00 -05:00
bbedward
1c92d39185 i18n: update translations 2025-11-18 17:21:45 -05:00
bbedward
c0f072217c dankbar: split up monolithic file 2025-11-18 16:18:24 -05:00
bbedward
542562f988 dankbar: missing background click handler for plugin popout 2025-11-18 16:03:30 -05:00
bbedward
4e6f0d5e87 bluez: fix disappearing popouts with modal maanger 2025-11-18 14:36:10 -05:00
bbedward
10639a5ead re-add bound lost my qmlfmt 2025-11-17 20:53:55 -05:00
bbedward
06d668e710 launcher: new search algo
- replace fzf.js with custom levenshtein distance matching
- tweak scoring system
- more graceful fuzzy, more weight to prefixes
- basic tokenization
2025-11-17 20:52:04 -05:00
bbedward
d1472dfcba osd: also have left center and right center options 2025-11-17 14:05:04 -05:00
bbedward
ccb4da3cd8 extws: fix force option 2025-11-17 10:08:06 -05:00
bbedward
46e96b49f0 extws: fix capability check & don't show names 2025-11-17 09:50:06 -05:00
bbedward
984cfe7f98 labwc: use dms dpms off/on for idle service 2025-11-17 09:12:38 -05:00
bbedward
d769300137 core/cli: add dpms off/on via wlr-output-power-management 2025-11-17 00:31:00 -05:00
Hikiru
d175d66828 Add NixOS module (#734)
* default.nix: fix "wavelength" typo

* Add nixos module

typo

fix

* nix: refactor and fix nix modules

* nix: fix NixOS module import

* nix: revert quickshell option change

* nix: fix nixosModules dmsPkgs definition

---------

Co-authored-by: LuckShiba <luckshiba@protonmail.com>
2025-11-16 21:12:01 -05:00
bbedward
c1a314332e wallpaper: rename blur layer option 2025-11-16 19:50:19 -05:00
bbedward
046ac59d21 core/extworkspace: only register outputs on name received 2025-11-16 19:40:46 -05:00
bbedward
00c06f07d0 workspace: fix ext-ws hiding 2025-11-16 18:52:12 -05:00
bbedward
3e2ab40c6a ws: 0 width when 0 workspaces, restore labwc to README 2025-11-16 17:53:50 -05:00
bbedward
350ffd0052 i18n: update terms 2025-11-16 16:33:55 -05:00
bbedward
ecd1a622d2 display: fix wallpaper when using monitor model 2025-11-16 16:33:21 -05:00
bbedward
f13968aa61 osd: configurable position 2025-11-16 16:27:01 -05:00
bbedward
4d1ffde54c launcher: allow launch prefix to run in shell 2025-11-16 16:14:19 -05:00
bbedward
d69017a706 also update per-monitor wallpaper to accout for display setting 2025-11-16 16:01:11 -05:00
bbedward
f2deaeccdb scaling: snap value reported by wlr-output 2025-11-16 15:56:59 -05:00
bbedward
ea9b0d2a79 powermenu: use consistent new-style on locker + greeter
fixes #739
2025-11-16 15:05:06 -05:00
bbedward
2e6dbedb8b dwl/mango: support keyboard layout 2025-11-16 14:24:56 -05:00
bbedward
6f359df8f9 displays: allow filtering by model over name 2025-11-16 13:58:53 -05:00
claymorwan
f6db20cd06 confirm-modal:add layer namespace (#743) 2025-11-16 13:09:44 -05:00
bbedward
6287fae065 running apps: don't wrap on scroll wheel
fixes #740
2025-11-16 13:06:40 -05:00
bbedward
e441607ce3 colorpicker: don't include line break in copy
fixes #741
2025-11-16 13:00:13 -05:00
bbedward
b5379a95fa qs/dankbar/meta: add a mask region to the bar
- Allows bar items to be clickable evn when popouts open
- Add state machines to manage state across monitors
- change focuses to ondemand on hyprland
2025-11-16 12:52:13 -05:00
bbedward
64ec5be919 wallpaper: empty input region 2025-11-15 23:41:24 -05:00
bbedward
3916512d66 systemtray: fix erroneous undefined condition 2025-11-15 21:46:34 -05:00
bbedward
e2f426a1bd Revert "systemtray: fix UI thread freeze when opening menu on Hyprland"
This reverts commit 4cb652abd9.
2025-11-15 21:42:50 -05:00
bbedward
aa1df8dfcf core: more syncmap conversions 2025-11-15 20:00:47 -05:00
bbedward
67557555f2 core: refactor to use a generic-compatible syncmap 2025-11-15 19:45:19 -05:00
bbedward
4cb652abd9 systemtray: fix UI thread freeze when opening menu on Hyprland
- Similar pattern as fix from Noctalia
2025-11-15 17:57:23 -05:00
bbedward
d11868b99f systray: don't try to force focus of menus 2025-11-15 14:57:47 -05:00
bbedward
1798417e6a systemtray: don't take keyboard focus
- bricks hyprland
2025-11-15 14:48:13 -05:00
github-actions[bot]
43dc3e5bb1 nix: update vendorHash for go.mod changes 2025-11-15 19:43:35 +00:00
bbedward
91891a14ed core/wayland: thread-safety meta fixes + cleanups + hypr workaround
- fork go-wayland/client and modify to make it thread-safe internally
- use sync.Map and atomic values in many places to cut down on mutex
  boilerplate
- do not create extworkspace client unless explicitly requested
2025-11-15 14:41:00 -05:00
bbedward
20f7d60147 settings: various consistency issues fixed
part of #725
2025-11-15 12:05:44 -05:00
bbedward
7e17e7d37a osd: fix opacity
part of #725
2025-11-15 11:43:05 -05:00
bbedward
cbb244f785 osd: add option to disable each OSD 2025-11-15 11:36:33 -05:00
Sunner
1c264d858b Follow symlinks when searching for sessions (#728) 2025-11-15 10:29:34 -05:00
bbedward
217037c2ae evdev: fix test 2025-11-14 23:26:14 -05:00
bbedward
b4dbd0b69c evdev: enhance keyboard detection for capslock 2025-11-14 23:22:06 -05:00
github-actions[bot]
89a2b5c00b chore: bump version to v0.5.2 2025-11-15 00:31:06 +00:00
bbedward
929b6dae1a widgets: fix some 0-width issues 2025-11-14 19:26:51 -05:00
Pi Home Server
52fe493da9 Feature/privacy widget - Settings to force icons on (#715)
* Update

* Update

* Update

* Update

* Update

* Set default to false

* Update SettingsData.qml

Set default visibility to false

* privacy widget: fix truncated settings menu

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2025-11-14 19:16:17 -05:00
purian23
3e6be3e762 Greet path updates 2025-11-14 17:54:35 -05:00
purian23
7a8cc449b9 Add local ACL greeter permissions to dms core installer 2025-11-14 16:32:06 -05:00
purian23
8f5a9d6e9f Update dms greeter to scan system & local directories 2025-11-14 15:36:14 -05:00
bbedward
1c5e31fea9 greeter: allow mangowc as compositor 2025-11-14 14:51:28 -05:00
claymorwan
fd08ae18ab feat: plugin layer namespace (#717) 2025-11-14 14:50:29 -05:00
bbedward
a7eb3de06e dankbar: configurable auto-hide delay 2025-11-14 14:00:37 -05:00
bbedward
8902dd7c44 launcher: grid re-style and customizable column counts 2025-11-14 13:54:44 -05:00
bbedward
6387d8400c osd: account for bar position when on bottom 2025-11-14 13:47:26 -05:00
bbedward
597cacb9cc matugen: update gtk4/gtk3-dark colors
- also some change to dankinstall to use niri/xwls from system repos,
  too lazy to split the commits
2025-11-14 13:20:59 -05:00
bbedward
3e285ad9ff dankdash: remove useless tint rectangle
part of #716
2025-11-14 13:09:46 -05:00
bbedward
cc1fa89790 clock: use precision minutes instead of seconds, unless needed
part of #716
2025-11-14 12:42:23 -05:00
bbedward
b0ed007751 core/dankinstall: more deb fixes 2025-11-14 12:22:13 -05:00
bbedward
e1e2650d2b core/dankinstall: fix hyprland util manual compile on debian 2025-11-14 12:13:49 -05:00
bbedward
b23f17b633 core/dankinstall: fix hyprpicker build 2025-11-14 12:07:03 -05:00
github-actions[bot]
818e40b2df nix: update vendorHash for go.mod changes 2025-11-14 17:06:06 +00:00
bbedward
5685e39631 core: improve evdev capslock detection, wayland context fixes 2025-11-14 12:04:47 -05:00
kritag
72534b7674 adding tokyonight, everforest, nord and rose-pine themes (#714)
Co-authored-by: Kristian Tagesen <kristian.tagesen@tietoevry.com>
2025-11-14 11:40:26 -05:00
bbedward
328490d23d powermenu: smarter positioning in control center 2025-11-14 10:45:16 -05:00
bbedward
97a0696930 clock: fix overview clock when seconds is on 2025-11-14 10:29:41 -05:00
bbedward
cb4e0660e0 dock: add reveal IPCs 2025-11-14 10:08:16 -05:00
bbedward
67c642de4c keybinds: add toggleWithPath 2025-11-14 09:03:27 -05:00
bbedward
0d7c2e1024 core/cli: fix keybind provider path override 2025-11-14 08:56:16 -05:00
bbedward
16a779a41b powermenu: restore grid as an option
fixes #712
2025-11-14 08:51:15 -05:00
purian23
c4ca3c8644 Add root dms-cli build script 2025-11-14 00:22:49 -05:00
bbedward
aabcbe34f3 Merge branch 'master' of github.com:AvengeMedia/DankMaterialShell 2025-11-14 00:06:50 -05:00
bbedward
f06626e441 dock: use modded app IDs for grouping logic
fixes #710
2025-11-14 00:06:27 -05:00
purian23
c4e1a71776 Relocate notification tests to scripts dir 2025-11-13 23:53:18 -05:00
bbedward
77e6c16bd2 core/extworkspace: fix some thread-safety issues 2025-11-13 23:52:32 -05:00
purian23
9d1fac3570 Relocate Nix dir under distro/nix 2025-11-13 23:47:00 -05:00
bbedward
b7aeaa7fc5 systemtray: better hide/unhide behavioro 2025-11-13 22:49:30 -05:00
bbedward
f6d8c9ff61 Merge branch 'master' of github.com:AvengeMedia/DankMaterialShell 2025-11-13 22:41:47 -05:00
bbedward
0490794d6c dankbar: add caps lock indicator widget 2025-11-13 22:41:33 -05:00
github-actions[bot]
335c83dd3c nix: update vendorHash for go.mod changes 2025-11-14 03:26:50 +00:00
bbedward
91da720c26 i18n:update translations 2025-11-13 22:25:22 -05:00
bbedward
b6ac744a68 Merge branch 'master' of github.com:AvengeMedia/DankMaterialShell 2025-11-13 22:24:51 -05:00
bbedward
526c4092fd evdev: add evdev monitor for caps lock state 2025-11-13 22:24:27 -05:00
github-actions[bot]
ed06dda384 nix: update vendorHash for go.mod changes 2025-11-14 02:54:15 +00:00
bbedward
6465b11e9b core: ensure all NM tests use mock backend + re-orgs + dep updates 2025-11-13 21:44:03 -05:00
purian23
b2879878a1 feat: Priority pinned items in Control Center 2025-11-13 21:23:54 -05:00
bbedward
3e17b086fb ci: add docs to release archive 2025-11-13 20:19:54 -05:00
purian23
0545e6bcda Remove release tags 2025-11-13 20:01:38 -05:00
purian23
27a907433f Test Copr workflow update 2025-11-13 19:40:16 -05:00
purian23
69616800e3 Release update 2025-11-13 18:54:01 -05:00
github-actions[bot]
abf1f53432 chore: bump version to v0.5.1 2025-11-13 23:45:49 +00:00
bbedward
881c5f75cb ci: ensure version on tag 2025-11-13 18:44:03 -05:00
bbedward
4e45796ade ci: no flake version update 2025-11-13 18:38:47 -05:00
bbedward
1ce4ea5230 ci: update 2025-11-13 18:30:34 -05:00
purian23
f2a2437baa fix Copr dms-greeter 2025-11-13 18:00:30 -05:00
bbedward
508dc9db1e weather: imperial switch not just fahrenheit
fixes #699
2025-11-13 17:41:03 -05:00
bbedward
a914e3557f Merge branch 'master' of github.com:AvengeMedia/DankMaterialShell 2025-11-13 17:31:29 -05:00
bbedward
f489dc062f dankinstall: fix variant passing 2025-11-13 17:31:02 -05:00
purian23
a7e09f4850 Update Copr dms-greeter paths 2025-11-13 17:29:22 -05:00
bbedward
8ea97530d4 matugen: add terminals always dark option 2025-11-13 17:19:37 -05:00
bbedward
13ab54e83a matugen: vscode theme repairs 2025-11-13 17:06:04 -05:00
bbedward
4bc40325cb hyprland: re-add special workspace filtering 2025-11-13 16:56:12 -05:00
bbedward
58d9355ea3 matugen: fix multi vscode themes 2025-11-13 16:51:16 -05:00
bbedward
d46b7528e7 systemtray: new tray detail menu 2025-11-13 16:30:07 -05:00
bbedward
1858597fc9 fix sudo usages 2025-11-13 15:41:41 -05:00
bbedward
83cce5afe4 dankinstall: re-simplify installation 2025-11-13 14:34:42 -05:00
bbedward
201bd8dc1f cli: fix greeter enable, and color sync 2025-11-13 13:21:18 -05:00
bbedward
b62ba69060 dankbar: fix hiding widgets that should not be enabled 2025-11-13 12:55:52 -05:00
bbedward
5d2f5557e5 dwl/mangowc: add layout switcher and viewer widget 2025-11-13 12:44:56 -05:00
bbedward
cf75c1aad0 show a power profile OSD 2025-11-13 10:23:14 -05:00
Saurabh
76a60df88b Feat: wezterm theming support (#705)
* implemented logic for wezterm theming

added matugen configs and dank16 functions, updated matugen worked
scripta

* fixed theme dir

fixed path and moved output location to default wezterm dir
2025-11-13 08:54:47 -05:00
bbedward
9322c79b4e nix: fix greeter path 2025-11-13 08:53:02 -05:00
Lucas
12365edcf0 flake: update to new monorepo structure (#701)
* nix: move alejandra.toml to root

* nix: build using local dms cli

* workflow: update update-vendor-hash to new structure
2025-11-13 00:26:03 -05:00
bbedward
5efc1f9dad powermenu: switch back to a list based style 2025-11-12 23:26:56 -05:00
bbedward
ab976cbb24 popout: add separate variable for layer override
fixes #700
2025-11-12 23:20:04 -05:00
bbedward
db584b7897 rename backend to core 2025-11-12 23:12:31 -05:00
bbedward
0fdc0748cf nix: fix flake 2025-11-12 22:44:17 -05:00
bbedward
2e79c21dc2 fedora: fix spec 2025-11-12 22:24:38 -05:00
bbedward
5490a230bd systemtray: fix menu positioning 2025-11-12 22:21:02 -05:00
bbedward
a6b059b30d don't gitignore Makefile 2025-11-12 22:19:08 -05:00
bbedward
712e6011aa fix contributing ref 2025-11-12 22:14:27 -05:00
bbedward
68f6f87410 disable vendor hash update 2025-11-12 22:06:46 -05:00
470 changed files with 24421 additions and 6664 deletions

View File

@@ -7,6 +7,10 @@ on:
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)' description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
required: false required: false
default: '' default: ''
release:
description: 'Release number (e.g., 1, 2, 3 for hotfixes)'
required: false
default: '1'
jobs: jobs:
build-and-upload: build-and-upload:
@@ -19,6 +23,7 @@ jobs:
- name: Determine version - name: Determine version
id: version id: version
run: | run: |
# Get version from manual input or latest release
if [ -n "${{ github.event.inputs.version }}" ]; then if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}" VERSION="${{ github.event.inputs.version }}"
echo "Using manual version: $VERSION" echo "Using manual version: $VERSION"
@@ -27,8 +32,14 @@ jobs:
echo "Using latest release version: $VERSION" echo "Using latest release version: $VERSION"
fi fi
RELEASE="${{ github.event.inputs.release }}"
if [ -z "$RELEASE" ]; then
RELEASE="1"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✅ Building DMS stable version: $VERSION" echo "release=$RELEASE" >> $GITHUB_OUTPUT
echo "✅ Building DMS hotfix version: $VERSION-$RELEASE"
- name: Setup build environment - name: Setup build environment
run: | run: |
@@ -57,6 +68,7 @@ jobs:
- name: Generate stable spec file - name: Generate stable spec file
run: | run: |
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
RELEASE="${{ steps.version.outputs.release }}"
CHANGELOG_DATE="$(date '+%a %b %d %Y')" CHANGELOG_DATE="$(date '+%a %b %d %Y')"
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF' cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
@@ -68,7 +80,7 @@ jobs:
Name: dms Name: dms
Version: %{version} Version: %{version}
Release: 1%{?dist} Release: RELEASE_PLACEHOLDER%{?dist}
Summary: %{pkg_summary} Summary: %{pkg_summary}
License: MIT License: MIT
@@ -212,16 +224,17 @@ jobs:
%{_bindir}/dgop %{_bindir}/dgop
%changelog %changelog
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1 * CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
- Stable release VERSION_PLACEHOLDER - Stable release VERSION_PLACEHOLDER
- Built from GitHub release - Built from GitHub release
- Includes latest dms-cli and dgop binaries - Includes latest dms-cli and dgop binaries
SPECEOF SPECEOF
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
echo "✅ Spec file generated for v${VERSION}" echo "✅ Spec file generated for v${VERSION}-${RELEASE}"
echo "" echo ""
echo "=== Spec file preview ===" echo "=== Spec file preview ==="
head -40 ~/rpmbuild/SPECS/dms.spec head -40 ~/rpmbuild/SPECS/dms.spec
@@ -295,7 +308,7 @@ jobs:
run: | run: |
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "- **Version:** ${{ steps.version.outputs.version }}-${{ steps.version.outputs.release }}" >> $GITHUB_STEP_SUMMARY
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY

View File

@@ -5,7 +5,7 @@ on:
branches: branches:
- '**' - '**'
paths: paths:
- 'backend/**' - 'core/**'
- '.github/workflows/go-ci.yml' - '.github/workflows/go-ci.yml'
jobs: jobs:
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: backend working-directory: core
steps: steps:
- name: Checkout - name: Checkout
@@ -22,7 +22,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version-file: ./backend/go.mod go-version-file: ./core/go.mod
- name: Format check - name: Format check
run: | run: |

View File

@@ -14,7 +14,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build-backend: build-core:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -22,7 +22,7 @@ jobs:
defaults: defaults:
run: run:
working-directory: backend working-directory: core
steps: steps:
- name: Checkout - name: Checkout
@@ -33,7 +33,15 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version-file: ./backend/go.mod go-version-file: ./core/go.mod
- name: Format check
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "The following files are not formatted:"
gofmt -s -l .
exit 1
fi
- name: Run tests - name: Run tests
run: go test -v ./... run: go test -v ./...
@@ -93,35 +101,36 @@ jobs:
if: matrix.arch == 'arm64' if: matrix.arch == 'arm64'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: backend-assets-${{ matrix.arch }} name: core-assets-${{ matrix.arch }}
path: | path: |
backend/dankinstall-${{ matrix.arch }}.gz core/dankinstall-${{ matrix.arch }}.gz
backend/dankinstall-${{ matrix.arch }}.gz.sha256 core/dankinstall-${{ matrix.arch }}.gz.sha256
backend/dms-${{ matrix.arch }}.gz core/dms-${{ matrix.arch }}.gz
backend/dms-${{ matrix.arch }}.gz.sha256 core/dms-${{ matrix.arch }}.gz.sha256
backend/dms-distropkg-${{ matrix.arch }}.gz core/dms-distropkg-${{ matrix.arch }}.gz
backend/dms-distropkg-${{ matrix.arch }}.gz.sha256 core/dms-distropkg-${{ matrix.arch }}.gz.sha256
if-no-files-found: error if-no-files-found: error
- name: Upload artifacts with completions - name: Upload artifacts with completions
if: matrix.arch == 'amd64' if: matrix.arch == 'amd64'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: backend-assets-${{ matrix.arch }} name: core-assets-${{ matrix.arch }}
path: | path: |
backend/dankinstall-${{ matrix.arch }}.gz core/dankinstall-${{ matrix.arch }}.gz
backend/dankinstall-${{ matrix.arch }}.gz.sha256 core/dankinstall-${{ matrix.arch }}.gz.sha256
backend/dms-${{ matrix.arch }}.gz core/dms-${{ matrix.arch }}.gz
backend/dms-${{ matrix.arch }}.gz.sha256 core/dms-${{ matrix.arch }}.gz.sha256
backend/dms-distropkg-${{ matrix.arch }}.gz core/dms-distropkg-${{ matrix.arch }}.gz
backend/dms-distropkg-${{ matrix.arch }}.gz.sha256 core/dms-distropkg-${{ matrix.arch }}.gz.sha256
backend/completion.bash core/completion.bash
backend/completion.fish core/completion.fish
backend/completion.zsh core/completion.zsh
if-no-files-found: error if-no-files-found: error
update-versions: update-versions:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build-core
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -129,7 +138,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 fetch-depth: 0
- name: Update VERSION and flake.nix - name: Update VERSION
run: | run: |
set -euo pipefail set -euo pipefail
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
@@ -142,10 +151,7 @@ jobs:
# Update VERSION file in quickshell/ # Update VERSION file in quickshell/
echo "${version}" > quickshell/VERSION echo "${version}" > quickshell/VERSION
# Update version in backend/flake.nix git add quickshell/VERSION
sed -i "s/version = \"[^\"]*\"/version = \"$version_no_v\"/" backend/flake.nix
git add quickshell/VERSION backend/flake.nix
if ! git diff --cached --quiet; then if ! git diff --cached --quiet; then
git commit -m "chore: bump version to $version" git commit -m "chore: bump version to $version"
@@ -155,9 +161,13 @@ jobs:
echo "No version changes needed" echo "No version changes needed"
fi fi
# Force-push the tag to point to the commit with updated VERSION
git tag -f "${version}"
git push -f origin "${version}"
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: build-backend needs: [build-core, update-versions]
env: env:
TAG: ${{ github.ref_name }} TAG: ${{ github.ref_name }}
steps: steps:
@@ -166,12 +176,17 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download backend artifacts - name: Fetch updated tag after version bump
run: |
git fetch origin --force tag ${{ github.ref_name }}
git checkout ${{ github.ref_name }}
- name: Download core artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
pattern: backend-assets-* pattern: core-assets-*
merge-multiple: true merge-multiple: true
path: ./_backend_assets path: ./_core_assets
- name: Generate Changelog - name: Generate Changelog
id: changelog id: changelog
@@ -233,8 +248,8 @@ jobs:
mkdir -p _release_assets mkdir -p _release_assets
# Copy backend binaries and rename dms-*.gz to dms-cli-*.gz # Copy core binaries and rename dms-*.gz to dms-cli-*.gz
for file in _backend_assets/dms-*.gz*; do for file in _core_assets/dms-*.gz*; do
if [ -f "$file" ]; then if [ -f "$file" ]; then
basename=$(basename "$file") basename=$(basename "$file")
if [[ "$basename" == dms-distropkg-* ]]; then if [[ "$basename" == dms-distropkg-* ]]; then
@@ -247,12 +262,15 @@ jobs:
done done
# Copy dankinstall binaries # Copy dankinstall binaries
cp _backend_assets/dankinstall-*.gz* _release_assets/ cp _core_assets/dankinstall-*.gz* _release_assets/
# Copy completions # Copy completions
cp _backend_assets/completion.* _release_assets/ 2>/dev/null || true cp _core_assets/completion.* _release_assets/ 2>/dev/null || true
# Create QML source package (exclude build artifacts and git files) # Create QML source package (exclude build artifacts and git files)
# Copy root LICENSE and CONTRIBUTING.md to quickshell/ for packaging
cp LICENSE CONTRIBUTING.md quickshell/
# Tar the CONTENTS of quickshell/, not the directory itself # Tar the CONTENTS of quickshell/, not the directory itself
(cd quickshell && tar --exclude='.git' \ (cd quickshell && tar --exclude='.git' \
--exclude='.github' \ --exclude='.github' \
@@ -272,23 +290,28 @@ jobs:
tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms
# Add CLI binaries # Add CLI binaries
if [ -f "_backend_assets/dms-${arch}.gz" ]; then if [ -f "_core_assets/dms-${arch}.gz" ]; then
gunzip -c "_backend_assets/dms-${arch}.gz" > _temp_full/bin/dms gunzip -c "_core_assets/dms-${arch}.gz" > _temp_full/bin/dms
chmod +x _temp_full/bin/dms chmod +x _temp_full/bin/dms
fi fi
if [ -f "_backend_assets/dms-distropkg-${arch}.gz" ]; then if [ -f "_core_assets/dms-distropkg-${arch}.gz" ]; then
gunzip -c "_backend_assets/dms-distropkg-${arch}.gz" > _temp_full/bin/dms-distropkg gunzip -c "_core_assets/dms-distropkg-${arch}.gz" > _temp_full/bin/dms-distropkg
chmod +x _temp_full/bin/dms-distropkg chmod +x _temp_full/bin/dms-distropkg
fi fi
# Add shell completions # Add shell completions
for completion in _backend_assets/completion.*; do for completion in _core_assets/completion.*; do
if [ -f "$completion" ]; then if [ -f "$completion" ]; then
cp "$completion" _temp_full/completions/ cp "$completion" _temp_full/completions/
fi fi
done done
# Copy docs directory
if [ -d "docs" ]; then
cp -r docs _temp_full/
fi
# Create installation guide # Create installation guide
cat > _temp_full/INSTALL.md << 'EOFINSTALL' cat > _temp_full/INSTALL.md << 'EOFINSTALL'
# DankMaterialShell Installation # DankMaterialShell Installation
@@ -337,8 +360,7 @@ jobs:
## Troubleshooting ## Troubleshooting
- Run with verbose output: `quickshell -v -p ~/.config/quickshell/dms` - Run with verbose output: `DMS_LOG_LEVEL=debug dms run`
- Check logs in `~/.local/state/DankMaterialShell/`
- Ensure all dependencies are installed - Ensure all dependencies are installed
EOFINSTALL EOFINSTALL

View File

@@ -3,8 +3,8 @@ name: Update Vendor Hash
on: on:
push: push:
paths: paths:
- "backend/go.mod" - "core/go.mod"
- "backend/go.sum" - "core/go.sum"
branches: branches:
- master - master
@@ -20,14 +20,14 @@ jobs:
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v31 uses: cachix/install-nix-action@v31
- name: Update vendorHash in backend/flake.nix - name: Update vendorHash in flake.nix
run: | run: |
set -euo pipefail set -euo pipefail
# Try to build and capture the expected hash from error message # Try to build and capture the expected hash from error message
echo "Attempting nix build to get new vendorHash..." echo "Attempting nix build to get new vendorHash..."
cd backend
if output=$(nix build .#dms-cli 2>&1); then if output=$(nix build .#dmsCli 2>&1); then
echo "Build succeeded, no hash update needed" echo "Build succeeded, no hash update needed"
exit 0 exit 0
fi fi
@@ -58,7 +58,7 @@ jobs:
# Verify the build works with the new hash # Verify the build works with the new hash
echo "Verifying build with new vendorHash..." echo "Verifying build with new vendorHash..."
nix build .#dms-cli nix build .#dmsCli
echo "vendorHash updated successfully!" echo "vendorHash updated successfully!"
@@ -66,12 +66,12 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
if ! git diff --quiet backend/flake.nix; then if ! git diff --quiet flake.nix; then
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
git add backend/flake.nix git add flake.nix
git commit -m "flake: update vendorHash for go.mod changes" git commit -m "nix: update vendorHash for go.mod changes"
for attempt in 1 2 3; do for attempt in 1 2 3; do
if git push; then if git push; then
@@ -86,5 +86,5 @@ jobs:
echo "Failed to push after retries" >&2 echo "Failed to push after retries" >&2
exit 1 exit 1
else else
echo "No changes to backend/flake.nix" echo "No changes to flake.nix"
fi fi

1
.gitignore vendored
View File

@@ -27,7 +27,6 @@ qrc_*.cpp
ui_*.h ui_*.h
*.qmlc *.qmlc
*.jsc *.jsc
Makefile*
*build-* *build-*
*.qm *.qm
*.prl *.prl

View File

@@ -19,11 +19,11 @@
</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), 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/), 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
This is a monorepo containing both the shell interface and backend services: This is a monorepo containing both the shell interface and the core backend services:
``` ```
DankMaterialShell/ DankMaterialShell/
@@ -32,12 +32,14 @@ DankMaterialShell/
│ ├── Services/ # System integration (audio, network, bluetooth) │ ├── Services/ # System integration (audio, network, bluetooth)
│ ├── Widgets/ # Reusable UI controls │ ├── Widgets/ # Reusable UI controls
│ └── Common/ # Shared resources and themes │ └── Common/ # Shared resources and themes
├── backend/ # Go backend and CLI ├── core/ # Go backend and CLI
│ ├── cmd/ # dms CLI and dankinstall binaries │ ├── cmd/ # dms CLI and dankinstall binaries
│ ├── internal/ # System integration, IPC, distro support │ ├── internal/ # System integration, IPC, distro support
│ └── pkg/ # Shared packages │ └── pkg/ # Shared packages
├── distro/ # Distribution packaging (Fedora RPM specs) ├── distro/ # Distribution packaging
├── nix/ # NixOS/home-manager modules │ ├── fedora/ # Fedora RPM specs
│ ├── debian/ # Debian packaging
│ └── nix/ # NixOS/home-manager modules
└── flake.nix # Nix flake for declarative installation └── flake.nix # Nix flake for declarative installation
``` ```
@@ -103,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/), and [MangoWC](https://github.com/DreamMaoMao/mangowc) 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), and [labwc](https://labwc.github.io/) 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)
@@ -135,15 +137,14 @@ dms plugins search # Browse plugin registry
See component-specific documentation: See component-specific documentation:
- **[quickshell/](quickshell/)** - QML shell development, widgets, and modules - **[quickshell/](quickshell/)** - QML shell development, widgets, and modules
- **[backend/](backend/)** - Go backend, CLI tools, and system integration - **[core/](core/)** - Go backend, CLI tools, and system integration
- **[distro/](distro/)** - Distribution packaging - **[distro/](distro/)** - Distribution packaging (Fedora, Debian, NixOS)
- **[nix/](nix/)** - NixOS and home-manager modules
### Building from Source ### Building from Source
**Backend:** **Core + Dankinstall:**
```bash ```bash
cd backend cd core
make # Build dms CLI make # Build dms CLI
make dankinstall # Build installer make dankinstall # Build installer
``` ```
@@ -182,6 +183,10 @@ For documentation contributions, see [DankLinux-Docs](https://github.com/AvengeM
- [soramanew](https://github.com/soramanew) - [Caelestia](https://github.com/caelestia-dots/shell) inspiration - [soramanew](https://github.com/soramanew) - [Caelestia](https://github.com/caelestia-dots/shell) inspiration
- [end-4](https://github.com/end-4) - [dots-hyprland](https://github.com/end-4/dots-hyprland) inspiration - [end-4](https://github.com/end-4) - [dots-hyprland](https://github.com/end-4/dots-hyprland) inspiration
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=AvengeMedia/DankMaterialShell&type=date&legend=top-left)](https://www.star-history.com/#AvengeMedia/DankMaterialShell&type=date&legend=top-left)
## License ## License
MIT License - See [LICENSE](LICENSE) for details. MIT License - See [LICENSE](LICENSE) for details.

View File

@@ -1,14 +0,0 @@
package main
import "os/exec"
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
func isArchPackageInstalled(packageName string) bool {
cmd := exec.Command("pacman", "-Q", packageName)
err := cmd.Run()
return err == nil
}

48
core/.mockery.yml Normal file
View File

@@ -0,0 +1,48 @@
with-expecter: true
dir: "internal/mocks/{{.InterfaceDirRelative}}"
mockname: "Mock{{.InterfaceName}}"
outpkg: "{{.PackageName}}"
packages:
github.com/Wifx/gonetworkmanager/v2:
interfaces:
NetworkManager:
Device:
DeviceWireless:
AccessPoint:
Connection:
Settings:
ActiveConnection:
IP4Config:
net:
interfaces:
Conn:
github.com/AvengeMedia/danklinux/internal/plugins:
interfaces:
GitClient:
github.com/godbus/dbus/v5:
interfaces:
BusObject:
github.com/AvengeMedia/danklinux/internal/server/brightness:
config:
dir: "internal/mocks/brightness"
outpkg: mocks_brightness
interfaces:
DBusConn:
github.com/AvengeMedia/danklinux/internal/server/network:
config:
dir: "internal/mocks/network"
outpkg: mocks_network
interfaces:
Backend:
github.com/AvengeMedia/danklinux/internal/server/cups:
config:
dir: "internal/mocks/cups"
outpkg: mocks_cups
interfaces:
CUPSClientInterface:
github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev:
config:
dir: "internal/mocks/evdev"
outpkg: mocks_evdev
interfaces:
EvdevDevice:

157
core/Makefile Normal file
View File

@@ -0,0 +1,157 @@
BINARY_NAME=dms
BINARY_NAME_INSTALL=dankinstall
SOURCE_DIR=cmd/dms
SOURCE_DIR_INSTALL=cmd/dankinstall
BUILD_DIR=bin
PREFIX ?= /usr/local
INSTALL_DIR=$(PREFIX)/bin
GO=go
GOFLAGS=-ldflags="-s -w"
# Version and build info
VERSION=$(shell git describe --tags --always 2>/dev/null || echo "dev")
BUILD_TIME=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_LDFLAGS=-ldflags='-s -w -X main.Version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.commit=$(COMMIT)'
# Architecture to build for dist target (amd64, arm64, or all)
ARCH ?= all
.PHONY: all build dankinstall dist clean install install-all install-dankinstall uninstall uninstall-all uninstall-dankinstall install-config uninstall-config test fmt vet deps help
# Default target
all: build
# Build the main binary (dms)
build:
@echo "Building $(BINARY_NAME)..."
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 $(GO) build $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./$(SOURCE_DIR)
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
dankinstall:
@echo "Building $(BINARY_NAME_INSTALL)..."
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 $(GO) build $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME_INSTALL) ./$(SOURCE_DIR_INSTALL)
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME_INSTALL)"
# Build distro binaries for amd64 and arm64 (Linux only, no update/greeter support)
dist:
ifeq ($(ARCH),all)
@echo "Building $(BINARY_NAME) for distribution (amd64 and arm64)..."
@mkdir -p $(BUILD_DIR)
@echo "Building for linux/amd64..."
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -tags distro_binary $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(SOURCE_DIR)
@echo "Building for linux/arm64..."
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -tags distro_binary $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(SOURCE_DIR)
@echo "Distribution builds complete:"
@echo " $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64"
@echo " $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64"
else
@echo "Building $(BINARY_NAME) for distribution ($(ARCH))..."
@mkdir -p $(BUILD_DIR)
@echo "Building for linux/$(ARCH)..."
CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) $(GO) build -tags distro_binary $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-$(ARCH) ./$(SOURCE_DIR)
@echo "Distribution build complete:"
@echo " $(BUILD_DIR)/$(BINARY_NAME)-linux-$(ARCH)"
endif
build-all: build dankinstall
install: build
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
@echo "Installation complete"
install-all: build-all
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
@echo "Installing $(BINARY_NAME_INSTALL) to $(INSTALL_DIR)..."
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME_INSTALL) $(INSTALL_DIR)/$(BINARY_NAME_INSTALL)
@echo "Installation complete"
install-dankinstall: dankinstall
@echo "Installing $(BINARY_NAME_INSTALL) to $(INSTALL_DIR)..."
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME_INSTALL) $(INSTALL_DIR)/$(BINARY_NAME_INSTALL)
@echo "Installation complete"
uninstall:
@echo "Uninstalling $(BINARY_NAME) from $(INSTALL_DIR)..."
@rm -f $(INSTALL_DIR)/$(BINARY_NAME)
@echo "Uninstall complete"
uninstall-all:
@echo "Uninstalling $(BINARY_NAME) from $(INSTALL_DIR)..."
@rm -f $(INSTALL_DIR)/$(BINARY_NAME)
@echo "Uninstalling $(BINARY_NAME_INSTALL) from $(INSTALL_DIR)..."
@rm -f $(INSTALL_DIR)/$(BINARY_NAME_INSTALL)
@echo "Uninstall complete"
uninstall-dankinstall:
@echo "Uninstalling $(BINARY_NAME_INSTALL) from $(INSTALL_DIR)..."
@rm -f $(INSTALL_DIR)/$(BINARY_NAME_INSTALL)
@echo "Uninstall complete"
clean:
@echo "Cleaning build artifacts..."
@rm -rf $(BUILD_DIR)
@echo "Clean complete"
test:
@echo "Running tests..."
$(GO) test -v ./...
fmt:
@echo "Formatting Go code..."
$(GO) fmt ./...
vet:
@echo "Running go vet..."
$(GO) vet ./...
deps:
@echo "Updating dependencies..."
$(GO) mod tidy
$(GO) mod download
dev:
@echo "Building $(BINARY_NAME) for development..."
@mkdir -p $(BUILD_DIR)
$(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) ./$(SOURCE_DIR)
@echo "Development build complete: $(BUILD_DIR)/$(BINARY_NAME)"
check-go:
@echo "Checking Go version..."
@go version | grep -E "go1\.(2[2-9]|[3-9][0-9])" > /dev/null || (echo "ERROR: Go 1.22 or higher required" && exit 1)
@echo "Go version OK"
version: check-go
@echo "Version: $(VERSION)"
@echo "Build Time: $(BUILD_TIME)"
@echo "Commit: $(COMMIT)"
help:
@echo "Available targets:"
@echo " all - Build the main binary (dms) (default)"
@echo " build - Build the main binary (dms)"
@echo " dankinstall - Build dankinstall binary"
@echo " dist - Build dms for linux amd64/arm64 (no update/greeter)"
@echo " Use ARCH=amd64 or ARCH=arm64 to build only one"
@echo " build-all - Build both binaries"
@echo " install - Install dms to $(INSTALL_DIR)"
@echo " install-all - Install both dms and dankinstall to $(INSTALL_DIR)"
@echo " install-dankinstall - Install only dankinstall to $(INSTALL_DIR)"
@echo " uninstall - Remove dms from $(INSTALL_DIR)"
@echo " uninstall-all - Remove both binaries from $(INSTALL_DIR)"
@echo " uninstall-dankinstall - Remove only dankinstall from $(INSTALL_DIR)"
@echo " clean - Clean build artifacts"
@echo " test - Run tests"
@echo " fmt - Format Go code"
@echo " vet - Run go vet"
@echo " deps - Update dependencies"
@echo " dev - Build with debug info"
@echo " check-go - Check Go version compatibility"
@echo " version - Show version information"
@echo " help - Show this help message"

View File

@@ -31,6 +31,7 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
- DDC/CI protocol - External monitor brightness control (like `ddcutil`) - DDC/CI protocol - External monitor brightness control (like `ddcutil`)
- Backlight control - Internal display brightness via `login1` or sysfs - Backlight control - Internal display brightness via `login1` or sysfs
- LED control - Keyboard/device LED management - LED control - Keyboard/device LED management
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
**Plugin System** **Plugin System**
- Plugin registry integration - Plugin registry integration

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -4,15 +4,15 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/logger" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/tui" "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
var Version = "dev" var Version = "dev"
func main() { func main() {
fileLogger, err := logger.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)
fmt.Println("Continuing without file logging...") fmt.Println("Continuing without file logging...")

View File

@@ -5,8 +5,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/server/brightness" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View File

@@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/server" "github.com/AvengeMedia/DankMaterialShell/core/internal/server"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -368,6 +368,7 @@ func getCommonCommands() []*cobra.Command {
pluginsCmd, pluginsCmd,
dank16Cmd, dank16Cmd,
brightnessCmd, brightnessCmd,
dpmsCmd,
keybindsCmd, keybindsCmd,
greeterCmd, greeterCmd,
setupCmd, setupCmd,

View File

@@ -5,8 +5,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/dank16" "github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -25,6 +25,7 @@ func init() {
dank16Cmd.Flags().Bool("foot", false, "Output in Foot terminal format") dank16Cmd.Flags().Bool("foot", false, "Output in Foot terminal format")
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format") dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format") dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
dank16Cmd.Flags().String("vscode-enrich", "", "Enrich existing VSCode theme file with terminal colors") dank16Cmd.Flags().String("vscode-enrich", "", "Enrich existing VSCode theme file with terminal colors")
dank16Cmd.Flags().String("background", "", "Custom background color") dank16Cmd.Flags().String("background", "", "Custom background color")
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag") dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
@@ -42,6 +43,7 @@ func runDank16(cmd *cobra.Command, args []string) {
isFoot, _ := cmd.Flags().GetBool("foot") isFoot, _ := cmd.Flags().GetBool("foot")
isAlacritty, _ := cmd.Flags().GetBool("alacritty") isAlacritty, _ := cmd.Flags().GetBool("alacritty")
isGhostty, _ := cmd.Flags().GetBool("ghostty") isGhostty, _ := cmd.Flags().GetBool("ghostty")
isWezterm, _ := cmd.Flags().GetBool("wezterm")
vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich") vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich")
background, _ := cmd.Flags().GetString("background") background, _ := cmd.Flags().GetString("background")
contrastAlgo, _ := cmd.Flags().GetString("contrast") contrastAlgo, _ := cmd.Flags().GetString("contrast")
@@ -84,6 +86,8 @@ func runDank16(cmd *cobra.Command, args []string) {
fmt.Print(dank16.GenerateAlacrittyTheme(colors)) fmt.Print(dank16.GenerateAlacrittyTheme(colors))
} else if isGhostty { } else if isGhostty {
fmt.Print(dank16.GenerateGhosttyTheme(colors)) fmt.Print(dank16.GenerateGhosttyTheme(colors))
} else if isWezterm {
fmt.Print(dank16.GenerateWeztermTheme(colors))
} else { } else {
fmt.Print(dank16.GenerateGhosttyTheme(colors)) fmt.Print(dank16.GenerateGhosttyTheme(colors))
} }

View File

@@ -0,0 +1,84 @@
package main
import (
"fmt"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/cobra"
)
var dpmsCmd = &cobra.Command{
Use: "dpms",
Short: "Control display power management",
}
var dpmsOnCmd = &cobra.Command{
Use: "on [output]",
Short: "Turn display(s) on",
Args: cobra.MaximumNArgs(1),
Run: runDPMSOn,
}
var dpmsOffCmd = &cobra.Command{
Use: "off [output]",
Short: "Turn display(s) off",
Args: cobra.MaximumNArgs(1),
Run: runDPMSOff,
}
var dpmsListCmd = &cobra.Command{
Use: "list",
Short: "List outputs",
Args: cobra.NoArgs,
Run: runDPMSList,
}
func init() {
dpmsCmd.AddCommand(dpmsOnCmd, dpmsOffCmd, dpmsListCmd)
}
func runDPMSOn(cmd *cobra.Command, args []string) {
outputName := ""
if len(args) > 0 {
outputName = args[0]
}
client, err := newDPMSClient()
if err != nil {
log.Fatalf("%v", err)
}
defer client.Close()
if err := client.SetDPMS(outputName, true); err != nil {
log.Fatalf("%v", err)
}
}
func runDPMSOff(cmd *cobra.Command, args []string) {
outputName := ""
if len(args) > 0 {
outputName = args[0]
}
client, err := newDPMSClient()
if err != nil {
log.Fatalf("%v", err)
}
defer client.Close()
if err := client.SetDPMS(outputName, false); err != nil {
log.Fatalf("%v", err)
}
}
func runDPMSList(cmd *cobra.Command, args []string) {
client, err := newDPMSClient()
if err != nil {
log.Fatalf("%v", err)
}
defer client.Close()
for _, output := range client.ListOutputs() {
fmt.Println(output)
}
}

View File

@@ -12,10 +12,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/errdefs" "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/version" "github.com/AvengeMedia/DankMaterialShell/core/internal/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View File

@@ -8,8 +8,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/greeter" "github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -277,9 +277,9 @@ func enableGreeter() error {
} }
} }
wrapperCmd := "dms-greeter" wrapperCmd, err := findCommandPath("dms-greeter")
if !commandExists("dms-greeter") { if err != nil {
wrapperCmd = "/usr/local/bin/dms-greeter" return fmt.Errorf("dms-greeter not found in PATH. Please ensure it is installed and accessible")
} }
compositorLower := strings.ToLower(selectedCompositor) compositorLower := strings.ToLower(selectedCompositor)
@@ -444,7 +444,7 @@ func checkGreeterStatus() error {
desc: "Session state", desc: "Session state",
}, },
{ {
source: filepath.Join(homeDir, ".cache", "quickshell", "dankshell", "dms-colors.json"), source: filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json"),
target: filepath.Join(cacheDir, "colors.json"), target: filepath.Join(cacheDir, "colors.json"),
desc: "Color theme", desc: "Color theme",
}, },

View File

@@ -5,9 +5,9 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/keybinds" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/keybinds/providers" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds/providers"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -34,9 +34,7 @@ var keybindsShowCmd = &cobra.Command{
} }
func init() { func init() {
keybindsShowCmd.Flags().String("hyprland-path", "$HOME/.config/hypr", "Path to Hyprland config directory") keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
keybindsShowCmd.Flags().String("mangowc-path", "$HOME/.config/mango", "Path to MangoWC config directory")
keybindsShowCmd.Flags().String("sway-path", "$HOME/.config/sway", "Path to Sway config directory")
keybindsCmd.AddCommand(keybindsListCmd) keybindsCmd.AddCommand(keybindsListCmd)
keybindsCmd.AddCommand(keybindsShowCmd) keybindsCmd.AddCommand(keybindsShowCmd)
@@ -89,25 +87,34 @@ func runKeybindsList(cmd *cobra.Command, args []string) {
func runKeybindsShow(cmd *cobra.Command, args []string) { func runKeybindsShow(cmd *cobra.Command, args []string) {
providerName := args[0] providerName := args[0]
registry := keybinds.GetDefaultRegistry() registry := keybinds.GetDefaultRegistry()
if providerName == "hyprland" { customPath, _ := cmd.Flags().GetString("path")
hyprlandPath, _ := cmd.Flags().GetString("hyprland-path") if customPath != "" {
hyprlandProvider := providers.NewHyprlandProvider(hyprlandPath) var provider keybinds.Provider
registry.Register(hyprlandProvider) switch providerName {
case "hyprland":
provider = providers.NewHyprlandProvider(customPath)
case "mangowc":
provider = providers.NewMangoWCProvider(customPath)
case "sway":
provider = providers.NewSwayProvider(customPath)
default:
log.Fatalf("Provider %s does not support custom path", providerName)
} }
if providerName == "mangowc" { sheet, err := provider.GetCheatSheet()
mangowcPath, _ := cmd.Flags().GetString("mangowc-path") if err != nil {
mangowcProvider := providers.NewMangoWCProvider(mangowcPath) log.Fatalf("Error getting cheatsheet: %v", err)
registry.Register(mangowcProvider)
} }
if providerName == "sway" { output, err := json.MarshalIndent(sheet, "", " ")
swayPath, _ := cmd.Flags().GetString("sway-path") if err != nil {
swayProvider := providers.NewSwayProvider(swayPath) log.Fatalf("Error generating JSON: %v", err)
registry.Register(swayProvider) }
fmt.Fprintln(os.Stdout, string(output))
return
} }
provider, err := registry.Get(providerName) provider, err := registry.Get(providerName)

View File

@@ -7,10 +7,10 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/dms" "github.com/AvengeMedia/DankMaterialShell/core/internal/dms"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View File

@@ -7,9 +7,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

345
core/cmd/dms/dpms_client.go Normal file
View File

@@ -0,0 +1,345 @@
package main
import (
"fmt"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_power"
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
type cmd struct {
fn func()
done chan error
}
type dpmsClient struct {
display *wlclient.Display
ctx *wlclient.Context
powerMgr *wlr_output_power.ZwlrOutputPowerManagerV1
outputs map[string]*outputState
mu sync.Mutex
syncRound int
done bool
err error
cmdq chan cmd
stopChan chan struct{}
wg sync.WaitGroup
}
type outputState struct {
wlOutput *wlclient.Output
powerCtrl *wlr_output_power.ZwlrOutputPowerV1
name string
mode uint32
failed bool
waitCh chan struct{}
wantMode *uint32
}
func (c *dpmsClient) post(fn func()) {
done := make(chan error, 1)
select {
case c.cmdq <- cmd{fn: fn, done: done}:
<-done
case <-c.stopChan:
}
}
func (c *dpmsClient) waylandActor() {
defer c.wg.Done()
for {
select {
case <-c.stopChan:
return
case cmd := <-c.cmdq:
cmd.fn()
close(cmd.done)
}
}
}
func newDPMSClient() (*dpmsClient, error) {
display, err := wlclient.Connect("")
if err != nil {
return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
}
c := &dpmsClient{
display: display,
ctx: display.Context(),
outputs: make(map[string]*outputState),
cmdq: make(chan cmd, 128),
stopChan: make(chan struct{}),
}
c.wg.Add(1)
go c.waylandActor()
registry, err := display.GetRegistry()
if err != nil {
display.Context().Close()
return nil, fmt.Errorf("failed to get registry: %w", err)
}
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
switch e.Interface {
case wlr_output_power.ZwlrOutputPowerManagerV1InterfaceName:
powerMgr := wlr_output_power.NewZwlrOutputPowerManagerV1(c.ctx)
version := e.Version
if version > 1 {
version = 1
}
if err := registry.Bind(e.Name, e.Interface, version, powerMgr); err == nil {
c.powerMgr = powerMgr
}
case "wl_output":
output := wlclient.NewOutput(c.ctx)
version := e.Version
if version > 4 {
version = 4
}
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
outputID := fmt.Sprintf("output-%d", output.ID())
state := &outputState{
wlOutput: output,
name: outputID,
}
c.mu.Lock()
c.outputs[outputID] = state
c.mu.Unlock()
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
c.mu.Lock()
delete(c.outputs, state.name)
state.name = ev.Name
c.outputs[ev.Name] = state
c.mu.Unlock()
})
}
}
})
syncCallback, err := display.Sync()
if err != nil {
c.Close()
return nil, fmt.Errorf("failed to sync display: %w", err)
}
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
c.handleSync()
})
for !c.done {
if err := c.ctx.Dispatch(); err != nil {
c.Close()
return nil, fmt.Errorf("dispatch error: %w", err)
}
}
if c.err != nil {
c.Close()
return nil, c.err
}
return c, nil
}
func (c *dpmsClient) handleSync() {
c.syncRound++
switch c.syncRound {
case 1:
if c.powerMgr == nil {
c.err = fmt.Errorf("wlr-output-power-management protocol not supported by compositor")
c.done = true
return
}
c.mu.Lock()
for _, state := range c.outputs {
powerCtrl, err := c.powerMgr.GetOutputPower(state.wlOutput)
if err != nil {
continue
}
state.powerCtrl = powerCtrl
powerCtrl.SetModeHandler(func(e wlr_output_power.ZwlrOutputPowerV1ModeEvent) {
c.mu.Lock()
defer c.mu.Unlock()
if state.powerCtrl == nil {
return
}
state.mode = e.Mode
if state.wantMode != nil && e.Mode == *state.wantMode && state.waitCh != nil {
close(state.waitCh)
state.wantMode = nil
}
})
powerCtrl.SetFailedHandler(func(e wlr_output_power.ZwlrOutputPowerV1FailedEvent) {
c.mu.Lock()
defer c.mu.Unlock()
if state.powerCtrl == nil {
return
}
state.failed = true
if state.waitCh != nil {
close(state.waitCh)
state.wantMode = nil
}
})
}
c.mu.Unlock()
syncCallback, err := c.display.Sync()
if err != nil {
c.err = fmt.Errorf("failed to sync display: %w", err)
c.done = true
return
}
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
c.handleSync()
})
default:
c.done = true
}
}
func (c *dpmsClient) ListOutputs() []string {
c.mu.Lock()
defer c.mu.Unlock()
names := make([]string, 0, len(c.outputs))
for name := range c.outputs {
names = append(names, name)
}
return names
}
func (c *dpmsClient) SetDPMS(outputName string, on bool) error {
var mode uint32
if on {
mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOn)
} else {
mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOff)
}
var setErr error
c.post(func() {
c.mu.Lock()
var waitStates []*outputState
if outputName == "" || outputName == "all" {
if len(c.outputs) == 0 {
c.mu.Unlock()
setErr = fmt.Errorf("no outputs found")
return
}
for _, state := range c.outputs {
if state.powerCtrl == nil {
continue
}
state.wantMode = &mode
state.waitCh = make(chan struct{})
state.failed = false
waitStates = append(waitStates, state)
state.powerCtrl.SetMode(mode)
}
} else {
state, ok := c.outputs[outputName]
if !ok {
c.mu.Unlock()
setErr = fmt.Errorf("output not found: %s", outputName)
return
}
if state.powerCtrl == nil {
c.mu.Unlock()
setErr = fmt.Errorf("output %s has nil powerCtrl", outputName)
return
}
state.wantMode = &mode
state.waitCh = make(chan struct{})
state.failed = false
waitStates = append(waitStates, state)
state.powerCtrl.SetMode(mode)
}
c.mu.Unlock()
deadline := time.Now().Add(10 * time.Second)
for _, state := range waitStates {
c.mu.Lock()
ch := state.waitCh
c.mu.Unlock()
done := false
for !done {
if err := c.ctx.Dispatch(); err != nil {
setErr = fmt.Errorf("dispatch error: %w", err)
return
}
select {
case <-ch:
c.mu.Lock()
if state.failed {
setErr = fmt.Errorf("compositor reported failed for %s", state.name)
c.mu.Unlock()
return
}
c.mu.Unlock()
done = true
default:
if time.Now().After(deadline) {
setErr = fmt.Errorf("timeout waiting for mode change on %s", state.name)
return
}
time.Sleep(10 * time.Millisecond)
}
}
}
c.mu.Lock()
for _, state := range waitStates {
if state.powerCtrl != nil {
state.powerCtrl.Destroy()
state.powerCtrl = nil
}
}
c.mu.Unlock()
c.display.Roundtrip()
})
return setErr
}
func (c *dpmsClient) Close() {
close(c.stopChan)
c.wg.Wait()
c.mu.Lock()
defer c.mu.Unlock()
for _, state := range c.outputs {
if state.powerCtrl != nil {
state.powerCtrl.Destroy()
}
}
c.outputs = nil
if c.powerMgr != nil {
c.powerMgr.Destroy()
c.powerMgr = nil
}
if c.display != nil {
c.ctx.Close()
c.display = nil
}
}

View File

@@ -5,7 +5,7 @@ package main
import ( import (
"os" "os"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
) )
var Version = "dev" var Version = "dev"

View File

@@ -5,7 +5,7 @@ package main
import ( import (
"os" "os"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
) )
var Version = "dev" var Version = "dev"

View File

@@ -12,8 +12,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/server" "github.com/AvengeMedia/DankMaterialShell/core/internal/server"
) )
var isSessionManaged bool var isSessionManaged bool

View File

@@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/tui" "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )

26
core/cmd/dms/utils.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"os/exec"
)
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
// findCommandPath returns the absolute path to a command in PATH
func findCommandPath(cmd string) (string, error) {
path, err := exec.LookPath(cmd)
if err != nil {
return "", fmt.Errorf("command '%s' not found in PATH", cmd)
}
return path, nil
}
func isArchPackageInstalled(packageName string) bool {
cmd := exec.Command("pacman", "-Q", packageName)
err := cmd.Run()
return err == nil
}

View File

@@ -1,65 +1,68 @@
module github.com/AvengeMedia/DankMaterialShell/backend module github.com/AvengeMedia/DankMaterialShell/core
go 1.24.6 go 1.24.6
require ( require (
github.com/Wifx/gonetworkmanager/v2 v2.2.0 github.com/Wifx/gonetworkmanager/v2 v2.2.0
github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/log v0.4.2 github.com/charmbracelet/log v0.4.2
github.com/fsnotify/fsnotify v1.9.0
github.com/godbus/dbus/v5 v5.1.0 github.com/godbus/dbus/v5 v5.1.0
github.com/spf13/cobra v1.9.1 github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34 golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/clipperhouse/displaywidth v0.5.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 // indirect github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.3 // indirect
golang.org/x/crypto v0.42.0 // indirect golang.org/x/crypto v0.44.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/net v0.47.0 // indirect
) )
require ( require (
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/colorprofile v0.3.3 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect github.com/charmbracelet/x/ansi v0.11.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 github.com/lucasb-eyer/go-colorful v1.3.0
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/afero v1.15.0 github.com/spf13/afero v1.15.0
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.38.0
golang.org/x/sys v0.36.0 golang.org/x/text v0.31.0 // indirect
golang.org/x/text v0.29.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@@ -14,27 +14,33 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -44,23 +50,29 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs= github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 h1:4KqVJTL5eanN8Sgg3BV6f2/QzfZEFbCd+rTak1fGRRA= github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 h1:EC9n6hr6yKDoVJ6g7Ko523LbbceJfR0ohbOp809Fyf4=
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30/go.mod h1:snwvGrbywVFy2d6KJdQ132zapq4aLyzLMgpo79XdEfM= github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0/go.mod h1:E3VhlS+AKkrq6ZNn1axE2/nDRJ87l1FJk9r5HT2vPX0=
github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w= github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w=
github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU= github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd h1:30HEd5KKVM7GgMJ1GSNuYxuZXEg8Pdlngp6T51faxoc= github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9 h1:SOFrnF9LCssC6q6Rb0084Bzg2aBYbe8QXv9xKGXmt/w=
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd/go.mod h1:lz8PQr/p79XpFq5ODVBwRJu5LnOF8Et7j95ehqmCMJU= github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9/go.mod h1:0wtvm/JfPC9RFVEAP3ks0ec5h64/qmZkTTUE3pjz7Hc=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83 h1:B+A58zGFuDrvEZpPN+yS6swJA0nzqgZvDzgl/OPyefU=
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
@@ -79,8 +91,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -91,7 +103,6 @@ github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -101,36 +112,33 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34 h1:iTAt1me6SBYsuzrl/CmrxtATPlOG/pVviosM3DhUdKE= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34/go.mod h1:jzmUN5lUAv2O8e63OvcauV4S30rIZ1BvF/PNYE37vDo= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
type ConfigDeployer struct { type ConfigDeployer struct {

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -124,3 +124,17 @@ func GenerateGhosttyTheme(colors []string) string {
} }
return result.String() return result.String()
} }
func GenerateWeztermTheme(colors []string) string {
var result strings.Builder
labels := []string{"ansi", "brights"}
for j, label := range labels {
start := j * 8
colorSlice := make([]string, 8)
for i, color := range colors[start : start+8] {
colorSlice[i] = fmt.Sprintf("'%s'", color)
}
fmt.Fprintf(&result, "%s = [%s]\n", label, strings.Join(colorSlice, ", "))
}
return result.String()
}

View File

@@ -9,7 +9,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func init() { func init() {
@@ -321,7 +321,7 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
systemPkgs, aurPkgs, manualPkgs := a.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) systemPkgs, aurPkgs, manualPkgs, variantMap := a.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
// Phase 3: System Packages // Phase 3: System Packages
if len(systemPkgs) > 0 { if len(systemPkgs) > 0 {
@@ -361,7 +361,7 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
IsComplete: false, IsComplete: false,
LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")), LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
} }
if err := a.InstallManualPackages(ctx, manualPkgs, sudoPassword, progressChan); err != nil { if err := a.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install manual packages: %w", err) return fmt.Errorf("failed to install manual packages: %w", err)
} }
} }
@@ -387,7 +387,7 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
return nil return nil
} }
func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, []string) { func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, []string, map[string]deps.PackageVariant) {
systemPkgs := []string{} systemPkgs := []string{}
aurPkgs := []string{} aurPkgs := []string{}
manualPkgs := []string{} manualPkgs := []string{}
@@ -410,7 +410,6 @@ func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm
pkgInfo, exists := packageMap[dep.Name] pkgInfo, exists := packageMap[dep.Name]
if !exists { if !exists {
// If no mapping exists, treat as manual build
manualPkgs = append(manualPkgs, dep.Name) manualPkgs = append(manualPkgs, dep.Name)
continue continue
} }
@@ -425,7 +424,7 @@ func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm
} }
} }
return systemPkgs, aurPkgs, manualPkgs return systemPkgs, aurPkgs, manualPkgs, variantMap
} }
func (a *ArchDistribution) installSystemPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (a *ArchDistribution) installSystemPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {

View File

@@ -13,8 +13,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/version" "github.com/AvengeMedia/DankMaterialShell/core/internal/version"
) )
const forceQuickshellGit = false const forceQuickshellGit = false

View File

@@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func TestBaseDistribution_detectDMS_NotInstalled(t *testing.T) { func TestBaseDistribution_detectDMS_NotInstalled(t *testing.T) {

View File

@@ -6,7 +6,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func init() { func init() {
@@ -209,7 +209,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") "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)
} }
@@ -238,7 +238,7 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
systemPkgs, manualPkgs := d.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) systemPkgs, manualPkgs, variantMap := d.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
if len(systemPkgs) > 0 { if len(systemPkgs) > 0 {
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
@@ -273,7 +273,7 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
IsComplete: false, IsComplete: false,
LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")), LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
} }
if err := d.InstallManualPackages(ctx, manualPkgs, sudoPassword, progressChan); err != nil { if err := d.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install manual packages: %w", err) return fmt.Errorf("failed to install manual packages: %w", err)
} }
} }
@@ -297,10 +297,15 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
return nil return nil
} }
func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string) { func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, map[string]deps.PackageVariant) {
systemPkgs := []string{} systemPkgs := []string{}
manualPkgs := []string{} manualPkgs := []string{}
variantMap := make(map[string]deps.PackageVariant)
for _, dep := range dependencies {
variantMap[dep.Name] = dep.Variant
}
packageMap := d.GetPackageMapping(wm) packageMap := d.GetPackageMapping(wm)
for _, dep := range dependencies { for _, dep := range dependencies {
@@ -326,7 +331,7 @@ func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency,
} }
} }
return systemPkgs, manualPkgs return systemPkgs, manualPkgs, variantMap
} }
func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
@@ -513,7 +518,7 @@ func (d *DebianDistribution) installGhosttyDebian(ctx context.Context, sudoPassw
return nil return nil
} }
func (d *DebianDistribution) InstallManualPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (d *DebianDistribution) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
@@ -527,7 +532,7 @@ func (d *DebianDistribution) InstallManualPackages(ctx context.Context, packages
return fmt.Errorf("failed to install ghostty: %w", err) return fmt.Errorf("failed to install ghostty: %w", err)
} }
default: default:
if err := d.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, sudoPassword, progressChan); err != nil { if err := d.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install %s: %w", pkg, err) return fmt.Errorf("failed to install %s: %w", pkg, err)
} }
} }

View File

@@ -1,7 +1,7 @@
package distros package distros
import ( import (
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
// NewDependencyDetector creates a DependencyDetector for the specified distribution // NewDependencyDetector creates a DependencyDetector for the specified distribution

View File

@@ -6,7 +6,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func init() { func init() {
@@ -19,10 +19,12 @@ func init() {
Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution { Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
return NewFedoraDistribution(config, logChan) return NewFedoraDistribution(config, logChan)
}) })
Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution { Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
return NewFedoraDistribution(config, logChan) return NewFedoraDistribution(config, logChan)
}) })
Register("ultramarine", "#00078b", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
return NewFedoraDistribution(config, logChan)
})
} }
type FedoraDistribution struct { type FedoraDistribution struct {
@@ -165,7 +167,7 @@ func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem} packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
case deps.WindowManagerNiri: case deps.WindowManagerNiri:
packages["niri"] = f.getNiriMapping(variants["niri"]) packages["niri"] = f.getNiriMapping(variants["niri"])
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeCOPR, RepoURL: "yalter/niri"} packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
} }
return packages return packages
@@ -203,7 +205,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: RepoTypeCOPR, RepoURL: "yalter/niri"} return PackageMapping{Name: "niri", Repository: RepoTypeSystem}
} }
func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency { func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency {
@@ -314,7 +316,7 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
dnfPkgs, coprPkgs, manualPkgs := f.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) dnfPkgs, coprPkgs, manualPkgs, variantMap := f.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
// Phase 2: Enable COPR repositories // Phase 2: Enable COPR repositories
if len(coprPkgs) > 0 { if len(coprPkgs) > 0 {
@@ -369,7 +371,7 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
IsComplete: false, IsComplete: false,
LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")), LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
} }
if err := f.InstallManualPackages(ctx, manualPkgs, sudoPassword, progressChan); err != nil { if err := f.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install manual packages: %w", err) return fmt.Errorf("failed to install manual packages: %w", err)
} }
} }
@@ -395,7 +397,7 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
return nil return nil
} }
func (f *FedoraDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string) { func (f *FedoraDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
dnfPkgs := []string{} dnfPkgs := []string{}
coprPkgs := []PackageMapping{} coprPkgs := []PackageMapping{}
manualPkgs := []string{} manualPkgs := []string{}
@@ -432,7 +434,7 @@ func (f *FedoraDistribution) categorizePackages(dependencies []deps.Dependency,
} }
} }
return dnfPkgs, coprPkgs, manualPkgs return dnfPkgs, coprPkgs, manualPkgs, variantMap
} }
func (f *FedoraDistribution) extractPackageNames(packages []PackageMapping) []string { func (f *FedoraDistribution) extractPackageNames(packages []PackageMapping) []string {
@@ -483,7 +485,7 @@ func (f *FedoraDistribution) enableCOPRRepos(ctx context.Context, coprPkgs []Pac
} }
priorityCmd := ExecSudoCommand(ctx, sudoPassword, priorityCmd := ExecSudoCommand(ctx, sudoPassword,
fmt.Sprintf("bash -c 'echo \"priority=1\" | tee -a %s' 2>&1", repoFile)) fmt.Sprintf("bash -c 'echo \"priority=1\" | tee -a %s'", repoFile))
priorityOutput, err := priorityCmd.CombinedOutput() priorityOutput, err := priorityCmd.CombinedOutput()
if err != nil { if err != nil {
f.logError("failed to set niri COPR repo priority", err) f.logError("failed to set niri COPR repo priority", err)
@@ -506,6 +508,14 @@ func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", "))) f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
args := []string{"dnf", "install", "-y"} args := []string{"dnf", "install", "-y"}
for _, pkg := range packages {
if pkg == "niri" || pkg == "niri-git" {
args = append(args, "--setopt=install_weak_deps=False")
break
}
}
args = append(args, packages...) args = append(args, packages...)
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{

View File

@@ -7,7 +7,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
var GentooGlobalUseFlags = []string{ var GentooGlobalUseFlags = []string{
@@ -263,9 +263,9 @@ func (g *GentooDistribution) setGlobalUseFlags(ctx context.Context, sudoPassword
var cmd *exec.Cmd var cmd *exec.Cmd
if hasUse { if hasUse {
cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("sed -i 's/^USE=\"\\(.*\\)\"/USE=\"\\1 %s\"/' /etc/portage/make.conf; exit_code=$?; exit $exit_code", useFlags)) cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("sed -i 's/^USE=\"\\(.*\\)\"/USE=\"\\1 %s\"/' /etc/portage/make.conf", useFlags))
} else { } else {
cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("bash -c \"echo 'USE=\\\"%s\\\"' >> /etc/portage/make.conf\"; exit_code=$?; exit $exit_code", useFlags)) cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("bash -c \"echo 'USE=\\\"%s\\\"' >> /etc/portage/make.conf\"", useFlags))
} }
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
@@ -343,8 +343,7 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
LogOutput: "Syncing Portage tree with emerge --sync", LogOutput: "Syncing Portage tree with emerge --sync",
} }
syncCmd := ExecSudoCommand(ctx, sudoPassword, syncCmd := ExecSudoCommand(ctx, sudoPassword, "emerge --sync --quiet")
"emerge --sync --quiet; exit_code=$?; exit $exit_code")
syncOutput, syncErr := syncCmd.CombinedOutput() syncOutput, syncErr := syncCmd.CombinedOutput()
if syncErr != nil { if syncErr != nil {
g.log(fmt.Sprintf("emerge --sync output: %s", string(syncOutput))) g.log(fmt.Sprintf("emerge --sync output: %s", string(syncOutput)))
@@ -365,7 +364,7 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
args := []string{"emerge", "--ask=n", "--quiet"} args := []string{"emerge", "--ask=n", "--quiet"}
args = append(args, missingPkgs...) args = append(args, missingPkgs...)
cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("%s; exit_code=$?; exit $exit_code", strings.Join(args, " "))) cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
g.logError("failed to install prerequisites", err) g.logError("failed to install prerequisites", err)
@@ -392,7 +391,7 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
systemPkgs, guruPkgs, manualPkgs := g.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) systemPkgs, guruPkgs, manualPkgs, variantMap := g.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
g.log(fmt.Sprintf("CATEGORIZED PACKAGES: system=%d, guru=%d, manual=%d", len(systemPkgs), len(guruPkgs), len(manualPkgs))) g.log(fmt.Sprintf("CATEGORIZED PACKAGES: system=%d, guru=%d, manual=%d", len(systemPkgs), len(guruPkgs), len(manualPkgs)))
@@ -448,7 +447,7 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
IsComplete: false, IsComplete: false,
LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")), LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
} }
if err := g.InstallManualPackages(ctx, manualPkgs, sudoPassword, progressChan); err != nil { if err := g.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install manual packages: %w", err) return fmt.Errorf("failed to install manual packages: %w", err)
} }
} }
@@ -472,7 +471,7 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
return nil return nil
} }
func (g *GentooDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]PackageMapping, []PackageMapping, []string) { func (g *GentooDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]PackageMapping, []PackageMapping, []string, map[string]deps.PackageVariant) {
systemPkgs := []PackageMapping{} systemPkgs := []PackageMapping{}
guruPkgs := []PackageMapping{} guruPkgs := []PackageMapping{}
manualPkgs := []string{} manualPkgs := []string{}
@@ -509,7 +508,7 @@ func (g *GentooDistribution) categorizePackages(dependencies []deps.Dependency,
} }
} }
return systemPkgs, guruPkgs, manualPkgs return systemPkgs, guruPkgs, manualPkgs, variantMap
} }
func (g *GentooDistribution) extractPackageNames(packages []PackageMapping) []string { func (g *GentooDistribution) extractPackageNames(packages []PackageMapping) []string {
@@ -553,7 +552,7 @@ func (g *GentooDistribution) installPortagePackages(ctx context.Context, package
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")), CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
} }
cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("%s || exit $?", strings.Join(args, " "))) cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
return g.runWithProgressTimeout(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60, 0) return g.runWithProgressTimeout(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60, 0)
} }
@@ -745,6 +744,6 @@ func (g *GentooDistribution) installGURUPackages(ctx context.Context, packages [
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")), CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
} }
cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("%s || exit $?", strings.Join(args, " "))) cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
return g.runWithProgressTimeout(cmd, progressChan, PhaseAURPackages, 0.70, 0.85, 0) return g.runWithProgressTimeout(cmd, progressChan, PhaseAURPackages, 0.70, 0.85, 0)
} }

View File

@@ -3,7 +3,7 @@ package distros
import ( import (
"context" "context"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
// DistroFamily represents a family of related distributions // DistroFamily represents a family of related distributions

View File

@@ -7,6 +7,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
// ManualPackageInstaller provides methods for installing packages from source // ManualPackageInstaller provides methods for installing packages from source
@@ -42,8 +44,7 @@ func (m *ManualPackageInstaller) getLatestQuickshellTag(ctx context.Context) str
return m.parseLatestTagFromGitOutput(string(tagOutput)) return m.parseLatestTagFromGitOutput(string(tagOutput))
} }
// InstallManualPackages handles packages that need manual building func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
@@ -51,9 +52,10 @@ func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, pack
m.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", "))) m.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", ")))
for _, pkg := range packages { for _, pkg := range packages {
variant := variantMap[pkg]
switch pkg { switch pkg {
case "dms (DankMaterialShell)", "dms": case "dms (DankMaterialShell)", "dms":
if err := m.installDankMaterialShell(ctx, sudoPassword, progressChan); err != nil { if err := m.installDankMaterialShell(ctx, variant, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install DankMaterialShell: %w", err) return fmt.Errorf("failed to install DankMaterialShell: %w", err)
} }
case "dgop": case "dgop":
@@ -69,7 +71,7 @@ func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, pack
return fmt.Errorf("failed to install niri: %w", err) return fmt.Errorf("failed to install niri: %w", err)
} }
case "quickshell": case "quickshell":
if err := m.installQuickshell(ctx, sudoPassword, progressChan); err != nil { if err := m.installQuickshell(ctx, variant, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install quickshell: %w", err) return fmt.Errorf("failed to install quickshell: %w", err)
} }
case "hyprland": case "hyprland":
@@ -294,7 +296,7 @@ func (m *ManualPackageInstaller) installNiri(ctx context.Context, sudoPassword s
return nil return nil
} }
func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
m.log("Installing quickshell from source...") m.log("Installing quickshell from source...")
homeDir := os.Getenv("HOME") homeDir := os.Getenv("HOME")
@@ -322,10 +324,9 @@ func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, sudoPass
} }
var cloneCmd *exec.Cmd var cloneCmd *exec.Cmd
if forceQuickshellGit { if forceQuickshellGit || variant == deps.VariantGit {
cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir) cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
} else { } else {
// Get latest tag from repository
latestTag := m.getLatestQuickshellTag(ctx) latestTag := m.getLatestQuickshellTag(ctx)
if latestTag != "" { if latestTag != "" {
m.log(fmt.Sprintf("Using latest quickshell tag: %s", latestTag)) m.log(fmt.Sprintf("Using latest quickshell tag: %s", latestTag))
@@ -391,8 +392,8 @@ func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, sudoPass
CommandInfo: "sudo cmake --install build", CommandInfo: "sudo cmake --install build",
} }
installCmd := ExecSudoCommand(ctx, sudoPassword, installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
fmt.Sprintf("cd %s && cmake --install build", tmpDir)) installCmd.Dir = tmpDir
if err := installCmd.Run(); err != nil { if err := installCmd.Run(); err != nil {
return fmt.Errorf("failed to install quickshell: %w", err) return fmt.Errorf("failed to install quickshell: %w", err)
} }
@@ -454,8 +455,8 @@ func (m *ManualPackageInstaller) installHyprland(ctx context.Context, sudoPasswo
CommandInfo: "sudo make install", CommandInfo: "sudo make install",
} }
installCmd := ExecSudoCommand(ctx, sudoPassword, installCmd := ExecSudoCommand(ctx, sudoPassword, "make install")
fmt.Sprintf("cd %s && make install", tmpDir)) installCmd.Dir = tmpDir
if err := installCmd.Run(); err != nil { if err := installCmd.Run(); err != nil {
return fmt.Errorf("failed to install Hyprland: %w", err) return fmt.Errorf("failed to install Hyprland: %w", err)
} }
@@ -477,6 +478,95 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
return fmt.Errorf("failed to create cache directory: %w", err) return fmt.Errorf("failed to create cache directory: %w", err)
} }
// Install hyprutils first
progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages,
Progress: 0.05,
Step: "Building hyprutils dependency...",
IsComplete: false,
CommandInfo: "git clone https://github.com/hyprwm/hyprutils.git",
}
hyprutilsDir := filepath.Join(cacheDir, "hyprutils-build")
if err := os.MkdirAll(hyprutilsDir, 0755); err != nil {
return fmt.Errorf("failed to create hyprutils directory: %w", err)
}
defer os.RemoveAll(hyprutilsDir)
cloneUtilsCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/hyprwm/hyprutils.git", hyprutilsDir)
if err := cloneUtilsCmd.Run(); err != nil {
return fmt.Errorf("failed to clone hyprutils: %w", err)
}
configureUtilsCmd := exec.CommandContext(ctx, "cmake",
"--no-warn-unused-cli",
"-DCMAKE_BUILD_TYPE:STRING=Release",
"-DCMAKE_INSTALL_PREFIX:PATH=/usr",
"-DBUILD_TESTING=off",
"-S", ".",
"-B", "./build")
configureUtilsCmd.Dir = hyprutilsDir
configureUtilsCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
if err := m.runWithProgressStep(configureUtilsCmd, progressChan, PhaseSystemPackages, 0.05, 0.1, "Configuring hyprutils..."); err != nil {
return fmt.Errorf("failed to configure hyprutils: %w", err)
}
buildUtilsCmd := exec.CommandContext(ctx, "cmake", "--build", "./build", "--config", "Release", "--target", "all")
buildUtilsCmd.Dir = hyprutilsDir
buildUtilsCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
if err := m.runWithProgressStep(buildUtilsCmd, progressChan, PhaseSystemPackages, 0.1, 0.2, "Building hyprutils..."); err != nil {
return fmt.Errorf("failed to build hyprutils: %w", err)
}
installUtilsCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install ./build")
installUtilsCmd.Dir = hyprutilsDir
if err := installUtilsCmd.Run(); err != nil {
return fmt.Errorf("failed to install hyprutils: %w", err)
}
// Install hyprwayland-scanner
progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages,
Progress: 0.2,
Step: "Building hyprwayland-scanner dependency...",
IsComplete: false,
CommandInfo: "git clone https://github.com/hyprwm/hyprwayland-scanner.git",
}
scannerDir := filepath.Join(cacheDir, "hyprwayland-scanner-build")
if err := os.MkdirAll(scannerDir, 0755); err != nil {
return fmt.Errorf("failed to create scanner directory: %w", err)
}
defer os.RemoveAll(scannerDir)
cloneScannerCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/hyprwm/hyprwayland-scanner.git", scannerDir)
if err := cloneScannerCmd.Run(); err != nil {
return fmt.Errorf("failed to clone hyprwayland-scanner: %w", err)
}
configureScannerCmd := exec.CommandContext(ctx, "cmake",
"-DCMAKE_INSTALL_PREFIX=/usr",
"-B", "build")
configureScannerCmd.Dir = scannerDir
configureScannerCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
if err := m.runWithProgressStep(configureScannerCmd, progressChan, PhaseSystemPackages, 0.2, 0.25, "Configuring hyprwayland-scanner..."); err != nil {
return fmt.Errorf("failed to configure hyprwayland-scanner: %w", err)
}
buildScannerCmd := exec.CommandContext(ctx, "cmake", "--build", "build", "-j")
buildScannerCmd.Dir = scannerDir
buildScannerCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
if err := m.runWithProgressStep(buildScannerCmd, progressChan, PhaseSystemPackages, 0.25, 0.35, "Building hyprwayland-scanner..."); err != nil {
return fmt.Errorf("failed to build hyprwayland-scanner: %w", err)
}
installScannerCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
installScannerCmd.Dir = scannerDir
if err := installScannerCmd.Run(); err != nil {
return fmt.Errorf("failed to install hyprwayland-scanner: %w", err)
}
// Now build hyprpicker
tmpDir := filepath.Join(cacheDir, "hyprpicker-build") tmpDir := filepath.Join(cacheDir, "hyprpicker-build")
if err := os.MkdirAll(tmpDir, 0755); err != nil { if err := os.MkdirAll(tmpDir, 0755); err != nil {
return fmt.Errorf("failed to create temp directory: %w", err) return fmt.Errorf("failed to create temp directory: %w", err)
@@ -485,7 +575,7 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages, Phase: PhaseSystemPackages,
Progress: 0.2, Progress: 0.35,
Step: "Cloning hyprpicker repository...", Step: "Cloning hyprpicker repository...",
IsComplete: false, IsComplete: false,
CommandInfo: "git clone https://github.com/hyprwm/hyprpicker.git", CommandInfo: "git clone https://github.com/hyprwm/hyprpicker.git",
@@ -498,16 +588,39 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages, Phase: PhaseSystemPackages,
Progress: 0.4, Progress: 0.45,
Step: "Building hyprpicker...", Step: "Configuring hyprpicker build...",
IsComplete: false, IsComplete: false,
CommandInfo: "make all", CommandInfo: "cmake -B build -S . -DCMAKE_BUILD_TYPE=Release",
} }
buildCmd := exec.CommandContext(ctx, "make", "all") configureCmd := exec.CommandContext(ctx, "cmake",
"--no-warn-unused-cli",
"-DCMAKE_BUILD_TYPE:STRING=Release",
"-DCMAKE_INSTALL_PREFIX:PATH=/usr",
"-S", ".",
"-B", "./build")
configureCmd.Dir = tmpDir
configureCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
output, err := configureCmd.CombinedOutput()
if err != nil {
m.log(fmt.Sprintf("cmake configure failed. Output:\n%s", string(output)))
return fmt.Errorf("failed to configure hyprpicker: %w\nCMake output:\n%s", err, string(output))
}
progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages,
Progress: 0.55,
Step: "Building hyprpicker...",
IsComplete: false,
CommandInfo: "cmake --build build --target hyprpicker",
}
buildCmd := exec.CommandContext(ctx, "cmake", "--build", "./build", "--config", "Release", "--target", "hyprpicker")
buildCmd.Dir = tmpDir buildCmd.Dir = tmpDir
buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir) buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
if err := buildCmd.Run(); err != nil { if err := m.runWithProgressStep(buildCmd, progressChan, PhaseSystemPackages, 0.55, 0.8, "Building hyprpicker..."); err != nil {
return fmt.Errorf("failed to build hyprpicker: %w", err) return fmt.Errorf("failed to build hyprpicker: %w", err)
} }
@@ -517,11 +630,11 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
Step: "Installing hyprpicker...", Step: "Installing hyprpicker...",
IsComplete: false, IsComplete: false,
NeedsSudo: true, NeedsSudo: true,
CommandInfo: "sudo make install", CommandInfo: "sudo cmake --install build",
} }
installCmd := ExecSudoCommand(ctx, sudoPassword, installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install ./build")
fmt.Sprintf("cd %s && make install", tmpDir)) installCmd.Dir = tmpDir
if err := installCmd.Run(); err != nil { if err := installCmd.Run(); err != nil {
return fmt.Errorf("failed to install hyprpicker: %w", err) return fmt.Errorf("failed to install hyprpicker: %w", err)
} }
@@ -642,22 +755,20 @@ func (m *ManualPackageInstaller) installMatugen(ctx context.Context, sudoPasswor
return nil return nil
} }
func (m *ManualPackageInstaller) installDankMaterialShell(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (m *ManualPackageInstaller) installDankMaterialShell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
m.log("Installing DankMaterialShell (DMS)...") m.log("Installing DankMaterialShell (DMS)...")
// Always install/update the DMS binary
if err := m.installDMSBinary(ctx, sudoPassword, progressChan); err != nil { if err := m.installDMSBinary(ctx, sudoPassword, progressChan); err != nil {
m.logError("Failed to install DMS binary", err) m.logError("Failed to install DMS binary", err)
} }
// Handle DMS config - clone if missing, pull if exists
dmsPath := filepath.Join(os.Getenv("HOME"), ".config/quickshell/dms") dmsPath := filepath.Join(os.Getenv("HOME"), ".config/quickshell/dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) { if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
// Config doesn't exist, clone it
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages, Phase: PhaseSystemPackages,
Progress: 0.90, Progress: 0.90,
Step: "Cloning DankMaterialShell config...", Step: "Cloning DankMaterialShell...",
IsComplete: false, IsComplete: false,
CommandInfo: "git clone https://github.com/AvengeMedia/DankMaterialShell.git", CommandInfo: "git clone https://github.com/AvengeMedia/DankMaterialShell.git",
} }
@@ -667,81 +778,88 @@ func (m *ManualPackageInstaller) installDankMaterialShell(ctx context.Context, s
return fmt.Errorf("failed to create quickshell config directory: %w", err) return fmt.Errorf("failed to create quickshell config directory: %w", err)
} }
tmpRepoPath := filepath.Join(os.TempDir(), "dms-clone-tmp")
defer os.RemoveAll(tmpRepoPath)
cloneCmd := exec.CommandContext(ctx, "git", "clone", cloneCmd := exec.CommandContext(ctx, "git", "clone",
"https://github.com/AvengeMedia/DankMaterialShell.git", tmpRepoPath) "https://github.com/AvengeMedia/DankMaterialShell.git", dmsPath)
if err := cloneCmd.Run(); err != nil { if err := cloneCmd.Run(); err != nil {
return fmt.Errorf("failed to clone DankMaterialShell: %w", err) return fmt.Errorf("failed to clone DankMaterialShell: %w", err)
} }
if !forceDMSGit { if forceDMSGit || variant == deps.VariantGit {
fetchCmd := exec.CommandContext(ctx, "git", "-C", tmpRepoPath, "fetch", "--tags") m.log("Using git variant (master branch)")
if err := fetchCmd.Run(); err == nil { return nil
tagCmd := exec.CommandContext(ctx, "git", "-C", tmpRepoPath, "describe", "--tags", "--abbrev=0", "origin/master") }
if tagOutput, err := tagCmd.Output(); err == nil {
tagCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "describe", "--tags", "--abbrev=0", "origin/master")
tagOutput, err := tagCmd.Output()
if err != nil {
m.log("Using default branch (no tags found)")
return nil
}
latestTag := strings.TrimSpace(string(tagOutput)) latestTag := strings.TrimSpace(string(tagOutput))
checkoutCmd := exec.CommandContext(ctx, "git", "-C", tmpRepoPath, "checkout", latestTag) checkoutCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "checkout", latestTag)
if err := checkoutCmd.Run(); err == nil { if err := checkoutCmd.Run(); err != nil {
m.logError(fmt.Sprintf("Failed to checkout tag %s", latestTag), err)
return nil
}
m.log(fmt.Sprintf("Checked out latest tag: %s", latestTag)) m.log(fmt.Sprintf("Checked out latest tag: %s", latestTag))
} m.log("DankMaterialShell cloned successfully")
} return nil
}
} }
srcPath := filepath.Join(tmpRepoPath, "quickshell")
cpCmd := exec.CommandContext(ctx, "cp", "-r", srcPath, dmsPath)
if err := cpCmd.Run(); err != nil {
return fmt.Errorf("failed to copy quickshell directory: %w", err)
}
m.log("DankMaterialShell config cloned successfully")
} else {
// Config exists, update it
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseSystemPackages, Phase: PhaseSystemPackages,
Progress: 0.90, Progress: 0.90,
Step: "Updating DankMaterialShell config...", Step: "Updating DankMaterialShell...",
IsComplete: false, IsComplete: false,
CommandInfo: "Updating ~/.config/quickshell/dms", CommandInfo: "Updating ~/.config/quickshell/dms",
} }
tmpRepoPath := filepath.Join(os.TempDir(), "dms-update-tmp") fetchCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "fetch", "origin", "--tags", "--force")
defer os.RemoveAll(tmpRepoPath) if err := fetchCmd.Run(); err != nil {
m.logError("Failed to fetch updates", err)
return nil
}
if forceDMSGit || variant == deps.VariantGit {
branchCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "rev-parse", "--abbrev-ref", "HEAD")
branchOutput, err := branchCmd.Output()
if err != nil {
m.logError("Failed to get current branch", err)
return nil
}
branch := strings.TrimSpace(string(branchOutput))
if branch == "" {
branch = "master"
}
pullCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "pull", "origin", branch)
if err := pullCmd.Run(); err != nil {
m.logError("Failed to pull updates", err)
return nil
}
m.log("DankMaterialShell updated successfully (git variant)")
return nil
}
latestTagCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "describe", "--tags", "--abbrev=0", "origin/master")
tagOutput, err := latestTagCmd.Output()
if err != nil {
m.logError("Failed to get latest tag", err)
return nil
}
cloneCmd := exec.CommandContext(ctx, "git", "clone",
"https://github.com/AvengeMedia/DankMaterialShell.git", tmpRepoPath)
if err := cloneCmd.Run(); err != nil {
m.logError("Failed to clone DankMaterialShell for update", err)
} else {
if !forceDMSGit {
fetchCmd := exec.CommandContext(ctx, "git", "-C", tmpRepoPath, "fetch", "--tags")
if err := fetchCmd.Run(); err == nil {
tagCmd := exec.CommandContext(ctx, "git", "-C", tmpRepoPath, "describe", "--tags", "--abbrev=0", "origin/master")
if tagOutput, err := tagCmd.Output(); err == nil {
latestTag := strings.TrimSpace(string(tagOutput)) latestTag := strings.TrimSpace(string(tagOutput))
checkoutCmd := exec.CommandContext(ctx, "git", "-C", tmpRepoPath, "checkout", latestTag) checkoutCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "checkout", latestTag)
_ = checkoutCmd.Run() if err := checkoutCmd.Run(); err != nil {
} m.logError(fmt.Sprintf("Failed to checkout tag %s", latestTag), err)
} return nil
}
srcPath := filepath.Join(tmpRepoPath, "quickshell")
rsyncCmd := exec.CommandContext(ctx, "rsync", "-a", "--delete", srcPath+"/", dmsPath+"/")
if err := rsyncCmd.Run(); err != nil {
cpCmd := exec.CommandContext(ctx, "cp", "-rf", srcPath+"/.", dmsPath+"/")
if err := cpCmd.Run(); err != nil {
m.logError("Failed to update DankMaterialShell config", err)
} else {
m.log("DankMaterialShell config updated successfully")
}
} else {
m.log("DankMaterialShell config updated successfully")
}
}
} }
m.log(fmt.Sprintf("Updated to tag: %s", latestTag))
return nil return nil
} }

View File

@@ -6,7 +6,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func init() { func init() {
@@ -308,7 +308,7 @@ func (n *NixOSDistribution) InstallPackages(ctx context.Context, dependencies []
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
nixpkgsPkgs, flakePkgs := n.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) nixpkgsPkgs, flakePkgs, _ := n.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
// Phase 2: Nixpkgs Packages // Phase 2: Nixpkgs Packages
if len(nixpkgsPkgs) > 0 { if len(nixpkgsPkgs) > 0 {
@@ -362,10 +362,15 @@ func (n *NixOSDistribution) InstallPackages(ctx context.Context, dependencies []
return nil return nil
} }
func (n *NixOSDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string) { func (n *NixOSDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, map[string]deps.PackageVariant) {
nixpkgsPkgs := []string{} nixpkgsPkgs := []string{}
flakePkgs := []string{} flakePkgs := []string{}
variantMap := make(map[string]deps.PackageVariant)
for _, dep := range dependencies {
variantMap[dep.Name] = dep.Variant
}
packageMap := n.GetPackageMapping(wm) packageMap := n.GetPackageMapping(wm)
for _, dep := range dependencies { for _, dep := range dependencies {
@@ -391,7 +396,7 @@ func (n *NixOSDistribution) categorizePackages(dependencies []deps.Dependency, w
} }
} }
return nixpkgsPkgs, flakePkgs return nixpkgsPkgs, flakePkgs, variantMap
} }
func (n *NixOSDistribution) installNixpkgsPackages(ctx context.Context, packages []string, progressChan chan<- InstallProgressMsg) error { func (n *NixOSDistribution) installNixpkgsPackages(ctx context.Context, packages []string, progressChan chan<- InstallProgressMsg) error {

View File

@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func init() { func init() {
@@ -294,7 +294,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
systemPkgs, manualPkgs := o.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) systemPkgs, manualPkgs, variantMap := o.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
// Phase 2: System Packages (Zypper) // Phase 2: System Packages (Zypper)
if len(systemPkgs) > 0 { if len(systemPkgs) > 0 {
@@ -320,7 +320,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
IsComplete: false, IsComplete: false,
LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")), LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
} }
if err := o.InstallManualPackages(ctx, manualPkgs, sudoPassword, progressChan); err != nil { if err := o.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install manual packages: %w", err) return fmt.Errorf("failed to install manual packages: %w", err)
} }
} }
@@ -346,7 +346,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
return nil return nil
} }
func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string) { func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, map[string]deps.PackageVariant) {
systemPkgs := []string{} systemPkgs := []string{}
manualPkgs := []string{} manualPkgs := []string{}
@@ -380,7 +380,7 @@ func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency
} }
} }
return systemPkgs, manualPkgs return systemPkgs, manualPkgs, variantMap
} }
func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
@@ -406,8 +406,7 @@ func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packag
return o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60) return o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
} }
// installQuickshell overrides the base implementation to set openSUSE-specific CFLAGS func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
o.log("Installing quickshell from source (with openSUSE-specific build flags)...") o.log("Installing quickshell from source (with openSUSE-specific build flags)...")
homeDir := os.Getenv("HOME") homeDir := os.Getenv("HOME")
@@ -435,10 +434,9 @@ func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, sudoPasswo
} }
var cloneCmd *exec.Cmd var cloneCmd *exec.Cmd
if forceQuickshellGit { if forceQuickshellGit || variant == deps.VariantGit {
cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir) cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
} else { } else {
// Get latest tag from repository
latestTag := o.getLatestQuickshellTag(ctx) latestTag := o.getLatestQuickshellTag(ctx)
if latestTag != "" { if latestTag != "" {
o.log(fmt.Sprintf("Using latest quickshell tag: %s", latestTag)) o.log(fmt.Sprintf("Using latest quickshell tag: %s", latestTag))
@@ -524,8 +522,8 @@ func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, sudoPasswo
CommandInfo: "sudo cmake --install build", CommandInfo: "sudo cmake --install build",
} }
installCmd := ExecSudoCommand(ctx, sudoPassword, installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
fmt.Sprintf("cd %s && cmake --install build", tmpDir)) installCmd.Dir = tmpDir
if err := installCmd.Run(); err != nil { if err := installCmd.Run(); err != nil {
return fmt.Errorf("failed to install quickshell: %w", err) return fmt.Errorf("failed to install quickshell: %w", err)
} }
@@ -573,15 +571,13 @@ func (o *OpenSUSEDistribution) installRust(ctx context.Context, sudoPassword str
return nil return nil
} }
// InstallManualPackages overrides the base implementation to use openSUSE-specific builds func (o *OpenSUSEDistribution) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
func (o *OpenSUSEDistribution) InstallManualPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
o.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", "))) o.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", ")))
// Install Rust if needed for matugen
for _, pkg := range packages { for _, pkg := range packages {
if pkg == "matugen" { if pkg == "matugen" {
if err := o.installRust(ctx, sudoPassword, progressChan); err != nil { if err := o.installRust(ctx, sudoPassword, progressChan); err != nil {
@@ -592,13 +588,13 @@ func (o *OpenSUSEDistribution) InstallManualPackages(ctx context.Context, packag
} }
for _, pkg := range packages { for _, pkg := range packages {
variant := variantMap[pkg]
if pkg == "quickshell" { if pkg == "quickshell" {
if err := o.installQuickshell(ctx, sudoPassword, progressChan); err != nil { if err := o.installQuickshell(ctx, variant, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install quickshell: %w", err) return fmt.Errorf("failed to install quickshell: %w", err)
} }
} else { } else {
// Use the base ManualPackageInstaller for other packages if err := o.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, variantMap, sudoPassword, progressChan); err != nil {
if err := o.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install %s: %w", pkg, err) return fmt.Errorf("failed to install %s: %w", pkg, err)
} }
} }

View File

@@ -8,7 +8,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/errdefs" "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
) )
// DistroInfo contains basic information about a distribution // DistroInfo contains basic information about a distribution

View File

@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
) )
func init() { func init() {
@@ -263,7 +263,7 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
return fmt.Errorf("failed to install prerequisites: %w", err) return fmt.Errorf("failed to install prerequisites: %w", err)
} }
systemPkgs, ppaPkgs, manualPkgs := u.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags) systemPkgs, ppaPkgs, manualPkgs, variantMap := u.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
// Phase 2: Enable PPA repositories // Phase 2: Enable PPA repositories
if len(ppaPkgs) > 0 { if len(ppaPkgs) > 0 {
@@ -329,7 +329,7 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
IsComplete: false, IsComplete: false,
LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")), LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
} }
if err := u.InstallManualPackages(ctx, manualPkgs, sudoPassword, progressChan); err != nil { if err := u.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install manual packages: %w", err) return fmt.Errorf("failed to install manual packages: %w", err)
} }
} }
@@ -355,11 +355,16 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
return nil return nil
} }
func (u *UbuntuDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string) { func (u *UbuntuDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
systemPkgs := []string{} systemPkgs := []string{}
ppaPkgs := []PackageMapping{} ppaPkgs := []PackageMapping{}
manualPkgs := []string{} manualPkgs := []string{}
variantMap := make(map[string]deps.PackageVariant)
for _, dep := range dependencies {
variantMap[dep.Name] = dep.Variant
}
packageMap := u.GetPackageMapping(wm) packageMap := u.GetPackageMapping(wm)
for _, dep := range dependencies { for _, dep := range dependencies {
@@ -387,7 +392,7 @@ func (u *UbuntuDistribution) categorizePackages(dependencies []deps.Dependency,
} }
} }
return systemPkgs, ppaPkgs, manualPkgs return systemPkgs, ppaPkgs, manualPkgs, variantMap
} }
func (u *UbuntuDistribution) extractPackageNames(packages []PackageMapping) []string { func (u *UbuntuDistribution) extractPackageNames(packages []PackageMapping) []string {
@@ -729,8 +734,7 @@ func (u *UbuntuDistribution) installGhosttyUbuntu(ctx context.Context, sudoPassw
return nil return nil
} }
// Override InstallManualPackages for Ubuntu to handle Ubuntu-specific installations func (u *UbuntuDistribution) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
func (u *UbuntuDistribution) InstallManualPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
@@ -744,8 +748,7 @@ func (u *UbuntuDistribution) InstallManualPackages(ctx context.Context, packages
return fmt.Errorf("failed to install ghostty: %w", err) return fmt.Errorf("failed to install ghostty: %w", err)
} }
default: default:
// Use the base ManualPackageInstaller for other packages if err := u.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, variantMap, sudoPassword, progressChan); err != nil {
if err := u.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install %s: %w", pkg, err) return fmt.Errorf("failed to install %s: %w", pkg, err)
} }
} }

View File

@@ -6,7 +6,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )

View File

@@ -5,9 +5,9 @@ import (
"os" "os"
"os/exec" "os/exec"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
) )
type Detector struct { type Detector struct {

View File

@@ -4,7 +4,7 @@ import (
"os/exec" "os/exec"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )

View File

@@ -9,9 +9,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/greeter" "github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )

View File

@@ -3,7 +3,7 @@ package dms
import ( import (
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/tui" "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )

View File

@@ -9,8 +9,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
) )
// DetectDMSPath checks for DMS installation following XDG Base Directory specification // DetectDMSPath checks for DMS installation following XDG Base Directory specification
@@ -227,6 +227,7 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
{filepath.Join(homeDir, ".local"), ".local directory"}, {filepath.Join(homeDir, ".local"), ".local directory"},
{filepath.Join(homeDir, ".cache"), ".cache directory"}, {filepath.Join(homeDir, ".cache"), ".cache directory"},
{filepath.Join(homeDir, ".local", "state"), ".local/state directory"}, {filepath.Join(homeDir, ".local", "state"), ".local/state directory"},
{filepath.Join(homeDir, ".local", "share"), ".local/share directory"},
} }
logFunc("\nSetting up parent directory ACLs for greeter user access...") logFunc("\nSetting up parent directory ACLs for greeter user access...")
@@ -239,8 +240,8 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
} }
} }
// Set ACL to allow greeter user execute (traverse) permission // Set ACL to allow greeter user read+execute permission (for session discovery)
if err := runSudoCmd(sudoPassword, "setfacl", "-m", "u:greeter:x", dir.path); err != nil { if err := runSudoCmd(sudoPassword, "setfacl", "-m", "u:greeter:rx", dir.path); err != nil {
logFunc(fmt.Sprintf("⚠ Warning: Failed to set ACL on %s: %v", dir.desc, err)) logFunc(fmt.Sprintf("⚠ Warning: Failed to set ACL on %s: %v", dir.desc, err))
logFunc(fmt.Sprintf(" You may need to run manually: setfacl -m u:greeter:x %s", dir.path)) logFunc(fmt.Sprintf(" You may need to run manually: setfacl -m u:greeter:x %s", dir.path))
continue continue
@@ -287,6 +288,8 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
{filepath.Join(homeDir, ".local", "state", "DankMaterialShell"), "DankMaterialShell state"}, {filepath.Join(homeDir, ".local", "state", "DankMaterialShell"), "DankMaterialShell state"},
{filepath.Join(homeDir, ".cache", "quickshell"), "quickshell cache"}, {filepath.Join(homeDir, ".cache", "quickshell"), "quickshell cache"},
{filepath.Join(homeDir, ".config", "quickshell"), "quickshell config"}, {filepath.Join(homeDir, ".config", "quickshell"), "quickshell config"},
{filepath.Join(homeDir, ".local", "share", "wayland-sessions"), "wayland sessions"},
{filepath.Join(homeDir, ".local", "share", "xsessions"), "xsessions"},
} }
for _, dir := range configDirs { for _, dir := range configDirs {
@@ -342,7 +345,7 @@ func SyncDMSConfigs(dmsPath string, logFunc func(string), sudoPassword string) e
desc: "state (wallpaper configuration)", desc: "state (wallpaper configuration)",
}, },
{ {
source: filepath.Join(homeDir, ".cache", "quickshell", "dankshell", "dms-colors.json"), source: filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json"),
target: filepath.Join(cacheDir, "colors.json"), target: filepath.Join(cacheDir, "colors.json"),
desc: "wallpaper based theming", desc: "wallpaper based theming",
}, },

View File

@@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/hyprland" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/keybinds"
) )
type HyprlandProvider struct { type HyprlandProvider struct {
@@ -26,7 +25,7 @@ func (h *HyprlandProvider) Name() string {
} }
func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) { func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
section, err := hyprland.ParseKeys(h.configPath) section, err := ParseHyprlandKeys(h.configPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse hyprland config: %w", err) return nil, fmt.Errorf("failed to parse hyprland config: %w", err)
} }
@@ -41,7 +40,7 @@ func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
}, nil }, nil
} }
func (h *HyprlandProvider) convertSection(section *hyprland.Section, subcategory string, categorizedBinds map[string][]keybinds.Keybind) { func (h *HyprlandProvider) convertSection(section *HyprlandSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
currentSubcat := subcategory currentSubcat := subcategory
if section.Name != "" { if section.Name != "" {
currentSubcat = section.Name currentSubcat = section.Name
@@ -86,7 +85,7 @@ func (h *HyprlandProvider) categorizeByDispatcher(dispatcher string) string {
} }
} }
func (h *HyprlandProvider) convertKeybind(kb *hyprland.KeyBinding, subcategory string) keybinds.Keybind { func (h *HyprlandProvider) convertKeybind(kb *HyprlandKeyBinding, subcategory string) keybinds.Keybind {
key := h.formatKey(kb) key := h.formatKey(kb)
desc := kb.Comment desc := kb.Comment
@@ -108,7 +107,7 @@ func (h *HyprlandProvider) generateDescription(dispatcher, params string) string
return dispatcher return dispatcher
} }
func (h *HyprlandProvider) formatKey(kb *hyprland.KeyBinding) string { func (h *HyprlandProvider) formatKey(kb *HyprlandKeyBinding) string {
parts := make([]string, 0, len(kb.Mods)+1) parts := make([]string, 0, len(kb.Mods)+1)
parts = append(parts, kb.Mods...) parts = append(parts, kb.Mods...)
parts = append(parts, kb.Key) parts = append(parts, kb.Key)

View File

@@ -1,4 +1,4 @@
package hyprland package providers
import ( import (
"os" "os"
@@ -15,7 +15,7 @@ const (
var ModSeparators = []rune{'+', ' '} var ModSeparators = []rune{'+', ' '}
type KeyBinding struct { type HyprlandKeyBinding struct {
Mods []string `json:"mods"` Mods []string `json:"mods"`
Key string `json:"key"` Key string `json:"key"`
Dispatcher string `json:"dispatcher"` Dispatcher string `json:"dispatcher"`
@@ -23,25 +23,25 @@ type KeyBinding struct {
Comment string `json:"comment"` Comment string `json:"comment"`
} }
type Section struct { type HyprlandSection struct {
Children []Section `json:"children"` Children []HyprlandSection `json:"children"`
Keybinds []KeyBinding `json:"keybinds"` Keybinds []HyprlandKeyBinding `json:"keybinds"`
Name string `json:"name"` Name string `json:"name"`
} }
type Parser struct { type HyprlandParser struct {
contentLines []string contentLines []string
readingLine int readingLine int
} }
func NewParser() *Parser { func NewHyprlandParser() *HyprlandParser {
return &Parser{ return &HyprlandParser{
contentLines: []string{}, contentLines: []string{},
readingLine: 0, readingLine: 0,
} }
} }
func (p *Parser) ReadContent(directory string) error { func (p *HyprlandParser) ReadContent(directory string) error {
expandedDir := os.ExpandEnv(directory) expandedDir := os.ExpandEnv(directory)
expandedDir = filepath.Clean(expandedDir) expandedDir = filepath.Clean(expandedDir)
if strings.HasPrefix(expandedDir, "~") { if strings.HasPrefix(expandedDir, "~") {
@@ -87,7 +87,7 @@ func (p *Parser) ReadContent(directory string) error {
return nil return nil
} }
func autogenerateComment(dispatcher, params string) string { func hyprlandAutogenerateComment(dispatcher, params string) string {
switch dispatcher { switch dispatcher {
case "resizewindow": case "resizewindow":
return "Resize window" return "Resize window"
@@ -196,7 +196,7 @@ func autogenerateComment(dispatcher, params string) string {
} }
} }
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding { func (p *HyprlandParser) getKeybindAtLine(lineNumber int) *HyprlandKeyBinding {
line := p.contentLines[lineNumber] line := p.contentLines[lineNumber]
parts := strings.SplitN(line, "=", 2) parts := strings.SplitN(line, "=", 2)
if len(parts) < 2 { if len(parts) < 2 {
@@ -232,7 +232,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
return nil return nil
} }
} else { } else {
comment = autogenerateComment(dispatcher, params) comment = hyprlandAutogenerateComment(dispatcher, params)
} }
var modList []string var modList []string
@@ -256,7 +256,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
} }
} }
return &KeyBinding{ return &HyprlandKeyBinding{
Mods: modList, Mods: modList,
Key: key, Key: key,
Dispatcher: dispatcher, Dispatcher: dispatcher,
@@ -265,7 +265,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
} }
} }
func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section { func (p *HyprlandParser) getBindsRecursive(currentContent *HyprlandSection, scope int) *HyprlandSection {
titleRegex := regexp.MustCompile(TitleRegex) titleRegex := regexp.MustCompile(TitleRegex)
for p.readingLine < len(p.contentLines) { for p.readingLine < len(p.contentLines) {
@@ -283,9 +283,9 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
sectionName := strings.TrimSpace(line[headingScope+1:]) sectionName := strings.TrimSpace(line[headingScope+1:])
p.readingLine++ p.readingLine++
childSection := &Section{ childSection := &HyprlandSection{
Children: []Section{}, Children: []HyprlandSection{},
Keybinds: []KeyBinding{}, Keybinds: []HyprlandKeyBinding{},
Name: sectionName, Name: sectionName,
} }
result := p.getBindsRecursive(childSection, headingScope) result := p.getBindsRecursive(childSection, headingScope)
@@ -312,18 +312,18 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
return currentContent return currentContent
} }
func (p *Parser) ParseKeys() *Section { func (p *HyprlandParser) ParseKeys() *HyprlandSection {
p.readingLine = 0 p.readingLine = 0
rootSection := &Section{ rootSection := &HyprlandSection{
Children: []Section{}, Children: []HyprlandSection{},
Keybinds: []KeyBinding{}, Keybinds: []HyprlandKeyBinding{},
Name: "", Name: "",
} }
return p.getBindsRecursive(rootSection, 0) return p.getBindsRecursive(rootSection, 0)
} }
func ParseKeys(path string) (*Section, error) { func ParseHyprlandKeys(path string) (*HyprlandSection, error) {
parser := NewParser() parser := NewHyprlandParser()
if err := parser.ReadContent(path); err != nil { if err := parser.ReadContent(path); err != nil {
return nil, err return nil, err
} }

View File

@@ -1,4 +1,4 @@
package hyprland package providers
import ( import (
"os" "os"
@@ -6,7 +6,7 @@ import (
"testing" "testing"
) )
func TestAutogenerateComment(t *testing.T) { func TestHyprlandAutogenerateComment(t *testing.T) {
tests := []struct { tests := []struct {
dispatcher string dispatcher string
params string params string
@@ -51,25 +51,25 @@ func TestAutogenerateComment(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.dispatcher+"_"+tt.params, func(t *testing.T) { t.Run(tt.dispatcher+"_"+tt.params, func(t *testing.T) {
result := autogenerateComment(tt.dispatcher, tt.params) result := hyprlandAutogenerateComment(tt.dispatcher, tt.params)
if result != tt.expected { if result != tt.expected {
t.Errorf("autogenerateComment(%q, %q) = %q, want %q", t.Errorf("hyprlandAutogenerateComment(%q, %q) = %q, want %q",
tt.dispatcher, tt.params, result, tt.expected) tt.dispatcher, tt.params, result, tt.expected)
} }
}) })
} }
} }
func TestGetKeybindAtLine(t *testing.T) { func TestHyprlandGetKeybindAtLine(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
line string line string
expected *KeyBinding expected *HyprlandKeyBinding
}{ }{
{ {
name: "basic_keybind", name: "basic_keybind",
line: "bind = SUPER, Q, killactive", line: "bind = SUPER, Q, killactive",
expected: &KeyBinding{ expected: &HyprlandKeyBinding{
Mods: []string{"SUPER"}, Mods: []string{"SUPER"},
Key: "Q", Key: "Q",
Dispatcher: "killactive", Dispatcher: "killactive",
@@ -80,7 +80,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_params", name: "keybind_with_params",
line: "bind = SUPER, left, movefocus, l", line: "bind = SUPER, left, movefocus, l",
expected: &KeyBinding{ expected: &HyprlandKeyBinding{
Mods: []string{"SUPER"}, Mods: []string{"SUPER"},
Key: "left", Key: "left",
Dispatcher: "movefocus", Dispatcher: "movefocus",
@@ -91,7 +91,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_comment", name: "keybind_with_comment",
line: "bind = SUPER, T, exec, kitty # Open terminal", line: "bind = SUPER, T, exec, kitty # Open terminal",
expected: &KeyBinding{ expected: &HyprlandKeyBinding{
Mods: []string{"SUPER"}, Mods: []string{"SUPER"},
Key: "T", Key: "T",
Dispatcher: "exec", Dispatcher: "exec",
@@ -107,7 +107,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_multiple_mods", name: "keybind_multiple_mods",
line: "bind = SUPER+SHIFT, F, fullscreen, 0", line: "bind = SUPER+SHIFT, F, fullscreen, 0",
expected: &KeyBinding{ expected: &HyprlandKeyBinding{
Mods: []string{"SUPER", "SHIFT"}, Mods: []string{"SUPER", "SHIFT"},
Key: "F", Key: "F",
Dispatcher: "fullscreen", Dispatcher: "fullscreen",
@@ -118,7 +118,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_no_mods", name: "keybind_no_mods",
line: "bind = , Print, exec, screenshot", line: "bind = , Print, exec, screenshot",
expected: &KeyBinding{ expected: &HyprlandKeyBinding{
Mods: []string{}, Mods: []string{},
Key: "Print", Key: "Print",
Dispatcher: "exec", Dispatcher: "exec",
@@ -130,7 +130,7 @@ func TestGetKeybindAtLine(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
parser := NewParser() parser := NewHyprlandParser()
parser.contentLines = []string{tt.line} parser.contentLines = []string{tt.line}
result := parser.getKeybindAtLine(0) result := parser.getKeybindAtLine(0)
@@ -171,7 +171,7 @@ func TestGetKeybindAtLine(t *testing.T) {
} }
} }
func TestParseKeysWithSections(t *testing.T) { func TestHyprlandParseKeysWithSections(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "hyprland.conf") configFile := filepath.Join(tmpDir, "hyprland.conf")
@@ -191,9 +191,9 @@ bind = SUPER, T, exec, kitty # Terminal
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(tmpDir) section, err := ParseHyprlandKeys(tmpDir)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseHyprlandKeys failed: %v", err)
} }
if len(section.Children) != 2 { if len(section.Children) != 2 {
@@ -236,7 +236,7 @@ bind = SUPER, T, exec, kitty # Terminal
} }
} }
func TestParseKeysWithCommentBinds(t *testing.T) { func TestHyprlandParseKeysWithCommentBinds(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "test.conf") configFile := filepath.Join(tmpDir, "test.conf")
@@ -249,9 +249,9 @@ bind = SUPER, B, exec, app2
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(tmpDir) section, err := ParseHyprlandKeys(tmpDir)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseHyprlandKeys failed: %v", err)
} }
if len(section.Keybinds) != 3 { if len(section.Keybinds) != 3 {
@@ -269,7 +269,7 @@ bind = SUPER, B, exec, app2
} }
} }
func TestReadContentMultipleFiles(t *testing.T) { func TestHyprlandReadContentMultipleFiles(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
file1 := filepath.Join(tmpDir, "a.conf") file1 := filepath.Join(tmpDir, "a.conf")
@@ -285,7 +285,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
t.Fatalf("Failed to write file2: %v", err) t.Fatalf("Failed to write file2: %v", err)
} }
parser := NewParser() parser := NewHyprlandParser()
if err := parser.ReadContent(tmpDir); err != nil { if err := parser.ReadContent(tmpDir); err != nil {
t.Fatalf("ReadContent failed: %v", err) t.Fatalf("ReadContent failed: %v", err)
} }
@@ -296,7 +296,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
} }
} }
func TestReadContentErrors(t *testing.T) { func TestHyprlandReadContentErrors(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
path string path string
@@ -313,7 +313,7 @@ func TestReadContentErrors(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := ParseKeys(tt.path) _, err := ParseHyprlandKeys(tt.path)
if err == nil { if err == nil {
t.Error("Expected error, got nil") t.Error("Expected error, got nil")
} }
@@ -321,7 +321,7 @@ func TestReadContentErrors(t *testing.T) {
} }
} }
func TestReadContentWithTildeExpansion(t *testing.T) { func TestHyprlandReadContentWithTildeExpansion(t *testing.T) {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
t.Skip("Cannot get home directory") t.Skip("Cannot get home directory")
@@ -343,7 +343,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
t.Skip("Cannot create relative path") t.Skip("Cannot create relative path")
} }
parser := NewParser() parser := NewHyprlandParser()
tildePathMatch := "~/" + relPath tildePathMatch := "~/" + relPath
err = parser.ReadContent(tildePathMatch) err = parser.ReadContent(tildePathMatch)
@@ -352,8 +352,8 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
} }
} }
func TestKeybindWithParamsContainingCommas(t *testing.T) { func TestHyprlandKeybindWithParamsContainingCommas(t *testing.T) {
parser := NewParser() parser := NewHyprlandParser()
parser.contentLines = []string{"bind = SUPER, R, exec, notify-send 'Title' 'Message, with comma'"} parser.contentLines = []string{"bind = SUPER, R, exec, notify-send 'Title' 'Message, with comma'"}
result := parser.getKeybindAtLine(0) result := parser.getKeybindAtLine(0)
@@ -368,7 +368,7 @@ func TestKeybindWithParamsContainingCommas(t *testing.T) {
} }
} }
func TestEmptyAndCommentLines(t *testing.T) { func TestHyprlandEmptyAndCommentLines(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "test.conf") configFile := filepath.Join(tmpDir, "test.conf")
@@ -385,9 +385,9 @@ bind = SUPER, T, exec, kitty
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(tmpDir) section, err := ParseHyprlandKeys(tmpDir)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseHyprlandKeys failed: %v", err)
} }
if len(section.Keybinds) != 2 { if len(section.Keybinds) != 2 {

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/keybinds" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
) )
type JSONFileProvider struct { type JSONFileProvider struct {

View File

@@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/keybinds" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/mangowc"
) )
type MangoWCProvider struct { type MangoWCProvider struct {
@@ -26,7 +25,7 @@ func (m *MangoWCProvider) Name() string {
} }
func (m *MangoWCProvider) GetCheatSheet() (*keybinds.CheatSheet, error) { func (m *MangoWCProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
keybinds_list, err := mangowc.ParseKeys(m.configPath) keybinds_list, err := ParseMangoWCKeys(m.configPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse mangowc config: %w", err) return nil, fmt.Errorf("failed to parse mangowc config: %w", err)
} }
@@ -83,7 +82,7 @@ func (m *MangoWCProvider) categorizeByCommand(command string) string {
} }
} }
func (m *MangoWCProvider) convertKeybind(kb *mangowc.KeyBinding) keybinds.Keybind { func (m *MangoWCProvider) convertKeybind(kb *MangoWCKeyBinding) keybinds.Keybind {
key := m.formatKey(kb) key := m.formatKey(kb)
desc := kb.Comment desc := kb.Comment
@@ -104,7 +103,7 @@ func (m *MangoWCProvider) generateDescription(command, params string) string {
return command return command
} }
func (m *MangoWCProvider) formatKey(kb *mangowc.KeyBinding) string { func (m *MangoWCProvider) formatKey(kb *MangoWCKeyBinding) string {
parts := make([]string, 0, len(kb.Mods)+1) parts := make([]string, 0, len(kb.Mods)+1)
parts = append(parts, kb.Mods...) parts = append(parts, kb.Mods...)
parts = append(parts, kb.Key) parts = append(parts, kb.Key)

View File

@@ -1,4 +1,4 @@
package mangowc package providers
import ( import (
"os" "os"
@@ -8,12 +8,12 @@ import (
) )
const ( const (
HideComment = "[hidden]" MangoWCHideComment = "[hidden]"
) )
var ModSeparators = []rune{'+', ' '} var MangoWCModSeparators = []rune{'+', ' '}
type KeyBinding struct { type MangoWCKeyBinding struct {
Mods []string `json:"mods"` Mods []string `json:"mods"`
Key string `json:"key"` Key string `json:"key"`
Command string `json:"command"` Command string `json:"command"`
@@ -21,19 +21,19 @@ type KeyBinding struct {
Comment string `json:"comment"` Comment string `json:"comment"`
} }
type Parser struct { type MangoWCParser struct {
contentLines []string contentLines []string
readingLine int readingLine int
} }
func NewParser() *Parser { func NewMangoWCParser() *MangoWCParser {
return &Parser{ return &MangoWCParser{
contentLines: []string{}, contentLines: []string{},
readingLine: 0, readingLine: 0,
} }
} }
func (p *Parser) ReadContent(path string) error { func (p *MangoWCParser) ReadContent(path string) error {
expandedPath := os.ExpandEnv(path) expandedPath := os.ExpandEnv(path)
expandedPath = filepath.Clean(expandedPath) expandedPath = filepath.Clean(expandedPath)
if strings.HasPrefix(expandedPath, "~") { if strings.HasPrefix(expandedPath, "~") {
@@ -82,7 +82,7 @@ func (p *Parser) ReadContent(path string) error {
return nil return nil
} }
func autogenerateComment(command, params string) string { func mangowcAutogenerateComment(command, params string) string {
switch command { switch command {
case "spawn", "spawn_shell": case "spawn", "spawn_shell":
return params return params
@@ -196,7 +196,7 @@ func autogenerateComment(command, params string) string {
} }
} }
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding { func (p *MangoWCParser) getKeybindAtLine(lineNumber int) *MangoWCKeyBinding {
if lineNumber >= len(p.contentLines) { if lineNumber >= len(p.contentLines) {
return nil return nil
} }
@@ -220,7 +220,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
comment = strings.TrimSpace(parts[1]) comment = strings.TrimSpace(parts[1])
} }
if strings.HasPrefix(comment, HideComment) { if strings.HasPrefix(comment, MangoWCHideComment) {
return nil return nil
} }
@@ -239,16 +239,16 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
} }
if comment == "" { if comment == "" {
comment = autogenerateComment(command, params) comment = mangowcAutogenerateComment(command, params)
} }
var modList []string var modList []string
if mods != "" && !strings.EqualFold(mods, "none") { if mods != "" && !strings.EqualFold(mods, "none") {
modstring := mods + string(ModSeparators[0]) modstring := mods + string(MangoWCModSeparators[0])
p := 0 p := 0
for index, char := range modstring { for index, char := range modstring {
isModSep := false isModSep := false
for _, sep := range ModSeparators { for _, sep := range MangoWCModSeparators {
if char == sep { if char == sep {
isModSep = true isModSep = true
break break
@@ -265,7 +265,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
_ = bindType _ = bindType
return &KeyBinding{ return &MangoWCKeyBinding{
Mods: modList, Mods: modList,
Key: key, Key: key,
Command: command, Command: command,
@@ -274,8 +274,8 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
} }
} }
func (p *Parser) ParseKeys() []KeyBinding { func (p *MangoWCParser) ParseKeys() []MangoWCKeyBinding {
var keybinds []KeyBinding var keybinds []MangoWCKeyBinding
for lineNumber := 0; lineNumber < len(p.contentLines); lineNumber++ { for lineNumber := 0; lineNumber < len(p.contentLines); lineNumber++ {
line := p.contentLines[lineNumber] line := p.contentLines[lineNumber]
@@ -296,8 +296,8 @@ func (p *Parser) ParseKeys() []KeyBinding {
return keybinds return keybinds
} }
func ParseKeys(path string) ([]KeyBinding, error) { func ParseMangoWCKeys(path string) ([]MangoWCKeyBinding, error) {
parser := NewParser() parser := NewMangoWCParser()
if err := parser.ReadContent(path); err != nil { if err := parser.ReadContent(path); err != nil {
return nil, err return nil, err
} }

View File

@@ -1,4 +1,4 @@
package mangowc package providers
import ( import (
"os" "os"
@@ -6,7 +6,7 @@ import (
"testing" "testing"
) )
func TestAutogenerateComment(t *testing.T) { func TestMangoWCAutogenerateComment(t *testing.T) {
tests := []struct { tests := []struct {
command string command string
params string params string
@@ -60,25 +60,25 @@ func TestAutogenerateComment(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.command+"_"+tt.params, func(t *testing.T) { t.Run(tt.command+"_"+tt.params, func(t *testing.T) {
result := autogenerateComment(tt.command, tt.params) result := mangowcAutogenerateComment(tt.command, tt.params)
if result != tt.expected { if result != tt.expected {
t.Errorf("autogenerateComment(%q, %q) = %q, want %q", t.Errorf("mangowcAutogenerateComment(%q, %q) = %q, want %q",
tt.command, tt.params, result, tt.expected) tt.command, tt.params, result, tt.expected)
} }
}) })
} }
} }
func TestGetKeybindAtLine(t *testing.T) { func TestMangoWCGetKeybindAtLine(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
line string line string
expected *KeyBinding expected *MangoWCKeyBinding
}{ }{
{ {
name: "basic_keybind", name: "basic_keybind",
line: "bind=ALT,q,killclient,", line: "bind=ALT,q,killclient,",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"ALT"}, Mods: []string{"ALT"},
Key: "q", Key: "q",
Command: "killclient", Command: "killclient",
@@ -89,7 +89,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_params", name: "keybind_with_params",
line: "bind=ALT,Left,focusdir,left", line: "bind=ALT,Left,focusdir,left",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"ALT"}, Mods: []string{"ALT"},
Key: "Left", Key: "Left",
Command: "focusdir", Command: "focusdir",
@@ -100,7 +100,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_comment", name: "keybind_with_comment",
line: "bind=Alt,t,spawn,kitty # Open terminal", line: "bind=Alt,t,spawn,kitty # Open terminal",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"Alt"}, Mods: []string{"Alt"},
Key: "t", Key: "t",
Command: "spawn", Command: "spawn",
@@ -116,7 +116,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_multiple_mods", name: "keybind_multiple_mods",
line: "bind=SUPER+SHIFT,Up,exchange_client,up", line: "bind=SUPER+SHIFT,Up,exchange_client,up",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"SUPER", "SHIFT"}, Mods: []string{"SUPER", "SHIFT"},
Key: "Up", Key: "Up",
Command: "exchange_client", Command: "exchange_client",
@@ -127,7 +127,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_no_mods", name: "keybind_no_mods",
line: "bind=NONE,Print,spawn,screenshot", line: "bind=NONE,Print,spawn,screenshot",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{}, Mods: []string{},
Key: "Print", Key: "Print",
Command: "spawn", Command: "spawn",
@@ -138,7 +138,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_multiple_params", name: "keybind_multiple_params",
line: "bind=Ctrl,1,view,1,0", line: "bind=Ctrl,1,view,1,0",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"Ctrl"}, Mods: []string{"Ctrl"},
Key: "1", Key: "1",
Command: "view", Command: "view",
@@ -149,7 +149,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "bindl_flag", name: "bindl_flag",
line: "bindl=SUPER+ALT,l,spawn,dms ipc call lock lock", line: "bindl=SUPER+ALT,l,spawn,dms ipc call lock lock",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"SUPER", "ALT"}, Mods: []string{"SUPER", "ALT"},
Key: "l", Key: "l",
Command: "spawn", Command: "spawn",
@@ -160,7 +160,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_spaces", name: "keybind_with_spaces",
line: "bind = SUPER, r, reload_config", line: "bind = SUPER, r, reload_config",
expected: &KeyBinding{ expected: &MangoWCKeyBinding{
Mods: []string{"SUPER"}, Mods: []string{"SUPER"},
Key: "r", Key: "r",
Command: "reload_config", Command: "reload_config",
@@ -172,7 +172,7 @@ func TestGetKeybindAtLine(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
parser := NewParser() parser := NewMangoWCParser()
parser.contentLines = []string{tt.line} parser.contentLines = []string{tt.line}
result := parser.getKeybindAtLine(0) result := parser.getKeybindAtLine(0)
@@ -213,7 +213,7 @@ func TestGetKeybindAtLine(t *testing.T) {
} }
} }
func TestParseKeys(t *testing.T) { func TestMangoWCParseKeys(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config.conf") configFile := filepath.Join(tmpDir, "config.conf")
@@ -242,9 +242,9 @@ bind=Ctrl,2,view,2,0
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
keybinds, err := ParseKeys(configFile) keybinds, err := ParseMangoWCKeys(configFile)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseMangoWCKeys failed: %v", err)
} }
expectedCount := 7 expectedCount := 7
@@ -267,7 +267,7 @@ bind=Ctrl,2,view,2,0
} }
} }
func TestReadContentMultipleFiles(t *testing.T) { func TestMangoWCReadContentMultipleFiles(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
file1 := filepath.Join(tmpDir, "a.conf") file1 := filepath.Join(tmpDir, "a.conf")
@@ -283,7 +283,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
t.Fatalf("Failed to write file2: %v", err) t.Fatalf("Failed to write file2: %v", err)
} }
parser := NewParser() parser := NewMangoWCParser()
if err := parser.ReadContent(tmpDir); err != nil { if err := parser.ReadContent(tmpDir); err != nil {
t.Fatalf("ReadContent failed: %v", err) t.Fatalf("ReadContent failed: %v", err)
} }
@@ -294,7 +294,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
} }
} }
func TestReadContentSingleFile(t *testing.T) { func TestMangoWCReadContentSingleFile(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config.conf") configFile := filepath.Join(tmpDir, "config.conf")
@@ -304,7 +304,7 @@ func TestReadContentSingleFile(t *testing.T) {
t.Fatalf("Failed to write config: %v", err) t.Fatalf("Failed to write config: %v", err)
} }
parser := NewParser() parser := NewMangoWCParser()
if err := parser.ReadContent(configFile); err != nil { if err := parser.ReadContent(configFile); err != nil {
t.Fatalf("ReadContent failed: %v", err) t.Fatalf("ReadContent failed: %v", err)
} }
@@ -315,7 +315,7 @@ func TestReadContentSingleFile(t *testing.T) {
} }
} }
func TestReadContentErrors(t *testing.T) { func TestMangoWCReadContentErrors(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
path string path string
@@ -332,7 +332,7 @@ func TestReadContentErrors(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := ParseKeys(tt.path) _, err := ParseMangoWCKeys(tt.path)
if err == nil { if err == nil {
t.Error("Expected error, got nil") t.Error("Expected error, got nil")
} }
@@ -340,7 +340,7 @@ func TestReadContentErrors(t *testing.T) {
} }
} }
func TestReadContentWithTildeExpansion(t *testing.T) { func TestMangoWCReadContentWithTildeExpansion(t *testing.T) {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
t.Skip("Cannot get home directory") t.Skip("Cannot get home directory")
@@ -362,7 +362,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
t.Skip("Cannot create relative path") t.Skip("Cannot create relative path")
} }
parser := NewParser() parser := NewMangoWCParser()
tildePathMatch := "~/" + relPath tildePathMatch := "~/" + relPath
err = parser.ReadContent(tildePathMatch) err = parser.ReadContent(tildePathMatch)
@@ -371,7 +371,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
} }
} }
func TestEmptyAndCommentLines(t *testing.T) { func TestMangoWCEmptyAndCommentLines(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config.conf") configFile := filepath.Join(tmpDir, "config.conf")
@@ -388,9 +388,9 @@ bind=Alt,t,spawn,kitty
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
keybinds, err := ParseKeys(configFile) keybinds, err := ParseMangoWCKeys(configFile)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseMangoWCKeys failed: %v", err)
} }
if len(keybinds) != 2 { if len(keybinds) != 2 {
@@ -398,7 +398,7 @@ bind=Alt,t,spawn,kitty
} }
} }
func TestInvalidBindLines(t *testing.T) { func TestMangoWCInvalidBindLines(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
line string line string
@@ -419,7 +419,7 @@ func TestInvalidBindLines(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
parser := NewParser() parser := NewMangoWCParser()
parser.contentLines = []string{tt.line} parser.contentLines = []string{tt.line}
result := parser.getKeybindAtLine(0) result := parser.getKeybindAtLine(0)
@@ -430,7 +430,7 @@ func TestInvalidBindLines(t *testing.T) {
} }
} }
func TestRealWorldConfig(t *testing.T) { func TestMangoWCRealWorldConfig(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config.conf") configFile := filepath.Join(tmpDir, "config.conf")
@@ -462,9 +462,9 @@ bind=Ctrl,3,view,3,0
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
keybinds, err := ParseKeys(configFile) keybinds, err := ParseMangoWCKeys(configFile)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseMangoWCKeys failed: %v", err)
} }
if len(keybinds) < 14 { if len(keybinds) < 14 {

View File

@@ -4,8 +4,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/mangowc"
) )
func TestMangoWCProviderName(t *testing.T) { func TestMangoWCProviderName(t *testing.T) {
@@ -88,12 +86,12 @@ func TestMangoWCCategorizeByCommand(t *testing.T) {
func TestMangoWCFormatKey(t *testing.T) { func TestMangoWCFormatKey(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
keybind *mangowc.KeyBinding keybind *MangoWCKeyBinding
expected string expected string
}{ }{
{ {
name: "single_mod", name: "single_mod",
keybind: &mangowc.KeyBinding{ keybind: &MangoWCKeyBinding{
Mods: []string{"ALT"}, Mods: []string{"ALT"},
Key: "q", Key: "q",
}, },
@@ -101,7 +99,7 @@ func TestMangoWCFormatKey(t *testing.T) {
}, },
{ {
name: "multiple_mods", name: "multiple_mods",
keybind: &mangowc.KeyBinding{ keybind: &MangoWCKeyBinding{
Mods: []string{"SUPER", "SHIFT"}, Mods: []string{"SUPER", "SHIFT"},
Key: "Up", Key: "Up",
}, },
@@ -109,7 +107,7 @@ func TestMangoWCFormatKey(t *testing.T) {
}, },
{ {
name: "no_mods", name: "no_mods",
keybind: &mangowc.KeyBinding{ keybind: &MangoWCKeyBinding{
Mods: []string{}, Mods: []string{},
Key: "Print", Key: "Print",
}, },
@@ -131,13 +129,13 @@ func TestMangoWCFormatKey(t *testing.T) {
func TestMangoWCConvertKeybind(t *testing.T) { func TestMangoWCConvertKeybind(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
keybind *mangowc.KeyBinding keybind *MangoWCKeyBinding
wantKey string wantKey string
wantDesc string wantDesc string
}{ }{
{ {
name: "with_comment", name: "with_comment",
keybind: &mangowc.KeyBinding{ keybind: &MangoWCKeyBinding{
Mods: []string{"ALT"}, Mods: []string{"ALT"},
Key: "t", Key: "t",
Command: "spawn", Command: "spawn",
@@ -149,7 +147,7 @@ func TestMangoWCConvertKeybind(t *testing.T) {
}, },
{ {
name: "without_comment", name: "without_comment",
keybind: &mangowc.KeyBinding{ keybind: &MangoWCKeyBinding{
Mods: []string{"SUPER"}, Mods: []string{"SUPER"},
Key: "r", Key: "r",
Command: "reload_config", Command: "reload_config",
@@ -161,7 +159,7 @@ func TestMangoWCConvertKeybind(t *testing.T) {
}, },
{ {
name: "with_params_no_comment", name: "with_params_no_comment",
keybind: &mangowc.KeyBinding{ keybind: &MangoWCKeyBinding{
Mods: []string{"CTRL"}, Mods: []string{"CTRL"},
Key: "1", Key: "1",
Command: "view", Command: "view",

View File

@@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/keybinds" "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/sway"
) )
type SwayProvider struct { type SwayProvider struct {
@@ -26,7 +25,7 @@ func (s *SwayProvider) Name() string {
} }
func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) { func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
section, err := sway.ParseKeys(s.configPath) section, err := ParseSwayKeys(s.configPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse sway config: %w", err) return nil, fmt.Errorf("failed to parse sway config: %w", err)
} }
@@ -41,7 +40,7 @@ func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
}, nil }, nil
} }
func (s *SwayProvider) convertSection(section *sway.Section, subcategory string, categorizedBinds map[string][]keybinds.Keybind) { func (s *SwayProvider) convertSection(section *SwaySection, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
currentSubcat := subcategory currentSubcat := subcategory
if section.Name != "" { if section.Name != "" {
currentSubcat = section.Name currentSubcat = section.Name
@@ -89,7 +88,7 @@ func (s *SwayProvider) categorizeByCommand(command string) string {
} }
} }
func (s *SwayProvider) convertKeybind(kb *sway.KeyBinding, subcategory string) keybinds.Keybind { func (s *SwayProvider) convertKeybind(kb *SwayKeyBinding, subcategory string) keybinds.Keybind {
key := s.formatKey(kb) key := s.formatKey(kb)
desc := kb.Comment desc := kb.Comment
@@ -104,7 +103,7 @@ func (s *SwayProvider) convertKeybind(kb *sway.KeyBinding, subcategory string) k
} }
} }
func (s *SwayProvider) formatKey(kb *sway.KeyBinding) string { func (s *SwayProvider) formatKey(kb *SwayKeyBinding) string {
parts := make([]string, 0, len(kb.Mods)+1) parts := make([]string, 0, len(kb.Mods)+1)
parts = append(parts, kb.Mods...) parts = append(parts, kb.Mods...)
parts = append(parts, kb.Key) parts = append(parts, kb.Key)

View File

@@ -1,4 +1,4 @@
package sway package providers
import ( import (
"os" "os"
@@ -8,40 +8,40 @@ import (
) )
const ( const (
TitleRegex = "#+!" SwayTitleRegex = "#+!"
HideComment = "[hidden]" SwayHideComment = "[hidden]"
) )
var ModSeparators = []rune{'+', ' '} var SwayModSeparators = []rune{'+', ' '}
type KeyBinding struct { type SwayKeyBinding struct {
Mods []string `json:"mods"` Mods []string `json:"mods"`
Key string `json:"key"` Key string `json:"key"`
Command string `json:"command"` Command string `json:"command"`
Comment string `json:"comment"` Comment string `json:"comment"`
} }
type Section struct { type SwaySection struct {
Children []Section `json:"children"` Children []SwaySection `json:"children"`
Keybinds []KeyBinding `json:"keybinds"` Keybinds []SwayKeyBinding `json:"keybinds"`
Name string `json:"name"` Name string `json:"name"`
} }
type Parser struct { type SwayParser struct {
contentLines []string contentLines []string
readingLine int readingLine int
variables map[string]string variables map[string]string
} }
func NewParser() *Parser { func NewSwayParser() *SwayParser {
return &Parser{ return &SwayParser{
contentLines: []string{}, contentLines: []string{},
readingLine: 0, readingLine: 0,
variables: make(map[string]string), variables: make(map[string]string),
} }
} }
func (p *Parser) ReadContent(path string) error { func (p *SwayParser) ReadContent(path string) error {
expandedPath := os.ExpandEnv(path) expandedPath := os.ExpandEnv(path)
expandedPath = filepath.Clean(expandedPath) expandedPath = filepath.Clean(expandedPath)
if strings.HasPrefix(expandedPath, "~") { if strings.HasPrefix(expandedPath, "~") {
@@ -88,7 +88,7 @@ func (p *Parser) ReadContent(path string) error {
return nil return nil
} }
func (p *Parser) parseVariables() { func (p *SwayParser) parseVariables() {
setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`) setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`)
for _, line := range p.contentLines { for _, line := range p.contentLines {
matches := setRegex.FindStringSubmatch(line) matches := setRegex.FindStringSubmatch(line)
@@ -100,7 +100,7 @@ func (p *Parser) parseVariables() {
} }
} }
func (p *Parser) expandVariables(text string) string { func (p *SwayParser) expandVariables(text string) string {
result := text result := text
for varName, varValue := range p.variables { for varName, varValue := range p.variables {
result = strings.ReplaceAll(result, "$"+varName, varValue) result = strings.ReplaceAll(result, "$"+varName, varValue)
@@ -108,7 +108,7 @@ func (p *Parser) expandVariables(text string) string {
return result return result
} }
func autogenerateComment(command string) string { func swayAutogenerateComment(command string) string {
command = strings.TrimSpace(command) command = strings.TrimSpace(command)
if strings.HasPrefix(command, "exec ") { if strings.HasPrefix(command, "exec ") {
@@ -200,7 +200,7 @@ func autogenerateComment(command string) string {
} }
} }
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding { func (p *SwayParser) getKeybindAtLine(lineNumber int) *SwayKeyBinding {
if lineNumber >= len(p.contentLines) { if lineNumber >= len(p.contentLines) {
return nil return nil
} }
@@ -223,7 +223,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
comment = strings.TrimSpace(parts[1]) comment = strings.TrimSpace(parts[1])
} }
if strings.HasPrefix(comment, HideComment) { if strings.HasPrefix(comment, SwayHideComment) {
return nil return nil
} }
@@ -249,11 +249,11 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
var modList []string var modList []string
var key string var key string
modstring := keyCombo + string(ModSeparators[0]) modstring := keyCombo + string(SwayModSeparators[0])
pos := 0 pos := 0
for index, char := range modstring { for index, char := range modstring {
isModSep := false isModSep := false
for _, sep := range ModSeparators { for _, sep := range SwayModSeparators {
if char == sep { if char == sep {
isModSep = true isModSep = true
break break
@@ -262,7 +262,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
if isModSep { if isModSep {
if index-pos > 0 { if index-pos > 0 {
part := modstring[pos:index] part := modstring[pos:index]
if isMod(part) { if swayIsMod(part) {
modList = append(modList, part) modList = append(modList, part)
} else { } else {
key = part key = part
@@ -273,12 +273,12 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
} }
if comment == "" { if comment == "" {
comment = autogenerateComment(command) comment = swayAutogenerateComment(command)
} }
_ = flags _ = flags
return &KeyBinding{ return &SwayKeyBinding{
Mods: modList, Mods: modList,
Key: key, Key: key,
Command: command, Command: command,
@@ -286,7 +286,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
} }
} }
func isMod(s string) bool { func swayIsMod(s string) bool {
s = strings.ToLower(s) s = strings.ToLower(s)
if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" || if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" ||
s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" || s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" ||
@@ -307,8 +307,8 @@ func isMod(s string) bool {
return false return false
} }
func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section { func (p *SwayParser) getBindsRecursive(currentContent *SwaySection, scope int) *SwaySection {
titleRegex := regexp.MustCompile(TitleRegex) titleRegex := regexp.MustCompile(SwayTitleRegex)
for p.readingLine < len(p.contentLines) { for p.readingLine < len(p.contentLines) {
line := p.contentLines[p.readingLine] line := p.contentLines[p.readingLine]
@@ -325,9 +325,9 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
sectionName := strings.TrimSpace(line[headingScope+1:]) sectionName := strings.TrimSpace(line[headingScope+1:])
p.readingLine++ p.readingLine++
childSection := &Section{ childSection := &SwaySection{
Children: []Section{}, Children: []SwaySection{},
Keybinds: []KeyBinding{}, Keybinds: []SwayKeyBinding{},
Name: sectionName, Name: sectionName,
} }
result := p.getBindsRecursive(childSection, headingScope) result := p.getBindsRecursive(childSection, headingScope)
@@ -348,18 +348,18 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
return currentContent return currentContent
} }
func (p *Parser) ParseKeys() *Section { func (p *SwayParser) ParseKeys() *SwaySection {
p.readingLine = 0 p.readingLine = 0
rootSection := &Section{ rootSection := &SwaySection{
Children: []Section{}, Children: []SwaySection{},
Keybinds: []KeyBinding{}, Keybinds: []SwayKeyBinding{},
Name: "", Name: "",
} }
return p.getBindsRecursive(rootSection, 0) return p.getBindsRecursive(rootSection, 0)
} }
func ParseKeys(path string) (*Section, error) { func ParseSwayKeys(path string) (*SwaySection, error) {
parser := NewParser() parser := NewSwayParser()
if err := parser.ReadContent(path); err != nil { if err := parser.ReadContent(path); err != nil {
return nil, err return nil, err
} }

View File

@@ -1,4 +1,4 @@
package sway package providers
import ( import (
"os" "os"
@@ -6,7 +6,7 @@ import (
"testing" "testing"
) )
func TestAutogenerateComment(t *testing.T) { func TestSwayAutogenerateComment(t *testing.T) {
tests := []struct { tests := []struct {
command string command string
expected string expected string
@@ -46,25 +46,25 @@ func TestAutogenerateComment(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.command, func(t *testing.T) { t.Run(tt.command, func(t *testing.T) {
result := autogenerateComment(tt.command) result := swayAutogenerateComment(tt.command)
if result != tt.expected { if result != tt.expected {
t.Errorf("autogenerateComment(%q) = %q, want %q", t.Errorf("swayAutogenerateComment(%q) = %q, want %q",
tt.command, result, tt.expected) tt.command, result, tt.expected)
} }
}) })
} }
} }
func TestGetKeybindAtLine(t *testing.T) { func TestSwayGetKeybindAtLine(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
line string line string
expected *KeyBinding expected *SwayKeyBinding
}{ }{
{ {
name: "basic_keybind", name: "basic_keybind",
line: "bindsym Mod4+q kill", line: "bindsym Mod4+q kill",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "q", Key: "q",
Command: "kill", Command: "kill",
@@ -74,7 +74,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_exec", name: "keybind_with_exec",
line: "bindsym Mod4+t exec kitty", line: "bindsym Mod4+t exec kitty",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "t", Key: "t",
Command: "exec kitty", Command: "exec kitty",
@@ -84,7 +84,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_comment", name: "keybind_with_comment",
line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher", line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "Space", Key: "Space",
Command: "exec dms ipc call spotlight toggle", Command: "exec dms ipc call spotlight toggle",
@@ -99,7 +99,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_multiple_mods", name: "keybind_multiple_mods",
line: "bindsym Mod4+Shift+e exit", line: "bindsym Mod4+Shift+e exit",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4", "Shift"}, Mods: []string{"Mod4", "Shift"},
Key: "e", Key: "e",
Command: "exit", Command: "exit",
@@ -109,7 +109,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_no_mods", name: "keybind_no_mods",
line: "bindsym Print exec grim screenshot.png", line: "bindsym Print exec grim screenshot.png",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{}, Mods: []string{},
Key: "Print", Key: "Print",
Command: "exec grim screenshot.png", Command: "exec grim screenshot.png",
@@ -119,7 +119,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_with_flags", name: "keybind_with_flags",
line: "bindsym --release Mod4+x exec notify-send released", line: "bindsym --release Mod4+x exec notify-send released",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "x", Key: "x",
Command: "exec notify-send released", Command: "exec notify-send released",
@@ -129,7 +129,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_focus_direction", name: "keybind_focus_direction",
line: "bindsym Mod4+Left focus left", line: "bindsym Mod4+Left focus left",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "Left", Key: "Left",
Command: "focus left", Command: "focus left",
@@ -139,7 +139,7 @@ func TestGetKeybindAtLine(t *testing.T) {
{ {
name: "keybind_workspace", name: "keybind_workspace",
line: "bindsym Mod4+1 workspace number 1", line: "bindsym Mod4+1 workspace number 1",
expected: &KeyBinding{ expected: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "1", Key: "1",
Command: "workspace number 1", Command: "workspace number 1",
@@ -150,7 +150,7 @@ func TestGetKeybindAtLine(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
parser := NewParser() parser := NewSwayParser()
parser.contentLines = []string{tt.line} parser.contentLines = []string{tt.line}
result := parser.getKeybindAtLine(0) result := parser.getKeybindAtLine(0)
@@ -188,7 +188,7 @@ func TestGetKeybindAtLine(t *testing.T) {
} }
} }
func TestVariableExpansion(t *testing.T) { func TestSwayVariableExpansion(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config") configFile := filepath.Join(tmpDir, "config")
@@ -204,9 +204,9 @@ bindsym $mod+d exec $menu
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(configFile) section, err := ParseSwayKeys(configFile)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseSwayKeys failed: %v", err)
} }
if len(section.Keybinds) != 2 { if len(section.Keybinds) != 2 {
@@ -229,7 +229,7 @@ bindsym $mod+d exec $menu
} }
} }
func TestParseKeysWithSections(t *testing.T) { func TestSwayParseKeysWithSections(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config") configFile := filepath.Join(tmpDir, "config")
@@ -251,9 +251,9 @@ bindsym $mod+t exec kitty # Terminal
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(tmpDir) section, err := ParseSwayKeys(tmpDir)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseSwayKeys failed: %v", err)
} }
if len(section.Children) != 2 { if len(section.Children) != 2 {
@@ -296,7 +296,7 @@ bindsym $mod+t exec kitty # Terminal
} }
} }
func TestReadContentErrors(t *testing.T) { func TestSwayReadContentErrors(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
path string path string
@@ -313,7 +313,7 @@ func TestReadContentErrors(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := ParseKeys(tt.path) _, err := ParseSwayKeys(tt.path)
if err == nil { if err == nil {
t.Error("Expected error, got nil") t.Error("Expected error, got nil")
} }
@@ -321,7 +321,7 @@ func TestReadContentErrors(t *testing.T) {
} }
} }
func TestReadContentWithTildeExpansion(t *testing.T) { func TestSwayReadContentWithTildeExpansion(t *testing.T) {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
t.Skip("Cannot get home directory") t.Skip("Cannot get home directory")
@@ -343,7 +343,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
t.Skip("Cannot create relative path") t.Skip("Cannot create relative path")
} }
parser := NewParser() parser := NewSwayParser()
tildePathMatch := "~/" + relPath tildePathMatch := "~/" + relPath
err = parser.ReadContent(tildePathMatch) err = parser.ReadContent(tildePathMatch)
@@ -352,7 +352,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
} }
} }
func TestEmptyAndCommentLines(t *testing.T) { func TestSwayEmptyAndCommentLines(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config") configFile := filepath.Join(tmpDir, "config")
@@ -369,9 +369,9 @@ bindsym Mod4+t exec kitty
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(configFile) section, err := ParseSwayKeys(configFile)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseSwayKeys failed: %v", err)
} }
if len(section.Keybinds) != 2 { if len(section.Keybinds) != 2 {
@@ -379,7 +379,7 @@ bindsym Mod4+t exec kitty
} }
} }
func TestRealWorldConfig(t *testing.T) { func TestSwayRealWorldConfig(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config") configFile := filepath.Join(tmpDir, "config")
@@ -408,9 +408,9 @@ bindsym $mod+Shift+1 move container to workspace number 1
t.Fatalf("Failed to write test config: %v", err) t.Fatalf("Failed to write test config: %v", err)
} }
section, err := ParseKeys(configFile) section, err := ParseSwayKeys(configFile)
if err != nil { if err != nil {
t.Fatalf("ParseKeys failed: %v", err) t.Fatalf("ParseSwayKeys failed: %v", err)
} }
if len(section.Keybinds) < 9 { if len(section.Keybinds) < 9 {
@@ -444,7 +444,7 @@ bindsym $mod+Shift+1 move container to workspace number 1
} }
} }
func TestIsMod(t *testing.T) { func TestSwayIsMod(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expected bool expected bool
@@ -462,9 +462,9 @@ func TestIsMod(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) { t.Run(tt.input, func(t *testing.T) {
result := isMod(tt.input) result := swayIsMod(tt.input)
if result != tt.expected { if result != tt.expected {
t.Errorf("isMod(%q) = %v, want %v", tt.input, result, tt.expected) t.Errorf("swayIsMod(%q) = %v, want %v", tt.input, result, tt.expected)
} }
}) })
} }

View File

@@ -4,8 +4,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/sway"
) )
func TestSwayProviderName(t *testing.T) { func TestSwayProviderName(t *testing.T) {
@@ -76,12 +74,12 @@ func TestSwayCategorizeByCommand(t *testing.T) {
func TestSwayFormatKey(t *testing.T) { func TestSwayFormatKey(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
keybind *sway.KeyBinding keybind *SwayKeyBinding
expected string expected string
}{ }{
{ {
name: "single_mod", name: "single_mod",
keybind: &sway.KeyBinding{ keybind: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "q", Key: "q",
}, },
@@ -89,7 +87,7 @@ func TestSwayFormatKey(t *testing.T) {
}, },
{ {
name: "multiple_mods", name: "multiple_mods",
keybind: &sway.KeyBinding{ keybind: &SwayKeyBinding{
Mods: []string{"Mod4", "Shift"}, Mods: []string{"Mod4", "Shift"},
Key: "e", Key: "e",
}, },
@@ -97,7 +95,7 @@ func TestSwayFormatKey(t *testing.T) {
}, },
{ {
name: "no_mods", name: "no_mods",
keybind: &sway.KeyBinding{ keybind: &SwayKeyBinding{
Mods: []string{}, Mods: []string{},
Key: "Print", Key: "Print",
}, },
@@ -119,13 +117,13 @@ func TestSwayFormatKey(t *testing.T) {
func TestSwayConvertKeybind(t *testing.T) { func TestSwayConvertKeybind(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
keybind *sway.KeyBinding keybind *SwayKeyBinding
wantKey string wantKey string
wantDesc string wantDesc string
}{ }{
{ {
name: "with_comment", name: "with_comment",
keybind: &sway.KeyBinding{ keybind: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "t", Key: "t",
Command: "exec kitty", Command: "exec kitty",
@@ -136,7 +134,7 @@ func TestSwayConvertKeybind(t *testing.T) {
}, },
{ {
name: "without_comment", name: "without_comment",
keybind: &sway.KeyBinding{ keybind: &SwayKeyBinding{
Mods: []string{"Mod4"}, Mods: []string{"Mod4"},
Key: "r", Key: "r",
Command: "reload", Command: "reload",

View File

@@ -1,4 +1,4 @@
package logger package log
import ( import (
"bufio" "bufio"

Some files were not shown because too many files have changed in this diff Show More