mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2026-04-06 16:01:41 +02:00
Compare commits
No commits in common. "48493c6193462a92fe6e11e8d5b5582d8a0e8543" and "2c68b4a58c1ab1400068a6267deb835bd1f723f2" have entirely different histories.
48493c6193
...
2c68b4a58c
165 changed files with 6070 additions and 13783 deletions
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -2,38 +2,37 @@
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Report an issue with the bar not working as expected
|
about: Report an issue with the bar not working as expected
|
||||||
title: ''
|
title: ''
|
||||||
labels: T:Bug
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
> A clear and concise description of what the bug is.
|
||||||
|
|
||||||
**To reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
1. Add module `clock`
|
2. Click on '....'
|
||||||
2. Click popup
|
3. Scroll down to '....'
|
||||||
3. Observe X is broken
|
4. See error
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
> A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**System information:**
|
**System information:**
|
||||||
- Distro: [e.g. Arch Linux, Ubuntu 22.10]
|
- Distro: [e.g. Arch Linux, Ubuntu 22.10]
|
||||||
- Compositor: [e.g. Sway]
|
- Compositor: [e.g. Sway]
|
||||||
- Ironbar version: [e.g. 0.16.1]
|
- Ironbar version: [e.g. 0.8.0]
|
||||||
|
|
||||||
**Configuration**
|
**Configuration**
|
||||||
<!-- Share your bar configuration and stylesheet as applicable: -->
|
> Share your bar configuration and stylesheet as applicable:
|
||||||
|
|
||||||
<details><summary>Config</summary>
|
<details><summary>Config</summary>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,11 +41,10 @@ Steps to reproduce the behavior:
|
||||||
```css
|
```css
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
<!-- Add any other context about the problem here. -->
|
> Add any other context about the problem here.
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
> If applicable, add screenshots to help explain your problem.
|
||||||
|
|
|
||||||
2
.github/scripts/ubuntu_setup.sh
vendored
2
.github/scripts/ubuntu_setup.sh
vendored
|
|
@ -17,7 +17,5 @@ $SUDO apt-get update && $SUDO apt-get install --assume-yes \
|
||||||
libssl-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libssl-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libgtk-3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libgtk-3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libgtk-layer-shell-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libgtk-layer-shell-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libinput-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
|
||||||
libdbusmenu-gtk3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
|
||||||
libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libluajit-5.1-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH}
|
libluajit-5.1-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH}
|
||||||
|
|
|
||||||
82
.github/workflows/build.yml
vendored
82
.github/workflows/build.yml
vendored
|
|
@ -4,17 +4,8 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
paths:
|
|
||||||
- 'src/**/*'
|
|
||||||
- 'Cargo.*'
|
|
||||||
- 'build.rs'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
paths:
|
|
||||||
- 'src/**/*'
|
|
||||||
- 'Cargo.*'
|
|
||||||
- 'build.rs'
|
|
||||||
- '.github/workflows/build.yml'
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
@ -33,7 +24,6 @@ jobs:
|
||||||
|
|
||||||
clippy-base:
|
clippy-base:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
name: 'Clippy (Base features)'
|
name: 'Clippy (Base features)'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -41,6 +31,9 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
name: Cache dependencies
|
name: Cache dependencies
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: ./.github/scripts/ubuntu_setup.sh
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --no-default-features --features config+json
|
run: cargo clippy --no-default-features --features config+json
|
||||||
env:
|
env:
|
||||||
|
|
@ -50,7 +43,6 @@ jobs:
|
||||||
|
|
||||||
clippy-all:
|
clippy-all:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
name: 'Clippy (All features)'
|
name: 'Clippy (All features)'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -58,16 +50,16 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
name: Cache dependencies
|
name: Cache dependencies
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: ./.github/scripts/ubuntu_setup.sh
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --all-targets --all-features
|
run: cargo clippy --all-targets --all-features
|
||||||
env:
|
|
||||||
RUSTFLAGS: '-W clippy::unwrap_used'
|
|
||||||
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: 'Build & Test'
|
name: 'Build & Test'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -75,6 +67,9 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
name: Cache dependencies
|
name: Cache dependencies
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: ./.github/scripts/ubuntu_setup.sh
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
|
|
||||||
|
|
@ -82,62 +77,3 @@ jobs:
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
|
|
||||||
feature-checks:
|
|
||||||
name: 'Check feature flag'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
feature:
|
|
||||||
- http
|
|
||||||
- ipc
|
|
||||||
- cli
|
|
||||||
- config+all
|
|
||||||
- config+json
|
|
||||||
- config+yaml
|
|
||||||
- config+toml
|
|
||||||
- config+corn
|
|
||||||
- config+ron
|
|
||||||
- bindmode+all
|
|
||||||
- bindmode+sway
|
|
||||||
- bindmode+hyprland
|
|
||||||
- cairo
|
|
||||||
- clipboard
|
|
||||||
- clock
|
|
||||||
- custom
|
|
||||||
- focused
|
|
||||||
- keyboard+all
|
|
||||||
- keyboard+sway
|
|
||||||
- keyboard+hyprland
|
|
||||||
- label
|
|
||||||
- launcher
|
|
||||||
- menu
|
|
||||||
- music+all
|
|
||||||
- music+mpris
|
|
||||||
- music+mpd
|
|
||||||
- network_manager
|
|
||||||
- notifications
|
|
||||||
- sys_info
|
|
||||||
- script
|
|
||||||
- tray
|
|
||||||
- upower
|
|
||||||
- volume
|
|
||||||
- workspaces+all
|
|
||||||
- workspaces+sway
|
|
||||||
- workspaces+hyprland
|
|
||||||
- workspaces+niri
|
|
||||||
- schema
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
name: Cache dependencies
|
|
||||||
|
|
||||||
- name: Check
|
|
||||||
run: cargo check --no-default-features --features ${{ matrix.feature }}
|
|
||||||
env:
|
|
||||||
# Allow some warnings through as we'll never get it perfect
|
|
||||||
RUSTFLAGS: '-A unused-imports -A unused-variables -A unused-mut -A dead-code'
|
|
||||||
11
.github/workflows/deploy.yml
vendored
11
.github/workflows/deploy.yml
vendored
|
|
@ -41,7 +41,6 @@ jobs:
|
||||||
publish-crate:
|
publish-crate:
|
||||||
name: 'Publish Crate'
|
name: 'Publish Crate'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
@ -51,6 +50,9 @@ jobs:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: ./.github/scripts/ubuntu_setup.sh
|
||||||
|
|
||||||
- name: Publish crate
|
- name: Publish crate
|
||||||
uses: katyo/publish-crates@v1
|
uses: katyo/publish-crates@v1
|
||||||
with:
|
with:
|
||||||
|
|
@ -60,7 +62,6 @@ jobs:
|
||||||
publish-schema:
|
publish-schema:
|
||||||
name: 'Publish Schema'
|
name: 'Publish Schema'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
@ -68,11 +69,11 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
name: Cache dependencies
|
name: Cache dependencies
|
||||||
|
|
||||||
- name: Ensure target folder
|
- name: Install build deps
|
||||||
run: mkdir -p target
|
run: ./.github/scripts/ubuntu_setup.sh
|
||||||
|
|
||||||
- name: Build schema
|
- name: Build schema
|
||||||
run: cargo run --features schema -- --print-schema > target/schema-${{ github.ref_name }}.json
|
run: cargo build --features schema -- --print-schema > target/schema-${{ github.ref_name }}.json
|
||||||
|
|
||||||
- name: Copy file via SSH
|
- name: Copy file via SSH
|
||||||
uses: appleboy/scp-action@v0.1.7
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
|
|
||||||
105
.github/workflows/docker.yml
vendored
105
.github/workflows/docker.yml
vendored
|
|
@ -1,105 +0,0 @@
|
||||||
name: Docker
|
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '32 23 * * *'
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
paths:
|
|
||||||
- '.github/scripts/ubuntu_setup.sh'
|
|
||||||
- 'Dockerfile'
|
|
||||||
# Publish semver tags as releases.
|
|
||||||
tags: [ 'v*.*.*' ]
|
|
||||||
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
paths:
|
|
||||||
- '.github/scripts/ubuntu_setup.sh'
|
|
||||||
- 'Dockerfile'
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Use docker.io for Docker Hub if empty
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
# github.repository as <account>/<repo>
|
|
||||||
IMAGE_NAME: ${{ github.repository }}-build
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
# This is used to complete the identity challenge
|
|
||||||
# with sigstore/fulcio when running outside of PRs.
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# Install the cosign tool except on PR
|
|
||||||
# https://github.com/sigstore/cosign-installer
|
|
||||||
- name: Install cosign
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
|
|
||||||
with:
|
|
||||||
cosign-release: 'v2.2.4'
|
|
||||||
|
|
||||||
# Set up BuildKit Docker container builder to be able to build
|
|
||||||
# multi-platform images and export cache
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
|
||||||
# https://github.com/docker/login-action
|
|
||||||
- name: Log into registry ${{ env.REGISTRY }}
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Extract metadata (tags, labels) for Docker
|
|
||||||
# https://github.com/docker/metadata-action
|
|
||||||
- name: Extract Docker metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
# Build and push Docker image with Buildx (don't push on PR)
|
|
||||||
# https://github.com/docker/build-push-action
|
|
||||||
- name: Build and push Docker image
|
|
||||||
id: build-and-push
|
|
||||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
# Sign the resulting Docker image digest except on PRs.
|
|
||||||
# This will only write to the public Rekor transparency log when the Docker
|
|
||||||
# repository is public to avoid leaking data. If you would like to publish
|
|
||||||
# transparency data even for private images, pass --force to cosign below.
|
|
||||||
# https://github.com/sigstore/cosign
|
|
||||||
- name: Sign the published Docker image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
env:
|
|
||||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
|
|
||||||
TAGS: ${{ steps.meta.outputs.tags }}
|
|
||||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
|
||||||
# This step uses the identity token to provision an ephemeral certificate
|
|
||||||
# against the sigstore community Fulcio instance.
|
|
||||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
|
||||||
9
.github/workflows/schema.yml
vendored
9
.github/workflows/schema.yml
vendored
|
|
@ -4,11 +4,6 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
paths:
|
|
||||||
- 'src/**/*'
|
|
||||||
- 'Cargo.*'
|
|
||||||
- 'build.rs'
|
|
||||||
- '.github/workflows/schema.yml'
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
@ -18,7 +13,6 @@ jobs:
|
||||||
publish-schema:
|
publish-schema:
|
||||||
name: 'Publish Schema'
|
name: 'Publish Schema'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/jakestanger/ironbar-build:master
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
@ -26,6 +20,9 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
name: Cache dependencies
|
name: Cache dependencies
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: ./.github/scripts/ubuntu_setup.sh
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --features schema
|
run: cargo build --features schema
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/wiki.yml
vendored
2
.github/workflows/wiki.yml
vendored
|
|
@ -3,8 +3,6 @@ name: Sync Wiki
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
paths:
|
|
||||||
- 'docs/**/*'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
|
||||||
6
.idea/git_toolbox_blame.xml
generated
6
.idea/git_toolbox_blame.xml
generated
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GitToolBoxBlameSettings">
|
|
||||||
<option name="version" value="2" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
2
.idea/runConfigurations/Run__Live_Config_.xml
generated
2
.idea/runConfigurations/Run__Live_Config_.xml
generated
|
|
@ -11,7 +11,7 @@
|
||||||
<option name="backtrace" value="SHORT" />
|
<option name="backtrace" value="SHORT" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
||||||
<env name="IRONBAR_LOG" value="debug" />
|
<env name="RUST_LOG" value="debug" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="isRedirectInput" value="false" />
|
<option name="isRedirectInput" value="false" />
|
||||||
<option name="redirectInputPath" value="" />
|
<option name="redirectInputPath" value="" />
|
||||||
|
|
|
||||||
20
.idea/runConfigurations/Test.xml
generated
20
.idea/runConfigurations/Test.xml
generated
|
|
@ -1,20 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Test" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
|
||||||
<option name="buildProfileId" value="test" />
|
|
||||||
<option name="command" value="test" />
|
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
|
||||||
<envs />
|
|
||||||
<option name="emulateTerminal" value="true" />
|
|
||||||
<option name="channel" value="DEFAULT" />
|
|
||||||
<option name="requiredFeatures" value="true" />
|
|
||||||
<option name="allFeatures" value="false" />
|
|
||||||
<option name="withSudo" value="false" />
|
|
||||||
<option name="buildTarget" value="REMOTE" />
|
|
||||||
<option name="backtrace" value="SHORT" />
|
|
||||||
<option name="isRedirectInput" value="false" />
|
|
||||||
<option name="redirectInputPath" value="" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
112
CHANGELOG.md
112
CHANGELOG.md
|
|
@ -4,116 +4,6 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [v0.16.1] - 2024-11-24
|
|
||||||
### :boom: BREAKING CHANGES
|
|
||||||
- due to [`e4e9632`](https://github.com/JakeStanger/ironbar/commit/e4e9632caab66f6a8627ffb03b2f82cd5404003f) - menu causing bar to lose focus on sway *(commit by [@JakeStanger](https://github.com/JakeStanger))*:
|
|
||||||
|
|
||||||
The `direction` option has been changed to only accept `horizontal` or `vertical`
|
|
||||||
|
|
||||||
|
|
||||||
### :sparkles: New Features
|
|
||||||
- [`662ddb6`](https://github.com/JakeStanger/ironbar/commit/662ddb69464ab45546231d337f7f4f3e5efcdc98) - **tray**: image support in menu items *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`e1f3b1b`](https://github.com/JakeStanger/ironbar/commit/e1f3b1bb72f3aa6562f7c7d98a0ef8d131e86600) - route gtk logging through tracing *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
### :bug: Bug Fixes
|
|
||||||
- [`82a6660`](https://github.com/JakeStanger/ironbar/commit/82a6660c8568fc6fe7661a7703173c0c8cd93085) - **workspaces**: incorrectly checking focus using name_map value *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`2aa55d8`](https://github.com/JakeStanger/ironbar/commit/2aa55d8d66c69ed02089811af4f3f2eaee11f2ee) - **popup**: incorrect pos when resolution changes *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`d8b68fd`](https://github.com/JakeStanger/ironbar/commit/d8b68fd378b4ece7260511386115b363ed8eec2e) - **launcher**: showing xwayland menus and tooltips *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`e08027f`](https://github.com/JakeStanger/ironbar/commit/e08027fe6992756f8cb4800ec19782024543b19b) - **workspaces**: prevent crash when clicking current workspace *(PR [#733](https://github.com/JakeStanger/ironbar/pull/733) by [@Leshuguita](https://github.com/Leshuguita))*
|
|
||||||
- [`b2db7b0`](https://github.com/JakeStanger/ironbar/commit/b2db7b0bb546f0fc4b642a83cbe6303213480723) - markup escape issues *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`d87888d`](https://github.com/JakeStanger/ironbar/commit/d87888d173b2737bc5a3ae5ff4ae192cca2a87c7) - `on_scroll` events broken on touchpad *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`80403e3`](https://github.com/JakeStanger/ironbar/commit/80403e3ca9b1261d8374baf29d6971236494929c) - not properly redrawing on style reload *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`81c48fe`](https://github.com/JakeStanger/ironbar/commit/81c48fecadb629a0f915d58f59c0523ab54af162) - **clipboard**: crash when unsupported image type *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`e53a906`](https://github.com/JakeStanger/ironbar/commit/e53a9067b03de002a07f85e5302e3774e53521f2) - **tray**: cannot activate with mixed left/right click *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`55c0940`](https://github.com/JakeStanger/ironbar/commit/55c0940e1dc90069018e40254a2b079b31bc2da2) - **tray**: update `system-tray` dep to bring in a whole load of fixes *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`fb17995`](https://github.com/JakeStanger/ironbar/commit/fb1799531b4638f8cd3a36ccb94425643aaa6082) - **tray**: image updates lag 1 behind *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`05530cf`](https://github.com/JakeStanger/ironbar/commit/05530cf7769a47a49a185d228afaa934d6df7575) - regression caused by [#652](https://github.com/JakeStanger/ironbar/pull/652) *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`e4e9632`](https://github.com/JakeStanger/ironbar/commit/e4e9632caab66f6a8627ffb03b2f82cd5404003f) - **tray**: menu causing bar to lose focus on sway *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`42e25f5`](https://github.com/JakeStanger/ironbar/commit/42e25f5ef2ce9886d8fafb42aff9ced7ef183726) - **ipc**: support querying against duplicate bar names *(commit by [@zeroeightysix](https://github.com/zeroeightysix))*
|
|
||||||
- [`5aa9f37`](https://github.com/JakeStanger/ironbar/commit/5aa9f37fe4ff76d9ef61e8c0aacc110ecb9a89c3) - **tray**: menus not attaching to secondary bars *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`f364bb6`](https://github.com/JakeStanger/ironbar/commit/f364bb64fbfaef31a55bdc3c9e4cbb6f90fcdab5) - **tray**: tray icons not disappearing on close *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`ce48fc9`](https://github.com/JakeStanger/ironbar/commit/ce48fc9d0d8e603e69f01e58f3a717fc845888f7) - **tray**: prevent widget buttons from piling up *(PR [#788](https://github.com/JakeStanger/ironbar/pull/788) by [@cmeissl](https://github.com/cmeissl))*
|
|
||||||
- [`230dd8b`](https://github.com/JakeStanger/ironbar/commit/230dd8b13b024eb039613c579f940802ea5857e5) - **workspaces**: clicking currently focused workspace attempts to focus it *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
### :recycle: Refactors
|
|
||||||
- [`04f45cc`](https://github.com/JakeStanger/ironbar/commit/04f45ccae1498630a81edd34923c5920e864bacc) - fix some pedantic clippy warnings *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`486beff`](https://github.com/JakeStanger/ironbar/commit/486beff8a550e0bc757e79ea37c450cab3a810eb) - put in basic placeholders for menu icon diffs *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`cf38c37`](https://github.com/JakeStanger/ironbar/commit/cf38c37fe3223280bf89e50b6869e1912a8ed8bf) - **tray**: move some debug logging to trace logging *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`30de23d`](https://github.com/JakeStanger/ironbar/commit/30de23dc6487635c0e71edfb7a8780bea7ae23e1) - **tray**: switch over to `libdbusmenu-gtk3` *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
### :memo: Documentation Changes
|
|
||||||
- [`56153f1`](https://github.com/JakeStanger/ironbar/commit/56153f189a6496d01cbc42cdc9086d52d69a235a) - **dynamic values**: fix missing backtick *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`ff3f541`](https://github.com/JakeStanger/ironbar/commit/ff3f541183260c786d28e5c9e0a01af0f45408a9) - **tray**: fix formatting *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`f161429`](https://github.com/JakeStanger/ironbar/commit/f161429dfc4e3faa815a00ee9f9afe01ccc959cc) - **clock**: align table columns *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
### Note to maintainers
|
|
||||||
|
|
||||||
The tray module now depends on `libdbusmenu-gtk3` , which must be present at build time and runtime.
|
|
||||||
|
|
||||||
## [v0.16.0] - 2024-08-10
|
|
||||||
### :boom: BREAKING CHANGES
|
|
||||||
- due to [`9dd7112`](https://github.com/JakeStanger/ironbar/commit/9dd711235f21d9016fec240f1be5c8d6de1596df) - improve CLI structure, add new commands *(commit by [@JakeStanger](https://github.com/JakeStanger))*:
|
|
||||||
|
|
||||||
- `ok_value` responses will no longer print `ok` as the first line when using the CLI
|
|
||||||
- All IPC commands have changed. Namely, `type` has been changed to `command`, and bar/var related commands are now under a `subcommand`. The full spec can be found on the wiki.
|
|
||||||
- Several CLI commands are now located under the `var` and `bar` categories. Usage of any commands to get/set Ironvars or control bar visibility will need to be updated.
|
|
||||||
- The `open_popup` and `close_popup` IPC commands are now called `show_popup` and `hide_popup` respectively.
|
|
||||||
- The popup `name` argument has been renamed to `widget_name` on all IPC commands.
|
|
||||||
- The `set-visibility` CLI command now takes a `true`/`false` positional argument in place of the `-v` flag.
|
|
||||||
|
|
||||||
|
|
||||||
### :sparkles: New Features
|
|
||||||
- [`f11da3e`](https://github.com/JakeStanger/ironbar/commit/f11da3eca1b7d1bc5e1904266f285f0e28f290a0) - **music**: pango markup support *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`951576c`](https://github.com/JakeStanger/ironbar/commit/951576ce3c092d187fd6d1d2ff55b7dbf6198a25) - pango markup support in image icons *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`36d724f`](https://github.com/JakeStanger/ironbar/commit/36d724f148ed8ebe84cbb3c3e25cd4a361d94e66) - **config**: json schema support *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`7413f78`](https://github.com/JakeStanger/ironbar/commit/7413f78e04fe9b532397144e49b7545547980723) - **cli**: debug flag *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`a33e0a2`](https://github.com/JakeStanger/ironbar/commit/a33e0a241a8d5f65f7360b5c7e34a116f3f9f092) - **cli**: format flag, json output format *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`9dd7112`](https://github.com/JakeStanger/ironbar/commit/9dd711235f21d9016fec240f1be5c8d6de1596df) - improve CLI structure, add new commands *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`aa45396`](https://github.com/JakeStanger/ironbar/commit/aa4539606241cfd4d7b8e5512866d30ce92e432d) - ability to set bar layer and exclusive zone *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`92ae1a8`](https://github.com/JakeStanger/ironbar/commit/92ae1a8d73d68ebf51e008cd6322a9269cd10325) - **nix**: home-manager option to read style.css file *(commit by [@Alpha-Ursae-Minoris](https://github.com/Alpha-Ursae-Minoris))*
|
|
||||||
- [`6d0fe4c`](https://github.com/JakeStanger/ironbar/commit/6d0fe4c8ace9c8a4136fb65c9ff9cdb04e9b6408) - add networkmanager module *(commit by [@Zedfrigg](https://github.com/Zedfrigg))*
|
|
||||||
- [`e307e15`](https://github.com/JakeStanger/ironbar/commit/e307e15dc4462d1bdcaabff2375f5ac0c5a5df7b) - new sway-mode module *(PR [#671](https://github.com/JakeStanger/ironbar/pull/671) by [@Rodrigodd](https://github.com/Rodrigodd))*
|
|
||||||
|
|
||||||
### :bug: Bug Fixes
|
|
||||||
- [`5e7f576`](https://github.com/JakeStanger/ironbar/commit/5e7f576841f94bdfd89d401cb9a2ba1fabb45c1c) - **workspaces**: add support for hyprland rename event *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`c45ea02`](https://github.com/JakeStanger/ironbar/commit/c45ea02a7d39b30fece3986044a44a64aebf5e16) - **workspaces**: regression due to [#572](https://github.com/JakeStanger/ironbar/pull/572) *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`4a37429`](https://github.com/JakeStanger/ironbar/commit/4a37429634a32a2ffaeb1b591240bdb2a564cab9) - **launcher**: ghost windows in reload *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`520a94a`](https://github.com/JakeStanger/ironbar/commit/520a94abfa526c22df0bebecc42b9be8ae20881e) - all bars showing on same display due to GTK bug *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`4ad4b0e`](https://github.com/JakeStanger/ironbar/commit/4ad4b0e070cc4e271251763db7210e70857d68ca) - **ipc**: regression - reload not working due to [#592](https://github.com/JakeStanger/ironbar/pull/592) *(commit by [@SerraPi](https://github.com/SerraPi))*
|
|
||||||
- [`9a39420`](https://github.com/JakeStanger/ironbar/commit/9a39420ae28b185cb38a33817f1fc91b5c4e9f55) - **launcher**: favourites staying focused when closed in hyprland *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`8dda494`](https://github.com/JakeStanger/ironbar/commit/8dda49477b2ceb268b94c729aadc5986bdca8528) - **cli**: using zero exit code for error responses *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`9d8a3eb`](https://github.com/JakeStanger/ironbar/commit/9d8a3eb370195321d224c0a51a6752c62404ac1b) - correctly escape pango markup *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`277e6b6`](https://github.com/JakeStanger/ironbar/commit/277e6b62965608ae90defa9a2170d414e09d4c59) - **notifications**: unable to click through overlay *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`dbd385d`](https://github.com/JakeStanger/ironbar/commit/dbd385d225e27a7d732d60ba5a6d6f13c1184add) - **launcher**: apps with multiple windows stay focused when window closed *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`176af99`](https://github.com/JakeStanger/ironbar/commit/176af997f442833adcd7ada1919836d54530d7ef) - **music**: tokens with `&` not rendering *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`7e04e30`](https://github.com/JakeStanger/ironbar/commit/7e04e30171a1897de468592fe5c1f6082d12eb69) - **wayland**: exit on event dispatch error *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`461bee8`](https://github.com/JakeStanger/ironbar/commit/461bee8847590e769df186a2f24ab2ce957568f7) - **bar**: do not add start/center/end containers if empty *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`fb6ae42`](https://github.com/JakeStanger/ironbar/commit/fb6ae42f3bcc7ad35066e1182e617c739a8cfa8a) - crash due to clipboard fd incorrectly closed *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`2bc741d`](https://github.com/JakeStanger/ironbar/commit/2bc741d197867cd5f0c391b9532b4cf9c4d378f6) - **tray**: crash when provided empty pixmap *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`f819aec`](https://github.com/JakeStanger/ironbar/commit/f819aec259cfe418f050c57eb51a236a95039b57) - **notifications**: client broken by recent refactor *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`45d5bf5`](https://github.com/JakeStanger/ironbar/commit/45d5bf5feb88d0854a41faa5890b56188b3e051c) - popups not accounting for monitor scaling *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`474e1fe`](https://github.com/JakeStanger/ironbar/commit/474e1fe364ef70fa0afcff476034d5f307dcd54b) - **upower**: avoid panic on client init error *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
### :recycle: Refactors
|
|
||||||
- [`04a694e`](https://github.com/JakeStanger/ironbar/commit/04a694e2ad5998e82de8dd121cc2b486432c0a70) - fix latest clippy warnings *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`c876904`](https://github.com/JakeStanger/ironbar/commit/c876904bda7bb51ef2d3ec1661281df75fad60be) - update `nix` crate to latest version *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`dedb89b`](https://github.com/JakeStanger/ironbar/commit/dedb89bb027c4477620410d9103d64c3f2668517) - **popup**: rename `is_visible` to `visible` *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`a0cb01a`](https://github.com/JakeStanger/ironbar/commit/a0cb01ae5f2121581eb90f73b8f661862da12b03) - make `Ironbar#unique_id` `must_use` *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`b8fdd85`](https://github.com/JakeStanger/ironbar/commit/b8fdd8531e5516598f81e869b9284b8888f1d06b) - explicitly use `set_text` on non-pango labels *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`9d12535`](https://github.com/JakeStanger/ironbar/commit/9d125353c45a7a8ce3fee43192364745a3fba931) - small tidy *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`1899757`](https://github.com/JakeStanger/ironbar/commit/189975791f6424eca85fcfd76b796e5e9f9fb47f) - **mpris**: better logging, avoid panic on dbus error *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
### :memo: Documentation Changes
|
|
||||||
- [`c25440c`](https://github.com/JakeStanger/ironbar/commit/c25440cd3274cb7adf4e8a1c97b4bc88a53841b4) - update CHANGELOG.md for v0.15.1 [skip ci] *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`f7f991b`](https://github.com/JakeStanger/ironbar/commit/f7f991b2e68a19ff66387913b54127fd8808cc21) - **compiling**: fix wrong fedora package for pulse libs *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`c7743b2`](https://github.com/JakeStanger/ironbar/commit/c7743b28c68919e5bb1d8b9c53d63fb53fd3b081) - add rustdoc comments to all module options *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`7d19106`](https://github.com/JakeStanger/ironbar/commit/7d191065fc20e64befca64e8814aa86b2c654a7c) - add notes about nerd fonts *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`9db0cbc`](https://github.com/JakeStanger/ironbar/commit/9db0cbcbdc561ba929c300cec92156c873c3c151) - **upower**: fix incorrect css selectors *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`057fdff`](https://github.com/JakeStanger/ironbar/commit/057fdffc5f3219b60bbc1f095f88a9d8e3e8f750) - **examples**: fix incorrect cpu sensor name *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
- [`076c2df`](https://github.com/JakeStanger/ironbar/commit/076c2df4a29bb3af2183dc1617f101e1e39d3fa4) - add fedora copr package to readme *(commit by [@victorvintorez](https://github.com/victorvintorez))*
|
|
||||||
- [`860a676`](https://github.com/JakeStanger/ironbar/commit/860a6767f144610d6c1809ddadd52e31c8d8d68d) - **upower**: add note to make clear upower is required *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.15.1] - 2024-05-05
|
## [v0.15.1] - 2024-05-05
|
||||||
|
|
||||||
Release to bump hyprland-rs version due to Hyprland v0.40 socket path breaking change.
|
Release to bump hyprland-rs version due to Hyprland v0.40 socket path breaking change.
|
||||||
|
|
@ -685,5 +575,3 @@ It also requires `lua-lgi` as a runtime dependency.
|
||||||
[v0.14.1]: https://github.com/JakeStanger/ironbar/compare/v0.14.0...v0.14.1
|
[v0.14.1]: https://github.com/JakeStanger/ironbar/compare/v0.14.0...v0.14.1
|
||||||
[v0.15.0]: https://github.com/JakeStanger/ironbar/compare/v0.14.3...v0.15.0
|
[v0.15.0]: https://github.com/JakeStanger/ironbar/compare/v0.14.3...v0.15.0
|
||||||
[v0.15.1]: https://github.com/JakeStanger/ironbar/compare/v0.15.0...v0.15.1
|
[v0.15.1]: https://github.com/JakeStanger/ironbar/compare/v0.15.0...v0.15.1
|
||||||
[v0.16.0]: https://github.com/JakeStanger/ironbar/compare/v0.15.1...v0.16.0
|
|
||||||
[v0.16.1]: https://github.com/JakeStanger/ironbar/compare/v0.16.0...v0.16.1
|
|
||||||
|
|
|
||||||
3100
Cargo.lock
generated
3100
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
144
Cargo.toml
144
Cargo.toml
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.16.1"
|
version = "0.16.0-pre"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
||||||
repository = "https://github.com/jakestanger/ironbar"
|
repository = "https://github.com/jakestanger/ironbar"
|
||||||
|
|
@ -10,41 +10,30 @@ keywords = ["gtk", "bar", "wayland", "wlroots", "gtk-layer-shell"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [
|
default = [
|
||||||
"bindmode+all",
|
|
||||||
"cli",
|
"cli",
|
||||||
"cairo",
|
"cairo",
|
||||||
"clipboard",
|
"clipboard",
|
||||||
"clock",
|
"clock",
|
||||||
"config+all",
|
"config+all",
|
||||||
"custom",
|
|
||||||
"focused",
|
"focused",
|
||||||
"http",
|
"http",
|
||||||
"ipc",
|
"ipc",
|
||||||
"keyboard+all",
|
|
||||||
"launcher",
|
"launcher",
|
||||||
"label",
|
|
||||||
"menu",
|
|
||||||
"music+all",
|
"music+all",
|
||||||
"network_manager",
|
"network_manager",
|
||||||
"notifications",
|
"notifications",
|
||||||
"script",
|
|
||||||
"sys_info",
|
"sys_info",
|
||||||
"tray",
|
"tray",
|
||||||
"upower",
|
"upower",
|
||||||
"volume",
|
"volume",
|
||||||
"workspaces+all",
|
"workspaces+all"
|
||||||
]
|
]
|
||||||
|
|
||||||
cli = ["ipc"]
|
cli = ["dep:clap", "ipc"]
|
||||||
ipc = ["dep:serde_json", "dep:clap"]
|
ipc = ["dep:serde_json"]
|
||||||
|
|
||||||
http = ["dep:reqwest"]
|
http = ["dep:reqwest"]
|
||||||
|
|
||||||
bindmode = []
|
|
||||||
"bindmode+all" = ["bindmode+sway", "bindmode+hyprland"]
|
|
||||||
"bindmode+sway" = ["bindmode", "sway"]
|
|
||||||
"bindmode+hyprland" = ["bindmode", "hyprland"]
|
|
||||||
|
|
||||||
"config+all" = [
|
"config+all" = [
|
||||||
"config+json",
|
"config+json",
|
||||||
"config+yaml",
|
"config+yaml",
|
||||||
|
|
@ -60,62 +49,44 @@ bindmode = []
|
||||||
|
|
||||||
cairo = ["lua-src", "mlua", "cairo-rs"]
|
cairo = ["lua-src", "mlua", "cairo-rs"]
|
||||||
|
|
||||||
clipboard = ["dep:rustix"]
|
clipboard = ["nix"]
|
||||||
|
|
||||||
clock = ["chrono"]
|
clock = ["chrono"]
|
||||||
|
|
||||||
custom = []
|
|
||||||
|
|
||||||
focused = []
|
focused = []
|
||||||
|
|
||||||
keyboard = ["dep:colpetto", "dep:evdev-rs", "dep:rustix", "futures-lite"]
|
|
||||||
"keyboard+all" = ["keyboard", "keyboard+sway", "keyboard+hyprland"]
|
|
||||||
"keyboard+sway" = ["keyboard", "sway"]
|
|
||||||
"keyboard+hyprland" = ["keyboard", "hyprland"]
|
|
||||||
|
|
||||||
label = []
|
|
||||||
|
|
||||||
launcher = []
|
launcher = []
|
||||||
|
|
||||||
menu = []
|
music = ["regex"]
|
||||||
|
|
||||||
music = ["dep:regex"]
|
|
||||||
"music+all" = ["music", "music+mpris", "music+mpd"]
|
"music+all" = ["music", "music+mpris", "music+mpd"]
|
||||||
"music+mpris" = ["music", "mpris"]
|
"music+mpris" = ["music", "mpris"]
|
||||||
"music+mpd" = ["music", "mpd-utils"]
|
"music+mpd" = ["music", "mpd-utils"]
|
||||||
|
|
||||||
network_manager = ["futures-lite", "tokio-stream", "zbus"]
|
network_manager = ["futures-lite", "futures-signals", "zbus"]
|
||||||
|
|
||||||
notifications = ["zbus"]
|
notifications = ["zbus"]
|
||||||
|
|
||||||
script = []
|
sys_info = ["sysinfo", "regex"]
|
||||||
|
|
||||||
sys_info = ["dep:sysinfo"]
|
|
||||||
|
|
||||||
tray = ["system-tray"]
|
tray = ["system-tray"]
|
||||||
|
|
||||||
upower = ["zbus", "futures-lite"]
|
upower = ["upower_dbus", "zbus", "futures-lite"]
|
||||||
|
|
||||||
volume = ["libpulse-binding"]
|
volume = ["libpulse-binding"]
|
||||||
|
|
||||||
workspaces = ["futures-lite"]
|
workspaces = ["futures-lite"]
|
||||||
"workspaces+all" = ["workspaces", "workspaces+sway", "workspaces+hyprland", "workspaces+niri"]
|
"workspaces+all" = ["workspaces", "workspaces+sway", "workspaces+hyprland"]
|
||||||
"workspaces+sway" = ["workspaces", "sway"]
|
"workspaces+sway" = ["workspaces", "swayipc-async"]
|
||||||
"workspaces+hyprland" = ["workspaces", "hyprland"]
|
"workspaces+hyprland" = ["workspaces", "hyprland"]
|
||||||
"workspaces+niri" = ["workspaces", "niri"]
|
|
||||||
|
|
||||||
sway = ["swayipc-async", "futures-lite"]
|
|
||||||
|
|
||||||
niri = ["dep:serde_json"]
|
|
||||||
|
|
||||||
schema = ["dep:schemars"]
|
schema = ["dep:schemars"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# core
|
# core
|
||||||
gtk = "0.18.2"
|
gtk = "0.18.1"
|
||||||
gtk-layer-shell = "0.8.2"
|
gtk-layer-shell = "0.8.1"
|
||||||
glib = "0.18.5"
|
glib = "0.18.5"
|
||||||
tokio = { version = "1.46.1", features = [
|
tokio = { version = "1.39.2", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
|
|
@ -123,78 +94,81 @@ tokio = { version = "1.46.1", features = [
|
||||||
"sync",
|
"sync",
|
||||||
"io-util",
|
"io-util",
|
||||||
"net",
|
"net",
|
||||||
"fs"
|
|
||||||
] }
|
] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
tracing-error = { version = "0.2.1", default-features = false }
|
tracing-error = { version = "0.2.0" , default-features = false }
|
||||||
tracing-appender = "0.2.3"
|
tracing-appender = "0.2.3"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.3"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
indexmap = "2.10.0"
|
indexmap = "2.3.0"
|
||||||
dirs = "6.0.0"
|
dirs = "5.0.1"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
notify = { version = "8.1.0", default-features = false }
|
notify = { version = "6.1.1", default-features = false }
|
||||||
wayland-client = "0.31.1"
|
wayland-client = "0.31.1"
|
||||||
wayland-protocols-wlr = { version = "0.3.8", features = ["client"] }
|
wayland-protocols-wlr = { version = "0.2.0", features = ["client"] }
|
||||||
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [
|
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [
|
||||||
"calloop",
|
"calloop",
|
||||||
] }
|
] }
|
||||||
universal-config = { version = "0.5.1", default-features = false }
|
universal-config = { version = "0.5.0", default-features = false }
|
||||||
ctrlc = "3.4.7"
|
ctrlc = "3.4.4"
|
||||||
cfg-if = "1.0.1"
|
cfg-if = "1.0.0"
|
||||||
|
|
||||||
# cli
|
# cli
|
||||||
clap = { version = "4.5.40", optional = true, features = ["derive"] }
|
clap = { version = "4.5.13", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
|
# ipc
|
||||||
|
serde_json = { version = "1.0.122", optional = true }
|
||||||
|
|
||||||
# http
|
# http
|
||||||
reqwest = { version = "0.12.22", default-features = false, features = ["default-tls", "http2"], optional = true }
|
reqwest = { version = "0.12.5", default-features = false, features = ["default-tls", "http2"], optional = true }
|
||||||
|
|
||||||
# cairo
|
# cairo
|
||||||
lua-src = { version = "548.1.1", optional = true }
|
lua-src = { version = "547.0.0", optional = true }
|
||||||
mlua = { version = "0.10.5", optional = true, features = ["luajit", "send"] }
|
mlua = { version = "0.9.9", optional = true, features = ["luajit"] }
|
||||||
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
|
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
|
||||||
|
|
||||||
# clock
|
# clipboard
|
||||||
chrono = { version = "0.4.41", optional = true, default-features = false, features = ["clock", "unstable-locales"] }
|
nix = { version = "0.29.0", optional = true, features = ["event", "fs"] }
|
||||||
|
|
||||||
# keyboard
|
# clock
|
||||||
colpetto = { version = "0.6.0", features = ["tokio", "tracing"], optional = true }
|
chrono = { version = "0.4.38", optional = true, default-features = false, features = ["clock", "unstable-locales"] }
|
||||||
evdev-rs = { version = "0.6.1", optional = true }
|
|
||||||
|
|
||||||
# music
|
# music
|
||||||
mpd-utils = { version = "0.2.1", optional = true }
|
mpd-utils = { version = "0.2.1", optional = true }
|
||||||
mpris = { version = "2.0.1", optional = true }
|
mpris = { version = "2.0.1", optional = true }
|
||||||
regex = { version = "1.11.1", default-features = false, features = [
|
|
||||||
"std",
|
|
||||||
], optional = true }
|
|
||||||
|
|
||||||
# network_manager
|
# network_manager
|
||||||
tokio-stream = { version = "0.1.17", optional = true }
|
futures-signals = { version = "0.3.33", optional = true }
|
||||||
|
|
||||||
# sys_info
|
# sys_info
|
||||||
sysinfo = { version = "0.35.2", optional = true }
|
sysinfo = { version = "0.29.11", optional = true }
|
||||||
|
|
||||||
# tray
|
# tray
|
||||||
system-tray = { version = "0.7.0", features = ["dbusmenu-gtk3"], optional = true }
|
system-tray = { version = "0.2.0", optional = true }
|
||||||
|
|
||||||
|
# upower
|
||||||
|
upower_dbus = { version = "0.3.2", optional = true }
|
||||||
|
|
||||||
# volume
|
# volume
|
||||||
libpulse-binding = { version = "2.30.1", optional = true }
|
libpulse-binding = { version = "2.28.1", optional = true }
|
||||||
|
|
||||||
|
# workspaces
|
||||||
|
swayipc-async = { version = "2.0.1", optional = true }
|
||||||
|
hyprland = { version = "0.4.0-alpha.2", features = ["silent"], optional = true }
|
||||||
|
futures-util = { version = "0.3.30", optional = true }
|
||||||
|
|
||||||
# shared
|
# shared
|
||||||
futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces, keyboard
|
futures-lite = { version = "2.3.0", optional = true } # network_manager, upower, workspaces
|
||||||
zbus = { version = "5.7.1", default-features = false, features = ["blocking-api", "tokio"], optional = true } # network_manager, notifications, upower
|
regex = { version = "1.10.6", default-features = false, features = [
|
||||||
swayipc-async = { version = "2.1.0", optional = true } # workspaces, keyboard
|
"std",
|
||||||
hyprland = { version = "0.4.0-beta.2", optional = true } # workspaces, keyboard
|
], optional = true } # music, sys_info
|
||||||
rustix = { version = "1.0.7", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input
|
zbus = { version = "3.15.2", default-features = false, features = ["tokio"], optional = true } # network_manager, notifications, upower
|
||||||
serde_json = { version = "1.0.140", optional = true } # ipc, niri
|
|
||||||
|
|
||||||
# schema
|
# schema
|
||||||
schemars = { version = "0.8.22", optional = true }
|
schemars = { version = "0.8.21", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
# -- PATCH --
|
||||||
clap = { version = "4.5.40", features = ["derive"] }
|
# temp fix for tracing-appender/time
|
||||||
clap_complete = "4.5.54"
|
time = "0.3.36"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
|
||||||
serde_json = "1.0.140"
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
FROM rust:latest
|
|
||||||
|
|
||||||
COPY .github/scripts/ubuntu_setup.sh /scripts/ubuntu_setup.sh
|
|
||||||
RUN /scripts/ubuntu_setup.sh
|
|
||||||
|
|
||||||
RUN rustup component add clippy
|
|
||||||
35
README.md
35
README.md
|
|
@ -48,10 +48,10 @@ Ironbar is designed to support anything from a lightweight bar to a full desktop
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- First-class support for Sway and Hyprland, and partial support for Niri
|
- First-class support for Sway and Hyprland
|
||||||
- Fully themeable with hot-loaded CSS
|
- Fully themeable with hot-loaded CSS
|
||||||
- Popups to show rich content
|
- Popups to show rich content
|
||||||
- Ability to create custom widgets, run scripts and embed dynamic content (including via Lua)
|
- Ability to create custom widgets, run scripts and embed dynamic content
|
||||||
- Easy to configure anything from a single bar across all monitors, to multiple different unique bars per monitor
|
- Easy to configure anything from a single bar across all monitors, to multiple different unique bars per monitor
|
||||||
- Support for multiple config languages
|
- Support for multiple config languages
|
||||||
|
|
||||||
|
|
@ -119,35 +119,8 @@ A flake is included with the repo which can be used with Home Manager.
|
||||||
# And configure
|
# And configure
|
||||||
programs.ironbar = {
|
programs.ironbar = {
|
||||||
enable = true;
|
enable = true;
|
||||||
systemd = true;
|
config = {};
|
||||||
config = {
|
style = "";
|
||||||
# An example:
|
|
||||||
monitors = {
|
|
||||||
DP-1 = {
|
|
||||||
anchor_to_edges = true;
|
|
||||||
position = "top";
|
|
||||||
height = 16;
|
|
||||||
start = [
|
|
||||||
{ type = "clock"; }
|
|
||||||
];
|
|
||||||
end = [
|
|
||||||
{
|
|
||||||
type = "tray";
|
|
||||||
icon_size = 16;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
style = /* css */ ''
|
|
||||||
/* An example */
|
|
||||||
* {
|
|
||||||
font-family: Noto Sans Nerd Font, sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
package = inputs.ironbar;
|
package = inputs.ironbar;
|
||||||
features = ["feature" "another_feature"];
|
features = ["feature" "another_feature"];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
53
build.rs
53
build.rs
|
|
@ -1,53 +0,0 @@
|
||||||
// Importing from Ironbar modules brings in lots of things not used by the build script
|
|
||||||
// we can just globally suppress those.
|
|
||||||
#![allow(unused, dead_code)]
|
|
||||||
|
|
||||||
#[path = "src/cli.rs"]
|
|
||||||
mod cli;
|
|
||||||
|
|
||||||
#[path = "src/error.rs"]
|
|
||||||
mod error;
|
|
||||||
|
|
||||||
#[path = "src/ipc"]
|
|
||||||
mod ipc {
|
|
||||||
#[path = "commands.rs"]
|
|
||||||
mod commands;
|
|
||||||
|
|
||||||
#[path = "responses.rs"]
|
|
||||||
mod responses;
|
|
||||||
|
|
||||||
pub use commands::Command;
|
|
||||||
pub use responses::Response;
|
|
||||||
}
|
|
||||||
|
|
||||||
use clap::Command;
|
|
||||||
use clap::CommandFactory;
|
|
||||||
use clap_complete::Shell::{Bash, Fish, Zsh};
|
|
||||||
use clap_complete::generate_to;
|
|
||||||
use cli::Args;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const NAME: &str = "ironbar";
|
|
||||||
|
|
||||||
fn generate_shell_completions(mut cmd: Command) -> std::io::Result<()> {
|
|
||||||
const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
|
||||||
let comp_dir = PathBuf::from(MANIFEST_DIR).join("target/completions");
|
|
||||||
|
|
||||||
fs::create_dir_all(&comp_dir)?;
|
|
||||||
|
|
||||||
for shell in [Bash, Fish, Zsh] {
|
|
||||||
generate_to(shell, &mut cmd, NAME, &comp_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
|
||||||
let mut cmd = Args::command();
|
|
||||||
cmd.set_bin_name(NAME);
|
|
||||||
|
|
||||||
generate_shell_completions(cmd)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
12
default.nix
12
default.nix
|
|
@ -1,12 +0,0 @@
|
||||||
(import (
|
|
||||||
let
|
|
||||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
|
||||||
nodeName = lock.nodes.root.inputs.flake-compat;
|
|
||||||
in
|
|
||||||
fetchTarball {
|
|
||||||
url =
|
|
||||||
lock.nodes.${nodeName}.locked.url
|
|
||||||
or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
|
|
||||||
sha256 = lock.nodes.${nodeName}.locked.narHash;
|
|
||||||
}
|
|
||||||
) {src = ./.;}).defaultNix
|
|
||||||
|
|
@ -22,12 +22,8 @@ You also need rust; only the latest stable version is supported.
|
||||||
pacman -S gtk3 gtk-layer-shell
|
pacman -S gtk3 gtk-layer-shell
|
||||||
# for http support
|
# for http support
|
||||||
pacman -S openssl
|
pacman -S openssl
|
||||||
# for tray support
|
|
||||||
pacman -S libdbusmenu-gtk3
|
|
||||||
# for volume support
|
# for volume support
|
||||||
pacman -S libpulse
|
pacman -S libpulse
|
||||||
# for keyboard support
|
|
||||||
pacman -S libinput
|
|
||||||
# for lua/cairo support
|
# for lua/cairo support
|
||||||
pacman -S luajit lua51-lgi
|
pacman -S luajit lua51-lgi
|
||||||
```
|
```
|
||||||
|
|
@ -38,12 +34,8 @@ pacman -S luajit lua51-lgi
|
||||||
apt install build-essential libgtk-3-dev libgtk-layer-shell-dev
|
apt install build-essential libgtk-3-dev libgtk-layer-shell-dev
|
||||||
# for http support
|
# for http support
|
||||||
apt install libssl-dev
|
apt install libssl-dev
|
||||||
# for tray support
|
|
||||||
apt install libdbusmenu-gtk3-dev
|
|
||||||
# for volume support
|
# for volume support
|
||||||
apt install libpulse-dev
|
apt install libpulse-dev
|
||||||
# for keyboard support
|
|
||||||
apt install libinput-dev
|
|
||||||
# for lua/cairo support
|
# for lua/cairo support
|
||||||
apt install luajit-dev lua-lgi
|
apt install luajit-dev lua-lgi
|
||||||
```
|
```
|
||||||
|
|
@ -54,12 +46,8 @@ apt install luajit-dev lua-lgi
|
||||||
dnf install gtk3-devel gtk-layer-shell-devel
|
dnf install gtk3-devel gtk-layer-shell-devel
|
||||||
# for http support
|
# for http support
|
||||||
dnf install openssl-devel
|
dnf install openssl-devel
|
||||||
# for tray support
|
|
||||||
dnf install libdbusmenu-gtk3-devel
|
|
||||||
# for volume support
|
# for volume support
|
||||||
dnf install pulseaudio-libs-devel
|
dnf install pulseaudio-libs-devel
|
||||||
# for keyboard support
|
|
||||||
dnf install libinput-devel
|
|
||||||
# for lua/cairo support
|
# for lua/cairo support
|
||||||
dnf install luajit-devel lua-lgi
|
dnf install luajit-devel lua-lgi
|
||||||
```
|
```
|
||||||
|
|
@ -100,13 +88,7 @@ cargo build --release --no-default-features \
|
||||||
| cairo | Enables the `cairo` module |
|
| cairo | Enables the `cairo` module |
|
||||||
| clipboard | Enables the `clipboard` module. |
|
| clipboard | Enables the `clipboard` module. |
|
||||||
| clock | Enables the `clock` module. |
|
| clock | Enables the `clock` module. |
|
||||||
| custom | Enables the `custom` module. |
|
|
||||||
| focused | Enables the `focused` module. |
|
| focused | Enables the `focused` module. |
|
||||||
| keyboard | Enables the `keyboard` module without keyboard layout support. |
|
|
||||||
| keyboard+all | Enables the `keyboard` module with keyboard layout support for all compositors. |
|
|
||||||
| keyboard+sway | Enables the `keyboard` module with keyboard layout support for Sway. |
|
|
||||||
| keyboard+hyprland | Enables the `keyboard` module with keyboard layout support for Hyprland. |
|
|
||||||
| label | Enables the `label` module. |
|
|
||||||
| launcher | Enables the `launcher` module. |
|
| launcher | Enables the `launcher` module. |
|
||||||
| music+all | Enables the `music` module with support for all player types. |
|
| music+all | Enables the `music` module with support for all player types. |
|
||||||
| music+mpris | Enables the `music` module with MPRIS support. |
|
| music+mpris | Enables the `music` module with MPRIS support. |
|
||||||
|
|
@ -114,37 +96,15 @@ cargo build --release --no-default-features \
|
||||||
| network_manager | Enables the `network_manager` module. |
|
| network_manager | Enables the `network_manager` module. |
|
||||||
| notifications | Enables the `notiications` module. |
|
| notifications | Enables the `notiications` module. |
|
||||||
| sys_info | Enables the `sys_info` module. |
|
| sys_info | Enables the `sys_info` module. |
|
||||||
| script | Enables the `script` module. |
|
|
||||||
| tray | Enables the `tray` module. |
|
| tray | Enables the `tray` module. |
|
||||||
| upower | Enables the `upower` module. |
|
| upower | Enables the `upower` module. |
|
||||||
| volume | Enables the `volume` module. |
|
| volume | Enables the `volume` module. |
|
||||||
| workspaces+all | Enables the `workspaces` module with support for all compositors. |
|
| workspaces+all | Enables the `workspaces` module with support for all compositors. |
|
||||||
| workspaces+sway | Enables the `workspaces` module with support for Sway. |
|
| workspaces+sway | Enables the `workspaces` module with support for Sway. |
|
||||||
| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. |
|
| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. |
|
||||||
| workspaces+niri | Enables the `workspaces` module with support for Niri. |
|
|
||||||
| **Other** | |
|
| **Other** | |
|
||||||
| schema | Enables JSON schema support and the CLI `--print-schema` flag. |
|
| schema | Enables JSON schema support and the CLI `--print-schema` flag. |
|
||||||
|
|
||||||
## Shell completions
|
|
||||||
|
|
||||||
Compiling Ironbar will produce shell completions for bash, zsh and fish; these can be found in `target/completions`.
|
|
||||||
|
|
||||||
You can install these as follows:
|
|
||||||
|
|
||||||
Bash:
|
|
||||||
```shell
|
|
||||||
install -Dm644 completions/ironbar.bash /usr/share/bash-completion/completions/ironbar
|
|
||||||
```
|
|
||||||
|
|
||||||
Zsh:
|
|
||||||
```shell
|
|
||||||
install -Dm644 completions/_ironbar /usr/share/zsh/site-functions/_ironbar
|
|
||||||
```
|
|
||||||
|
|
||||||
Fish:
|
|
||||||
```shell
|
|
||||||
install -Dm644 completions/ironbar.fish /usr/share/fish/vendor_completions.d/ironbar.fish
|
|
||||||
```
|
|
||||||
|
|
||||||
## Speeding up compiling
|
## Speeding up compiling
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -281,11 +281,9 @@ Check [here](config) for an example config file for a fully configured bar in ea
|
||||||
The following table lists each of the top-level bar config options:
|
The following table lists each of the top-level bar config options:
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|--------------------|-----------------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|-----------------------------------------|---------|---------------------------------------------------------------|
|
||||||
| `ironvar_defaults` | `Map<string, string>` | `{}` | Map of [ironvar](ironvars) keys against their default values. |
|
| `ironvar_defaults` | `Map<string, string>` | `{}` | Map of [ironvar](ironvars) keys against their default values. |
|
||||||
| `monitors` | `Map<string, BarConfig or BarConfig[]>` | `null` | Map of monitor names against bar configs. |
|
| `monitors` | `Map<string, BarConfig or BarConfig[]>` | `null` | Map of monitor names against bar configs. |
|
||||||
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
|
|
||||||
| `icon_overrides` | `Map<string, string>` | `{}` | Map of image inputs to override names. Usually used for app IDs (or classes) to icon names, overriding the app's default icon. |
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> `monitors` is only required if you are following **2b** or **2c** (ie not the same bar across all monitors).
|
> `monitors` is only required if you are following **2b** or **2c** (ie not the same bar across all monitors).
|
||||||
|
|
@ -310,6 +308,7 @@ The following table lists each of the bar-level bar config options:
|
||||||
| `layer` | `background` or `bottom` or `top` or `overlay` | `top` | The layer-shell layer to place the bar on. |
|
| `layer` | `background` or `bottom` or `top` or `overlay` | `top` | The layer-shell layer to place the bar on. |
|
||||||
| `exclusive_zone` | `boolean` | `true` unless `start_hidden` is enabled. | Whether the bar should reserve an exclusive zone around it. |
|
| `exclusive_zone` | `boolean` | `true` unless `start_hidden` is enabled. | Whether the bar should reserve an exclusive zone around it. |
|
||||||
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
|
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
|
||||||
|
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
|
||||||
| `start_hidden` | `boolean` | `false`, or `true` if `autohide` set | Whether the bar should be hidden when the application starts. Enabled by default when `autohide` is set. |
|
| `start_hidden` | `boolean` | `false`, or `true` if `autohide` set | Whether the bar should be hidden when the application starts. Enabled by default when `autohide` is set. |
|
||||||
| `autohide` | `integer` | `null` | The duration in milliseconds before the bar is hidden after the cursor leaves. Leave unset to disable auto-hide behaviour. |
|
| `autohide` | `integer` | `null` | The duration in milliseconds before the bar is hidden after the cursor leaves. Leave unset to disable auto-hide behaviour. |
|
||||||
| `start` | `Module[]` | `[]` | Array of left or top modules. |
|
| `start` | `Module[]` | `[]` | Array of left or top modules. |
|
||||||
|
|
@ -351,14 +350,7 @@ For information on the `Script` type, and embedding scripts in strings, see [her
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------|----------|---------|-----------------------------------------------------------------------------------|
|
|-----------|----------|---------|-----------------------------------------------------------------------------------|
|
||||||
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
|
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
|
||||||
| `name` | `string` | `null` | The unique widget name, allowing you to style it using `#name`. |
|
| `name` | `string` | `null` | Sets the unique widget name, allowing you to style it using `#name`. |
|
||||||
| `class` | `string` | `null` | One or more CSS classes, allowing you to style it using `.class`. |
|
| `class` | `string` | `null` | Sets one or more CSS classes, allowing you to style it using `.class`. |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
For more information on styling, please see the [styling guide](styling-guide).
|
||||||
|
|
||||||
#### Formatting
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
|---------------|--------------------------------------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `orientation` | `horizontal` or `vertical` (shorthand: `'h'` or `'v'`) | `horizontal` or `vertical` | The direction in which the widget and its text are laid out. Some modules additionally provide a `direction` option to provide further control. |
|
|
||||||
| `justify` | `left`, `right`, `center`, `fill` | `left` | The justification (alignment) of the widget text shown on the bar. |
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ You can also view help per sub-command or command, for example using `ironbar va
|
||||||
The CLI supports plaintext and JSON output. Plaintext will:
|
The CLI supports plaintext and JSON output. Plaintext will:
|
||||||
|
|
||||||
- Print `ok` for empty success responses
|
- Print `ok` for empty success responses
|
||||||
- Print the returned body for each success response
|
- Print the returned body for success responses
|
||||||
- Some commands act on multiple objects, in which case the CLI will print one line for each body.
|
|
||||||
- Print `error` to followed by the error on the next line for error responses. This is printed to `stderr`.
|
- Print `error` to followed by the error on the next line for error responses. This is printed to `stderr`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
@ -151,9 +150,6 @@ Each key/value pair is on its own `\n` separated newline. The key and value are
|
||||||
|
|
||||||
### `bar`
|
### `bar`
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> If there are multiple bars by the same name, the `bar` subcommand will act on all of them and return a `multi` response for commands that get a value.
|
|
||||||
|
|
||||||
#### `show`
|
#### `show`
|
||||||
|
|
||||||
Forces a bar to be shown, regardless of the current visibility state.
|
Forces a bar to be shown, regardless of the current visibility state.
|
||||||
|
|
@ -328,17 +324,6 @@ The operation completed successfully, with response data.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `multi`
|
|
||||||
|
|
||||||
The operation completed successfully on multiple objects, with response data.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "multi",
|
|
||||||
"values": ["lorem ipsum", "dolor sit"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `error`
|
### `error`
|
||||||
|
|
||||||
The operation failed.
|
The operation failed.
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Dynamic booleans can use a single source of either a script or variable to contr
|
||||||
|
|
||||||
For scripts, you can just write these directly with no notation.
|
For scripts, you can just write these directly with no notation.
|
||||||
Only polling scripts are supported.
|
Only polling scripts are supported.
|
||||||
The script exit code is used, where `0` is `true` and any other code is `false`.
|
The script exit code is used, where `0` is `true` and any other code is `false.
|
||||||
|
|
||||||
For variables, use the standard `#name` notation.
|
For variables, use the standard `#name` notation.
|
||||||
An empty string, `0` and `false` are treated as false.
|
An empty string, `0` and `false` are treated as false.
|
||||||
|
|
|
||||||
50
docs/GTK4.md
50
docs/GTK4.md
|
|
@ -1,50 +0,0 @@
|
||||||
As the GTK3 and gtk-layer-shell crates are now deprecated, there is a need to move to GTK 4.
|
|
||||||
|
|
||||||
The `refactor/gtk-4` branch and PR [#112](https://github.com/JakeStanger/ironbar/pull/112) are tracking the code upgrade.
|
|
||||||
This page documents the port progress.
|
|
||||||
|
|
||||||
Assistance in the porting process is very much welcomed, no matter how small.
|
|
||||||
|
|
||||||
As many modules have not been ported, the default feature set will fail to compile.
|
|
||||||
It is therefore necessary to compile manually with `--no-default-features`, enabling only the working modules:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo run --no-default-features \
|
|
||||||
--features config+all,clock,cairo
|
|
||||||
```
|
|
||||||
|
|
||||||
A full list of feature flags can be found [here](Compiling#features).
|
|
||||||
|
|
||||||
## Core functionality
|
|
||||||
|
|
||||||
| Area | Status | Notes |
|
|
||||||
|-----------------|--------|-------------------------------------------------------------------------------------------------------|
|
|
||||||
| Bar | ✅ | |
|
|
||||||
| Popups | ✅ | Potential styling issues, otherwise working. |
|
|
||||||
| Theming - CSS | ✅ | |
|
|
||||||
| Theming - Icons | ⚠️ | GTK4 does not support icon theming - always uses default theme. Image scaling may be incorrect. |
|
|
||||||
| Config - Format | ❌ ️ | Angle/justify properties have been removed from widgets and should now be controlled via CSS instead. |
|
|
||||||
| IPC | ⚠️ | Some popup-related commands not implemented. |
|
|
||||||
|
|
||||||
## Modules
|
|
||||||
|
|
||||||
| Module | Status | Notes |
|
|
||||||
|-----------------|--------|------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| Bindmode | ❌ | |
|
|
||||||
| Cairo | ✅ | |
|
|
||||||
| Clipboard | ✅ | |
|
|
||||||
| Clock | ✅ | |
|
|
||||||
| Custom | ✅ | |
|
|
||||||
| Focused | ✅ | |
|
|
||||||
| Keyboard | ✅ | |
|
|
||||||
| Label | ✅ | |
|
|
||||||
| Launcher | ⚠️ | Popup behaviour may be a little strange. |
|
|
||||||
| Music | ✅ | |
|
|
||||||
| Network Manager | ❌ | |
|
|
||||||
| Notifications | ✅ | |
|
|
||||||
| Script | ✅ | |
|
|
||||||
| SysInfo | ✅ | |
|
|
||||||
| Tray | ❌ | GTK4 removes widgets required to move the tray. No `libdbusmenu-gtk4` either. will need to manually re-create menus with custom widgets. |
|
|
||||||
| UPower | ❌ | |
|
|
||||||
| Volume | ❌ | |
|
|
||||||
| Workspaces | ✅ | |
|
|
||||||
|
|
@ -7,18 +7,3 @@ Any UTF-8 string is a valid value.
|
||||||
Reference values using `#my_variable`. These update as soon as the value changes.
|
Reference values using `#my_variable`. These update as soon as the value changes.
|
||||||
|
|
||||||
You can set defaults using the `ironvar_defaults` key in your top-level config.
|
You can set defaults using the `ironvar_defaults` key in your top-level config.
|
||||||
|
|
||||||
Some modules (such as `sys_info`) expose their values over the Ironvar interface,
|
|
||||||
allowing you to build custom interfaces and integrate into scripts.
|
|
||||||
These present their values inside read-only namespaces.
|
|
||||||
|
|
||||||
Some examples below:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
ironbar var list
|
|
||||||
ironbar var list sysinfo
|
|
||||||
ironbar var list sysinfo.disk_percent
|
|
||||||
ironbar var get sysinfo.disk_percent./home
|
|
||||||
ironbar var get sysinfo.disk_percent.mean
|
|
||||||
ironbar var get sysinfo.memory_percent
|
|
||||||
```
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
- [Configuration guide](configuration-guide)
|
- [Configuration guide](configuration-guide)
|
||||||
- [Images](images)
|
- [Images](images)
|
||||||
- [Styling guide](styling-guide)
|
- [Styling guide](styling-guide)
|
||||||
- [GTK 4 Port](gtk4)
|
|
||||||
|
|
||||||
# Dynamic content
|
# Dynamic content
|
||||||
|
|
||||||
|
|
@ -25,16 +24,13 @@
|
||||||
|
|
||||||
# Modules
|
# Modules
|
||||||
|
|
||||||
- [Bindmode](bindmode)
|
|
||||||
- [Cairo](cairo)
|
- [Cairo](cairo)
|
||||||
- [Clipboard](clipboard)
|
- [Clipboard](clipboard)
|
||||||
- [Clock](clock)
|
- [Clock](clock)
|
||||||
- [Custom](custom)
|
- [Custom](custom)
|
||||||
- [Focused](focused)
|
- [Focused](focused)
|
||||||
- [Keyboard](keyboard)
|
|
||||||
- [Label](label)
|
- [Label](label)
|
||||||
- [Launcher](launcher)
|
- [Launcher](launcher)
|
||||||
- [Menu](menu)
|
|
||||||
- [Music](music)
|
- [Music](music)
|
||||||
- [Network Manager](network-manager)
|
- [Network Manager](network-manager)
|
||||||
- [Notifications](notifications)
|
- [Notifications](notifications)
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
> [!IMPORTANT]
|
|
||||||
> This module is currently only available on Sway and Hyprland.
|
|
||||||
|
|
||||||
Displays Sway's current binding mode or [Hyprland's current submap](https://wiki.hyprland.org/Configuring/Binds/#submaps)
|
|
||||||
in a label. Nothing is displayed if no binding mode is active.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
> Type: `bindmode`
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
| --------------------- | ------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>JSON</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"end": [
|
|
||||||
{
|
|
||||||
"type": "bindmode",
|
|
||||||
"truncate": "start"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>TOML</summary>
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[end]]
|
|
||||||
type = "bindmode"
|
|
||||||
truncate = "start"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
end:
|
|
||||||
- type: "bindmode"
|
|
||||||
truncate: "start"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Corn</summary>
|
|
||||||
|
|
||||||
```corn
|
|
||||||
{
|
|
||||||
end = [
|
|
||||||
{
|
|
||||||
type = "bindmode"
|
|
||||||
truncate = "start"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
| Selector | Description |
|
|
||||||
| ----------- | ---------------------- |
|
|
||||||
| `.bindmode` | Bind mode label widget |
|
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
|
|
@ -13,12 +13,12 @@ Supports plain text and images.
|
||||||
> Type: `clipboard`
|
> Type: `clipboard`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------------------|------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------|---------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `icon` | `string` or [image](images) | `` | Icon to show on the widget button. |
|
| `icon` | `string` or [image](images) | `` | Icon to show on the widget button. |
|
||||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||||
| `max_items` | `integer` | `10` | Maximum number of items to show in the popup. |
|
| `max_items` | `integer` | `10` | Maximum number of items to show in the popup. |
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
| `truncate` | `'start'` or `'middle'` or `'end'` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
| `truncate.mode` | `'start'` or `'middle'` or `'end'` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,11 @@ Clicking on the widget opens a popup with the time and a calendar.
|
||||||
> Type: `clock`
|
> Type: `clock`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|----------------|------------------------------------------------------------|------------------------------------|-------------------------------------------------------------------------------------|
|
|----------------|----------|------------------------------------|-------------------------------------------------------------------------------------|
|
||||||
| `format` | `string` | `%d/%m/%Y %H:%M` | Date/time format string. Pango markup is supported. |
|
| `format` | `string` | `%d/%m/%Y %H:%M` | Date/time format string. Pango markup is supported. |
|
||||||
| `format_popup` | `string` | `%H:%M:%S` | Date/time format string to display in the popup header. Pango markup is supported. |
|
| `format_popup` | `string` | `%H:%M:%S` | Date/time format string to display in the popup header. Pango markup is supported. |
|
||||||
| `locale` | `string` | `$LC_TIME` or `$LANG` or `'POSIX'` | Locale to use (eg `en_GB`). Defaults to the system language (reading from env var). |
|
| `locale` | `string` | `$LC_TIME` or `$LANG` or `'POSIX'` | Locale to use (eg `en_GB`). Defaults to the system language (reading from env var). |
|
||||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the time on the clock button. |
|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the time on the clock button. |
|
||||||
| `justify` | `'left'`', `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the date/time shown on the bar. |
|
|
||||||
|
|
||||||
> Detail on available tokens can be found here: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>
|
> Detail on available tokens can be found here: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,6 @@ A container to place nested widgets inside.
|
||||||
|---------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------|
|
|---------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------|
|
||||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Whether child widgets should be horizontally or vertically added. |
|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Whether child widgets should be horizontally or vertically added. |
|
||||||
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this box. |
|
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this box. |
|
||||||
| `halign` | `'start'` or `'center'` or `'end'` or `'fill'` | `'fill'` | The horizontal alignment of the box within its parent container. |
|
|
||||||
| `valign` | `'start'` or `'center'` or `'end'` or `'fill'` | `'fill'` | The vertical alignment of the box within its parent container. |
|
|
||||||
|
|
||||||
#### Label
|
#### Label
|
||||||
|
|
||||||
|
|
@ -57,15 +55,9 @@ A text label. Pango markup is supported.
|
||||||
> Type `label`
|
> Type `label`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|---------|-------------------------------------------------|---------|---------------------------------------------------------------------|
|
||||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. |
|
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. |
|
||||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label text. |
|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label. |
|
||||||
| `justify` | `'left'`, `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the label text. |
|
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
|
|
||||||
|
|
||||||
#### Button
|
#### Button
|
||||||
|
|
||||||
|
|
@ -74,12 +66,11 @@ A clickable button, which can run a command when clicked.
|
||||||
> Type `button`
|
> Type `button`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|---------------|------------------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------|
|
|------------|-------------------------------------------------|---------|--------------------------------------------------------------------------------------------------|
|
||||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. |
|
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. |
|
||||||
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. |
|
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. |
|
||||||
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
|
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
|
||||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label text. |
|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the button. |
|
||||||
| `justify` | `'left'`, `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the label text. |
|
|
||||||
|
|
||||||
#### Image
|
#### Image
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ Displays the title and/or icon of the currently focused window.
|
||||||
> Type: `focused`
|
> Type: `focused`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------------------|------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------|---------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `show_icon` | `boolean` | `true` | Whether to show the app's icon. |
|
| `show_icon` | `boolean` | `true` | Whether to show the app's icon. |
|
||||||
| `show_title` | `boolean` | `true` | Whether to show the app's title. |
|
| `show_title` | `boolean` | `true` | Whether to show the app's title. |
|
||||||
| `icon_size` | `integer` | `32` | Size of icon in pixels. |
|
| `icon_size` | `integer` | `32` | Size of icon in pixels. |
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
| `truncate` | `'start'` or `'middle'` or `'end'` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
| `truncate.mode` | `'start'` or `'middle'` or `'end'` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
> [!NOTE]
|
|
||||||
> This module requires your user is in the `input` group.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> The keyboard layout feature is only available on Sway and Hyprland.
|
|
||||||
|
|
||||||
Displays the toggle state of the capslock, num lock and scroll lock keys, and the current keyboard layout.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
> Type: `keyboard`
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
| ------------------ | ------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `show_caps` | `boolean` | `true` | Whether to show capslock indicator. |
|
|
||||||
| `show_num` | `boolean` | `true` | Whether to show num lock indicator. |
|
|
||||||
| `show_scroll` | `boolean` | `true` | Whether to show scroll lock indicator. |
|
|
||||||
| `show_layout` | `boolean` | `true` | Whether to show the keyboard layout button. |
|
|
||||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
|
||||||
| `icons.caps_on` | `string` or [image](images) | `` | Icon to show for enabled capslock indicator. |
|
|
||||||
| `icons.caps_off` | `string` or [image](images) | `''` | Icon to show for disabled capslock indicator. |
|
|
||||||
| `icons.num_on` | `string` or [image](images) | `` | Icon to show for enabled num lock indicator. |
|
|
||||||
| `icons.num_off` | `string` or [image](images) | `''` | Icon to show for disabled num lock indicator. |
|
|
||||||
| `icons.scroll_on` | `string` or [image](images) | `` | Icon to show for enabled scroll lock indicator. |
|
|
||||||
| `icons.scroll_off` | `string` or [image](images) | `''` | Icon to show for disabled scroll lock indicator. |
|
|
||||||
| `icons.layout_map` | `Map<string, string or image>` | `{}` | Map of icons or labels to show for a particular keyboard layout. Layouts use their actual name if not present in the map. |
|
|
||||||
| `seat` | `string` | `seat0` | ID of the Wayland seat to attach to. |
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>JSON</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"end": [
|
|
||||||
{
|
|
||||||
"type": "keyboard",
|
|
||||||
"show_scroll": false,
|
|
||||||
"icons": {
|
|
||||||
"caps_on": "",
|
|
||||||
"layout_map": {
|
|
||||||
"English (US)": "🇺🇸",
|
|
||||||
"Ukrainian": "🇺🇦"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>TOML</summary>
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[end]]
|
|
||||||
type = "keyboard"
|
|
||||||
show_scroll = false
|
|
||||||
|
|
||||||
[end.icons]
|
|
||||||
caps_on = ""
|
|
||||||
|
|
||||||
[end.icons.layout_map]
|
|
||||||
"English (US)" = "🇺🇸"
|
|
||||||
Ukrainian = "🇺🇦"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
end:
|
|
||||||
- type: keyboard
|
|
||||||
show_scroll: false
|
|
||||||
icons:
|
|
||||||
caps_on:
|
|
||||||
layout_map:
|
|
||||||
"English (US)": 🇺🇸
|
|
||||||
Ukrainian: 🇺🇦
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Corn</summary>
|
|
||||||
|
|
||||||
```corn
|
|
||||||
{
|
|
||||||
end = [
|
|
||||||
{
|
|
||||||
type = "keyboard"
|
|
||||||
show_scroll = false
|
|
||||||
icons.caps_on = ""
|
|
||||||
icons.layout_map.'English (US)' = "🇺🇸"
|
|
||||||
icons.layout_map.Ukrainian = "🇺🇦"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
| Selector | Description |
|
|
||||||
| -------------------------- | ------------------------------------------ |
|
|
||||||
| `.keyboard` | Keys box container widget. |
|
|
||||||
| `.keyboard .key` | Individual key indicator container widget. |
|
|
||||||
| `.keyboard .key.enabled` | Key indicator where key is toggled on. |
|
|
||||||
| `.keyboard .key.caps` | Capslock key indicator. |
|
|
||||||
| `.keyboard .key.num` | Num lock key indicator. |
|
|
||||||
| `.keyboard .key.scroll` | Scroll lock key indicator. |
|
|
||||||
| `.keyboard .key.image` | Key indicator image icon. |
|
|
||||||
| `.keyboard .key.text-icon` | Key indicator textual icon. |
|
|
||||||
| `.keyboard .layout` | Keyboard layout indicator. |
|
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
|
|
@ -8,13 +8,8 @@ For more advanced use-cases, use [custom](custom).
|
||||||
> Type: `label`
|
> Type: `label`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------------------|------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|---------|-------------------------------------------------|---------|------------------------|
|
||||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Text to show on label. |
|
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Text to show on label. |
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
|
|
||||||
Windows-style taskbar that displays running windows, grouped by program.
|
Windows-style taskbar that displays running windows, grouped by program.
|
||||||
Hovering over a program with multiple windows open shows a popup with each window.
|
Hovering over a program with multiple windows open shows a popup with each window.
|
||||||
Left clicking an icon/popup item focuses the program if it has any open instances or otherwise launches a new instance of the program.
|
Clicking an icon/popup item focuses or launches the program.
|
||||||
Middle clicking an icon always launches a new instance of the program.
|
|
||||||
Optionally displays a launchable set of favourites.
|
Optionally displays a launchable set of favourites.
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -14,23 +13,12 @@ Optionally displays a launchable set of favourites.
|
||||||
> Type: `launcher`
|
> Type: `launcher`
|
||||||
|
|
||||||
| | Type | Default | Description |
|
| | Type | Default | Description |
|
||||||
|-----------------------------|---------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------|------------|---------|-----------------------------------------------------------------------------------------------------|
|
||||||
| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher. |
|
| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher |
|
||||||
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
|
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
|
||||||
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
||||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||||
| `launch_command` | `string` | `gtk-launch {app_name}` | Command used to launch applications. |
|
|
||||||
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
|
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
|
||||||
| `minimize_focused` | `boolean` | `true` | Whether to minimize a focused window when its icon is clicked. Only minimizes single windows. |
|
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `end` | Location of the ellipses and where to truncate text from. Applies to application names when `show_names` is enabled. |
|
|
||||||
| `truncate.length` | `integer` | `null` | Fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate.max_length` | `integer` | `null` | Maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup.mode` | `'start'` or `'middle'` or `'end'` or `off` | `middle` | Location of the ellipses and where to truncate text from. Applies to window names within a group popup. |
|
|
||||||
| `truncate_popup.length` | `integer` | `null` | Fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup.max_length` | `integer` | `25` | Maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
| `page_size` | `integer` | `1000` | Number of items to show on a page. When the number of items is reached, controls appear which can be used to move forward/back through the list of items. |
|
|
||||||
| `icons.page_back` | `string` or [image](images) | `` | Icon to show for page back button. |
|
|
||||||
| `icons.page_forward` | `string` or [image](images) | `` | Icon to show for page forward button. |
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
||||||
|
|
@ -107,15 +95,12 @@ start:
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|--------------------------------------|---------------------------|
|
|-------------------------------|--------------------------|
|
||||||
| `.launcher` | Launcher widget box |
|
| `.launcher` | Launcher widget box |
|
||||||
| `.launcher .item` | App button |
|
| `.launcher .item` | App button |
|
||||||
| `.launcher .item.open` | App button (open app) |
|
| `.launcher .item.open` | App button (open app) |
|
||||||
| `.launcher .item.focused` | App button (focused app) |
|
| `.launcher .item.focused` | App button (focused app) |
|
||||||
| `.launcher .item.urgent` | App button (urgent app) |
|
| `.launcher .item.urgent` | App button (urgent app) |
|
||||||
| `.launcher .pagination` | Pagination controls box |
|
|
||||||
| `.launcher .pagination .btn-back` | Pagination back button |
|
|
||||||
| `.launcher .pagination .btn-forward` | Pagination forward button |
|
|
||||||
| `.popup-launcher` | Popup container |
|
| `.popup-launcher` | Popup container |
|
||||||
| `.popup-launcher .popup-item` | Window button in popup |
|
| `.popup-launcher .popup-item` | Window button in popup |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
Application menu that shows installed programs and optionally custom entries.
|
|
||||||
This works by reading all `.desktop` files on the system.
|
|
||||||
|
|
||||||
Clicking the menu button will open the main menu.
|
|
||||||
Clicking on any application category will open a sub-menu with any installed applications that match.
|
|
||||||
|
|
||||||
It is also possible to add custom categories and actions into the menu.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| | Type | Default | Description |
|
|
||||||
|-----------------------|------------------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `start` | `MenuEntry[]` | `[]` | Items to add to the start of the main menu. |
|
|
||||||
| `center` | `MenuEntry[]` | Default XDG menu | Items to add to the centre of the main menu. By default this shows a number of XDG entries that should cover all common applications. |
|
|
||||||
| `end` | `MenuEntry[]` | `[]` | Items to add to the end of the main menu. |
|
|
||||||
| `height` | `integer` | `null` | Height of the menu. Leave null to resize dynamically. |
|
|
||||||
| `width` | `integer` | `null` | Width of the menu. Leave null to resize dynamically. |
|
|
||||||
| `label` | `string` | `≡` | Label to show on the menu button on the bar. |
|
|
||||||
| `label_icon` | `string` | `null` | Icon to show on the menu button on the bar. |
|
|
||||||
| `label_icon_size` | `integer` | `16` | Size of the label_icon image. |
|
|
||||||
| `launch_command` | `string` | `gtk-launch {app_name}` | Command used to launch applications. |
|
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | Applies to popup. The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | Applies to popup. The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate.length` | `integer` | `null` | Applies to popup. The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate.max_length` | `integer` | `null` | Applies to popup. The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
|
|
||||||
|
|
||||||
### `MenuEntry`
|
|
||||||
|
|
||||||
Each entry can be one of three types:
|
|
||||||
|
|
||||||
- `xdg_entry` - Contains all applications matching the configured `categories`.
|
|
||||||
- `xdg_other` - Contains all applications not covered by `xdg_entry` categories.
|
|
||||||
- `custom` - Individual shell command entry.
|
|
||||||
|
|
||||||
| | Type | Default | Description |
|
|
||||||
|--------------|----------------------------------------|---------|----------------------------------------------------------------------------------------|
|
|
||||||
| `type` | `xdg_entry` or `xdg_other` or `custom` | | Type of the entry. |
|
|
||||||
| `label` | `string` | `''` | Label of the entry's button. |
|
|
||||||
| `icon` | `string` | `null` | Icon for the entry's button. |
|
|
||||||
| `categories` | `string[]` | `[]` | [`xfg_entry`] List of freedesktop.org categories to include in this entry's sub menu . |
|
|
||||||
| `on_click` | `string` | `''` | [`custom`] Shell command to execute when the entry's button is clicked |
|
|
||||||
|
|
||||||
### Default XDG Menu
|
|
||||||
|
|
||||||
Setting the `center` menu entries will override the default menu.
|
|
||||||
|
|
||||||
The default menu can be found in the `default` example files [here](https://github.com/jakestanger/ironbar/blob/examples/menu/).
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>JSON</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"start": [
|
|
||||||
{
|
|
||||||
"type": "menu",
|
|
||||||
"start": [
|
|
||||||
{
|
|
||||||
"type": "custom",
|
|
||||||
"label": "Terminal",
|
|
||||||
"on_click": "xterm"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"height": 440,
|
|
||||||
"width": 200,
|
|
||||||
"icon": "archlinux",
|
|
||||||
"label": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>TOML</summary>
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[start]]
|
|
||||||
type = "memu"
|
|
||||||
height = 400
|
|
||||||
width = 200
|
|
||||||
icon = "archlinux"
|
|
||||||
|
|
||||||
[[start.start]]
|
|
||||||
type = "custom"
|
|
||||||
label = "Terminal"
|
|
||||||
on_click = "xterm"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
start:
|
|
||||||
- type: "menu"
|
|
||||||
start:
|
|
||||||
- type: custom
|
|
||||||
label: Terminal
|
|
||||||
on_click: xterm
|
|
||||||
height: 440
|
|
||||||
width: 200
|
|
||||||
icon: archlinux
|
|
||||||
label: null
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Corn</summary>
|
|
||||||
|
|
||||||
```corn
|
|
||||||
{
|
|
||||||
start = [
|
|
||||||
{
|
|
||||||
type = "menu"
|
|
||||||
start = [
|
|
||||||
{
|
|
||||||
type = "custom"
|
|
||||||
label = "Terminal"
|
|
||||||
on_click = "xterm"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
height = 440
|
|
||||||
width = 200
|
|
||||||
icon = "archlinux"
|
|
||||||
label = null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
| Selector | Description |
|
|
||||||
|--------------------------------------|-----------------------------------|
|
|
||||||
| `.menu` | Menu button |
|
|
||||||
| `.popup-menu` | Main container of the popup |
|
|
||||||
| `.popup-menu .main` | Main menu of the menu |
|
|
||||||
| `.popup-menu .main .category` | Category button |
|
|
||||||
| `.popup-menu .main .category.open` | Open category button |
|
|
||||||
| `.popup-menu .main .main-start` | Container for `start` entries |
|
|
||||||
| `.popup-menu .main .main-center` | Container for `center` entries |
|
|
||||||
| `.popup-menu .main .main-end` | Container for `end` entries |
|
|
||||||
| `.popup-menu .sub-menu` | All sub-menus |
|
|
||||||
| `.popup-menu .sub-menu .application` | Application button within submenu |
|
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
|
|
@ -12,25 +12,13 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
|
||||||
> Type: `music`
|
> Type: `music`
|
||||||
|
|
||||||
| | Type | Default | Description |
|
| | Type | Default | Description |
|
||||||
|------------------------------------|------------------------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------|---------------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `player_type` | `'mpris'` or `'mpd'` | `mpris` | Whether to connect to MPRIS players or an MPD server. |
|
| `player_type` | `'mpris'` or `'mpd'` | `mpris` | Whether to connect to MPRIS players or an MPD server. |
|
||||||
| `format` | `string` | `{title} / {artist}` | Format string for the widget. More info below. |
|
| `format` | `string` | `{title} / {artist}` | Format string for the widget. More info below. |
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
| `truncate` | `'start'` or `'middle'` or `'end'` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
| `truncate.mode` | `'start'` or `'middle'` or `'end'` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate_popup_artist` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate_popup_artist.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate_popup_artist.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup_artist.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup_album` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate_popup_album.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate_popup_album.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup_album.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup_title` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate_popup_title.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate_popup_title.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate_popup_title.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
| `icons.play` | `string` or [image](images) | `` | Icon to show when playing. |
|
| `icons.play` | `string` or [image](images) | `` | Icon to show when playing. |
|
||||||
| `icons.pause` | `string` or [image](images) | `` | Icon to show when paused. |
|
| `icons.pause` | `string` or [image](images) | `` | Icon to show when paused. |
|
||||||
| `icons.prev` | `string` or [image](images) | `` | Icon to show on previous button. |
|
| `icons.prev` | `string` or [image](images) | `` | Icon to show on previous button. |
|
||||||
|
|
@ -45,6 +33,8 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
|
||||||
| `host` | `string` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. |
|
| `host` | `string` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. |
|
||||||
| `music_dir` | `string` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. |
|
| `music_dir` | `string` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. |
|
||||||
|
|
||||||
|
See [here](images) for information on images.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ Supports wired ethernet, wifi, cellular data and VPN connections among others.
|
||||||
|-------------|-----------|---------|-------------------------|
|
|-------------|-----------|---------|-------------------------|
|
||||||
| `icon_size` | `integer` | `24` | Size to render icon at. |
|
| `icon_size` | `integer` | `24` | Size to render icon at. |
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This module does not support module-level [layout options](module-level-options#layout).
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ Clicking the widget opens the SwayNC panel.
|
||||||
| `icons.open_some` | `string` | `` | Icon to show when the panel is open, with notifications. |
|
| `icons.open_some` | `string` | `` | Icon to show when the panel is open, with notifications. |
|
||||||
| `icons.open_dnd` | `string` | `` | Icon to show when the panel is open, with DnD enabled. Takes higher priority than count-based icons. |
|
| `icons.open_dnd` | `string` | `` | Icon to show when the panel is open, with DnD enabled. Takes higher priority than count-based icons. |
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This module does not support module-level [layout options](module-level-options#layout).
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ Displays one or more labels containing system information.
|
||||||
Separating information across several labels allows for styling each one independently.
|
Separating information across several labels allows for styling each one independently.
|
||||||
Pango markup is supported.
|
Pango markup is supported.
|
||||||
|
|
||||||
Options can be provided in a token to specify operations, units and formatting.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
@ -13,14 +11,14 @@ Options can be provided in a token to specify operations, units and formatting.
|
||||||
> Type: `sys_info`
|
> Type: `sys_info`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|--------------------|------------------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `format` | `string[]` | `null` | Array of strings including formatting tokens. For available tokens see below. |
|
| `format` | `string[]` | `null` | Array of strings including formatting tokens. For available tokens see below. |
|
||||||
| `interval` | `integer` or `Map` | `5` | Seconds between refreshing. Can be a single value for all data or a map of individual refresh values for different data types. |
|
| `interval` | `integer` or `Map` | `5` | Seconds between refreshing. Can be a single value for all data or a map of individual refresh values for different data types. |
|
||||||
| `interval.memory` | `integer` | `5` | Seconds between refreshing memory data. |
|
| `interval.memory` | `integer` | `5` | Seconds between refreshing memory data |
|
||||||
| `interval.cpu` | `integer` | `5` | Seconds between refreshing cpu data. |
|
| `interval.cpu` | `integer` | `5` | Seconds between refreshing cpu data |
|
||||||
| `interval.temps` | `integer` | `5` | Seconds between refreshing temperature data. |
|
| `interval.temps` | `integer` | `5` | Seconds between refreshing temperature data |
|
||||||
| `interval.disks` | `integer` | `5` | Seconds between refreshing disk data. |
|
| `interval.disks` | `integer` | `5` | Seconds between refreshing disk data |
|
||||||
| `interval.network` | `integer` | `5` | Seconds between refreshing network data. |
|
| `interval.network` | `integer` | `5` | Seconds between refreshing network data |
|
||||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the labels. |
|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the labels. |
|
||||||
| `direction` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | How the labels are laid out (not the rotation of an individual label). |
|
| `direction` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | How the labels are laid out (not the rotation of an individual label). |
|
||||||
|
|
||||||
|
|
@ -32,11 +30,12 @@ Options can be provided in a token to specify operations, units and formatting.
|
||||||
"end": [
|
"end": [
|
||||||
{
|
{
|
||||||
"format": [
|
"format": [
|
||||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
||||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||||
" {load_average_1} | {load_average_5} | {load_average_15}",
|
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||||
|
" {load_average:1} | {load_average:5} | {load_average:15}",
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
],
|
],
|
||||||
"interval": {
|
"interval": {
|
||||||
|
|
@ -61,12 +60,13 @@ Options can be provided in a token to specify operations, units and formatting.
|
||||||
[[end]]
|
[[end]]
|
||||||
type = 'sys_info'
|
type = 'sys_info'
|
||||||
format = [
|
format = [
|
||||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C',
|
||||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
' {memory_used} / {memory_total} GB ({memory_percent}%)',
|
||||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
'| {swap_used} / {swap_total} GB ({swap_percent}%)',
|
||||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
|
||||||
" {load_average_1} | {load_average_5} | {load_average_15}",
|
' {net_down:enp39s0} / {net_up:enp39s0} Mbps',
|
||||||
" {uptime}"
|
' {load_average:1} | {load_average:5} | {load_average:15}',
|
||||||
|
' {uptime}',
|
||||||
]
|
]
|
||||||
|
|
||||||
[end.interval]
|
[end.interval]
|
||||||
|
|
@ -87,12 +87,13 @@ temps = 5
|
||||||
```yaml
|
```yaml
|
||||||
end:
|
end:
|
||||||
- format:
|
- format:
|
||||||
- " {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C"
|
- ' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C'
|
||||||
- " {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)"
|
- ' {memory_used} / {memory_total} GB ({memory_percent}%)'
|
||||||
- " {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s"
|
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
||||||
- " {net_down@enp39s0} / {net_up@enp39s0} Mbps"
|
- ' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)'
|
||||||
- " {load_average_1} | {load_average_5} | {load_average_15}"
|
- ' {net_down:enp39s0} / {net_up:enp39s0} Mbps'
|
||||||
- " {uptime}"
|
- ' {load_average:1} | {load_average:5} | {load_average:15}'
|
||||||
|
- ' {uptime}'
|
||||||
interval:
|
interval:
|
||||||
cpu: 1
|
cpu: 1
|
||||||
disks: 300
|
disks: 300
|
||||||
|
|
@ -120,11 +121,12 @@ end:
|
||||||
interval.networks = 3
|
interval.networks = 3
|
||||||
|
|
||||||
format = [
|
format = [
|
||||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C"
|
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C"
|
||||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)"
|
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s"
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
||||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps"
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
||||||
" {load_average_1} | {load_average_5} | {load_average_15}"
|
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
||||||
|
" {load_average:1} | {load_average:5} | {load_average:15}"
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -136,179 +138,39 @@ end:
|
||||||
|
|
||||||
### Formatting Tokens
|
### Formatting Tokens
|
||||||
|
|
||||||
The below table lists the tokens which can be used in the `format` configuration option.
|
The following tokens can be used in the `format` configuration option:
|
||||||
More information about each of these and the additional options can be found further below.
|
|
||||||
|
|
||||||
| Token | Default Function | Default Unit | Default Formatting |
|
| Token | Description |
|
||||||
|--------------------------|------------------|--------------|--------------------|
|
|--------------------------|------------------------------------------------------------------------------------|
|
||||||
| **CPU** | | | |
|
| **CPU** | |
|
||||||
| `{cpu_frequency[#core]}` | `mean` | MHz | `.2` |
|
| `{cpu_percent}` | Total CPU utilisation percentage |
|
||||||
| `{cpu_percent[#core]}` | `mean` | % | `0<2` |
|
| **Memory** | |
|
||||||
| **Memory** | | | |
|
| `{memory_free}` | Memory free in GB. |
|
||||||
| `{memory_free}` | N/A | GB | `0<4.1` |
|
| `{memory_used}` | Memory used in GB. |
|
||||||
| `{memory_available}` | N/A | GB | `0<4.1` |
|
| `{memory_total}` | Memory total in GB. |
|
||||||
| `{memory_used}` | N/A | GB | `0<4.1` |
|
| `{memory_percent}` | Memory utilisation percentage. |
|
||||||
| `{memory_total}` | N/A | GB | `0<4.1` |
|
| `{swap_free}` | Swap free in GB. |
|
||||||
| `{memory_percent}` | N/A | GB | `0<4.1` |
|
| `{swap_used}` | Swap used in GB. |
|
||||||
| `{swap_free}` | N/A | GB | `0<4.1` |
|
| `{swap_total}` | Swap total in GB. |
|
||||||
| `{swap_used}` | N/A | GB | `0<4.1` |
|
| `{swap_percent}` | Swap utilisation percentage. |
|
||||||
| `{swap_total}` | N/A | GB | `0<4.1` |
|
| **Temperature** | |
|
||||||
| `{swap_percent}` | N/A | GB | `0<4.1` |
|
| `{temp_c:[sensor]}` | Temperature in degrees C. Replace `[sensor]` with the sensor label. |
|
||||||
| **Temperature** | | | |
|
| `{temp_f:[sensor]}` | Temperature in degrees F. Replace `[sensor]` with the sensor label. |
|
||||||
| `{temp_c[#sensor]}` | `max` | °C | |
|
| **Disk** | |
|
||||||
| `{temp_f[#sensor]}` | `max` | °F | |
|
| `{disk_free:[mount]}` | Disk free space in GB. Replace `[mount]` with the disk mountpoint. |
|
||||||
| **Disk** | | | |
|
| `{disk_used:[mount]}` | Disk used space in GB. Replace `[mount]` with the disk mountpoint. |
|
||||||
| `{disk_free[#mount]}` | `sum` | GB | |
|
| `{disk_total:[mount]}` | Disk total space in GB. Replace `[mount]` with the disk mountpoint. |
|
||||||
| `{disk_used[#mount]}` | `sum` | GB | |
|
| `{disk_percent:[mount]}` | Disk utilisation percentage. Replace `[mount]` with the disk mountpoint. |
|
||||||
| `{disk_total[#mount]}` | `sum` | GB | |
|
| **Network** | |
|
||||||
| `{disk_percent[#mount]}` | `sum` | % | |
|
| `{net_down:[adapter]}` | Average network download speed in Mbps. Replace `[adapter]` with the adapter name. |
|
||||||
| `{disk_read[#mount]}` | `sum` | MB/s | |
|
| `{net_up:[adapter]}` | Average network upload speed in Mbps. Replace `[adapter]` with the adapter name. |
|
||||||
| `{disk_write[#mount]}` | `sum` | MB/s | |
|
| **System** | |
|
||||||
| **Network** | | | |
|
| `{load_average:1}` | 1-minute load average. |
|
||||||
| `{net_down[#adapter]}` | `sum` | Mb/s | |
|
| `{load_average:5}` | 5-minute load average. |
|
||||||
| `{net_up[#adapter]}` | `sum` | Mb/s | |
|
| `{load_average:15}` | 15-minute load average. |
|
||||||
| **System** | | | |
|
| `{uptime}` | System uptime formatted as `HH:mm`. |
|
||||||
| `{load_average_1}` | N/A | - | `.2` |
|
|
||||||
| `{load_average_5}` | N/A | - | `.2` |
|
|
||||||
| `{load_average_15}` | N/A | - | `.2` |
|
|
||||||
| `{uptime}` | N/A | ??? | ??? |
|
|
||||||
|
|
||||||
#### Functions and names
|
For Intel CPUs, you can typically use `coretemp-Package-id-0` for the temperature sensor. For AMD, you can use `k10temp-Tccd1`.
|
||||||
|
|
||||||
Many of the tokens operate on a value set, as opposed to an individual value:
|
|
||||||
|
|
||||||
- CPU tokens operate on each physical thread.
|
|
||||||
- Temperature tokens operate on each sensor.
|
|
||||||
- Disk tokens operate on each mount.
|
|
||||||
- Network tokens operate on each adapter.
|
|
||||||
|
|
||||||
By default, these will apply a function to the full set to reduce them down to a single value.
|
|
||||||
The list of available functions is shown below:
|
|
||||||
|
|
||||||
| Function | Description |
|
|
||||||
|----------|-----------------------------------------|
|
|
||||||
| `sum` | Adds each value in the set. |
|
|
||||||
| `min` | Gets the smallest value in the set. |
|
|
||||||
| `max` | Gets the largest value in the set. |
|
|
||||||
| `mean` | Gets the mean average value of the set. |
|
|
||||||
|
|
||||||
It is also possible to get only a single value from the set by specifying a name instead of a function.
|
|
||||||
|
|
||||||
| Token category | Valid name |
|
|
||||||
|----------------|-------------------------------------------------------------------------|
|
|
||||||
| CPU | A CPU thread, eg `cpu0`, `cpu1`, ... |
|
|
||||||
| Temperature | A sensor name, eg `CPUTIN`. These line up with the output of `sensors`. |
|
|
||||||
| Disk | A disk mountpoint, eg `/`, `/home`, ... |
|
|
||||||
| Network | An adapter name, eg `eth0` or `enp30s0`. |
|
|
||||||
|
|
||||||
|
|
||||||
To specify a name or function, use a `@`. For example, to show disk percent for `/home`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{disk_percent@/home}%"
|
|
||||||
```
|
|
||||||
|
|
||||||
To show total CPU utilization where each core represents 100% (like `htop` etc):
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{cpu_percent@sum}%"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Prefixes and units
|
|
||||||
|
|
||||||
For tokens which return an appropriate unit, you can specify the SI prefix (or unit in some special cases).
|
|
||||||
The following options can be supplied:
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---------|-------|
|
|
||||||
| Kilo | `k` |
|
|
||||||
| Mega | `M` |
|
|
||||||
| Giga | `G` |
|
|
||||||
| Tera | `T` |
|
|
||||||
| Peta | `P` |
|
|
||||||
| | |
|
|
||||||
| Kibi | `ki` |
|
|
||||||
| Mebi | `Mi` |
|
|
||||||
| Gibi | `Gi` |
|
|
||||||
| Tebi | `Ti` |
|
|
||||||
| Pebi | `Pi` |
|
|
||||||
| | |
|
|
||||||
| Kilobit | `kb` |
|
|
||||||
| Megabit | `Mb` |
|
|
||||||
| Gigabit | `Gb` |
|
|
||||||
|
|
||||||
To specify a prefix or unit, use a `#`. For example, to show free total disk space in terabytes:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{disk_free#T} TB"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Formatting
|
|
||||||
|
|
||||||
To control the formatting of the resultant number,
|
|
||||||
a subset of Rust's string formatting is implemented. This includes:
|
|
||||||
|
|
||||||
- Width
|
|
||||||
- Fill/Alignment
|
|
||||||
- Precision
|
|
||||||
|
|
||||||
Formatting is specified with a `:` and MUST be the last part of a token.
|
|
||||||
|
|
||||||
##### Width
|
|
||||||
|
|
||||||
The width controls the minimum string length of the value.
|
|
||||||
Specifying just a width will left-pad the value with `0` until the value reaches the target length.
|
|
||||||
|
|
||||||
The width can be any value from `1-9`. Larger values are not supported.
|
|
||||||
|
|
||||||
For example, to render CPU usage as `045%`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{cpu_usage:3}%"
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Fill/Alignment
|
|
||||||
|
|
||||||
These options can be used to control the `width` property.
|
|
||||||
|
|
||||||
To specify the fill and alignment, prefix the width with a character and a direction.
|
|
||||||
Fill characters can be any single UTF-8 character EXCEPT 1-9. Alignment must be one of:
|
|
||||||
|
|
||||||
- `<` - Left fill
|
|
||||||
- `^` - Center fill
|
|
||||||
- `>` - Right fill
|
|
||||||
|
|
||||||
For example, to render CPU usage as ` 45%`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{cpu_usage: <3}%"
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Precision
|
|
||||||
|
|
||||||
The number of decimal places a value is shown to can be controlled using precision.
|
|
||||||
Any value is supported.
|
|
||||||
|
|
||||||
To specify precision, include a `.` followed by the value. If other options are supplied, this MUST come after.
|
|
||||||
|
|
||||||
For example, to render used disk space to 2dp:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{disk_used:.2} GB"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Combining Options
|
|
||||||
|
|
||||||
Each of the token options can be combined to create more complex solutions.
|
|
||||||
|
|
||||||
Putting it all together, you could show the free disk space on your `/home` partition in terabytes,
|
|
||||||
left-padded with spaces to a min width of 5, and shown to 2dp as follows:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"{disk_used@/home#T: <5.2} TB"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ Displays a fully interactive icon tray using the KDE `libappindicator` protocol.
|
||||||
> Type: `tray`
|
> Type: `tray`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|----------------------|------------------------------------------------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------------|-----------|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `direction` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | Matches bar orientation | The direction in which to pack tray icons. |
|
| `direction` | `string` | `left_to_right` if bar is horizontal, `top_to_bottom` otherwise | Direction to display the tray items. Possible values: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left` |
|
||||||
| `icon_size` | `integer` | `16` | Size in pixels to display tray icons as. |
|
| `icon_size` | `integer` | `16` | Size in pixels to display tray icons as. |
|
||||||
| `prefer_theme_icons` | `bool` | `true` | Requests that icons from the theme be used over the item-provided item. Most items only provide one or the other so this will have no effect in most circumstances. |
|
| `prefer_theme_icons` | `bool` | `true` | Requests that icons from the theme be used over the item-provided item. Most items only provide one or the other so this will have no effect in most circumstances. |
|
||||||
|
|
||||||
|
|
@ -55,10 +55,12 @@ end:
|
||||||
|
|
||||||
```corn
|
```corn
|
||||||
{
|
{
|
||||||
end = [{
|
end = [
|
||||||
|
{
|
||||||
type = "tray"
|
type = "tray"
|
||||||
direction = "top_to_bottom"
|
direction = "top_to_bottom"
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
Displays system power information such as the battery percentage, and estimated time to empty.
|
Displays system power information such as the battery percentage, and estimated time to empty.
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This module requires that `upower` is installed and its service running.
|
|
||||||
|
|
||||||
`TODO: ADD SCREENSHOT`
|
`TODO: ADD SCREENSHOT`
|
||||||
|
|
||||||
[//]: # ()
|
[//]: # ()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
Displays the current volume level.
|
Displays the current volume level.
|
||||||
Clicking on the widget opens a volume mixer, which allows you to change the device output level,
|
Clicking on the widget opens a volume mixer, which allows you to change the device output level,
|
||||||
the default playback device, and control application volume levels individually.
|
the default playback device, and control application volume levels individually.
|
||||||
Use `truncate` option to control the display of application titles in the volume mixer.
|
|
||||||
|
|
||||||
This requires PulseAudio to function (`pipewire-pulse` is supported).
|
This requires PulseAudio to function (`pipewire-pulse` is supported).
|
||||||
|
|
||||||
|
|
@ -12,17 +11,13 @@ This requires PulseAudio to function (`pipewire-pulse` is supported).
|
||||||
> Type: `volume`
|
> Type: `volume`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------------------|------------------------------------------------------|------------------------|----------------------------------------------------------------------------------------------------------------|
|
|-----------------------|----------|------------------------|----------------------------------------------------------------------------------------------------------------|
|
||||||
| `format` | `string` | `{icon} {percentage}%` | Format string to use for the widget button label. |
|
| `format` | `string` | `{icon} {percentage}%` | Format string to use for the widget button label. |
|
||||||
| `max_volume` | `float` | `100` | Maximum value to allow volume sliders to reach. Pulse supports values > 100 but this may result in distortion. |
|
| `max_volume` | `float` | `100` | Maximum value to allow volume sliders to reach. Pulse supports values > 100 but this may result in distortion. |
|
||||||
| `icons.volume_high` | `string` | `` | Icon to show for high volume levels. |
|
| `icons.volume_high` | `string` | `` | Icon to show for high volume levels. |
|
||||||
| `icons.volume_medium` | `string` | `` | Icon to show for medium volume levels. |
|
| `icons.volume_medium` | `string` | `` | Icon to show for medium volume levels. |
|
||||||
| `icons.volume_low` | `string` | `` | Icon to show for low volume levels. |
|
| `icons.volume_low` | `string` | `` | Icon to show for low volume levels. |
|
||||||
| `icons.muted` | `string` | `` | Icon to show for muted outputs. |
|
| `icons.muted` | `string` | `` | Icon to show for muted outputs. |
|
||||||
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
@ -34,7 +29,6 @@ This requires PulseAudio to function (`pipewire-pulse` is supported).
|
||||||
"type": "volume",
|
"type": "volume",
|
||||||
"format": "{icon} {percentage}%",
|
"format": "{icon} {percentage}%",
|
||||||
"max_volume": 100,
|
"max_volume": 100,
|
||||||
"truncate": "middle",
|
|
||||||
"icons": {
|
"icons": {
|
||||||
"volume_high": "",
|
"volume_high": "",
|
||||||
"volume_medium": "",
|
"volume_medium": "",
|
||||||
|
|
@ -57,7 +51,6 @@ This requires PulseAudio to function (`pipewire-pulse` is supported).
|
||||||
type = "volume"
|
type = "volume"
|
||||||
format = "{icon} {percentage}%"
|
format = "{icon} {percentage}%"
|
||||||
max_volume = 100
|
max_volume = 100
|
||||||
truncate = "middle"
|
|
||||||
|
|
||||||
[end.icons]
|
[end.icons]
|
||||||
volume_high = ""
|
volume_high = ""
|
||||||
|
|
@ -76,7 +69,6 @@ end:
|
||||||
- type: "volume"
|
- type: "volume"
|
||||||
format: "{icon} {percentage}%"
|
format: "{icon} {percentage}%"
|
||||||
max_volume: 100
|
max_volume: 100
|
||||||
truncate: "middle"
|
|
||||||
icons:
|
icons:
|
||||||
volume_high: ""
|
volume_high: ""
|
||||||
volume_medium: ""
|
volume_medium: ""
|
||||||
|
|
@ -96,7 +88,6 @@ end:
|
||||||
type = "volume"
|
type = "volume"
|
||||||
format = "{icon} {percentage}%"
|
format = "{icon} {percentage}%"
|
||||||
max_volume = 100
|
max_volume = 100
|
||||||
truncate = "end"
|
|
||||||
icons.volume_high = ""
|
icons.volume_high = ""
|
||||||
icons.volume_medium = ""
|
icons.volume_medium = ""
|
||||||
icons.volume_low = ""
|
icons.volume_low = ""
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
> [!IMPORTANT]
|
> ⚠ **This module is currently only supported on Sway and Hyprland**
|
||||||
> This module is currently only supported on Sway, Hyprland and Niri**
|
|
||||||
|
|
||||||
Shows all current workspaces. Clicking a workspace changes focus to it.
|
Shows all current workspaces. Clicking a workspace changes focus to it.
|
||||||
|
|
||||||
|
|
@ -10,13 +9,13 @@ Shows all current workspaces. Clicking a workspace changes focus to it.
|
||||||
> Type: `workspaces`
|
> Type: `workspaces`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|----------------|---------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------|---------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `name_map` | `Map<string, string or image>` | `{}` | A map of actual workspace names to their display labels/images. Workspaces use their actual name if not present in the map. See [here](images) for information on images. |
|
| `name_map` | `Map<string, string or image>` | `{}` | A map of actual workspace names to their display labels/images. Workspaces use their actual name if not present in the map. See [here](images) for information on images. |
|
||||||
| `favorites` | `Map<string, string[]>` or `string[]` | `[]` | Workspaces to always show. This can be for all monitors, or a map to set per monitor. |
|
| `favorites` | `Map<string, string[]>` or `string[]` | `[]` | Workspaces to always show. This can be for all monitors, or a map to set per monitor. |
|
||||||
| `hidden` | `string[]` | `[]` | A list of workspace names to never show |
|
| `hidden` | `string[]` | `[]` | A list of workspace names to never show |
|
||||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||||
| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. |
|
| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. |
|
||||||
| `sort` | `'added'` or `'label'` or `'name'` | `label` | The method used for sorting workspaces. `added` always appends to the end, `label` sorts by displayed value, and `name` sorts by workspace name. |
|
| `sort` | `'added'` or `'alphanumeric'` | `alphanumeric` | The method used for sorting workspaces. `added` always appends to the end, `alphanumeric` sorts by number/name. |
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
@ -100,13 +99,12 @@ end:
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
| ------------------------------ | ------------------------------------------------------- |
|
|--------------------------------|--------------------------------------|
|
||||||
| `.workspaces` | Workspaces widget box |
|
| `.workspaces` | Workspaces widget box |
|
||||||
| `.workspaces .item` | Workspace button |
|
| `.workspaces .item` | Workspace button |
|
||||||
| `.workspaces .item.focused` | Workspace button (workspace focused) |
|
| `.workspaces .item.focused` | Workspace button (workspace focused) |
|
||||||
| `.workspaces .item.visible` | Workspace button (workspace visible, including focused) |
|
| `.workspaces .item.visible` | Workspace button (workspace visible, including focused) |
|
||||||
| `.workspaces .item.urgent` | Workspace button (workspace contains urgent window) |
|
| `.workspaces .item.inactive` | Workspace button (favourite, not currently open)
|
||||||
| `.workspaces .item.inactive` | Workspace button (favourite, not currently open) |
|
|
||||||
| `.workspaces .item .icon` | Workspace button icon (any type) |
|
| `.workspaces .item .icon` | Workspace button icon (any type) |
|
||||||
| `.workspaces .item .text-icon` | Workspace button icon (textual only) |
|
| `.workspaces .item .text-icon` | Workspace button icon (textual only) |
|
||||||
| `.workspaces .item .image` | Workspace button icon (image only) |
|
| `.workspaces .item .image` | Workspace button icon (image only) |
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,12 @@ let {
|
||||||
interval.networks = 3
|
interval.networks = 3
|
||||||
|
|
||||||
format = [
|
format = [
|
||||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C"
|
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C"
|
||||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)"
|
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s"
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
||||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps"
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
||||||
" {load_average1} | {load_average5} | {load_average15}"
|
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
||||||
|
" {load_average:1} | {load_average:5} | {load_average:15}"
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"$schema": "https://f.jstanger.dev/github/ironbar/schema.json",
|
||||||
"anchor_to_edges": true,
|
"anchor_to_edges": true,
|
||||||
"position": "bottom",
|
"position": "bottom",
|
||||||
"icon_theme": "Paper",
|
"icon_theme": "Paper",
|
||||||
|
|
@ -63,11 +64,12 @@
|
||||||
"networks": 3
|
"networks": 3
|
||||||
},
|
},
|
||||||
"format": [
|
"format": [
|
||||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
||||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||||
" {load_average1} | {load_average5} | {load_average15}",
|
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||||
|
" {load_average:1} | {load_average:5} | {load_average:15}",
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,12 @@ interval = 500
|
||||||
[[end]]
|
[[end]]
|
||||||
type = "sys_info"
|
type = "sys_info"
|
||||||
format = [
|
format = [
|
||||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
||||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||||
" {load_average1} | {load_average5} | {load_average15}",
|
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||||
|
" {load_average:1} | {load_average:5} | {load_average:15}",
|
||||||
" {uptime}",
|
" {uptime}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
$schema: https://f.jstanger.dev/github/ironbar/schema.json
|
||||||
anchor_to_edges: true
|
anchor_to_edges: true
|
||||||
position: bottom
|
position: bottom
|
||||||
icon_theme: Paper
|
icon_theme: Paper
|
||||||
|
|
@ -43,11 +44,12 @@ end:
|
||||||
disks: 300
|
disks: 300
|
||||||
networks: 3
|
networks: 3
|
||||||
format:
|
format:
|
||||||
- {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C
|
- {cpu_percent}% | {temp_c:k10temp-Tccd1}°C
|
||||||
- {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)
|
- {memory_used} / {memory_total} GB ({memory_percent}%)
|
||||||
- {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s
|
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
||||||
- {net_down@enp39s0} / {net_up@enp39s0} Mbps
|
- {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)
|
||||||
- {load_average1} | {load_average5} | {load_average15}
|
- {net_down:enp39s0} / {net_up:enp39s0} Mbps
|
||||||
|
- {load_average:1} | {load_average:5} | {load_average:15}
|
||||||
- {uptime}
|
- {uptime}
|
||||||
- type: volume
|
- type: volume
|
||||||
format: '{icon} {percentage}%'
|
format: '{icon} {percentage}%'
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
let {
|
|
||||||
$menu = [
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Accessories"
|
|
||||||
icon = "accessories"
|
|
||||||
categories = [
|
|
||||||
"Accessibility"
|
|
||||||
"Core"
|
|
||||||
"Legacy"
|
|
||||||
"Utility"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Development"
|
|
||||||
icon = "applications-development"
|
|
||||||
categories = [
|
|
||||||
"Development"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Education"
|
|
||||||
icon = "applications-education"
|
|
||||||
categories = [
|
|
||||||
"Education"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Games"
|
|
||||||
icon = "applications-games"
|
|
||||||
categories = [
|
|
||||||
"Games"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Graphics"
|
|
||||||
icon = "applications-graphics"
|
|
||||||
categories = [
|
|
||||||
"Graphics"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Multimedia"
|
|
||||||
icon = "applications-multimedia"
|
|
||||||
categories = [
|
|
||||||
"Audio"
|
|
||||||
"Video"
|
|
||||||
"AudioVideo"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Network"
|
|
||||||
icon = "applications-internet"
|
|
||||||
categories = [
|
|
||||||
"Network"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Office"
|
|
||||||
icon = "applications-office"
|
|
||||||
categories = [
|
|
||||||
"Office"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Science"
|
|
||||||
icon = "applications-science"
|
|
||||||
categories = [
|
|
||||||
"Science"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "System"
|
|
||||||
icon = "applications-system"
|
|
||||||
categories = [
|
|
||||||
"Emulator"
|
|
||||||
"System"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{ type = "xdg_other" }
|
|
||||||
{
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Settings"
|
|
||||||
icon = "preferences-system"
|
|
||||||
categories = [
|
|
||||||
"Settings"
|
|
||||||
"Screensaver"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} in {
|
|
||||||
start = [ { type = "menu" center = $menu } ]
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
{
|
|
||||||
"start": [
|
|
||||||
{
|
|
||||||
"type": "menu",
|
|
||||||
"center": [
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Accessories",
|
|
||||||
"icon": "accessories",
|
|
||||||
"categories": [
|
|
||||||
"Accessibility",
|
|
||||||
"Core",
|
|
||||||
"Legacy",
|
|
||||||
"Utility"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Development",
|
|
||||||
"icon": "applications-development",
|
|
||||||
"categories": [
|
|
||||||
"Development"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Education",
|
|
||||||
"icon": "applications-education",
|
|
||||||
"categories": [
|
|
||||||
"Education"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Games",
|
|
||||||
"icon": "applications-games",
|
|
||||||
"categories": [
|
|
||||||
"Games"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Graphics",
|
|
||||||
"icon": "applications-graphics",
|
|
||||||
"categories": [
|
|
||||||
"Graphics"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Multimedia",
|
|
||||||
"icon": "applications-multimedia",
|
|
||||||
"categories": [
|
|
||||||
"Audio",
|
|
||||||
"Video",
|
|
||||||
"AudioVideo"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Network",
|
|
||||||
"icon": "applications-internet",
|
|
||||||
"categories": [
|
|
||||||
"Network"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Office",
|
|
||||||
"icon": "applications-office",
|
|
||||||
"categories": [
|
|
||||||
"Office"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Science",
|
|
||||||
"icon": "applications-science",
|
|
||||||
"categories": [
|
|
||||||
"Science"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "System",
|
|
||||||
"icon": "applications-system",
|
|
||||||
"categories": [
|
|
||||||
"Emulator",
|
|
||||||
"System"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_other"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xdg_entry",
|
|
||||||
"label": "Settings",
|
|
||||||
"icon": "preferences-system",
|
|
||||||
"categories": [
|
|
||||||
"Settings",
|
|
||||||
"Screensaver"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
[[start]]
|
|
||||||
type = "menu"
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Accessories"
|
|
||||||
icon = "accessories"
|
|
||||||
categories = [
|
|
||||||
"Accessibility",
|
|
||||||
"Core",
|
|
||||||
"Legacy",
|
|
||||||
"Utility",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Development"
|
|
||||||
icon = "applications-development"
|
|
||||||
categories = ["Development"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Education"
|
|
||||||
icon = "applications-education"
|
|
||||||
categories = ["Education"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Games"
|
|
||||||
icon = "applications-games"
|
|
||||||
categories = ["Games"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Graphics"
|
|
||||||
icon = "applications-graphics"
|
|
||||||
categories = ["Graphics"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Multimedia"
|
|
||||||
icon = "applications-multimedia"
|
|
||||||
categories = [
|
|
||||||
"Audio",
|
|
||||||
"Video",
|
|
||||||
"AudioVideo",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Network"
|
|
||||||
icon = "applications-internet"
|
|
||||||
categories = ["Network"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Office"
|
|
||||||
icon = "applications-office"
|
|
||||||
categories = ["Office"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Science"
|
|
||||||
icon = "applications-science"
|
|
||||||
categories = ["Science"]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "System"
|
|
||||||
icon = "applications-system"
|
|
||||||
categories = [
|
|
||||||
"Emulator",
|
|
||||||
"System",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_other"
|
|
||||||
|
|
||||||
[[start.center]]
|
|
||||||
type = "xdg_entry"
|
|
||||||
label = "Settings"
|
|
||||||
icon = "preferences-system"
|
|
||||||
categories = [
|
|
||||||
"Settings",
|
|
||||||
"Screensaver",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
start:
|
|
||||||
- type: menu
|
|
||||||
center:
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Accessories
|
|
||||||
icon: accessories
|
|
||||||
categories:
|
|
||||||
- Accessibility
|
|
||||||
- Core
|
|
||||||
- Legacy
|
|
||||||
- Utility
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Development
|
|
||||||
icon: applications-development
|
|
||||||
categories:
|
|
||||||
- Development
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Education
|
|
||||||
icon: applications-education
|
|
||||||
categories:
|
|
||||||
- Education
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Games
|
|
||||||
icon: applications-games
|
|
||||||
categories:
|
|
||||||
- Games
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Graphics
|
|
||||||
icon: applications-graphics
|
|
||||||
categories:
|
|
||||||
- Graphics
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Multimedia
|
|
||||||
icon: applications-multimedia
|
|
||||||
categories:
|
|
||||||
- Audio
|
|
||||||
- Video
|
|
||||||
- AudioVideo
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Network
|
|
||||||
icon: applications-internet
|
|
||||||
categories:
|
|
||||||
- Network
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Office
|
|
||||||
icon: applications-office
|
|
||||||
categories:
|
|
||||||
- Office
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Science
|
|
||||||
icon: applications-science
|
|
||||||
categories:
|
|
||||||
- Science
|
|
||||||
- type: xdg_entry
|
|
||||||
label: System
|
|
||||||
icon: applications-system
|
|
||||||
categories:
|
|
||||||
- Emulator
|
|
||||||
- System
|
|
||||||
- type: xdg_other
|
|
||||||
- type: xdg_entry
|
|
||||||
label: Settings
|
|
||||||
icon: preferences-system
|
|
||||||
categories:
|
|
||||||
- Settings
|
|
||||||
- Screensaver
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -201,10 +201,6 @@ scale trough {
|
||||||
background-color: @color_bg_dark;
|
background-color: @color_bg_dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspaces .item.urgent {
|
|
||||||
background-color: @color_urgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaces .item:hover {
|
.workspaces .item:hover {
|
||||||
box-shadow: inset 0 -3px;
|
box-shadow: inset 0 -3px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
60
examples/test.corn
Normal file
60
examples/test.corn
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
let {
|
||||||
|
$config_dir = "/home/jake/.config/ironbar"
|
||||||
|
|
||||||
|
$workspaces = { type = "workspaces" }
|
||||||
|
$launcher = { type = "launcher" }
|
||||||
|
$volume = {
|
||||||
|
type = "volume"
|
||||||
|
format = "{icon} {percentage}%"
|
||||||
|
max_volume = 100
|
||||||
|
icons.volume_high = ""
|
||||||
|
icons.volume_medium = ""
|
||||||
|
icons.volume_low = ""
|
||||||
|
icons.muted = ""
|
||||||
|
}
|
||||||
|
$network_manager = { type = "networkmanager" }
|
||||||
|
$clock = {
|
||||||
|
type = "clock"
|
||||||
|
// disable_popup = true
|
||||||
|
// format = "<span color='#f2777a'>%d/%m/%Y</span> <span color='#69c'>%H:%M:%S</span>"
|
||||||
|
}
|
||||||
|
$tray = { type = "tray" prefer_theme_icons = false }
|
||||||
|
|
||||||
|
// $label = { type = "label" label = "<span color='#color'>hello</span>" }
|
||||||
|
$label = { type = "label" label = "#random" }
|
||||||
|
$clipboard = { type = "clipboard" }
|
||||||
|
|
||||||
|
$notifications = {
|
||||||
|
type = "notifications"
|
||||||
|
show_count = true
|
||||||
|
|
||||||
|
icons.closed_none = ""
|
||||||
|
icons.closed_some = ""
|
||||||
|
icons.closed_dnd = ""
|
||||||
|
icons.open_none = ""
|
||||||
|
icons.open_some = ""
|
||||||
|
icons.open_dnd = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
$focused = { type = "focused" }
|
||||||
|
|
||||||
|
$cairo = { type = "cairo" path = "$config_dir/clock.lua" frequency = 50 width = 300 height = 300 }
|
||||||
|
|
||||||
|
$custom = {
|
||||||
|
type = "custom"
|
||||||
|
bar = [ { type = "button" on_click = "popup:toggle" widgets = [ $focused ] } ]
|
||||||
|
popup = [ { type = "box" orientation = "v" widgets = [ $clock $cairo ] } ]
|
||||||
|
}
|
||||||
|
|
||||||
|
$mpris = { type = "music" }
|
||||||
|
} in {
|
||||||
|
// ironvar_defaults.color = "red"
|
||||||
|
|
||||||
|
position = "bottom"
|
||||||
|
|
||||||
|
icon_theme = "Paper"
|
||||||
|
|
||||||
|
start = [ $workspaces $label ]
|
||||||
|
center = [ $custom ]
|
||||||
|
end = [ $notifications $clock ]
|
||||||
|
}
|
||||||
86
flake.lock
generated
86
flake.lock
generated
|
|
@ -1,17 +1,22 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-compat": {
|
"crane": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747046372,
|
"lastModified": 1722704454,
|
||||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
"narHash": "sha256-lcut8uZMSa80z+aWpxg+9nM8BKWtpU59rtcpMXtHd1Q=",
|
||||||
"owner": "edolstra",
|
"owner": "ipetkov",
|
||||||
"repo": "flake-compat",
|
"repo": "crane",
|
||||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
"rev": "852a59f9672c3413d75bca2b3e9cb4c661cacfc3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "edolstra",
|
"owner": "ipetkov",
|
||||||
"repo": "flake-compat",
|
"repo": "crane",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -20,11 +25,11 @@
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1745925850,
|
"lastModified": 1721727458,
|
||||||
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
|
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
|
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -33,44 +38,27 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1689347949,
|
|
||||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default-linux",
|
|
||||||
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default-linux",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751251929,
|
"lastModified": 1722640603,
|
||||||
"narHash": "sha256-IJWIzZSkBsDzS7iS/iwSwur+xFkWqeLYC4kdf8ObtOM=",
|
"narHash": "sha256-TcXjLVNd3VeH1qKPH335Tc4RbFDbZQX+d7rqnDUoRaY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b95255df2360a45ddbb03817a68869d5cb01bf96",
|
"rev": "81610abc161d4021b29199aa464d6a1a521e0cc9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"id": "nixpkgs",
|
||||||
"ref": "nixpkgs-unstable",
|
"type": "indirect"
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751011381,
|
"lastModified": 1722630782,
|
||||||
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=",
|
"narHash": "sha256-hMyG9/WlUi0Ho9VkRrrez7SeNlDzLxalm9FwY7n/Noo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7",
|
"rev": "d04953086551086b44b6f3c6b7eeb26294f207da",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -82,10 +70,30 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"crane": "crane",
|
||||||
"naersk": "naersk",
|
"naersk": "naersk",
|
||||||
"nix-systems": "nix-systems",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"nixpkgs": "nixpkgs_2"
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1722738111,
|
||||||
|
"narHash": "sha256-cWD5pCs9AYb+512/yCx9D0Pl5KcmyuXHeJpsDw/D1vs=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "27ec296d93cb4b2d03e8cbd019b1b4cde8c34280",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
258
flake.nix
258
flake.nix
|
|
@ -3,88 +3,224 @@
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
flake-compat.url = "github:edolstra/flake-compat";
|
|
||||||
naersk.url = "github:nix-community/naersk";
|
rust-overlay = {
|
||||||
nix-systems.url = "github:nix-systems/default-linux";
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
crane = {
|
||||||
self,
|
url = "github:ipetkov/crane";
|
||||||
nixpkgs,
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
naersk,
|
};
|
||||||
nix-systems,
|
|
||||||
...
|
naersk.url = "github:nix-community/naersk";
|
||||||
}: let
|
};
|
||||||
forAllSystems = function:
|
|
||||||
nixpkgs.lib.genAttrs (import nix-systems) (system: function nixpkgs.legacyPackages.${system});
|
outputs = { self, nixpkgs, rust-overlay, crane, naersk, ... }:
|
||||||
mkDate = longDate: (nixpkgs.lib.concatStringsSep "-" [
|
let
|
||||||
|
inherit (nixpkgs) lib;
|
||||||
|
|
||||||
|
genSystems = lib.genAttrs [ "aarch64-linux" "x86_64-linux" ];
|
||||||
|
|
||||||
|
pkgsFor = system:
|
||||||
|
import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
|
||||||
|
overlays = [ self.overlays.default rust-overlay.overlays.default ];
|
||||||
|
};
|
||||||
|
|
||||||
|
mkRustToolchain = pkgs:
|
||||||
|
pkgs.rust-bin.stable.latest.default.override {
|
||||||
|
extensions = [ "rust-src" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
overlays.default = final: prev:
|
||||||
|
let
|
||||||
|
rust = mkRustToolchain final;
|
||||||
|
|
||||||
|
craneLib = (crane.mkLib final).overrideToolchain rust;
|
||||||
|
|
||||||
|
naersk' = prev.callPackage naersk {
|
||||||
|
cargo = rust;
|
||||||
|
rustc = rust;
|
||||||
|
};
|
||||||
|
|
||||||
|
rustPlatform = prev.makeRustPlatform {
|
||||||
|
cargo = rust;
|
||||||
|
rustc = rust;
|
||||||
|
};
|
||||||
|
|
||||||
|
props = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||||
|
|
||||||
|
mkDate = longDate:
|
||||||
|
(lib.concatStringsSep "-" [
|
||||||
(builtins.substring 0 4 longDate)
|
(builtins.substring 0 4 longDate)
|
||||||
(builtins.substring 4 2 longDate)
|
(builtins.substring 4 2 longDate)
|
||||||
(builtins.substring 6 2 longDate)
|
(builtins.substring 6 2 longDate)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
builder = "naersk";
|
||||||
in {
|
in {
|
||||||
# Devshell
|
|
||||||
devShells = forAllSystems (pkgs: {
|
|
||||||
default = pkgs.mkShell {
|
|
||||||
packages = builtins.attrValues {
|
|
||||||
inherit
|
|
||||||
(pkgs)
|
|
||||||
cargo
|
|
||||||
clippy
|
|
||||||
rustfmt
|
|
||||||
gtk3
|
|
||||||
gtk-layer-shell
|
|
||||||
gcc
|
|
||||||
openssl
|
|
||||||
libdbusmenu-gtk3
|
|
||||||
libpulseaudio
|
|
||||||
libinput
|
|
||||||
libevdev
|
|
||||||
luajit
|
|
||||||
;
|
|
||||||
inherit (pkgs.luajitPackages) lgi;
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.pkg-config
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
# Packages
|
|
||||||
packages = forAllSystems (pkgs: {
|
|
||||||
ironbar = let
|
ironbar = let
|
||||||
props = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
version = props.package.version + "+date="
|
||||||
version =
|
+ (mkDate (self.lastModifiedDate or "19700101")) + "_"
|
||||||
props.package.version
|
|
||||||
+ "+date="
|
|
||||||
+ (mkDate (self.lastModifiedDate or "19700101"))
|
|
||||||
+ "_"
|
|
||||||
+ (self.shortRev or "dirty");
|
+ (self.shortRev or "dirty");
|
||||||
naersk' = pkgs.callPackage naersk {};
|
in if builder == "crane" then
|
||||||
in
|
prev.callPackage ./nix/default.nix {
|
||||||
pkgs.callPackage ./nix/package.nix {
|
|
||||||
inherit version;
|
inherit version;
|
||||||
naersk = naersk';
|
inherit rustPlatform;
|
||||||
|
builderName = builder;
|
||||||
|
builder = craneLib;
|
||||||
|
}
|
||||||
|
else if builder == "naersk" then
|
||||||
|
prev.callPackage ./nix/default.nix {
|
||||||
|
inherit version;
|
||||||
|
inherit rustPlatform;
|
||||||
|
builderName = builder;
|
||||||
|
builder = naersk';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
prev.callPackage ./nix/default.nix {
|
||||||
|
inherit version;
|
||||||
|
inherit rustPlatform;
|
||||||
|
builderName = builder;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
default = self.packages.${pkgs.hostPlatform.system}.ironbar;
|
packages = genSystems (system:
|
||||||
|
let pkgs = pkgsFor system;
|
||||||
|
in (self.overlays.default pkgs pkgs) // {
|
||||||
|
default = self.packages.${system}.ironbar;
|
||||||
});
|
});
|
||||||
|
|
||||||
# Apps
|
apps = genSystems (system:
|
||||||
apps = forAllSystems (pkgs: {
|
let pkgs = pkgsFor system;
|
||||||
|
in rec {
|
||||||
ironbar = {
|
ironbar = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = pkgs.lib.getExe self.packages.${pkgs.hostPlatform.system}.ironbar;
|
program = "${pkgs.ironbar}/bin/ironbar";
|
||||||
};
|
};
|
||||||
default = self.apps.ironbar;
|
|
||||||
|
default = ironbar;
|
||||||
});
|
});
|
||||||
|
|
||||||
homeManagerModules.default = import ./nix/module.nix self;
|
devShells = genSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = pkgsFor system;
|
||||||
|
rust = mkRustToolchain pkgs;
|
||||||
|
|
||||||
|
in {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
rust
|
||||||
|
rust-analyzer-unwrapped
|
||||||
|
gcc
|
||||||
|
gtk3
|
||||||
|
gtk-layer-shell
|
||||||
|
pkg-config
|
||||||
|
openssl
|
||||||
|
gdk-pixbuf
|
||||||
|
glib
|
||||||
|
glib-networking
|
||||||
|
shared-mime-info
|
||||||
|
gnome.adwaita-icon-theme
|
||||||
|
hicolor-icon-theme
|
||||||
|
gsettings-desktop-schemas
|
||||||
|
libxkbcommon
|
||||||
|
libpulseaudio
|
||||||
|
luajit
|
||||||
|
luajitPackages.lgi
|
||||||
|
];
|
||||||
|
|
||||||
|
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
homeManagerModules.default = { config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.programs.ironbar;
|
||||||
|
defaultIronbarPackage =
|
||||||
|
self.packages.${pkgs.hostPlatform.system}.default;
|
||||||
|
jsonFormat = pkgs.formats.json { };
|
||||||
|
in {
|
||||||
|
options.programs.ironbar = {
|
||||||
|
enable = lib.mkEnableOption "ironbar status bar";
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = with lib.types; package;
|
||||||
|
default = defaultIronbarPackage;
|
||||||
|
description = "The package for ironbar to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = pkgs.stdenv.isLinux;
|
||||||
|
description = "Whether to enable to systemd service for ironbar.";
|
||||||
|
};
|
||||||
|
|
||||||
|
style = lib.mkOption {
|
||||||
|
type = lib.types.either (lib.types.lines) (lib.types.path);
|
||||||
|
default = "";
|
||||||
|
description = "The stylesheet to apply to ironbar.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkOption {
|
||||||
|
type = jsonFormat.type;
|
||||||
|
default = { };
|
||||||
|
description = "The config to pass to ironbar.";
|
||||||
|
};
|
||||||
|
|
||||||
|
features = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.nonEmptyStr;
|
||||||
|
default = [ ];
|
||||||
|
description = "The features to be used.";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
config = let pkg = cfg.package.override { features = cfg.features; };
|
||||||
|
in lib.mkIf cfg.enable {
|
||||||
|
home.packages = [ pkg ];
|
||||||
|
|
||||||
|
xdg.configFile = {
|
||||||
|
"ironbar/config.json" = lib.mkIf (cfg.config != "") {
|
||||||
|
source = jsonFormat.generate "ironbar-config" cfg.config;
|
||||||
|
};
|
||||||
|
|
||||||
|
"ironbar/style.css" = lib.mkIf (cfg.style != "") (
|
||||||
|
if builtins.isPath cfg.style || lib.isStorePath cfg.style then
|
||||||
|
{ source = cfg.style; }
|
||||||
|
else
|
||||||
|
{ text = cfg.style; }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.user.services.ironbar = lib.mkIf cfg.systemd {
|
||||||
|
Unit = {
|
||||||
|
Description = "Systemd service for Ironbar";
|
||||||
|
Requires = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${pkg}/bin/ironbar";
|
||||||
|
};
|
||||||
|
|
||||||
|
Install.WantedBy = with config.wayland.windowManager; [
|
||||||
|
(lib.mkIf hyprland.systemd.enable "hyprland-session.target")
|
||||||
|
(lib.mkIf sway.systemd.enable "sway-session.target")
|
||||||
|
(lib.mkIf river.systemd.enable "river-session.target")
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nixConfig = {
|
nixConfig = {
|
||||||
extra-substituters = ["https://cache.garnix.io"];
|
extra-substituters = [ "https://cache.garnix.io" ];
|
||||||
extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="];
|
extra-trusted-public-keys =
|
||||||
|
[ "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
129
nix/default.nix
Normal file
129
nix/default.nix
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
{
|
||||||
|
gtk3,
|
||||||
|
gdk-pixbuf,
|
||||||
|
librsvg,
|
||||||
|
webp-pixbuf-loader,
|
||||||
|
gobject-introspection,
|
||||||
|
glib-networking,
|
||||||
|
glib,
|
||||||
|
shared-mime-info,
|
||||||
|
gsettings-desktop-schemas,
|
||||||
|
wrapGAppsHook,
|
||||||
|
gtk-layer-shell,
|
||||||
|
gnome,
|
||||||
|
libxkbcommon,
|
||||||
|
libpulseaudio,
|
||||||
|
openssl,
|
||||||
|
luajit,
|
||||||
|
luajitPackages,
|
||||||
|
pkg-config,
|
||||||
|
hicolor-icon-theme,
|
||||||
|
rustPlatform,
|
||||||
|
lib,
|
||||||
|
version ? "git",
|
||||||
|
features ? [],
|
||||||
|
builderName ? "nix",
|
||||||
|
builder ? {},
|
||||||
|
}: let
|
||||||
|
hasFeature = f: features == [ ] || builtins.elem f features;
|
||||||
|
|
||||||
|
basePkg = rec {
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
pname = "ironbar";
|
||||||
|
|
||||||
|
src = builtins.path {
|
||||||
|
name = "ironbar";
|
||||||
|
path = lib.cleanSource ../.;
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkg-config
|
||||||
|
wrapGAppsHook
|
||||||
|
gobject-introspection
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
gtk3
|
||||||
|
gdk-pixbuf
|
||||||
|
glib
|
||||||
|
gtk-layer-shell
|
||||||
|
glib-networking
|
||||||
|
shared-mime-info
|
||||||
|
gnome.adwaita-icon-theme
|
||||||
|
hicolor-icon-theme
|
||||||
|
gsettings-desktop-schemas
|
||||||
|
libxkbcommon ]
|
||||||
|
++ (if hasFeature "http" then [ openssl ] else [])
|
||||||
|
++ (if hasFeature "volume" then [ libpulseaudio ] else [])
|
||||||
|
++ (if hasFeature "cairo" then [ luajit ] else []);
|
||||||
|
|
||||||
|
propagatedBuildInputs = [ gtk3 ];
|
||||||
|
|
||||||
|
lgi = luajitPackages.lgi;
|
||||||
|
|
||||||
|
gappsWrapperArgs = ''
|
||||||
|
# Thumbnailers
|
||||||
|
--prefix XDG_DATA_DIRS : "${gdk-pixbuf}/share"
|
||||||
|
--prefix XDG_DATA_DIRS : "${librsvg}/share"
|
||||||
|
--prefix XDG_DATA_DIRS : "${webp-pixbuf-loader}/share"
|
||||||
|
--prefix XDG_DATA_DIRS : "${shared-mime-info}/share"
|
||||||
|
|
||||||
|
# gtk-launch
|
||||||
|
--suffix PATH : "${lib.makeBinPath [ gtk3 ]}"
|
||||||
|
''
|
||||||
|
+ (if hasFeature "cairo" then ''
|
||||||
|
--prefix LUA_PATH : "./?.lua;${lgi}/share/lua/5.1/?.lua;${lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/\?.lua;${luajit}/share/lua/5.1/?/init.lua"
|
||||||
|
--prefix LUA_CPATH : "./?.so;${lgi}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/loadall.so"
|
||||||
|
'' else "");
|
||||||
|
|
||||||
|
preFixup = ''
|
||||||
|
gappsWrapperArgs+=(
|
||||||
|
${gappsWrapperArgs}
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
updateScript = gnome.updateScript {
|
||||||
|
packageName = pname;
|
||||||
|
attrPath = "gnome.${pname}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
homepage = "https://github.com/JakeStanger/ironbar";
|
||||||
|
description =
|
||||||
|
"Customisable gtk-layer-shell wlroots/sway bar written in rust.";
|
||||||
|
license = licenses.mit;
|
||||||
|
platforms = platforms.linux;
|
||||||
|
mainProgram = "ironbar";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
flags = let
|
||||||
|
noDefault = if features == [ ] then "" else "--no-default-features";
|
||||||
|
|
||||||
|
featuresStr = if features == [ ] then
|
||||||
|
""
|
||||||
|
else
|
||||||
|
''-F "${builtins.concatStringsSep "," features}"'';
|
||||||
|
|
||||||
|
in [ noDefault featuresStr ];
|
||||||
|
in if builderName == "naersk" then
|
||||||
|
builder.buildPackage (basePkg // { cargoBuildOptions = old: old ++ flags; })
|
||||||
|
else if builderName == "crane" then
|
||||||
|
builder.buildPackage (basePkg // {
|
||||||
|
cargoExtraArgs = builtins.concatStringsSep " " flags;
|
||||||
|
doCheck = false;
|
||||||
|
})
|
||||||
|
else
|
||||||
|
rustPlatform.buildRustPackage (basePkg // {
|
||||||
|
buildNoDefaultFeatures = features != [ ];
|
||||||
|
|
||||||
|
buildFeatures = features;
|
||||||
|
cargoDeps = rustPlatform.importCargoLock { lockFile = ../Cargo.lock; };
|
||||||
|
cargoLock.lockFile = ../Cargo.lock;
|
||||||
|
cargoLock.outputHashes."stray-0.1.3" =
|
||||||
|
"sha256-7mvsWZFmPWti9AiX67h6ZlWiVVRZRWIxq3pVaviOUtc=";
|
||||||
|
})
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
self: {
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
cfg = config.programs.ironbar;
|
|
||||||
defaultIronbarPackage = self.packages.${pkgs.hostPlatform.system}.default;
|
|
||||||
jsonFormat = pkgs.formats.json {};
|
|
||||||
inherit
|
|
||||||
(lib)
|
|
||||||
types
|
|
||||||
mkOption
|
|
||||||
mkEnableOption
|
|
||||||
mkIf
|
|
||||||
getExe
|
|
||||||
;
|
|
||||||
in {
|
|
||||||
options.programs.ironbar = {
|
|
||||||
enable = mkEnableOption "ironbar status bar";
|
|
||||||
|
|
||||||
package = mkOption {
|
|
||||||
type = types.package;
|
|
||||||
default = defaultIronbarPackage;
|
|
||||||
apply = pkg: pkg.override {features = cfg.features;};
|
|
||||||
description = "The package for ironbar to use.";
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd = mkEnableOption "systemd service for ironbar.";
|
|
||||||
|
|
||||||
style = mkOption {
|
|
||||||
type = types.either (types.lines) (types.path);
|
|
||||||
default = "";
|
|
||||||
description = "The stylesheet to apply to ironbar.";
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkOption {
|
|
||||||
type = jsonFormat.type;
|
|
||||||
default = null;
|
|
||||||
description = "The config to pass to ironbar.";
|
|
||||||
};
|
|
||||||
|
|
||||||
features = mkOption {
|
|
||||||
type = types.listOf types.nonEmptyStr;
|
|
||||||
default = [];
|
|
||||||
description = "The features to be used.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
|
||||||
home.packages = [
|
|
||||||
cfg.package
|
|
||||||
];
|
|
||||||
|
|
||||||
xdg.configFile = {
|
|
||||||
"ironbar/config.json" = mkIf (cfg.config != null) {
|
|
||||||
onChange = "${getExe cfg.package} reload";
|
|
||||||
source = jsonFormat.generate "ironbar-config" cfg.config;
|
|
||||||
};
|
|
||||||
|
|
||||||
"ironbar/style.css" = mkIf (cfg.style != "") (
|
|
||||||
if builtins.isPath cfg.style || lib.isStorePath cfg.style
|
|
||||||
then {source = cfg.style;}
|
|
||||||
else {text = cfg.style;}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.user.services.ironbar = mkIf cfg.systemd {
|
|
||||||
Unit = {
|
|
||||||
Description = "Systemd service for Ironbar";
|
|
||||||
Documentation = "https://github.com/JakeStanger/ironbar";
|
|
||||||
PartOf = [
|
|
||||||
config.wayland.systemd.target
|
|
||||||
"tray.target"
|
|
||||||
];
|
|
||||||
After = [config.wayland.systemd.target];
|
|
||||||
ConditionEnvironment = "WAYLAND_DISPLAY";
|
|
||||||
};
|
|
||||||
|
|
||||||
Service = {
|
|
||||||
ExecReload = "${getExe cfg.package} reload";
|
|
||||||
ExecStart = "${getExe cfg.package}";
|
|
||||||
KillMode = "mixed";
|
|
||||||
Restart = "on-failure";
|
|
||||||
};
|
|
||||||
|
|
||||||
Install.WantedBy = [
|
|
||||||
config.wayland.systemd.target
|
|
||||||
"tray.target"
|
|
||||||
(mkIf config.wayland.windowManager.hyprland.enable "hyprland-session.target")
|
|
||||||
(mkIf config.wayland.windowManager.sway.enable "sway-session.target")
|
|
||||||
(mkIf config.wayland.windowManager.river.enable "river-session.target")
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
152
nix/package.nix
152
nix/package.nix
|
|
@ -1,152 +0,0 @@
|
||||||
{
|
|
||||||
gtk3,
|
|
||||||
gdk-pixbuf,
|
|
||||||
librsvg,
|
|
||||||
webp-pixbuf-loader,
|
|
||||||
gobject-introspection,
|
|
||||||
glib-networking,
|
|
||||||
glib,
|
|
||||||
shared-mime-info,
|
|
||||||
gsettings-desktop-schemas,
|
|
||||||
wrapGAppsHook,
|
|
||||||
gtk-layer-shell,
|
|
||||||
gnome,
|
|
||||||
libxkbcommon,
|
|
||||||
libdbusmenu-gtk3,
|
|
||||||
libpulseaudio,
|
|
||||||
libinput,
|
|
||||||
libevdev,
|
|
||||||
openssl,
|
|
||||||
luajit,
|
|
||||||
luajitPackages,
|
|
||||||
pkg-config,
|
|
||||||
installShellFiles,
|
|
||||||
adwaita-icon-theme,
|
|
||||||
hicolor-icon-theme,
|
|
||||||
lib,
|
|
||||||
version ? "git",
|
|
||||||
features ? [],
|
|
||||||
naersk,
|
|
||||||
}: let
|
|
||||||
hasFeature = f: features == [] || builtins.elem f features;
|
|
||||||
flags = let
|
|
||||||
noDefault =
|
|
||||||
if features == []
|
|
||||||
then ""
|
|
||||||
else "--no-default-features";
|
|
||||||
|
|
||||||
featuresStr =
|
|
||||||
if features == []
|
|
||||||
then ""
|
|
||||||
else ''-F "${builtins.concatStringsSep "," features}"'';
|
|
||||||
in [
|
|
||||||
noDefault
|
|
||||||
featuresStr
|
|
||||||
];
|
|
||||||
lgi = luajitPackages.lgi;
|
|
||||||
gappsWrapperArgs =
|
|
||||||
''
|
|
||||||
# Thumbnailers
|
|
||||||
--prefix XDG_DATA_DIRS : "${gdk-pixbuf}/share"
|
|
||||||
--prefix XDG_DATA_DIRS : "${librsvg}/share"
|
|
||||||
--prefix XDG_DATA_DIRS : "${webp-pixbuf-loader}/share"
|
|
||||||
--prefix XDG_DATA_DIRS : "${shared-mime-info}/share"
|
|
||||||
|
|
||||||
# gtk-launch
|
|
||||||
--suffix PATH : "${lib.makeBinPath [gtk3]}"
|
|
||||||
''
|
|
||||||
+ lib.optionalString (hasFeature "cairo") ''
|
|
||||||
--prefix LUA_PATH : "./?.lua;${lgi}/share/lua/5.1/?.lua;${lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/\?.lua;${luajit}/share/lua/5.1/?/init.lua"
|
|
||||||
--prefix LUA_CPATH : "./?.so;${lgi}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/loadall.so"
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
naersk.buildPackage {
|
|
||||||
inherit version;
|
|
||||||
|
|
||||||
pname = "ironbar";
|
|
||||||
|
|
||||||
src = let
|
|
||||||
fs = lib.fileset;
|
|
||||||
root = ../.;
|
|
||||||
nixRelated = fs.fileFilter (file: file.hasExt "nix" || file.name == "flake.lock") root;
|
|
||||||
cicdRelated = fs.unions [
|
|
||||||
(lib.path.append root "Dockerfile")
|
|
||||||
(lib.path.append root ".github")
|
|
||||||
];
|
|
||||||
ideRelated = fs.unions [
|
|
||||||
(lib.path.append root ".idea")
|
|
||||||
];
|
|
||||||
in
|
|
||||||
fs.toSource {
|
|
||||||
inherit root;
|
|
||||||
# NOTE: can possibly filter out more
|
|
||||||
fileset = fs.difference root (
|
|
||||||
fs.unions [
|
|
||||||
nixRelated
|
|
||||||
cicdRelated
|
|
||||||
ideRelated
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkg-config
|
|
||||||
wrapGAppsHook
|
|
||||||
gobject-introspection
|
|
||||||
installShellFiles
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs =
|
|
||||||
[
|
|
||||||
gtk3
|
|
||||||
gdk-pixbuf
|
|
||||||
glib
|
|
||||||
gtk-layer-shell
|
|
||||||
glib-networking
|
|
||||||
shared-mime-info
|
|
||||||
adwaita-icon-theme
|
|
||||||
hicolor-icon-theme
|
|
||||||
gsettings-desktop-schemas
|
|
||||||
libxkbcommon
|
|
||||||
]
|
|
||||||
++ lib.optionals (hasFeature "http") [openssl]
|
|
||||||
++ lib.optionals (hasFeature "tray") [libdbusmenu-gtk3]
|
|
||||||
++ lib.optionals (hasFeature "volume") [libpulseaudio]
|
|
||||||
++ lib.optionals (hasFeature "cairo") [luajit]
|
|
||||||
++ lib.optionals (hasFeature "keyboard") [
|
|
||||||
libinput
|
|
||||||
libevdev
|
|
||||||
];
|
|
||||||
|
|
||||||
propagatedBuildInputs = [gtk3];
|
|
||||||
|
|
||||||
cargoBuildOptions = old: old ++ flags;
|
|
||||||
|
|
||||||
preFixup = ''
|
|
||||||
gappsWrapperArgs+=(
|
|
||||||
${gappsWrapperArgs}
|
|
||||||
)
|
|
||||||
'';
|
|
||||||
|
|
||||||
postInstall = ''
|
|
||||||
installShellCompletion --cmd ironbar \
|
|
||||||
--bash target/completions/ironbar.bash \
|
|
||||||
--fish target/completions/ironbar.fish \
|
|
||||||
--zsh target/completions/_ironbar
|
|
||||||
'';
|
|
||||||
|
|
||||||
passthru = {
|
|
||||||
updateScript = gnome.updateScript {
|
|
||||||
packageName = "ironbar";
|
|
||||||
attrPath = "gnome.ironbar";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
homepage = "https://github.com/JakeStanger/ironbar";
|
|
||||||
description = "Customisable gtk-layer-shell wlroots/sway bar written in rust.";
|
|
||||||
license = lib.licenses.mit;
|
|
||||||
platforms = lib.platforms.linux;
|
|
||||||
mainProgram = "ironbar";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
32
shell.nix
32
shell.nix
|
|
@ -1,12 +1,20 @@
|
||||||
(import (
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
let
|
|
||||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
pkgs.mkShell {
|
||||||
nodeName = lock.nodes.root.inputs.flake-compat;
|
buildInputs = with pkgs; [
|
||||||
in
|
cargo
|
||||||
fetchTarball {
|
clippy
|
||||||
url =
|
rustfmt
|
||||||
lock.nodes.${nodeName}.locked.url
|
gtk3
|
||||||
or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
|
gtk-layer-shell
|
||||||
sha256 = lock.nodes.${nodeName}.locked.narHash;
|
gcc
|
||||||
}
|
openssl
|
||||||
) {src = ./.;}).shellNix
|
libpulseaudio
|
||||||
|
luajit
|
||||||
|
luajitPackages.lgi
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
}
|
||||||
41
src/bar.rs
41
src/bar.rs
|
|
@ -1,16 +1,16 @@
|
||||||
use crate::Ironbar;
|
|
||||||
use crate::config::{BarConfig, BarPosition, MarginConfig, ModuleConfig};
|
use crate::config::{BarConfig, BarPosition, MarginConfig, ModuleConfig};
|
||||||
use crate::modules::{BarModuleFactory, ModuleInfo, ModuleLocation};
|
use crate::modules::{BarModuleFactory, ModuleInfo, ModuleLocation};
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
|
use crate::Ironbar;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::Propagation;
|
use glib::Propagation;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Orientation, Window, WindowType};
|
use gtk::{Application, ApplicationWindow, IconTheme, Orientation, Window, WindowType};
|
||||||
use gtk_layer_shell::LayerShell;
|
use gtk_layer_shell::LayerShell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Inner {
|
enum Inner {
|
||||||
|
|
@ -22,7 +22,6 @@ enum Inner {
|
||||||
pub struct Bar {
|
pub struct Bar {
|
||||||
name: String,
|
name: String,
|
||||||
monitor_name: String,
|
monitor_name: String,
|
||||||
monitor_size: (i32, i32),
|
|
||||||
position: BarPosition,
|
position: BarPosition,
|
||||||
|
|
||||||
ironbar: Rc<Ironbar>,
|
ironbar: Rc<Ironbar>,
|
||||||
|
|
@ -42,7 +41,6 @@ impl Bar {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
app: &Application,
|
app: &Application,
|
||||||
monitor_name: String,
|
monitor_name: String,
|
||||||
monitor_size: (i32, i32),
|
|
||||||
config: BarConfig,
|
config: BarConfig,
|
||||||
ironbar: Rc<Ironbar>,
|
ironbar: Rc<Ironbar>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -91,7 +89,6 @@ impl Bar {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
monitor_name,
|
monitor_name,
|
||||||
monitor_size,
|
|
||||||
position,
|
position,
|
||||||
ironbar,
|
ironbar,
|
||||||
window,
|
window,
|
||||||
|
|
@ -149,7 +146,7 @@ impl Bar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let load_result = self.load_modules(config, monitor, self.monitor_size)?;
|
let load_result = self.load_modules(config, monitor)?;
|
||||||
|
|
||||||
self.show(!start_hidden);
|
self.show(!start_hidden);
|
||||||
|
|
||||||
|
|
@ -246,12 +243,12 @@ impl Bar {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the configured modules onto a bar.
|
/// Loads the configured modules onto a bar.
|
||||||
fn load_modules(
|
fn load_modules(&self, config: BarConfig, monitor: &Monitor) -> Result<BarLoadResult> {
|
||||||
&self,
|
let icon_theme = IconTheme::new();
|
||||||
config: BarConfig,
|
if let Some(ref theme) = config.icon_theme {
|
||||||
monitor: &Monitor,
|
icon_theme.set_custom_theme(Some(theme));
|
||||||
output_size: (i32, i32),
|
}
|
||||||
) -> Result<BarLoadResult> {
|
|
||||||
let app = &self.window.application().expect("to exist");
|
let app = &self.window.application().expect("to exist");
|
||||||
|
|
||||||
macro_rules! info {
|
macro_rules! info {
|
||||||
|
|
@ -262,17 +259,13 @@ impl Bar {
|
||||||
monitor,
|
monitor,
|
||||||
output_name: &self.monitor_name,
|
output_name: &self.monitor_name,
|
||||||
location: $location,
|
location: $location,
|
||||||
|
icon_theme: &icon_theme,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// popup ignores module location so can bodge this for now
|
// popup ignores module location so can bodge this for now
|
||||||
let popup = Popup::new(
|
let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap);
|
||||||
&self.ironbar,
|
|
||||||
&info!(ModuleLocation::Left),
|
|
||||||
output_size,
|
|
||||||
config.popup_gap,
|
|
||||||
);
|
|
||||||
let popup = Rc::new(popup);
|
let popup = Rc::new(popup);
|
||||||
|
|
||||||
if let Some(modules) = config.start {
|
if let Some(modules) = config.start {
|
||||||
|
|
@ -340,7 +333,7 @@ impl Bar {
|
||||||
|
|
||||||
/// Sets the window visibility status
|
/// Sets the window visibility status
|
||||||
pub fn set_visible(&self, visible: bool) {
|
pub fn set_visible(&self, visible: bool) {
|
||||||
self.window.set_visible(visible);
|
self.window.set_visible(visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_exclusive(&self, exclusive: bool) {
|
pub fn set_exclusive(&self, exclusive: bool) {
|
||||||
|
|
@ -381,10 +374,7 @@ fn add_modules(
|
||||||
let module_factory = BarModuleFactory::new(ironbar.clone(), popup.clone()).into();
|
let module_factory = BarModuleFactory::new(ironbar.clone(), popup.clone()).into();
|
||||||
|
|
||||||
for config in modules {
|
for config in modules {
|
||||||
let name = config.name();
|
config.create(&module_factory, content, info)?;
|
||||||
if let Err(err) = config.create(&module_factory, content, info) {
|
|
||||||
error!("failed to create module {name}: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -394,10 +384,9 @@ pub fn create_bar(
|
||||||
app: &Application,
|
app: &Application,
|
||||||
monitor: &Monitor,
|
monitor: &Monitor,
|
||||||
monitor_name: String,
|
monitor_name: String,
|
||||||
monitor_size: (i32, i32),
|
|
||||||
config: BarConfig,
|
config: BarConfig,
|
||||||
ironbar: Rc<Ironbar>,
|
ironbar: Rc<Ironbar>,
|
||||||
) -> Result<Bar> {
|
) -> Result<Bar> {
|
||||||
let bar = Bar::new(app, monitor_name, monitor_size, config, ironbar);
|
let bar = Bar::new(app, monitor_name, config, ironbar);
|
||||||
bar.init(monitor)
|
bar.init(monitor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
261
src/channels.rs
261
src/channels.rs
|
|
@ -1,261 +0,0 @@
|
||||||
use crate::modules::ModuleUpdateEvent;
|
|
||||||
use crate::spawn;
|
|
||||||
use smithay_client_toolkit::reexports::calloop;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use tokio::sync::{broadcast, mpsc};
|
|
||||||
|
|
||||||
pub trait SyncSenderExt<T> {
|
|
||||||
/// Asynchronously sends a message on the channel,
|
|
||||||
/// panicking if it cannot be sent.
|
|
||||||
///
|
|
||||||
/// This should be used in cases where sending should *never* fail,
|
|
||||||
/// or where failing indicates a serious bug.
|
|
||||||
fn send_expect(&self, message: T);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> SyncSenderExt<T> for std::sync::mpsc::Sender<T> {
|
|
||||||
#[inline]
|
|
||||||
fn send_expect(&self, message: T) {
|
|
||||||
self.send(message).expect(crate::error::ERR_CHANNEL_SEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> SyncSenderExt<T> for calloop::channel::Sender<T> {
|
|
||||||
#[inline]
|
|
||||||
fn send_expect(&self, message: T) {
|
|
||||||
self.send(message).expect(crate::error::ERR_CHANNEL_SEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Debug> SyncSenderExt<T> for broadcast::Sender<T> {
|
|
||||||
#[inline]
|
|
||||||
fn send_expect(&self, message: T) {
|
|
||||||
self.send(message).expect(crate::error::ERR_CHANNEL_SEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AsyncSenderExt<T>: Sync + Send + Sized + Clone {
|
|
||||||
/// Asynchronously sends a message on the channel,
|
|
||||||
/// panicking if it cannot be sent.
|
|
||||||
///
|
|
||||||
/// This should be used in cases where sending should *never* fail,
|
|
||||||
/// or where failing indicates a serious bug.
|
|
||||||
fn send_expect(&self, message: T) -> impl Future<Output = ()> + Send;
|
|
||||||
|
|
||||||
/// Asynchronously sends a message on the channel,
|
|
||||||
/// spawning a task to allow it to be sent in the background,
|
|
||||||
/// and panicking if it cannot be sent.
|
|
||||||
///
|
|
||||||
/// Note that this function will return *before* the message is sent.
|
|
||||||
///
|
|
||||||
/// This should be used in cases where sending should *never* fail,
|
|
||||||
/// or where failing indicates a serious bug.
|
|
||||||
#[inline]
|
|
||||||
fn send_spawn(&self, message: T)
|
|
||||||
where
|
|
||||||
Self: 'static,
|
|
||||||
T: Send + 'static,
|
|
||||||
{
|
|
||||||
let tx = self.clone();
|
|
||||||
spawn(async move { tx.send_expect(message).await });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand for [`AsyncSenderExt::send_expect`]
|
|
||||||
/// when sending a [`ModuleUpdateEvent::Update`].
|
|
||||||
#[inline]
|
|
||||||
async fn send_update<U: Clone>(&self, update: U)
|
|
||||||
where
|
|
||||||
Self: AsyncSenderExt<ModuleUpdateEvent<U>>,
|
|
||||||
{
|
|
||||||
self.send_expect(ModuleUpdateEvent::Update(update)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand for [`AsyncSenderExt::send_spawn`]
|
|
||||||
/// when sending a [`ModuleUpdateEvent::Update`].
|
|
||||||
#[inline]
|
|
||||||
fn send_update_spawn<U>(&self, update: U)
|
|
||||||
where
|
|
||||||
Self: AsyncSenderExt<ModuleUpdateEvent<U>> + 'static,
|
|
||||||
U: Clone + Send + 'static,
|
|
||||||
{
|
|
||||||
self.send_spawn(ModuleUpdateEvent::Update(update));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Send> AsyncSenderExt<T> for mpsc::Sender<T> {
|
|
||||||
#[inline]
|
|
||||||
async fn send_expect(&self, message: T) {
|
|
||||||
self.send(message)
|
|
||||||
.await
|
|
||||||
.expect(crate::error::ERR_CHANNEL_SEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MpscReceiverExt<T> {
|
|
||||||
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
|
|
||||||
/// in a loop, passing the message to `f`.
|
|
||||||
///
|
|
||||||
/// This allows use of `GObjects` and futures in the same context.#
|
|
||||||
///
|
|
||||||
/// `deps` is a single reference, or tuple of references of clonable objects,
|
|
||||||
/// to be consumed inside the closure.
|
|
||||||
/// This avoids needing to `element.clone()` everywhere.
|
|
||||||
fn recv_glib<D, Fn>(self, deps: D, f: Fn)
|
|
||||||
where
|
|
||||||
D: Dependency,
|
|
||||||
D::Target: 'static,
|
|
||||||
Fn: FnMut(&D::Target, T) + 'static;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> MpscReceiverExt<T> for mpsc::Receiver<T> {
|
|
||||||
fn recv_glib<D, Fn>(mut self, deps: D, mut f: Fn)
|
|
||||||
where
|
|
||||||
D: Dependency,
|
|
||||||
D::Target: 'static,
|
|
||||||
Fn: FnMut(&D::Target, T) + 'static,
|
|
||||||
{
|
|
||||||
let deps = deps.clone_content();
|
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
while let Some(val) = self.recv().await {
|
|
||||||
f(&deps, val);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BroadcastReceiverExt<T>
|
|
||||||
where
|
|
||||||
T: Debug + Clone + 'static,
|
|
||||||
{
|
|
||||||
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
|
|
||||||
/// in a loop, passing the message to `f`.
|
|
||||||
///
|
|
||||||
/// This allows use of `GObjects` and futures in the same context.
|
|
||||||
///
|
|
||||||
/// `deps` is a single reference, or tuple of references of clonable objects,
|
|
||||||
/// to be consumed inside the closure.
|
|
||||||
/// This avoids needing to `element.clone()` everywhere.
|
|
||||||
fn recv_glib<D, Fn>(self, deps: D, f: Fn)
|
|
||||||
where
|
|
||||||
D: Dependency,
|
|
||||||
D::Target: 'static,
|
|
||||||
Fn: FnMut(&D::Target, T) + 'static;
|
|
||||||
|
|
||||||
/// Like [`BroadcastReceiverExt::recv_glib`], but the closure must return a [`Future`].
|
|
||||||
fn recv_glib_async<D, Fn, F>(self, deps: D, f: Fn)
|
|
||||||
where
|
|
||||||
D: Dependency,
|
|
||||||
D::Target: 'static,
|
|
||||||
Fn: FnMut(&D::Target, T) -> F + 'static,
|
|
||||||
F: Future;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> BroadcastReceiverExt<T> for broadcast::Receiver<T>
|
|
||||||
where
|
|
||||||
T: Debug + Clone + 'static,
|
|
||||||
{
|
|
||||||
fn recv_glib<D, Fn>(mut self, deps: D, mut f: Fn)
|
|
||||||
where
|
|
||||||
D: Dependency,
|
|
||||||
D::Target: 'static,
|
|
||||||
Fn: FnMut(&D::Target, T) + 'static,
|
|
||||||
{
|
|
||||||
let deps = deps.clone_content();
|
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
loop {
|
|
||||||
match self.recv().await {
|
|
||||||
Ok(val) => f(&deps, val),
|
|
||||||
Err(broadcast::error::RecvError::Lagged(count)) => {
|
|
||||||
tracing::warn!(
|
|
||||||
"Channel lagged behind by {count}, this may result in unexpected or broken behaviour"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("{err:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv_glib_async<D, Fn, F>(mut self, deps: D, mut f: Fn)
|
|
||||||
where
|
|
||||||
D: Dependency,
|
|
||||||
D::Target: 'static,
|
|
||||||
Fn: FnMut(&D::Target, T) -> F + 'static,
|
|
||||||
F: Future,
|
|
||||||
{
|
|
||||||
let deps = deps.clone_content();
|
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
loop {
|
|
||||||
match self.recv().await {
|
|
||||||
Ok(val) => {
|
|
||||||
f(&deps, val).await;
|
|
||||||
}
|
|
||||||
Err(broadcast::error::RecvError::Lagged(count)) => {
|
|
||||||
tracing::warn!(
|
|
||||||
"Channel lagged behind by {count}, this may result in unexpected or broken behaviour"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("{err:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `recv_glib` callback dependency
|
|
||||||
/// or dependency tuple.
|
|
||||||
pub trait Dependency: Clone {
|
|
||||||
type Target;
|
|
||||||
|
|
||||||
fn clone_content(&self) -> Self::Target;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dependency for () {
|
|
||||||
type Target = ();
|
|
||||||
|
|
||||||
fn clone_content(&self) -> Self::Target {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Dependency for &'a T
|
|
||||||
where
|
|
||||||
T: Clone + 'a,
|
|
||||||
{
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn clone_content(&self) -> T {
|
|
||||||
T::clone(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_dependency {
|
|
||||||
($($idx:tt $t:ident),+) => {
|
|
||||||
impl<'a, $($t),+> Dependency for ($(&'a $t),+)
|
|
||||||
where
|
|
||||||
$($t: Clone + 'a),+
|
|
||||||
{
|
|
||||||
type Target = ($($t),+);
|
|
||||||
|
|
||||||
fn clone_content(&self) -> Self::Target {
|
|
||||||
($(self.$idx.clone()),+)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_dependency!(0 T1, 1 T2);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10, 10 T11);
|
|
||||||
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10, 10 T11, 11 T12);
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::error::ExitCode;
|
use crate::error::ExitCode;
|
||||||
use crate::ipc::{Command, Response};
|
use crate::ipc::commands::Command;
|
||||||
|
use crate::ipc::responses::Response;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
@ -45,7 +46,6 @@ pub fn handle_response(response: Response, format: Format) {
|
||||||
Format::Plain => match response {
|
Format::Plain => match response {
|
||||||
Response::Ok => println!("ok"),
|
Response::Ok => println!("ok"),
|
||||||
Response::OkValue { value } => println!("{value}"),
|
Response::OkValue { value } => println!("{value}"),
|
||||||
Response::Multi { values } => println!("{}", values.join("\n")),
|
|
||||||
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()),
|
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()),
|
||||||
},
|
},
|
||||||
Format::Json => println!(
|
Format::Json => println!(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
use super::wayland::{self, ClipboardItem};
|
use super::wayland::{self, ClipboardItem};
|
||||||
use crate::channels::AsyncSenderExt;
|
use crate::{arc_mut, lock, register_client, spawn, try_send};
|
||||||
use crate::{arc_mut, lock, register_client, spawn};
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use indexmap::map::Iter;
|
use indexmap::map::Iter;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
@ -47,7 +46,7 @@ impl Client {
|
||||||
let senders = lock!(senders);
|
let senders = lock!(senders);
|
||||||
let iter = senders.iter();
|
let iter = senders.iter();
|
||||||
for (tx, _) in iter {
|
for (tx, _) in iter {
|
||||||
tx.send_spawn(ClipboardEvent::Add(item.clone()));
|
try_send!(tx, ClipboardEvent::Add(item.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
lock!(cache).insert(item, senders.len());
|
lock!(cache).insert(item, senders.len());
|
||||||
|
|
@ -75,17 +74,16 @@ impl Client {
|
||||||
let removed_id = lock!(cache)
|
let removed_id = lock!(cache)
|
||||||
.remove_ref_first()
|
.remove_ref_first()
|
||||||
.expect("Clipboard cache unexpectedly empty");
|
.expect("Clipboard cache unexpectedly empty");
|
||||||
|
try_send!(tx, ClipboardEvent::Remove(removed_id));
|
||||||
tx.send_spawn(ClipboardEvent::Remove(removed_id));
|
|
||||||
}
|
}
|
||||||
tx.send_spawn(ClipboardEvent::Add(item.clone()));
|
try_send!(tx, ClipboardEvent::Add(item.clone()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|existing_id| {
|
|existing_id| {
|
||||||
let senders = lock!(senders);
|
let senders = lock!(senders);
|
||||||
let iter = senders.iter();
|
let iter = senders.iter();
|
||||||
for (tx, _) in iter {
|
for (tx, _) in iter {
|
||||||
tx.send_spawn(ClipboardEvent::Activate(existing_id));
|
try_send!(tx, ClipboardEvent::Activate(existing_id));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -108,7 +106,7 @@ impl Client {
|
||||||
|
|
||||||
let iter = cache.iter();
|
let iter = cache.iter();
|
||||||
for (_, (item, _)) in iter {
|
for (_, (item, _)) in iter {
|
||||||
tx.send_spawn(ClipboardEvent::Add(item.clone()));
|
try_send!(tx, ClipboardEvent::Add(item.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +130,7 @@ impl Client {
|
||||||
let senders = lock!(self.senders);
|
let senders = lock!(self.senders);
|
||||||
let iter = senders.iter();
|
let iter = senders.iter();
|
||||||
for (tx, _) in iter {
|
for (tx, _) in iter {
|
||||||
tx.send_spawn(ClipboardEvent::Activate(id));
|
try_send!(tx, ClipboardEvent::Activate(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +140,7 @@ impl Client {
|
||||||
let senders = lock!(self.senders);
|
let senders = lock!(self.senders);
|
||||||
let iter = senders.iter();
|
let iter = senders.iter();
|
||||||
for (tx, _) in iter {
|
for (tx, _) in iter {
|
||||||
tx.send_spawn(ClipboardEvent::Remove(id));
|
try_send!(tx, ClipboardEvent::Remove(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,37 @@
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||||
use super::{BindModeClient, BindModeUpdate};
|
use crate::{arc_mut, lock, send, spawn_blocking};
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
use super::{KeyboardLayoutClient, KeyboardLayoutUpdate};
|
|
||||||
use super::{Visibility, Workspace};
|
|
||||||
use crate::channels::SyncSenderExt;
|
|
||||||
use crate::{arc_mut, lock, spawn_blocking};
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use hyprland::ctl::switch_xkb_layout;
|
use hyprland::data::{Workspace as HWorkspace, Workspaces};
|
||||||
use hyprland::data::{Devices, Workspace as HWorkspace, Workspaces};
|
|
||||||
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
||||||
use hyprland::event_listener::EventListener;
|
use hyprland::event_listener::EventListener;
|
||||||
use hyprland::prelude::*;
|
use hyprland::prelude::*;
|
||||||
use hyprland::shared::{HyprDataVec, WorkspaceType};
|
use hyprland::shared::{HyprDataVec, WorkspaceType};
|
||||||
use tokio::sync::broadcast::{Receiver, Sender, channel};
|
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
#[cfg(feature = "workspaces")]
|
|
||||||
use super::WorkspaceUpdate;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct TxRx<T> {
|
|
||||||
tx: Sender<T>,
|
|
||||||
_rx: Receiver<T>,
|
|
||||||
}
|
|
||||||
impl<T: Clone> TxRx<T> {
|
|
||||||
fn new() -> Self {
|
|
||||||
let (tx, rx) = channel(16);
|
|
||||||
Self { tx, _rx: rx }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
workspace_tx: Sender<WorkspaceUpdate>,
|
||||||
workspace: TxRx<WorkspaceUpdate>,
|
_workspace_rx: Receiver<WorkspaceUpdate>,
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
keyboard_layout: TxRx<KeyboardLayoutUpdate>,
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
|
||||||
bindmode: TxRx<BindModeUpdate>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
|
let (workspace_tx, workspace_rx) = channel(16);
|
||||||
|
|
||||||
let instance = Self {
|
let instance = Self {
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
workspace_tx,
|
||||||
workspace: TxRx::new(),
|
_workspace_rx: workspace_rx,
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
keyboard_layout: TxRx::new(),
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
|
||||||
bindmode: TxRx::new(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.listen_events();
|
instance.listen_workspace_events();
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listen_events(&self) {
|
fn listen_workspace_events(&self) {
|
||||||
info!("Starting Hyprland event listener");
|
info!("Starting Hyprland event listener");
|
||||||
|
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
let tx = self.workspace_tx.clone();
|
||||||
let workspace_tx = self.workspace.tx.clone();
|
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
let keyboard_layout_tx = self.keyboard_layout.tx.clone();
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
|
||||||
let bindmode_tx = self.bindmode.tx.clone();
|
|
||||||
|
|
||||||
spawn_blocking(move || {
|
spawn_blocking(move || {
|
||||||
let mut event_listener = EventListener::new();
|
let mut event_listener = EventListener::new();
|
||||||
|
|
@ -76,56 +40,25 @@ impl Client {
|
||||||
let lock = arc_mut!(());
|
let lock = arc_mut!(());
|
||||||
|
|
||||||
// cache the active workspace since Hyprland doesn't give us the prev active
|
// cache the active workspace since Hyprland doesn't give us the prev active
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
let active = Self::get_active_workspace().expect("Failed to get active workspace");
|
||||||
Self::listen_workspace_events(&workspace_tx, &mut event_listener, &lock);
|
let active = arc_mut!(Some(active));
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
Self::listen_keyboard_events(&keyboard_layout_tx, &mut event_listener, &lock);
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
|
||||||
Self::listen_bindmode_events(&bindmode_tx, &mut event_listener, &lock);
|
|
||||||
|
|
||||||
if let Err(err) = event_listener.start_listener() {
|
|
||||||
error!("Failed to start listener: {err:#}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
|
||||||
fn listen_workspace_events(
|
|
||||||
tx: &Sender<WorkspaceUpdate>,
|
|
||||||
event_listener: &mut EventListener,
|
|
||||||
lock: &std::sync::Arc<std::sync::Mutex<()>>,
|
|
||||||
) {
|
|
||||||
let active = Self::get_active_workspace().map_or_else(
|
|
||||||
|err| {
|
|
||||||
error!("Failed to get active workspace: {err:#?}");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
Some,
|
|
||||||
);
|
|
||||||
let active = arc_mut!(active);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let lock = lock.clone();
|
let lock = lock.clone();
|
||||||
let active = active.clone();
|
let active = active.clone();
|
||||||
|
|
||||||
event_listener.add_workspace_added_handler(move |event| {
|
event_listener.add_workspace_added_handler(move |workspace_type| {
|
||||||
let _lock = lock!(lock);
|
let _lock = lock!(lock);
|
||||||
debug!("Added workspace: {event:?}");
|
debug!("Added workspace: {workspace_type:?}");
|
||||||
|
|
||||||
let workspace_name = get_workspace_name(event.name);
|
let workspace_name = get_workspace_name(workspace_type);
|
||||||
let prev_workspace = lock!(active);
|
let prev_workspace = lock!(active);
|
||||||
|
|
||||||
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
||||||
|
|
||||||
match workspace {
|
if let Some(workspace) = workspace {
|
||||||
Ok(Some(workspace)) => {
|
send!(tx, WorkspaceUpdate::Add(workspace));
|
||||||
tx.send_expect(WorkspaceUpdate::Add(workspace));
|
|
||||||
}
|
|
||||||
Err(e) => error!("Failed to get workspace: {e:#}"),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -135,29 +68,30 @@ impl Client {
|
||||||
let lock = lock.clone();
|
let lock = lock.clone();
|
||||||
let active = active.clone();
|
let active = active.clone();
|
||||||
|
|
||||||
event_listener.add_workspace_changed_handler(move |event| {
|
event_listener.add_workspace_change_handler(move |workspace_type| {
|
||||||
let _lock = lock!(lock);
|
let _lock = lock!(lock);
|
||||||
|
|
||||||
let mut prev_workspace = lock!(active);
|
let mut prev_workspace = lock!(active);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Received workspace change: {:?} -> {event:?}",
|
"Received workspace change: {:?} -> {workspace_type:?}",
|
||||||
prev_workspace.as_ref().map(|w| &w.id)
|
prev_workspace.as_ref().map(|w| &w.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
let workspace_name = get_workspace_name(event.name);
|
let workspace_name = get_workspace_name(workspace_type);
|
||||||
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
||||||
|
|
||||||
match workspace {
|
workspace.map_or_else(
|
||||||
Ok(Some(workspace)) if !workspace.visibility.is_focused() => {
|
|| {
|
||||||
|
error!("Unable to locate workspace");
|
||||||
|
},
|
||||||
|
|workspace| {
|
||||||
|
// there may be another type of update so dispatch that regardless of focus change
|
||||||
|
if !workspace.visibility.is_focused() {
|
||||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
},
|
||||||
error!("Unable to locate workspace");
|
);
|
||||||
}
|
|
||||||
Err(e) => error!("Failed to get workspace: {e:#}"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,12 +100,9 @@ impl Client {
|
||||||
let lock = lock.clone();
|
let lock = lock.clone();
|
||||||
let active = active.clone();
|
let active = active.clone();
|
||||||
|
|
||||||
event_listener.add_active_monitor_changed_handler(move |event_data| {
|
event_listener.add_active_monitor_change_handler(move |event_data| {
|
||||||
let _lock = lock!(lock);
|
let _lock = lock!(lock);
|
||||||
let Some(workspace_type) = event_data.workspace_name else {
|
let workspace_type = event_data.workspace;
|
||||||
warn!("Received active monitor change with no workspace name");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut prev_workspace = lock!(active);
|
let mut prev_workspace = lock!(active);
|
||||||
|
|
||||||
|
|
@ -183,16 +114,13 @@ impl Client {
|
||||||
let workspace_name = get_workspace_name(workspace_type);
|
let workspace_name = get_workspace_name(workspace_type);
|
||||||
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
||||||
|
|
||||||
match workspace {
|
if let Some((false, workspace)) =
|
||||||
Ok(Some(workspace)) if !workspace.visibility.is_focused() => {
|
workspace.map(|w| (w.visibility.is_focused(), w))
|
||||||
|
{
|
||||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||||
}
|
} else {
|
||||||
Ok(None) => {
|
|
||||||
error!("Unable to locate workspace");
|
error!("Unable to locate workspace");
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to get workspace: {e:#}"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +130,7 @@ impl Client {
|
||||||
|
|
||||||
event_listener.add_workspace_moved_handler(move |event_data| {
|
event_listener.add_workspace_moved_handler(move |event_data| {
|
||||||
let _lock = lock!(lock);
|
let _lock = lock!(lock);
|
||||||
let workspace_type = event_data.name;
|
let workspace_type = event_data.workspace;
|
||||||
debug!("Received workspace move: {workspace_type:?}");
|
debug!("Received workspace move: {workspace_type:?}");
|
||||||
|
|
||||||
let mut prev_workspace = lock!(active);
|
let mut prev_workspace = lock!(active);
|
||||||
|
|
@ -210,15 +138,12 @@ impl Client {
|
||||||
let workspace_name = get_workspace_name(workspace_type);
|
let workspace_name = get_workspace_name(workspace_type);
|
||||||
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
||||||
|
|
||||||
match workspace {
|
if let Some(workspace) = workspace {
|
||||||
Ok(Some(workspace)) if !workspace.visibility.is_focused() => {
|
send!(tx, WorkspaceUpdate::Move(workspace.clone()));
|
||||||
|
|
||||||
|
if !workspace.visibility.is_focused() {
|
||||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
|
||||||
error!("Unable to locate workspace");
|
|
||||||
}
|
|
||||||
Err(e) => error!("Failed to get workspace: {e:#}"),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -227,156 +152,57 @@ impl Client {
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let lock = lock.clone();
|
let lock = lock.clone();
|
||||||
|
|
||||||
event_listener.add_workspace_renamed_handler(move |data| {
|
event_listener.add_workspace_rename_handler(move |data| {
|
||||||
let _lock = lock!(lock);
|
let _lock = lock!(lock);
|
||||||
debug!("Received workspace rename: {data:?}");
|
|
||||||
|
|
||||||
tx.send_expect(WorkspaceUpdate::Rename {
|
send!(
|
||||||
id: data.id as i64,
|
tx,
|
||||||
name: data.name,
|
WorkspaceUpdate::Rename {
|
||||||
});
|
id: data.workspace_id as i64,
|
||||||
});
|
name: data.workspace_name
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
let tx = tx.clone();
|
|
||||||
let lock = lock.clone();
|
|
||||||
|
|
||||||
event_listener.add_workspace_deleted_handler(move |data| {
|
|
||||||
let _lock = lock!(lock);
|
|
||||||
debug!("Received workspace destroy: {data:?}");
|
|
||||||
tx.send_expect(WorkspaceUpdate::Remove(data.id as i64));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let tx = tx.clone();
|
|
||||||
let lock = lock.clone();
|
|
||||||
|
|
||||||
event_listener.add_urgent_state_changed_handler(move |address| {
|
|
||||||
let _lock = lock!(lock);
|
|
||||||
debug!("Received urgent state: {address:?}");
|
|
||||||
|
|
||||||
let clients = match hyprland::data::Clients::get() {
|
|
||||||
Ok(clients) => clients,
|
|
||||||
Err(err) => {
|
|
||||||
error!("Failed to get clients: {err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
clients.iter().find(|c| c.address == address).map_or_else(
|
|
||||||
|| {
|
|
||||||
error!("Unable to locate client");
|
|
||||||
},
|
|
||||||
|c| {
|
|
||||||
tx.send_expect(WorkspaceUpdate::Urgent {
|
|
||||||
id: c.workspace.id as i64,
|
|
||||||
urgent: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
{
|
||||||
fn listen_keyboard_events(
|
event_listener.add_workspace_destroy_handler(move |data| {
|
||||||
keyboard_layout_tx: &Sender<KeyboardLayoutUpdate>,
|
|
||||||
event_listener: &mut EventListener,
|
|
||||||
lock: &std::sync::Arc<std::sync::Mutex<()>>,
|
|
||||||
) {
|
|
||||||
let tx = keyboard_layout_tx.clone();
|
|
||||||
let lock = lock.clone();
|
|
||||||
|
|
||||||
event_listener.add_layout_changed_handler(move |layout_event| {
|
|
||||||
let _lock = lock!(lock);
|
let _lock = lock!(lock);
|
||||||
|
debug!("Received workspace destroy: {data:?}");
|
||||||
let layout = if layout_event.layout_name.is_empty() {
|
send!(tx, WorkspaceUpdate::Remove(data.workspace_id as i64));
|
||||||
// FIXME: This field is empty due to bug in `hyprland-rs_0.4.0-alpha.3`. Which is already fixed in last betas
|
|
||||||
|
|
||||||
// The layout may be empty due to a bug in `hyprland-rs`, because of which the `layout_event` is incorrect.
|
|
||||||
//
|
|
||||||
// Instead of:
|
|
||||||
// ```
|
|
||||||
// LayoutEvent {
|
|
||||||
// keyboard_name: "keychron-keychron-c2",
|
|
||||||
// layout_name: "English (US)",
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// We get:
|
|
||||||
// ```
|
|
||||||
// LayoutEvent {
|
|
||||||
// keyboard_name: "keychron-keychron-c2,English (US)",
|
|
||||||
// layout_name: "",
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// Here we are trying to recover `layout_name` from `keyboard_name`
|
|
||||||
|
|
||||||
let layout = layout_event.keyboard_name.as_str().split(',').nth(1);
|
|
||||||
let Some(layout) = layout else {
|
|
||||||
error!(
|
|
||||||
"Failed to get layout from string: {}. The failed logic is a workaround for a bug in `hyprland 0.4.0-alpha.3`", layout_event.keyboard_name);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
layout.into()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
layout_event.layout_name
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("Received layout: {layout:?}");
|
|
||||||
tx.send_expect(KeyboardLayoutUpdate(layout));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
event_listener
|
||||||
fn listen_bindmode_events(
|
.start_listener()
|
||||||
bindmode_tx: &Sender<BindModeUpdate>,
|
.expect("Failed to start listener");
|
||||||
event_listener: &mut EventListener,
|
|
||||||
lock: &std::sync::Arc<std::sync::Mutex<()>>,
|
|
||||||
) {
|
|
||||||
let tx = bindmode_tx.clone();
|
|
||||||
let lock = lock.clone();
|
|
||||||
|
|
||||||
event_listener.add_sub_map_changed_handler(move |bind_mode| {
|
|
||||||
let _lock = lock!(lock);
|
|
||||||
debug!("Received bind mode: {bind_mode:?}");
|
|
||||||
|
|
||||||
tx.send_expect(BindModeUpdate {
|
|
||||||
name: bind_mode,
|
|
||||||
pango_markup: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a `WorkspaceUpdate::Focus` event
|
/// Sends a `WorkspaceUpdate::Focus` event
|
||||||
/// and updates the active workspace cache.
|
/// and updates the active workspace cache.
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
|
||||||
fn send_focus_change(
|
fn send_focus_change(
|
||||||
prev_workspace: &mut Option<Workspace>,
|
prev_workspace: &mut Option<Workspace>,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
tx: &Sender<WorkspaceUpdate>,
|
tx: &Sender<WorkspaceUpdate>,
|
||||||
) {
|
) {
|
||||||
tx.send_expect(WorkspaceUpdate::Focus {
|
send!(
|
||||||
|
tx,
|
||||||
|
WorkspaceUpdate::Focus {
|
||||||
old: prev_workspace.take(),
|
old: prev_workspace.take(),
|
||||||
new: workspace.clone(),
|
new: workspace.clone(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
tx.send_expect(WorkspaceUpdate::Urgent {
|
|
||||||
id: workspace.id,
|
|
||||||
urgent: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
prev_workspace.replace(workspace);
|
prev_workspace.replace(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a workspace by name from the server, given the active workspace if known.
|
/// Gets a workspace by name from the server, given the active workspace if known.
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
fn get_workspace(name: &str, active: Option<&Workspace>) -> Option<Workspace> {
|
||||||
fn get_workspace(name: &str, active: Option<&Workspace>) -> Result<Option<Workspace>> {
|
Workspaces::get()
|
||||||
let workspace = Workspaces::get()?.into_iter().find_map(|w| {
|
.expect("Failed to get workspaces")
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|w| {
|
||||||
if w.name == name {
|
if w.name == name {
|
||||||
let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| {
|
let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| {
|
||||||
create_is_visible()(w)
|
create_is_visible()(w)
|
||||||
|
|
@ -386,9 +212,7 @@ impl Client {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
Ok(workspace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the active workspace from the server.
|
/// Gets the active workspace from the server.
|
||||||
|
|
@ -398,100 +222,43 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
impl WorkspaceClient for Client {
|
||||||
impl super::WorkspaceClient for Client {
|
fn focus(&self, id: String) -> Result<()> {
|
||||||
fn focus(&self, id: i64) {
|
let identifier = id.parse::<i32>().map_or_else(
|
||||||
let identifier = WorkspaceIdentifierWithSpecial::Id(id as i32);
|
|_| WorkspaceIdentifierWithSpecial::Name(&id),
|
||||||
|
WorkspaceIdentifierWithSpecial::Id,
|
||||||
|
);
|
||||||
|
|
||||||
if let Err(e) = Dispatch::call(DispatchType::Workspace(identifier)) {
|
Dispatch::call(DispatchType::Workspace(identifier))?;
|
||||||
error!("Couldn't focus workspace '{id}': {e:#}");
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscribe(&self) -> Receiver<WorkspaceUpdate> {
|
fn subscribe_workspace_change(&self) -> Receiver<WorkspaceUpdate> {
|
||||||
let rx = self.workspace.tx.subscribe();
|
let rx = self.workspace_tx.subscribe();
|
||||||
|
|
||||||
|
{
|
||||||
|
let tx = self.workspace_tx.clone();
|
||||||
|
|
||||||
let active_id = HWorkspace::get_active().ok().map(|active| active.name);
|
let active_id = HWorkspace::get_active().ok().map(|active| active.name);
|
||||||
let is_visible = create_is_visible();
|
let is_visible = create_is_visible();
|
||||||
|
|
||||||
match Workspaces::get() {
|
let workspaces = Workspaces::get()
|
||||||
Ok(workspaces) => {
|
.expect("Failed to get workspaces")
|
||||||
let workspaces = workspaces
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|w| {
|
.map(|w| {
|
||||||
let vis = Visibility::from((&w, active_id.as_deref(), &is_visible));
|
let vis = Visibility::from((&w, active_id.as_deref(), &is_visible));
|
||||||
|
|
||||||
Workspace::from((vis, w))
|
Workspace::from((vis, w))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
self.workspace
|
send!(tx, WorkspaceUpdate::Init(workspaces));
|
||||||
.tx
|
|
||||||
.send_expect(WorkspaceUpdate::Init(workspaces));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to get workspaces: {e:#}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
impl KeyboardLayoutClient for Client {
|
|
||||||
fn set_next_active(&self) {
|
|
||||||
let Ok(devices) = Devices::get() else {
|
|
||||||
error!("Failed to get devices");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let device = devices
|
|
||||||
.keyboards
|
|
||||||
.iter()
|
|
||||||
.find(|k| k.main)
|
|
||||||
.map(|k| k.name.clone());
|
|
||||||
|
|
||||||
if let Some(device) = device {
|
|
||||||
if let Err(e) =
|
|
||||||
switch_xkb_layout::call(device, switch_xkb_layout::SwitchXKBLayoutCmdTypes::Next)
|
|
||||||
{
|
|
||||||
error!("Failed to switch keyboard layout due to Hyprland error: {e}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("Failed to get keyboard device from hyprland");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscribe(&self) -> Receiver<KeyboardLayoutUpdate> {
|
|
||||||
let rx = self.keyboard_layout.tx.subscribe();
|
|
||||||
|
|
||||||
match Devices::get().map(|devices| {
|
|
||||||
devices
|
|
||||||
.keyboards
|
|
||||||
.iter()
|
|
||||||
.find(|k| k.main)
|
|
||||||
.map(|k| k.active_keymap.clone())
|
|
||||||
}) {
|
|
||||||
Ok(Some(layout)) => {
|
|
||||||
self.keyboard_layout
|
|
||||||
.tx
|
|
||||||
.send_expect(KeyboardLayoutUpdate(layout));
|
|
||||||
}
|
|
||||||
Ok(None) => error!("Failed to get current keyboard layout hyprland"),
|
|
||||||
Err(err) => error!("Failed to get devices: {err:#?}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
|
||||||
impl BindModeClient for Client {
|
|
||||||
fn subscribe(&self) -> Result<Receiver<BindModeUpdate>> {
|
|
||||||
Ok(self.bindmode.tx.subscribe())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_workspace_name(name: WorkspaceType) -> String {
|
fn get_workspace_name(name: WorkspaceType) -> String {
|
||||||
match name {
|
match name {
|
||||||
WorkspaceType::Regular(name) => name,
|
WorkspaceType::Regular(name) => name,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::clients::ClientResult;
|
use crate::{await_sync, register_fallible_client};
|
||||||
use crate::register_fallible_client;
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::{Help, Report, Result};
|
use color_eyre::{Help, Report, Result};
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
|
@ -7,20 +6,16 @@ use std::sync::Arc;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[cfg(feature = "hyprland")]
|
#[cfg(feature = "workspaces+hyprland")]
|
||||||
pub mod hyprland;
|
pub mod hyprland;
|
||||||
#[cfg(feature = "niri")]
|
#[cfg(feature = "workspaces+sway")]
|
||||||
pub mod niri;
|
|
||||||
#[cfg(feature = "sway")]
|
|
||||||
pub mod sway;
|
pub mod sway;
|
||||||
|
|
||||||
pub enum Compositor {
|
pub enum Compositor {
|
||||||
#[cfg(feature = "sway")]
|
#[cfg(feature = "workspaces+sway")]
|
||||||
Sway,
|
Sway,
|
||||||
#[cfg(feature = "hyprland")]
|
#[cfg(feature = "workspaces+hyprland")]
|
||||||
Hyprland,
|
Hyprland,
|
||||||
#[cfg(feature = "niri")]
|
|
||||||
Niri,
|
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,12 +25,10 @@ impl Display for Compositor {
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
#[cfg(any(feature = "sway"))]
|
#[cfg(feature = "workspaces+sway")]
|
||||||
Self::Sway => "Sway",
|
Self::Sway => "Sway",
|
||||||
#[cfg(any(feature = "hyprland"))]
|
#[cfg(feature = "workspaces+hyprland")]
|
||||||
Self::Hyprland => "Hyprland",
|
Self::Hyprland => "Hyprland",
|
||||||
#[cfg(feature = "workspaces+niri")]
|
|
||||||
Self::Niri => "Niri",
|
|
||||||
Self::Unsupported => "Unsupported",
|
Self::Unsupported => "Unsupported",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -48,90 +41,32 @@ impl Compositor {
|
||||||
fn get_current() -> Self {
|
fn get_current() -> Self {
|
||||||
if std::env::var("SWAYSOCK").is_ok() {
|
if std::env::var("SWAYSOCK").is_ok() {
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "sway")] { Self::Sway }
|
if #[cfg(feature = "workspaces+sway")] { Self::Sway }
|
||||||
else { tracing::error!("Not compiled with Sway support"); Self::Unsupported }
|
else { tracing::error!("Not compiled with Sway support"); Self::Unsupported }
|
||||||
}
|
}
|
||||||
} else if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
|
} else if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "hyprland")] { Self::Hyprland }
|
if #[cfg(feature = "workspaces+hyprland")] { Self::Hyprland }
|
||||||
else { tracing::error!("Not compiled with Hyprland support"); Self::Unsupported }
|
else { tracing::error!("Not compiled with Hyprland support"); Self::Unsupported }
|
||||||
}
|
}
|
||||||
} else if std::env::var("NIRI_SOCKET").is_ok() {
|
|
||||||
cfg_if! {
|
|
||||||
if #[cfg(feature = "niri")] { Self::Niri }
|
|
||||||
else {tracing::error!("Not compiled with Niri support"); Self::Unsupported }
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Self::Unsupported
|
Self::Unsupported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
pub fn create_bindmode_client(
|
|
||||||
clients: &mut super::Clients,
|
|
||||||
) -> ClientResult<dyn BindModeClient + Send + Sync> {
|
|
||||||
let current = Self::get_current();
|
|
||||||
debug!("Getting keyboard_layout client for: {current}");
|
|
||||||
match current {
|
|
||||||
#[cfg(feature = "bindmode+sway")]
|
|
||||||
Self::Sway => Ok(clients.sway()?),
|
|
||||||
#[cfg(feature = "bindmode+hyprland")]
|
|
||||||
Self::Hyprland => Ok(clients.hyprland()),
|
|
||||||
#[cfg(feature = "niri")]
|
|
||||||
Self::Niri => Err(Report::msg("Unsupported compositor")
|
|
||||||
.note("Currently bindmode is only supported by Sway and Hyprland")),
|
|
||||||
Self::Unsupported => Err(Report::msg("Unsupported compositor")
|
|
||||||
.note("Currently bindmode is only supported by Sway and Hyprland")),
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => Err(Report::msg("Unsupported compositor")
|
|
||||||
.note("Bindmode feature is disabled for this compositor")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
pub fn create_keyboard_layout_client(
|
|
||||||
clients: &mut super::Clients,
|
|
||||||
) -> ClientResult<dyn KeyboardLayoutClient + Send + Sync> {
|
|
||||||
let current = Self::get_current();
|
|
||||||
debug!("Getting keyboard_layout client for: {current}");
|
|
||||||
match current {
|
|
||||||
#[cfg(feature = "keyboard+sway")]
|
|
||||||
Self::Sway => Ok(clients.sway()?),
|
|
||||||
#[cfg(feature = "keyboard+hyprland")]
|
|
||||||
Self::Hyprland => Ok(clients.hyprland()),
|
|
||||||
#[cfg(feature = "niri")]
|
|
||||||
Self::Niri => Err(Report::msg("Unsupported compositor").note(
|
|
||||||
"Currently keyboard layout functionality are only supported by Sway and Hyprland",
|
|
||||||
)),
|
|
||||||
Self::Unsupported => Err(Report::msg("Unsupported compositor").note(
|
|
||||||
"Currently keyboard layout functionality are only supported by Sway and Hyprland",
|
|
||||||
)),
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => Err(Report::msg("Unsupported compositor")
|
|
||||||
.note("Keyboard layout feature is disabled for this compositor")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance of
|
/// Creates a new instance of
|
||||||
/// the workspace client for the current compositor.
|
/// the workspace client for the current compositor.
|
||||||
#[cfg(feature = "workspaces")]
|
pub fn create_workspace_client() -> Result<Arc<dyn WorkspaceClient + Send + Sync>> {
|
||||||
pub fn create_workspace_client(
|
|
||||||
clients: &mut super::Clients,
|
|
||||||
) -> Result<Arc<dyn WorkspaceClient + Send + Sync>> {
|
|
||||||
let current = Self::get_current();
|
let current = Self::get_current();
|
||||||
debug!("Getting workspace client for: {current}");
|
debug!("Getting workspace client for: {current}");
|
||||||
match current {
|
match current {
|
||||||
#[cfg(feature = "workspaces+sway")]
|
#[cfg(feature = "workspaces+sway")]
|
||||||
Self::Sway => Ok(clients.sway()?),
|
Self::Sway => await_sync(async { sway::Client::new().await })
|
||||||
|
.map(|client| Arc::new(client) as Arc<dyn WorkspaceClient + Send + Sync>),
|
||||||
#[cfg(feature = "workspaces+hyprland")]
|
#[cfg(feature = "workspaces+hyprland")]
|
||||||
Self::Hyprland => Ok(clients.hyprland()),
|
Self::Hyprland => Ok(Arc::new(hyprland::Client::new())),
|
||||||
#[cfg(feature = "workspaces+niri")]
|
|
||||||
Self::Niri => Ok(Arc::new(niri::Client::new())),
|
|
||||||
Self::Unsupported => Err(Report::msg("Unsupported compositor")
|
Self::Unsupported => Err(Report::msg("Unsupported compositor")
|
||||||
.note("Currently workspaces are only supported by Sway, Niri and Hyprland")),
|
.note("Currently workspaces are only supported by Sway and Hyprland")),
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => Err(Report::msg("Unsupported compositor")
|
|
||||||
.note("Workspaces feature is disabled for this compositor")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,29 +83,29 @@ pub struct Workspace {
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates workspace visibility.
|
/// Indicates workspace visibility. Visible workspaces have a boolean flag to indicate if they are also focused.
|
||||||
/// Visible workspaces have a boolean flag to indicate if they are also focused.
|
/// Yes, this is the same signature as Option<bool>, but it's impl is a lot more suited for our case.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Visibility {
|
pub enum Visibility {
|
||||||
Visible { focused: bool },
|
Visible(bool),
|
||||||
Hidden,
|
Hidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visibility {
|
impl Visibility {
|
||||||
pub fn visible() -> Self {
|
pub fn visible() -> Self {
|
||||||
Self::Visible { focused: false }
|
Self::Visible(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focused() -> Self {
|
pub fn focused() -> Self {
|
||||||
Self::Visible { focused: true }
|
Self::Visible(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_visible(self) -> bool {
|
pub fn is_visible(self) -> bool {
|
||||||
matches!(self, Self::Visible { .. })
|
matches!(self, Self::Visible(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_focused(self) -> bool {
|
pub fn is_focused(self) -> bool {
|
||||||
if let Self::Visible { focused } = self {
|
if let Self::Visible(focused) = self {
|
||||||
focused
|
focused
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -179,11 +114,6 @@ impl Visibility {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
pub struct KeyboardLayoutUpdate(pub String);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg(feature = "workspaces")]
|
|
||||||
pub enum WorkspaceUpdate {
|
pub enum WorkspaceUpdate {
|
||||||
/// Provides an initial list of workspaces.
|
/// Provides an initial list of workspaces.
|
||||||
/// This is re-sent to all subscribers when a new subscription is created.
|
/// This is re-sent to all subscribers when a new subscription is created.
|
||||||
|
|
@ -202,12 +132,6 @@ pub enum WorkspaceUpdate {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The urgent state of a node changed.
|
|
||||||
Urgent {
|
|
||||||
id: i64,
|
|
||||||
urgent: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An update was triggered by the compositor but this was not mapped by Ironbar.
|
/// An update was triggered by the compositor but this was not mapped by Ironbar.
|
||||||
///
|
///
|
||||||
/// This is purely used for ergonomics within the compositor clients
|
/// This is purely used for ergonomics within the compositor clients
|
||||||
|
|
@ -215,44 +139,12 @@ pub enum WorkspaceUpdate {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
pub struct BindModeUpdate {
|
|
||||||
/// The binding mode that became active.
|
|
||||||
pub name: String,
|
|
||||||
/// Whether the mode should be parsed as pango markup.
|
|
||||||
pub pango_markup: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "workspaces")]
|
|
||||||
pub trait WorkspaceClient: Debug + Send + Sync {
|
pub trait WorkspaceClient: Debug + Send + Sync {
|
||||||
/// Requests the workspace with this id is focused.
|
/// Requests the workspace with this name is focused.
|
||||||
fn focus(&self, id: i64);
|
fn focus(&self, name: String) -> Result<()>;
|
||||||
|
|
||||||
/// Creates a new to workspace event receiver.
|
/// Creates a new to workspace event receiver.
|
||||||
fn subscribe(&self) -> broadcast::Receiver<WorkspaceUpdate>;
|
fn subscribe_workspace_change(&self) -> broadcast::Receiver<WorkspaceUpdate>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "workspaces")]
|
|
||||||
register_fallible_client!(dyn WorkspaceClient, workspaces);
|
register_fallible_client!(dyn WorkspaceClient, workspaces);
|
||||||
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
pub trait KeyboardLayoutClient: Debug + Send + Sync {
|
|
||||||
/// Switches to the next layout.
|
|
||||||
fn set_next_active(&self);
|
|
||||||
|
|
||||||
/// Creates a new to keyboard layout event receiver.
|
|
||||||
fn subscribe(&self) -> broadcast::Receiver<KeyboardLayoutUpdate>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
register_fallible_client!(dyn KeyboardLayoutClient, keyboard_layout);
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
pub trait BindModeClient: Debug + Send + Sync {
|
|
||||||
/// Add a callback for bindmode updates.
|
|
||||||
fn subscribe(&self) -> Result<broadcast::Receiver<BindModeUpdate>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
register_fallible_client!(dyn BindModeClient, bindmode);
|
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
/// Taken from the `niri_ipc` crate.
|
|
||||||
/// Only a relevant snippet has been extracted
|
|
||||||
/// to reduce compile times.
|
|
||||||
use crate::clients::compositor::Workspace as IronWorkspace;
|
|
||||||
use crate::{await_sync, clients::compositor::Visibility};
|
|
||||||
use color_eyre::eyre::{Result, eyre};
|
|
||||||
use core::str;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{env, path::Path};
|
|
||||||
use tokio::{
|
|
||||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
|
||||||
net::UnixStream,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub enum Request {
|
|
||||||
Action(Action),
|
|
||||||
EventStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Reply = Result<Response, String>;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub enum Response {
|
|
||||||
Handled,
|
|
||||||
Workspaces(Vec<Workspace>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub enum Action {
|
|
||||||
FocusWorkspace { reference: WorkspaceReferenceArg },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum WorkspaceReferenceArg {
|
|
||||||
Name(String),
|
|
||||||
Id(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct Workspace {
|
|
||||||
pub id: u64,
|
|
||||||
pub idx: u8,
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub output: Option<String>,
|
|
||||||
pub is_active: bool,
|
|
||||||
pub is_focused: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Workspace> for IronWorkspace {
|
|
||||||
fn from(workspace: &Workspace) -> IronWorkspace {
|
|
||||||
// Workspaces in niri don't neccessarily have names.
|
|
||||||
// If the niri workspace has a name then it is assigned as is,
|
|
||||||
// but if it does not have a name, the monitor index is used.
|
|
||||||
Self {
|
|
||||||
id: workspace.id as i64,
|
|
||||||
name: workspace.name.clone().unwrap_or(workspace.idx.to_string()),
|
|
||||||
monitor: workspace.output.clone().unwrap_or_default(),
|
|
||||||
visibility: if workspace.is_active {
|
|
||||||
Visibility::Visible {
|
|
||||||
focused: workspace.is_focused,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Visibility::Hidden
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
WorkspacesChanged { workspaces: Vec<Workspace> },
|
|
||||||
WorkspaceActivated { id: u64, focused: bool },
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Connection(UnixStream);
|
|
||||||
impl Connection {
|
|
||||||
pub async fn connect() -> Result<Self> {
|
|
||||||
let socket_path =
|
|
||||||
env::var_os("NIRI_SOCKET").ok_or_else(|| eyre!("NIRI_SOCKET not found!"))?;
|
|
||||||
Self::connect_to(socket_path).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_to(path: impl AsRef<Path>) -> Result<Self> {
|
|
||||||
let raw_stream = UnixStream::connect(path.as_ref()).await?;
|
|
||||||
let stream = raw_stream;
|
|
||||||
Ok(Self(stream))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send(
|
|
||||||
&mut self,
|
|
||||||
request: Request,
|
|
||||||
) -> Result<(Reply, impl FnMut() -> Result<Event> + '_)> {
|
|
||||||
let Self(stream) = self;
|
|
||||||
let mut buf = serde_json::to_string(&request)?;
|
|
||||||
|
|
||||||
stream.write_all(buf.as_bytes()).await?;
|
|
||||||
stream.shutdown().await?;
|
|
||||||
|
|
||||||
buf.clear();
|
|
||||||
let mut reader = BufReader::new(stream);
|
|
||||||
reader.read_line(&mut buf).await?;
|
|
||||||
let reply = serde_json::from_str(&buf)?;
|
|
||||||
|
|
||||||
let events = move || {
|
|
||||||
buf.clear();
|
|
||||||
await_sync(async {
|
|
||||||
reader.read_line(&mut buf).await.unwrap_or(0);
|
|
||||||
});
|
|
||||||
let event: Event = serde_json::from_str(&buf).unwrap_or(Event::Other);
|
|
||||||
Ok(event)
|
|
||||||
};
|
|
||||||
Ok((reply, events))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
use super::{Workspace as IronWorkspace, WorkspaceClient, WorkspaceUpdate};
|
|
||||||
use crate::channels::SyncSenderExt;
|
|
||||||
use crate::clients::compositor::Visibility;
|
|
||||||
use crate::{arc_rw, read_lock, spawn, write_lock};
|
|
||||||
use color_eyre::Report;
|
|
||||||
use connection::{Action, Connection, Event, Request, WorkspaceReferenceArg};
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use tokio::sync::broadcast;
|
|
||||||
use tracing::{debug, error, warn};
|
|
||||||
|
|
||||||
mod connection;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client {
|
|
||||||
tx: broadcast::Sender<WorkspaceUpdate>,
|
|
||||||
_rx: broadcast::Receiver<WorkspaceUpdate>,
|
|
||||||
|
|
||||||
workspaces: Arc<RwLock<Vec<IronWorkspace>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (tx, rx) = broadcast::channel(32);
|
|
||||||
let tx2 = tx.clone();
|
|
||||||
|
|
||||||
let workspace_state = arc_rw!(vec![]);
|
|
||||||
let workspace_state2 = workspace_state.clone();
|
|
||||||
|
|
||||||
spawn(async move {
|
|
||||||
let mut conn = Connection::connect().await?;
|
|
||||||
let (_, mut event_listener) = conn.send(Request::EventStream).await?;
|
|
||||||
|
|
||||||
let mut first_event = true;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let events = match event_listener() {
|
|
||||||
Ok(Event::WorkspacesChanged { workspaces }) => {
|
|
||||||
debug!("WorkspacesChanged: {:?}", workspaces);
|
|
||||||
|
|
||||||
// Niri only has a WorkspacesChanged Event and Ironbar has 4 events which have to be handled: Add, Remove, Rename and Move.
|
|
||||||
// This is handled by keeping a previous state of workspaces and comparing with the new state for changes.
|
|
||||||
let new_workspaces: Vec<IronWorkspace> = workspaces
|
|
||||||
.into_iter()
|
|
||||||
.map(|w| IronWorkspace::from(&w))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut updates: Vec<WorkspaceUpdate> = vec![];
|
|
||||||
|
|
||||||
if first_event {
|
|
||||||
// Niri's WorkspacesChanged event does not initially sort workspaces by ID when first output,
|
|
||||||
// which makes sort = added meaningless. Therefore, new_workspaces are sorted by ID here to ensure a consistent addition order.
|
|
||||||
let mut new_workspaces = new_workspaces.clone();
|
|
||||||
new_workspaces.sort_by_key(|w| w.id);
|
|
||||||
updates.push(WorkspaceUpdate::Init(new_workspaces));
|
|
||||||
first_event = false;
|
|
||||||
} else {
|
|
||||||
// first pass - add/update
|
|
||||||
for workspace in &new_workspaces {
|
|
||||||
let workspace_state = read_lock!(workspace_state);
|
|
||||||
let old_workspace = workspace_state
|
|
||||||
.iter()
|
|
||||||
.find(|&w: &&IronWorkspace| w.id == workspace.id);
|
|
||||||
|
|
||||||
match old_workspace {
|
|
||||||
None => updates.push(WorkspaceUpdate::Add(workspace.clone())),
|
|
||||||
Some(old_workspace) => {
|
|
||||||
if workspace.name != old_workspace.name {
|
|
||||||
updates.push(WorkspaceUpdate::Rename {
|
|
||||||
id: workspace.id,
|
|
||||||
name: workspace.name.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if workspace.monitor != old_workspace.monitor {
|
|
||||||
updates.push(WorkspaceUpdate::Move(workspace.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// second pass - delete
|
|
||||||
for workspace in read_lock!(workspace_state).iter() {
|
|
||||||
let exists = new_workspaces.iter().any(|w| w.id == workspace.id);
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
updates.push(WorkspaceUpdate::Remove(workspace.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*write_lock!(workspace_state) = new_workspaces;
|
|
||||||
updates
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Event::WorkspaceActivated { id, focused }) => {
|
|
||||||
debug!("WorkspaceActivated: id: {}, focused: {}", id, focused);
|
|
||||||
|
|
||||||
// workspace with id is activated, if focus is true then it is also focused
|
|
||||||
// if focused is true then focus has changed => find old focused workspace. set it to inactive and set current
|
|
||||||
//
|
|
||||||
// we use indexes here as both new/old need to be mutable
|
|
||||||
|
|
||||||
let new_index = read_lock!(workspace_state)
|
|
||||||
.iter()
|
|
||||||
.position(|w| w.id == id as i64);
|
|
||||||
|
|
||||||
if let Some(new_index) = new_index {
|
|
||||||
if focused {
|
|
||||||
let old_index = read_lock!(workspace_state)
|
|
||||||
.iter()
|
|
||||||
.position(|w| w.visibility.is_focused());
|
|
||||||
|
|
||||||
if let Some(old_index) = old_index {
|
|
||||||
write_lock!(workspace_state)[new_index].visibility =
|
|
||||||
Visibility::focused();
|
|
||||||
|
|
||||||
if read_lock!(workspace_state)[old_index].monitor
|
|
||||||
== read_lock!(workspace_state)[new_index].monitor
|
|
||||||
{
|
|
||||||
write_lock!(workspace_state)[old_index].visibility =
|
|
||||||
Visibility::Hidden;
|
|
||||||
} else {
|
|
||||||
write_lock!(workspace_state)[old_index].visibility =
|
|
||||||
Visibility::visible();
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![WorkspaceUpdate::Focus {
|
|
||||||
old: Some(read_lock!(workspace_state)[old_index].clone()),
|
|
||||||
new: read_lock!(workspace_state)[new_index].clone(),
|
|
||||||
}]
|
|
||||||
} else {
|
|
||||||
write_lock!(workspace_state)[new_index].visibility =
|
|
||||||
Visibility::focused();
|
|
||||||
|
|
||||||
vec![WorkspaceUpdate::Focus {
|
|
||||||
old: None,
|
|
||||||
new: read_lock!(workspace_state)[new_index].clone(),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if focused is false means active workspace on a particular monitor has changed =>
|
|
||||||
// change all workspaces on monitor to inactive and change current workspace as active
|
|
||||||
write_lock!(workspace_state)[new_index].visibility =
|
|
||||||
Visibility::visible();
|
|
||||||
|
|
||||||
let old_index = read_lock!(workspace_state).iter().position(|w| {
|
|
||||||
(w.visibility.is_focused() || w.visibility.is_visible())
|
|
||||||
&& w.monitor
|
|
||||||
== read_lock!(workspace_state)[new_index].monitor
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(old_index) = old_index {
|
|
||||||
write_lock!(workspace_state)[old_index].visibility =
|
|
||||||
Visibility::Hidden;
|
|
||||||
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("No workspace with id for new focus/visible workspace found");
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Event::Other) => {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("{err:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for event in events {
|
|
||||||
tx.send_expect(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<(), Report>(())
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
tx: tx2,
|
|
||||||
_rx: rx,
|
|
||||||
workspaces: workspace_state2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkspaceClient for Client {
|
|
||||||
fn focus(&self, id: i64) {
|
|
||||||
debug!("focusing workspace with id: {}", id);
|
|
||||||
|
|
||||||
// this does annoyingly require spawning a separate connection for every focus call
|
|
||||||
// the alternative is sticking the conn behind a mutex which could perform worse
|
|
||||||
spawn(async move {
|
|
||||||
let mut conn = Connection::connect().await?;
|
|
||||||
|
|
||||||
let command = Request::Action(Action::FocusWorkspace {
|
|
||||||
reference: WorkspaceReferenceArg::Id(id as u64),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(err) = conn.send(command).await {
|
|
||||||
error!("failed to send command: {err:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<(), Report>(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscribe(&self) -> broadcast::Receiver<WorkspaceUpdate> {
|
|
||||||
let rx = self.tx.subscribe();
|
|
||||||
|
|
||||||
let workspaces = read_lock!(self.workspaces);
|
|
||||||
if !workspaces.is_empty() {
|
|
||||||
self.tx
|
|
||||||
.send_expect(WorkspaceUpdate::Init(workspaces.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +1,75 @@
|
||||||
use super::{Visibility, Workspace};
|
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||||
use crate::channels::SyncSenderExt;
|
use crate::{await_sync, send, spawn};
|
||||||
use crate::clients::sway::Client;
|
use color_eyre::{Report, Result};
|
||||||
use crate::{await_sync, error, spawn};
|
use futures_lite::StreamExt;
|
||||||
use color_eyre::Report;
|
use std::sync::Arc;
|
||||||
use swayipc_async::{InputChange, InputEvent, Node, WorkspaceChange, WorkspaceEvent};
|
use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent};
|
||||||
use tokio::sync::broadcast::{Receiver, channel};
|
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tracing::{info, trace};
|
||||||
|
|
||||||
#[cfg(feature = "workspaces")]
|
#[derive(Debug)]
|
||||||
use super::WorkspaceUpdate;
|
pub struct Client {
|
||||||
|
client: Arc<Mutex<Connection>>,
|
||||||
|
workspace_tx: Sender<WorkspaceUpdate>,
|
||||||
|
_workspace_rx: Receiver<WorkspaceUpdate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub(crate) async fn new() -> Result<Self> {
|
||||||
|
// Avoid using `arc_mut!` here because we need tokio Mutex.
|
||||||
|
let client = Arc::new(Mutex::new(Connection::new().await?));
|
||||||
|
info!("Sway IPC subscription client connected");
|
||||||
|
|
||||||
|
let (workspace_tx, workspace_rx) = channel(16);
|
||||||
|
|
||||||
|
{
|
||||||
|
// create 2nd client as subscription takes ownership
|
||||||
|
let client = Connection::new().await?;
|
||||||
|
let workspace_tx = workspace_tx.clone();
|
||||||
|
|
||||||
#[cfg(feature = "workspaces+sway")]
|
|
||||||
impl super::WorkspaceClient for Client {
|
|
||||||
fn focus(&self, id: i64) {
|
|
||||||
let client = self.connection().clone();
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut client = client.lock().await;
|
let event_types = [EventType::Workspace];
|
||||||
|
let mut events = client.subscribe(event_types).await?;
|
||||||
|
|
||||||
let name = client
|
while let Some(event) = events.next().await {
|
||||||
.get_workspaces()
|
trace!("event: {:?}", event);
|
||||||
.await?
|
if let Event::Workspace(event) = event? {
|
||||||
.into_iter()
|
let event = WorkspaceUpdate::from(*event);
|
||||||
.find(|w| w.id == id)
|
if !matches!(event, WorkspaceUpdate::Unknown) {
|
||||||
.map(|w| w.name);
|
workspace_tx.send(event)?;
|
||||||
|
}
|
||||||
let Some(name) = name else {
|
|
||||||
return Err(Report::msg(format!("couldn't find workspace with id {id}")));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = client.run_command(format!("workspace {name}")).await {
|
|
||||||
return Err(Report::msg(format!(
|
|
||||||
"Couldn't focus workspace '{id}': {e:#}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok::<(), Report>(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscribe(&self) -> Receiver<WorkspaceUpdate> {
|
Ok(Self {
|
||||||
let (tx, rx) = channel(16);
|
client,
|
||||||
|
workspace_tx,
|
||||||
|
_workspace_rx: workspace_rx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let client = self.connection().clone();
|
impl WorkspaceClient for Client {
|
||||||
|
fn focus(&self, id: String) -> Result<()> {
|
||||||
|
await_sync(async move {
|
||||||
|
let mut client = self.client.lock().await;
|
||||||
|
client.run_command(format!("workspace {id}")).await
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscribe_workspace_change(&self) -> Receiver<WorkspaceUpdate> {
|
||||||
|
let rx = self.workspace_tx.subscribe();
|
||||||
|
|
||||||
|
{
|
||||||
|
let tx = self.workspace_tx.clone();
|
||||||
|
let client = self.client.clone();
|
||||||
|
|
||||||
// TODO: this needs refactoring
|
|
||||||
await_sync(async {
|
await_sync(async {
|
||||||
let mut client = client.lock().await;
|
let mut client = client.lock().await;
|
||||||
let workspaces = client.get_workspaces().await.expect("to get workspaces");
|
let workspaces = client.get_workspaces().await.expect("to get workspaces");
|
||||||
|
|
@ -50,17 +77,9 @@ impl super::WorkspaceClient for Client {
|
||||||
let event =
|
let event =
|
||||||
WorkspaceUpdate::Init(workspaces.into_iter().map(Workspace::from).collect());
|
WorkspaceUpdate::Init(workspaces.into_iter().map(Workspace::from).collect());
|
||||||
|
|
||||||
tx.send_expect(event);
|
send!(tx, event);
|
||||||
|
|
||||||
drop(client);
|
|
||||||
|
|
||||||
self.add_listener::<WorkspaceEvent>(move |event| {
|
|
||||||
let update = WorkspaceUpdate::from(event.clone());
|
|
||||||
tx.send_expect(update);
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.expect("to add listener");
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +135,6 @@ impl From<&swayipc_async::Workspace> for Visibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "workspaces")]
|
|
||||||
impl From<WorkspaceEvent> for WorkspaceUpdate {
|
impl From<WorkspaceEvent> for WorkspaceUpdate {
|
||||||
fn from(event: WorkspaceEvent) -> Self {
|
fn from(event: WorkspaceEvent) -> Self {
|
||||||
match event.change {
|
match event.change {
|
||||||
|
|
@ -133,136 +151,7 @@ impl From<WorkspaceEvent> for WorkspaceUpdate {
|
||||||
WorkspaceChange::Move => {
|
WorkspaceChange::Move => {
|
||||||
Self::Move(event.current.expect("Missing current workspace").into())
|
Self::Move(event.current.expect("Missing current workspace").into())
|
||||||
}
|
}
|
||||||
WorkspaceChange::Rename => {
|
|
||||||
if let Some(node) = event.current {
|
|
||||||
Self::Rename {
|
|
||||||
id: node.id,
|
|
||||||
name: node.name.unwrap_or_default(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self::Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WorkspaceChange::Urgent => {
|
|
||||||
if let Some(node) = event.current {
|
|
||||||
Self::Urgent {
|
|
||||||
id: node.id,
|
|
||||||
urgent: node.urgent,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self::Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Self::Unknown,
|
_ => Self::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+sway")]
|
|
||||||
use super::{KeyboardLayoutClient, KeyboardLayoutUpdate};
|
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+sway")]
|
|
||||||
impl KeyboardLayoutClient for Client {
|
|
||||||
fn set_next_active(&self) {
|
|
||||||
let client = self.connection().clone();
|
|
||||||
spawn(async move {
|
|
||||||
let mut client = client.lock().await;
|
|
||||||
|
|
||||||
let inputs = client.get_inputs().await.expect("to get inputs");
|
|
||||||
|
|
||||||
if let Some(keyboard) = inputs
|
|
||||||
.into_iter()
|
|
||||||
.find(|i| i.xkb_active_layout_name.is_some())
|
|
||||||
{
|
|
||||||
if let Err(e) = client
|
|
||||||
.run_command(format!(
|
|
||||||
"input {} xkb_switch_layout next",
|
|
||||||
keyboard.identifier
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!("Failed to switch keyboard layout due to Sway error: {e}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("Failed to get keyboard identifier from Sway");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscribe(&self) -> Receiver<KeyboardLayoutUpdate> {
|
|
||||||
let (tx, rx) = channel(16);
|
|
||||||
|
|
||||||
let client = self.connection().clone();
|
|
||||||
|
|
||||||
await_sync(async {
|
|
||||||
let mut client = client.lock().await;
|
|
||||||
let inputs = client.get_inputs().await.expect("to get inputs");
|
|
||||||
|
|
||||||
if let Some(layout) = inputs.into_iter().find_map(|i| i.xkb_active_layout_name) {
|
|
||||||
tx.send_expect(KeyboardLayoutUpdate(layout));
|
|
||||||
} else {
|
|
||||||
error!("Failed to get keyboard layout from Sway!");
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(client);
|
|
||||||
|
|
||||||
self.add_listener::<InputEvent>(move |event| {
|
|
||||||
if let Ok(layout) = KeyboardLayoutUpdate::try_from(event.clone()) {
|
|
||||||
tx.send_expect(layout);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.expect("to add listener");
|
|
||||||
});
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "keyboard+sway")]
|
|
||||||
impl TryFrom<InputEvent> for KeyboardLayoutUpdate {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: InputEvent) -> Result<Self, Self::Error> {
|
|
||||||
match value.change {
|
|
||||||
InputChange::XkbLayout => {
|
|
||||||
if let Some(layout) = value.input.xkb_active_layout_name {
|
|
||||||
Ok(KeyboardLayoutUpdate(layout))
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+sway")]
|
|
||||||
use super::{BindModeClient, BindModeUpdate};
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode+sway")]
|
|
||||||
impl BindModeClient for Client {
|
|
||||||
fn subscribe(&self) -> Result<Receiver<BindModeUpdate>, Report> {
|
|
||||||
let (tx, rx) = channel(16);
|
|
||||||
await_sync(async {
|
|
||||||
self.add_listener::<swayipc_async::ModeEvent>(move |mode| {
|
|
||||||
tracing::trace!("mode: {:?}", mode);
|
|
||||||
|
|
||||||
// when no binding is active the bindmode is named "default", but we must display
|
|
||||||
// nothing in this case.
|
|
||||||
let name = if mode.change == "default" {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
mode.change.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
tx.send_expect(BindModeUpdate {
|
|
||||||
name,
|
|
||||||
pango_markup: mode.pango_markup,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
})?;
|
|
||||||
Ok(rx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,236 +0,0 @@
|
||||||
use crate::channels::SyncSenderExt;
|
|
||||||
use crate::{Ironbar, arc_rw, read_lock, spawn, write_lock};
|
|
||||||
use color_eyre::{Report, Result};
|
|
||||||
use colpetto::event::{AsRawEvent, DeviceEvent, KeyState, KeyboardEvent};
|
|
||||||
use colpetto::{DeviceCapability, Libinput};
|
|
||||||
use evdev_rs::DeviceWrapper;
|
|
||||||
use evdev_rs::enums::{EV_KEY, EV_LED, EventCode, int_to_ev_key};
|
|
||||||
use futures_lite::StreamExt;
|
|
||||||
use rustix::fs::{Mode, OFlags, open};
|
|
||||||
use rustix::io::Errno;
|
|
||||||
use std::ffi::{CStr, CString, c_int};
|
|
||||||
use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
|
|
||||||
use std::os::unix::io::OwnedFd;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use std::time::Duration;
|
|
||||||
use tokio::sync::broadcast;
|
|
||||||
use tokio::task::LocalSet;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
use tracing::{debug, error};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Key {
|
|
||||||
Caps,
|
|
||||||
Num,
|
|
||||||
Scroll,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Key> for EV_KEY {
|
|
||||||
fn from(value: Key) -> Self {
|
|
||||||
match value {
|
|
||||||
Key::Caps => Self::KEY_CAPSLOCK,
|
|
||||||
Key::Num => Self::KEY_NUMLOCK,
|
|
||||||
Key::Scroll => Self::KEY_SCROLLLOCK,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<EV_KEY> for Key {
|
|
||||||
type Error = Report;
|
|
||||||
|
|
||||||
fn try_from(value: EV_KEY) -> std::result::Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
EV_KEY::KEY_CAPSLOCK => Ok(Key::Caps),
|
|
||||||
EV_KEY::KEY_NUMLOCK => Ok(Key::Num),
|
|
||||||
EV_KEY::KEY_SCROLLLOCK => Ok(Key::Scroll),
|
|
||||||
_ => Err(Report::msg("provided key is not supported toggle key")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Key {
|
|
||||||
fn get_state<P: AsRef<Path>>(self, device_path: P) -> Result<bool> {
|
|
||||||
let device = evdev_rs::Device::new_from_path(device_path)?;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::Caps => device.event_value(&EventCode::EV_LED(EV_LED::LED_CAPSL)),
|
|
||||||
Self::Num => device.event_value(&EventCode::EV_LED(EV_LED::LED_NUML)),
|
|
||||||
Self::Scroll => device.event_value(&EventCode::EV_LED(EV_LED::LED_SCROLLL)),
|
|
||||||
}
|
|
||||||
.map(|v| v > 0)
|
|
||||||
.ok_or_else(|| Report::msg("failed to get key status"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct KeyEvent {
|
|
||||||
pub key: Key,
|
|
||||||
pub state: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
Device,
|
|
||||||
Key(KeyEvent),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct KeyData<P: AsRef<Path>> {
|
|
||||||
device_path: P,
|
|
||||||
key: EV_KEY,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: AsRef<Path>> TryFrom<KeyData<P>> for Event {
|
|
||||||
type Error = Report;
|
|
||||||
|
|
||||||
fn try_from(data: KeyData<P>) -> Result<Self> {
|
|
||||||
let key = Key::try_from(data.key)?;
|
|
||||||
|
|
||||||
key.get_state(data.device_path)
|
|
||||||
.map(|state| KeyEvent { key, state })
|
|
||||||
.map(Event::Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client {
|
|
||||||
tx: broadcast::Sender<Event>,
|
|
||||||
_rx: broadcast::Receiver<Event>,
|
|
||||||
|
|
||||||
seat: String,
|
|
||||||
known_devices: Arc<RwLock<Vec<PathBuf>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn init(seat: String) -> Arc<Self> {
|
|
||||||
let client = Arc::new(Self::new(seat));
|
|
||||||
|
|
||||||
{
|
|
||||||
let client = client.clone();
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let local = LocalSet::new();
|
|
||||||
|
|
||||||
local.spawn_local(async move {
|
|
||||||
if let Err(err) = client.run().await {
|
|
||||||
error!("{err:?}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ironbar::runtime().block_on(local);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
client
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(seat: String) -> Self {
|
|
||||||
let (tx, rx) = broadcast::channel(4);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
tx,
|
|
||||||
_rx: rx,
|
|
||||||
seat,
|
|
||||||
known_devices: arc_rw!(vec![]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_restricted(path: &CStr, flags: c_int) -> std::result::Result<RawFd, i32> {
|
|
||||||
open(path, OFlags::from_bits_retain(flags as u32), Mode::empty())
|
|
||||||
.map(IntoRawFd::into_raw_fd)
|
|
||||||
.map_err(Errno::raw_os_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_restricted(fd: c_int) {
|
|
||||||
drop(unsafe { OwnedFd::from_raw_fd(fd) });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(&self) -> Result<()> {
|
|
||||||
let mut libinput = Libinput::with_tracing(Self::open_restricted, Self::close_restricted)?;
|
|
||||||
|
|
||||||
libinput.udev_assign_seat(CString::new(&*self.seat)?.as_c_str())?;
|
|
||||||
|
|
||||||
let mut stream = libinput.event_stream()?;
|
|
||||||
while let Some(event) = stream.try_next().await? {
|
|
||||||
match event {
|
|
||||||
colpetto::Event::Device(DeviceEvent::Added(event)) => {
|
|
||||||
let device = event.device();
|
|
||||||
if !device.has_capability(DeviceCapability::Keyboard) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = device.name();
|
|
||||||
let Some(device) = event.device().udev_device() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(device_path) = device.devnode() {
|
|
||||||
// not all devices which report as keyboards actually are one -
|
|
||||||
// fire test event so we can figure out if it is
|
|
||||||
let caps_event: Result<Event> = KeyData {
|
|
||||||
device_path,
|
|
||||||
key: EV_KEY::KEY_CAPSLOCK,
|
|
||||||
}
|
|
||||||
.try_into();
|
|
||||||
|
|
||||||
if caps_event.is_ok() {
|
|
||||||
debug!(
|
|
||||||
"new keyboard device: {} | {}",
|
|
||||||
name.to_string_lossy(),
|
|
||||||
device_path.display()
|
|
||||||
);
|
|
||||||
write_lock!(self.known_devices).push(device_path.to_path_buf());
|
|
||||||
self.tx.send_expect(Event::Device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
colpetto::Event::Keyboard(KeyboardEvent::Key(event))
|
|
||||||
if event.key_state() == KeyState::Released =>
|
|
||||||
{
|
|
||||||
let Some(device) = event.device().udev_device() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(
|
|
||||||
key @ (EV_KEY::KEY_CAPSLOCK | EV_KEY::KEY_NUMLOCK | EV_KEY::KEY_SCROLLLOCK),
|
|
||||||
) = int_to_ev_key(event.key())
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(device_path) = device.devnode().map(PathBuf::from) {
|
|
||||||
let tx = self.tx.clone();
|
|
||||||
|
|
||||||
// need to spawn a task to avoid blocking
|
|
||||||
spawn(async move {
|
|
||||||
// wait for kb to change
|
|
||||||
sleep(Duration::from_millis(50)).await;
|
|
||||||
|
|
||||||
let data = KeyData { device_path, key };
|
|
||||||
|
|
||||||
if let Ok(event) = data.try_into() {
|
|
||||||
tx.send_expect(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Report::msg("unexpected end of stream"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_state(&self, key: Key) -> bool {
|
|
||||||
read_lock!(self.known_devices)
|
|
||||||
.iter()
|
|
||||||
.map(|device_path| key.get_state(device_path))
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.reduce(|state, curr| state || curr)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
|
||||||
self.tx.subscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +1,21 @@
|
||||||
use crate::{Ironbar, await_sync};
|
use crate::{await_sync, Ironbar};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
#[cfg(any(
|
#[cfg(feature = "workspaces")]
|
||||||
feature = "bindmode",
|
|
||||||
feature = "hyprland",
|
|
||||||
feature = "keyboard",
|
|
||||||
feature = "workspaces",
|
|
||||||
))]
|
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
pub mod libinput;
|
|
||||||
#[cfg(feature = "cairo")]
|
#[cfg(feature = "cairo")]
|
||||||
pub mod lua;
|
pub mod lua;
|
||||||
#[cfg(feature = "music")]
|
#[cfg(feature = "music")]
|
||||||
pub mod music;
|
pub mod music;
|
||||||
#[cfg(feature = "network_manager")]
|
#[cfg(feature = "network_manager")]
|
||||||
pub mod networkmanager;
|
pub mod networkmanager;
|
||||||
#[cfg(feature = "sway")]
|
|
||||||
pub mod sway;
|
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
pub mod swaync;
|
pub mod swaync;
|
||||||
#[cfg(feature = "sys_info")]
|
|
||||||
pub mod sysinfo;
|
|
||||||
#[cfg(feature = "tray")]
|
#[cfg(feature = "tray")]
|
||||||
pub mod tray;
|
pub mod tray;
|
||||||
#[cfg(feature = "upower")]
|
#[cfg(feature = "upower")]
|
||||||
|
|
@ -43,28 +31,16 @@ pub struct Clients {
|
||||||
wayland: Option<Arc<wayland::Client>>,
|
wayland: Option<Arc<wayland::Client>>,
|
||||||
#[cfg(feature = "workspaces")]
|
#[cfg(feature = "workspaces")]
|
||||||
workspaces: Option<Arc<dyn compositor::WorkspaceClient>>,
|
workspaces: Option<Arc<dyn compositor::WorkspaceClient>>,
|
||||||
#[cfg(feature = "sway")]
|
|
||||||
sway: Option<Arc<sway::Client>>,
|
|
||||||
#[cfg(feature = "hyprland")]
|
|
||||||
hyprland: Option<Arc<compositor::hyprland::Client>>,
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
bindmode: Option<Arc<dyn compositor::BindModeClient>>,
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard: Option<Arc<clipboard::Client>>,
|
clipboard: Option<Arc<clipboard::Client>>,
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
libinput: HashMap<Box<str>, Arc<libinput::Client>>,
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
keyboard_layout: Option<Arc<dyn compositor::KeyboardLayoutClient>>,
|
|
||||||
#[cfg(feature = "cairo")]
|
#[cfg(feature = "cairo")]
|
||||||
lua: Option<Rc<lua::LuaEngine>>,
|
lua: Option<Rc<lua::LuaEngine>>,
|
||||||
#[cfg(feature = "music")]
|
#[cfg(feature = "music")]
|
||||||
music: HashMap<music::ClientType, Arc<dyn music::MusicClient>>,
|
music: std::collections::HashMap<music::ClientType, Arc<dyn music::MusicClient>>,
|
||||||
#[cfg(feature = "network_manager")]
|
#[cfg(feature = "network_manager")]
|
||||||
network_manager: Option<Arc<networkmanager::Client>>,
|
network_manager: Option<Arc<networkmanager::Client>>,
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
notifications: Option<Arc<swaync::Client>>,
|
notifications: Option<Arc<swaync::Client>>,
|
||||||
#[cfg(feature = "sys_info")]
|
|
||||||
sys_info: Option<Arc<sysinfo::Client>>,
|
|
||||||
#[cfg(feature = "tray")]
|
#[cfg(feature = "tray")]
|
||||||
tray: Option<Arc<tray::Client>>,
|
tray: Option<Arc<tray::Client>>,
|
||||||
#[cfg(feature = "upower")]
|
#[cfg(feature = "upower")]
|
||||||
|
|
@ -97,68 +73,18 @@ impl Clients {
|
||||||
|
|
||||||
#[cfg(feature = "workspaces")]
|
#[cfg(feature = "workspaces")]
|
||||||
pub fn workspaces(&mut self) -> ClientResult<dyn compositor::WorkspaceClient> {
|
pub fn workspaces(&mut self) -> ClientResult<dyn compositor::WorkspaceClient> {
|
||||||
let client = if let Some(workspaces) = &self.workspaces {
|
let client = match &self.workspaces {
|
||||||
workspaces.clone()
|
Some(workspaces) => workspaces.clone(),
|
||||||
} else {
|
None => {
|
||||||
let client = compositor::Compositor::create_workspace_client(self)?;
|
let client = compositor::Compositor::create_workspace_client()?;
|
||||||
self.workspaces.replace(client.clone());
|
self.workspaces.replace(client.clone());
|
||||||
client
|
client
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
pub fn keyboard_layout(&mut self) -> ClientResult<dyn compositor::KeyboardLayoutClient> {
|
|
||||||
let client = if let Some(keyboard_layout) = &self.keyboard_layout {
|
|
||||||
keyboard_layout.clone()
|
|
||||||
} else {
|
|
||||||
let client = compositor::Compositor::create_keyboard_layout_client(self)?;
|
|
||||||
self.keyboard_layout.replace(client.clone());
|
|
||||||
client
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
pub fn bindmode(&mut self) -> ClientResult<dyn compositor::BindModeClient> {
|
|
||||||
let client = if let Some(client) = &self.bindmode {
|
|
||||||
client.clone()
|
|
||||||
} else {
|
|
||||||
let client = compositor::Compositor::create_bindmode_client(self)?;
|
|
||||||
self.bindmode.replace(client.clone());
|
|
||||||
client
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sway")]
|
|
||||||
pub fn sway(&mut self) -> ClientResult<sway::Client> {
|
|
||||||
let client = if let Some(client) = &self.sway {
|
|
||||||
client.clone()
|
|
||||||
} else {
|
|
||||||
let client = await_sync(async { sway::Client::new().await })?;
|
|
||||||
let client = Arc::new(client);
|
|
||||||
self.sway.replace(client.clone());
|
|
||||||
client
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "hyprland")]
|
|
||||||
pub fn hyprland(&mut self) -> Arc<compositor::hyprland::Client> {
|
|
||||||
if let Some(client) = &self.hyprland {
|
|
||||||
client.clone()
|
|
||||||
} else {
|
|
||||||
let client = Arc::new(compositor::hyprland::Client::new());
|
|
||||||
self.hyprland.replace(client.clone());
|
|
||||||
client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "cairo")]
|
#[cfg(feature = "cairo")]
|
||||||
pub fn lua(&mut self, config_dir: &Path) -> Rc<lua::LuaEngine> {
|
pub fn lua(&mut self, config_dir: &Path) -> Rc<lua::LuaEngine> {
|
||||||
self.lua
|
self.lua
|
||||||
|
|
@ -166,17 +92,6 @@ impl Clients {
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
pub fn libinput(&mut self, seat: &str) -> Arc<libinput::Client> {
|
|
||||||
if let Some(client) = self.libinput.get(seat) {
|
|
||||||
client.clone()
|
|
||||||
} else {
|
|
||||||
let client = libinput::Client::init(seat.to_string());
|
|
||||||
self.libinput.insert(seat.into(), client.clone());
|
|
||||||
client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "music")]
|
#[cfg(feature = "music")]
|
||||||
pub fn music(&mut self, client_type: music::ClientType) -> Arc<dyn music::MusicClient> {
|
pub fn music(&mut self, client_type: music::ClientType) -> Arc<dyn music::MusicClient> {
|
||||||
self.music
|
self.music
|
||||||
|
|
@ -187,68 +102,55 @@ impl Clients {
|
||||||
|
|
||||||
#[cfg(feature = "network_manager")]
|
#[cfg(feature = "network_manager")]
|
||||||
pub fn network_manager(&mut self) -> ClientResult<networkmanager::Client> {
|
pub fn network_manager(&mut self) -> ClientResult<networkmanager::Client> {
|
||||||
if let Some(client) = &self.network_manager {
|
match &self.network_manager {
|
||||||
Ok(client.clone())
|
Some(client) => Ok(client.clone()),
|
||||||
} else {
|
None => {
|
||||||
let client = networkmanager::create_client()?;
|
let client = networkmanager::create_client()?;
|
||||||
self.network_manager = Some(client.clone());
|
self.network_manager = Some(client.clone());
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
pub fn notifications(&mut self) -> ClientResult<swaync::Client> {
|
pub fn notifications(&mut self) -> ClientResult<swaync::Client> {
|
||||||
let client = if let Some(client) = &self.notifications {
|
let client = match &self.notifications {
|
||||||
client.clone()
|
Some(client) => client.clone(),
|
||||||
} else {
|
None => {
|
||||||
let client = await_sync(async { swaync::Client::new().await })?;
|
let client = await_sync(async { swaync::Client::new().await })?;
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
self.notifications.replace(client.clone());
|
self.notifications.replace(client.clone());
|
||||||
client
|
client
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sys_info")]
|
|
||||||
pub fn sys_info(&mut self) -> Arc<sysinfo::Client> {
|
|
||||||
self.sys_info
|
|
||||||
.get_or_insert_with(|| {
|
|
||||||
let client = Arc::new(sysinfo::Client::new());
|
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
Ironbar::variable_manager().register_namespace("sysinfo", client.clone());
|
|
||||||
|
|
||||||
client
|
|
||||||
})
|
|
||||||
.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tray")]
|
#[cfg(feature = "tray")]
|
||||||
pub fn tray(&mut self) -> ClientResult<tray::Client> {
|
pub fn tray(&mut self) -> ClientResult<tray::Client> {
|
||||||
let client = if let Some(client) = &self.tray {
|
let client = match &self.tray {
|
||||||
client.clone()
|
Some(client) => client.clone(),
|
||||||
} else {
|
None => {
|
||||||
let client = await_sync(async { tray::Client::new().await })?;
|
let service_name = format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id());
|
||||||
|
|
||||||
|
let client = await_sync(async { tray::Client::new(&service_name).await })?;
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
self.tray.replace(client.clone());
|
self.tray.replace(client.clone());
|
||||||
client
|
client
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "upower")]
|
#[cfg(feature = "upower")]
|
||||||
pub fn upower(&mut self) -> ClientResult<zbus::fdo::PropertiesProxy<'static>> {
|
pub fn upower(&mut self) -> Arc<zbus::fdo::PropertiesProxy<'static>> {
|
||||||
let client = if let Some(client) = &self.upower {
|
self.upower
|
||||||
client.clone()
|
.get_or_insert_with(|| {
|
||||||
} else {
|
crate::await_sync(async { upower::create_display_proxy().await })
|
||||||
let client = await_sync(async { upower::create_display_proxy().await })?;
|
})
|
||||||
self.upower.replace(client.clone());
|
.clone()
|
||||||
client
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "volume")]
|
#[cfg(feature = "volume")]
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,13 @@ pub trait MusicClient: Debug + Send + Sync {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum ClientType {
|
pub enum ClientType {
|
||||||
#[cfg(feature = "music+mpd")]
|
|
||||||
Mpd { host: String, music_dir: PathBuf },
|
Mpd { host: String, music_dir: PathBuf },
|
||||||
#[cfg(feature = "music+mpris")]
|
|
||||||
Mpris,
|
Mpris,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_client(client_type: ClientType) -> Arc<dyn MusicClient> {
|
pub fn create_client(client_type: ClientType) -> Arc<dyn MusicClient> {
|
||||||
match client_type {
|
match client_type {
|
||||||
#[cfg(feature = "music+mpd")]
|
|
||||||
ClientType::Mpd { host, music_dir } => Arc::new(mpd::Client::new(host, music_dir)),
|
ClientType::Mpd { host, music_dir } => Arc::new(mpd::Client::new(host, music_dir)),
|
||||||
#[cfg(feature = "music+mpris")]
|
|
||||||
ClientType::Mpris => Arc::new(mpris::Client::new()),
|
ClientType::Mpris => Arc::new(mpris::Client::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use super::{
|
use super::{
|
||||||
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, TICK_INTERVAL_MS, Track,
|
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
|
||||||
};
|
};
|
||||||
use crate::channels::SyncSenderExt;
|
use crate::{await_sync, send, spawn, Ironbar};
|
||||||
use crate::{Ironbar, await_sync, spawn};
|
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use mpd_client::client::{ConnectionEvent, Subsystem};
|
use mpd_client::client::{ConnectionEvent, Subsystem};
|
||||||
use mpd_client::commands::{self, SeekMode};
|
use mpd_client::commands::{self, SeekMode};
|
||||||
use mpd_client::responses::{PlayState, Song};
|
use mpd_client::responses::{PlayState, Song};
|
||||||
use mpd_client::tag::Tag;
|
use mpd_client::tag::Tag;
|
||||||
use mpd_utils::{PersistentClient, mpd_client};
|
use mpd_utils::{mpd_client, PersistentClient};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -98,7 +97,7 @@ impl Client {
|
||||||
let status = Status::from(status);
|
let status = Status::from(status);
|
||||||
|
|
||||||
let update = PlayerUpdate::Update(Box::new(track), status);
|
let update = PlayerUpdate::Update(Box::new(track), status);
|
||||||
tx.send_expect(update);
|
send!(tx, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -114,7 +113,7 @@ impl Client {
|
||||||
elapsed: status.elapsed,
|
elapsed: status.elapsed,
|
||||||
});
|
});
|
||||||
|
|
||||||
tx.send_expect(update);
|
send!(tx, update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use super::{MusicClient, PlayerState, PlayerUpdate, Status, TICK_INTERVAL_MS, Track};
|
use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
|
||||||
use crate::channels::SyncSenderExt;
|
|
||||||
use crate::clients::music::ProgressTick;
|
use crate::clients::music::ProgressTick;
|
||||||
use crate::{arc_mut, lock, spawn_blocking};
|
use crate::{arc_mut, lock, send, spawn_blocking};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
@ -48,14 +47,10 @@ impl Client {
|
||||||
)) if transport_error.name() == Some(NO_ACTIVE_PLAYER)
|
)) if transport_error.name() == Some(NO_ACTIVE_PLAYER)
|
||||||
|| transport_error.name() == Some(NO_REPLY) =>
|
|| transport_error.name() == Some(NO_REPLY) =>
|
||||||
{
|
{
|
||||||
vec![]
|
Vec::new()
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("D-Bus error getting MPRIS players: {e:?}");
|
|
||||||
vec![]
|
|
||||||
}
|
}
|
||||||
|
_ => panic!("Failed to connect to D-Bus"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Acquire the lock of current_player before players to avoid deadlock.
|
// Acquire the lock of current_player before players to avoid deadlock.
|
||||||
// There are places where we lock on current_player and players, but we always lock on current_player first.
|
// There are places where we lock on current_player and players, but we always lock on current_player first.
|
||||||
// This is because we almost never need to lock on players without locking on current_player.
|
// This is because we almost never need to lock on players without locking on current_player.
|
||||||
|
|
@ -138,7 +133,7 @@ impl Client {
|
||||||
let mut players_locked = lock!(players);
|
let mut players_locked = lock!(players);
|
||||||
players_locked.remove(identity);
|
players_locked.remove(identity);
|
||||||
if players_locked.is_empty() {
|
if players_locked.is_empty() {
|
||||||
tx.send_expect(PlayerUpdate::Update(Box::new(None), Status::default()));
|
send!(tx, PlayerUpdate::Update(Box::new(None), Status::default()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -213,7 +208,7 @@ impl Client {
|
||||||
let track = Track::from(metadata);
|
let track = Track::from(metadata);
|
||||||
|
|
||||||
let player_update = PlayerUpdate::Update(Box::new(Some(track)), status);
|
let player_update = PlayerUpdate::Update(Box::new(Some(track)), status);
|
||||||
tx.send_expect(player_update);
|
send!(tx, player_update);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +238,7 @@ impl Client {
|
||||||
duration: metadata.length(),
|
duration: metadata.length(),
|
||||||
});
|
});
|
||||||
|
|
||||||
tx.send_expect(update);
|
send!(tx, update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -291,12 +286,6 @@ impl MusicClient for Client {
|
||||||
|
|
||||||
fn seek(&self, duration: Duration) -> Result<()> {
|
fn seek(&self, duration: Duration) -> Result<()> {
|
||||||
if let Some(player) = Self::get_player(self) {
|
if let Some(player) = Self::get_player(self) {
|
||||||
// if possible, use `set_position` instead of `seek` because some players have issues with seeking
|
|
||||||
// see https://github.com/JakeStanger/ironbar/issues/970
|
|
||||||
if let Ok(metadata) = player.get_metadata() {
|
|
||||||
if let Some(track_id) = metadata.track_id() {
|
|
||||||
player.set_position(track_id, &duration)?;
|
|
||||||
} else {
|
|
||||||
let pos = player.get_position().unwrap_or_default();
|
let pos = player.get_position().unwrap_or_default();
|
||||||
|
|
||||||
let duration = duration.as_micros() as i64;
|
let duration = duration.as_micros() as i64;
|
||||||
|
|
@ -305,8 +294,6 @@ impl MusicClient for Client {
|
||||||
let seek = cmp::max(duration, 0) - position;
|
let seek = cmp::max(duration, 0) - position;
|
||||||
|
|
||||||
player.seek(seek)?;
|
player.seek(seek)?;
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
error!("Could not find player");
|
error!("Could not find player");
|
||||||
}
|
}
|
||||||
|
|
@ -328,9 +315,7 @@ impl MusicClient for Client {
|
||||||
state: PlayerState::Stopped,
|
state: PlayerState::Stopped,
|
||||||
volume_percent: None,
|
volume_percent: None,
|
||||||
};
|
};
|
||||||
|
send!(self.tx, PlayerUpdate::Update(Box::new(None), status));
|
||||||
self.tx
|
|
||||||
.send_expect(PlayerUpdate::Update(Box::new(None), status));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rx
|
rx
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,77 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use zbus::proxy;
|
use zbus::dbus_proxy;
|
||||||
use zbus::zvariant::{ObjectPath, OwnedValue, Str};
|
use zbus::zvariant::{ObjectPath, OwnedValue, Str};
|
||||||
|
|
||||||
#[proxy(
|
#[dbus_proxy(
|
||||||
default_service = "org.freedesktop.NetworkManager",
|
default_service = "org.freedesktop.NetworkManager",
|
||||||
interface = "org.freedesktop.NetworkManager",
|
interface = "org.freedesktop.NetworkManager",
|
||||||
default_path = "/org/freedesktop/NetworkManager"
|
default_path = "/org/freedesktop/NetworkManager"
|
||||||
)]
|
)]
|
||||||
pub(super) trait Dbus {
|
trait Dbus {
|
||||||
#[zbus(property)]
|
#[dbus_proxy(property)]
|
||||||
fn all_devices(&self) -> Result<Vec<ObjectPath<'_>>>;
|
fn active_connections(&self) -> Result<Vec<ObjectPath>>;
|
||||||
|
|
||||||
|
#[dbus_proxy(property)]
|
||||||
|
fn devices(&self) -> Result<Vec<ObjectPath>>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn networking_enabled(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn primary_connection(&self) -> Result<ObjectPath>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn primary_connection_type(&self) -> Result<Str>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn wireless_enabled(&self) -> Result<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proxy(
|
#[dbus_proxy(
|
||||||
|
default_service = "org.freedesktop.NetworkManager",
|
||||||
|
interface = "org.freedesktop.NetworkManager.Connection.Active"
|
||||||
|
)]
|
||||||
|
trait ActiveConnectionDbus {
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn connection(&self) -> Result<ObjectPath>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn default(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn default6(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
#[dbus_proxy(property)]
|
||||||
|
fn devices(&self) -> Result<Vec<ObjectPath>>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn id(&self) -> Result<Str>;
|
||||||
|
|
||||||
|
#[dbus_proxy(property)]
|
||||||
|
fn type_(&self) -> Result<Str>;
|
||||||
|
|
||||||
|
// #[dbus_proxy(property)]
|
||||||
|
// fn uuid(&self) -> Result<Str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[dbus_proxy(
|
||||||
default_service = "org.freedesktop.NetworkManager",
|
default_service = "org.freedesktop.NetworkManager",
|
||||||
interface = "org.freedesktop.NetworkManager.Device"
|
interface = "org.freedesktop.NetworkManager.Device"
|
||||||
)]
|
)]
|
||||||
pub(super) trait DeviceDbus {
|
trait DeviceDbus {
|
||||||
#[zbus(property)]
|
// #[dbus_proxy(property)]
|
||||||
|
// fn active_connection(&self) -> Result<ObjectPath>;
|
||||||
|
|
||||||
|
#[dbus_proxy(property)]
|
||||||
fn device_type(&self) -> Result<DeviceType>;
|
fn device_type(&self) -> Result<DeviceType>;
|
||||||
|
|
||||||
#[zbus(property)]
|
#[dbus_proxy(property)]
|
||||||
fn interface(&self) -> Result<Str<'_>>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn state(&self) -> Result<DeviceState>;
|
fn state(&self) -> Result<DeviceState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For reference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/e1a7d5ac062f4f23ce3a6b33c62e856056161ad8/src/libnm-core-public/nm-dbus-interface.h#L212-L253
|
#[derive(Clone, Debug, OwnedValue, PartialEq)]
|
||||||
#[derive(Clone, Debug, Eq, Hash, OwnedValue, PartialEq)]
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum DeviceType {
|
pub(super) enum DeviceType {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Ethernet = 1,
|
Ethernet = 1,
|
||||||
Wifi = 2,
|
Wifi = 2,
|
||||||
|
|
@ -64,10 +105,9 @@ pub enum DeviceType {
|
||||||
Hsr = 33,
|
Hsr = 33,
|
||||||
}
|
}
|
||||||
|
|
||||||
// For reference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/e1a7d5ac062f4f23ce3a6b33c62e856056161ad8/src/libnm-core-public/nm-dbus-interface.h#L501-L538
|
|
||||||
#[derive(Clone, Debug, OwnedValue, PartialEq)]
|
#[derive(Clone, Debug, OwnedValue, PartialEq)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum DeviceState {
|
pub(super) enum DeviceState {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Unmanaged = 10,
|
Unmanaged = 10,
|
||||||
Unavailable = 20,
|
Unavailable = 20,
|
||||||
|
|
@ -82,3 +122,12 @@ pub enum DeviceState {
|
||||||
Deactivating = 110,
|
Deactivating = 110,
|
||||||
Failed = 120,
|
Failed = 120,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeviceState {
|
||||||
|
pub(super) fn is_enabled(&self) -> bool {
|
||||||
|
!matches!(
|
||||||
|
self,
|
||||||
|
DeviceState::Unknown | DeviceState::Unmanaged | DeviceState::Unavailable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
DeviceAdded {
|
|
||||||
interface: String,
|
|
||||||
},
|
|
||||||
DeviceStateChanged {
|
|
||||||
interface: String,
|
|
||||||
r#type: DeviceType,
|
|
||||||
state: DeviceState,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +1,235 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use color_eyre::eyre::Ok;
|
use futures_signals::signal::{Mutable, MutableSignalCloned};
|
||||||
use futures_lite::StreamExt;
|
use tracing::error;
|
||||||
use std::collections::HashSet;
|
use zbus::blocking::Connection;
|
||||||
use std::sync::Arc;
|
use zbus::zvariant::ObjectPath;
|
||||||
use tokio::sync::broadcast;
|
|
||||||
use zbus::Connection;
|
|
||||||
use zbus::zvariant::{ObjectPath, Str};
|
|
||||||
|
|
||||||
use crate::clients::ClientResult;
|
use crate::clients::networkmanager::dbus::{
|
||||||
use crate::clients::networkmanager::dbus::{DbusProxy, DeviceDbusProxy};
|
ActiveConnectionDbusProxyBlocking, DbusProxyBlocking, DeviceDbusProxyBlocking,
|
||||||
use crate::clients::networkmanager::event::Event;
|
};
|
||||||
use crate::{register_fallible_client, spawn};
|
use crate::clients::networkmanager::state::{
|
||||||
|
determine_cellular_state, determine_vpn_state, determine_wifi_state, determine_wired_state,
|
||||||
|
CellularState, State, VpnState, WifiState, WiredState,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
read_lock, register_fallible_client, spawn_blocking, spawn_blocking_result, write_lock,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod dbus;
|
mod dbus;
|
||||||
pub mod event;
|
pub mod state;
|
||||||
|
|
||||||
|
type PathMap<'l, ValueType> = HashMap<ObjectPath<'l>, ValueType>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Client {
|
pub struct Client(Arc<ClientInner<'static>>);
|
||||||
tx: broadcast::Sender<Event>,
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ClientInner<'l> {
|
||||||
|
state: Mutable<State>,
|
||||||
|
root_object: &'l DbusProxyBlocking<'l>,
|
||||||
|
active_connections: RwLock<PathMap<'l, ActiveConnectionDbusProxyBlocking<'l>>>,
|
||||||
|
devices: RwLock<PathMap<'l, DeviceDbusProxyBlocking<'l>>>,
|
||||||
|
dbus_connection: Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
fn new() -> Result<Client> {
|
fn new() -> Result<Client> {
|
||||||
let (tx, _) = broadcast::channel(64);
|
let state = Mutable::new(State {
|
||||||
Ok(Client { tx })
|
wired: WiredState::Unknown,
|
||||||
|
wifi: WifiState::Unknown,
|
||||||
|
cellular: CellularState::Unknown,
|
||||||
|
vpn: VpnState::Unknown,
|
||||||
|
});
|
||||||
|
let dbus_connection = Connection::system()?;
|
||||||
|
let root_object = {
|
||||||
|
let root_object = DbusProxyBlocking::new(&dbus_connection)?;
|
||||||
|
// Workaround for the fact that zbus (unnecessarily) requires a static lifetime here
|
||||||
|
Box::leak(Box::new(root_object))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Client(Arc::new(ClientInner {
|
||||||
|
state,
|
||||||
|
root_object,
|
||||||
|
active_connections: RwLock::new(HashMap::new()),
|
||||||
|
devices: RwLock::new(HashMap::new()),
|
||||||
|
dbus_connection,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
let tx = self.tx.clone();
|
macro_rules! update_state_for_device_change {
|
||||||
spawn(async move {
|
($client:ident) => {
|
||||||
let dbus_connection = Connection::system().await?;
|
$client.state.set(State {
|
||||||
let root = DbusProxy::new(&dbus_connection).await?;
|
wired: determine_wired_state(&read_lock!($client.devices))?,
|
||||||
|
wifi: determine_wifi_state(&read_lock!($client.devices))?,
|
||||||
let mut devices = HashSet::new();
|
cellular: determine_cellular_state(&read_lock!($client.devices))?,
|
||||||
|
vpn: $client.state.get_cloned().vpn,
|
||||||
let mut devices_changes = root.receive_all_devices_changed().await;
|
});
|
||||||
while let Some(devices_change) = devices_changes.next().await {
|
};
|
||||||
// The new list of devices from dbus, not to be confused with the added devices below
|
|
||||||
let new_devices = HashSet::from_iter(devices_change.get().await?);
|
|
||||||
|
|
||||||
let added_devices = new_devices.difference(&devices);
|
|
||||||
for added_device in added_devices {
|
|
||||||
spawn(watch_device(added_device.to_owned(), tx.clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let removed_devices = devices.difference(&new_devices);
|
macro_rules! initialise_path_map {
|
||||||
// TODO: Cook up some way to notify closures for removed devices to exit
|
(
|
||||||
|
$client:expr,
|
||||||
devices = new_devices;
|
$path_map:ident,
|
||||||
|
$proxy_type:ident
|
||||||
|
$(, |$new_path:ident| $property_watcher:expr)*
|
||||||
|
) => {
|
||||||
|
let new_paths = $client.root_object.$path_map()?;
|
||||||
|
let mut path_map = HashMap::new();
|
||||||
|
for new_path in new_paths {
|
||||||
|
let new_proxy = $proxy_type::builder(&$client.dbus_connection)
|
||||||
|
.path(new_path.clone())?
|
||||||
|
.build()?;
|
||||||
|
path_map.insert(new_path.clone(), new_proxy);
|
||||||
|
$({
|
||||||
|
let $new_path = &new_path;
|
||||||
|
$property_watcher;
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
*write_lock!($client.$path_map) = path_map;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! spawn_path_list_watcher {
|
||||||
|
(
|
||||||
|
$client:expr,
|
||||||
|
$property:ident,
|
||||||
|
$property_changes:ident,
|
||||||
|
$proxy_type:ident,
|
||||||
|
|$state_client:ident| $state_update:expr
|
||||||
|
$(, |$property_client:ident, $new_path:ident| $property_watcher:expr)*
|
||||||
|
) => {
|
||||||
|
let client = $client.clone();
|
||||||
|
spawn_blocking_result!({
|
||||||
|
let changes = client.root_object.$property_changes();
|
||||||
|
for _ in changes {
|
||||||
|
let mut new_path_map = HashMap::new();
|
||||||
|
{
|
||||||
|
let new_paths = client.root_object.$property()?;
|
||||||
|
let path_map = read_lock!(client.$property);
|
||||||
|
for new_path in new_paths {
|
||||||
|
if path_map.contains_key(&new_path) {
|
||||||
|
let proxy = path_map
|
||||||
|
.get(&new_path)
|
||||||
|
.expect("Should contain the key, guarded by runtime check");
|
||||||
|
new_path_map.insert(new_path, proxy.to_owned());
|
||||||
|
} else {
|
||||||
|
let new_proxy = $proxy_type::builder(&client.dbus_connection)
|
||||||
|
.path(new_path.clone())?
|
||||||
|
.build()?;
|
||||||
|
new_path_map.insert(new_path.clone(), new_proxy);
|
||||||
|
$({
|
||||||
|
let $property_client = &client;
|
||||||
|
let $new_path = &new_path;
|
||||||
|
$property_watcher;
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*write_lock!(client.$property) = new_path_map;
|
||||||
|
let $state_client = &client;
|
||||||
|
$state_update;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! spawn_property_watcher {
|
||||||
|
(
|
||||||
|
$client:expr,
|
||||||
|
$path:expr,
|
||||||
|
$property_changes:ident,
|
||||||
|
$containing_list:ident,
|
||||||
|
|$inner_client:ident| $state_update:expr
|
||||||
|
) => {
|
||||||
|
let client = $client.clone();
|
||||||
|
let path = $path.clone();
|
||||||
|
spawn_blocking_result!({
|
||||||
|
let changes = read_lock!(client.$containing_list)
|
||||||
|
.get(&path)
|
||||||
|
.expect("Should contain the key upon watcher start")
|
||||||
|
.$property_changes();
|
||||||
|
for _ in changes {
|
||||||
|
if !read_lock!(client.$containing_list).contains_key(&path) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let $inner_client = &client;
|
||||||
|
$state_update;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
initialise_path_map!(
|
||||||
|
self.0,
|
||||||
|
active_connections,
|
||||||
|
ActiveConnectionDbusProxyBlocking
|
||||||
|
);
|
||||||
|
initialise_path_map!(self.0, devices, DeviceDbusProxyBlocking, |path| {
|
||||||
|
spawn_property_watcher!(self.0, path, receive_state_changed, devices, |client| {
|
||||||
|
update_state_for_device_change!(client);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
self.0.state.set(State {
|
||||||
|
wired: determine_wired_state(&read_lock!(self.0.devices))?,
|
||||||
|
wifi: determine_wifi_state(&read_lock!(self.0.devices))?,
|
||||||
|
cellular: determine_cellular_state(&read_lock!(self.0.devices))?,
|
||||||
|
vpn: determine_vpn_state(&read_lock!(self.0.active_connections))?,
|
||||||
|
});
|
||||||
|
|
||||||
|
spawn_path_list_watcher!(
|
||||||
|
self.0,
|
||||||
|
active_connections,
|
||||||
|
receive_active_connections_changed,
|
||||||
|
ActiveConnectionDbusProxyBlocking,
|
||||||
|
|client| {
|
||||||
|
client.state.set(State {
|
||||||
|
wired: client.state.get_cloned().wired,
|
||||||
|
wifi: client.state.get_cloned().wifi,
|
||||||
|
cellular: client.state.get_cloned().cellular,
|
||||||
|
vpn: determine_vpn_state(&read_lock!(client.active_connections))?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
spawn_path_list_watcher!(
|
||||||
|
self.0,
|
||||||
|
devices,
|
||||||
|
receive_devices_changed,
|
||||||
|
DeviceDbusProxyBlocking,
|
||||||
|
|client| {
|
||||||
|
update_state_for_device_change!(client);
|
||||||
|
},
|
||||||
|
|client, path| {
|
||||||
|
spawn_property_watcher!(client, path, receive_state_changed, devices, |client| {
|
||||||
|
update_state_for_device_change!(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
pub fn subscribe(&self) -> MutableSignalCloned<State> {
|
||||||
self.tx.subscribe()
|
self.0.state.signal_cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_client() -> ClientResult<Client> {
|
pub fn create_client() -> Result<Arc<Client>> {
|
||||||
let client = Arc::new(Client::new()?);
|
let client = Arc::new(Client::new()?);
|
||||||
|
{
|
||||||
|
let client = client.clone();
|
||||||
|
spawn_blocking_result!({
|
||||||
client.run()?;
|
client.run()?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn watch_device(device_path: ObjectPath<'_>, tx: broadcast::Sender<Event>) -> Result<()> {
|
|
||||||
let dbus_connection = Connection::system().await?;
|
|
||||||
let device = DeviceDbusProxy::new(&dbus_connection, device_path.to_owned()).await?;
|
|
||||||
|
|
||||||
let interface = device.interface().await?;
|
|
||||||
tx.send(Event::DeviceAdded {
|
|
||||||
interface: interface.to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
spawn(watch_device_state(
|
|
||||||
device_path.to_owned(),
|
|
||||||
interface.to_owned(),
|
|
||||||
tx.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn watch_device_state(
|
|
||||||
device_path: ObjectPath<'_>,
|
|
||||||
interface: Str<'_>,
|
|
||||||
tx: broadcast::Sender<Event>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let dbus_connection = Connection::system().await?;
|
|
||||||
let device = DeviceDbusProxy::new(&dbus_connection, &device_path).await?;
|
|
||||||
let r#type = device.device_type().await?;
|
|
||||||
|
|
||||||
// Send an event communicating the initial state
|
|
||||||
let state = device.state().await?;
|
|
||||||
tx.send(Event::DeviceStateChanged {
|
|
||||||
interface: interface.to_string(),
|
|
||||||
r#type: r#type.clone(),
|
|
||||||
state,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut state_changes = device.receive_state_changed().await;
|
|
||||||
while let Some(state_change) = state_changes.next().await {
|
|
||||||
let state = state_change.get().await?;
|
|
||||||
tx.send(Event::DeviceStateChanged {
|
|
||||||
interface: interface.to_string(),
|
|
||||||
r#type: r#type.clone(),
|
|
||||||
state,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
register_fallible_client!(Client, network_manager);
|
register_fallible_client!(Client, network_manager);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use crate::clients::networkmanager::dbus::{
|
|
||||||
ActiveConnectionDbusProxy, DeviceDbusProxy, DeviceState, DeviceType,
|
|
||||||
};
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use std::collections::HashMap;
|
|
||||||
use zbus::zvariant::ObjectPath;
|
|
||||||
|
|
||||||
type PathMap<'l, ValueType> = HashMap<ObjectPath<'l>, ValueType>;
|
use crate::clients::networkmanager::dbus::{
|
||||||
|
ActiveConnectionDbusProxyBlocking, DeviceDbusProxyBlocking, DeviceState, DeviceType,
|
||||||
|
};
|
||||||
|
use crate::clients::networkmanager::PathMap;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
|
@ -58,16 +56,16 @@ pub struct VpnConnectedState {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn determine_wired_state(
|
pub(super) fn determine_wired_state(
|
||||||
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
|
devices: &PathMap<DeviceDbusProxyBlocking>,
|
||||||
) -> Result<WiredState> {
|
) -> Result<WiredState> {
|
||||||
let mut present = false;
|
let mut present = false;
|
||||||
let mut connected = false;
|
let mut connected = false;
|
||||||
|
|
||||||
for device in devices.values() {
|
for device in devices.values() {
|
||||||
if device.device_type().await? == DeviceType::Ethernet {
|
if device.device_type()? == DeviceType::Ethernet {
|
||||||
present = true;
|
present = true;
|
||||||
if device.state().await?.is_enabled() {
|
if device.state()?.is_enabled() {
|
||||||
connected = true;
|
connected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -83,19 +81,19 @@ pub(super) async fn determine_wired_state(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn determine_wifi_state(
|
pub(super) fn determine_wifi_state(
|
||||||
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
|
devices: &PathMap<DeviceDbusProxyBlocking>,
|
||||||
) -> Result<WifiState> {
|
) -> Result<WifiState> {
|
||||||
let mut present = false;
|
let mut present = false;
|
||||||
let mut enabled = false;
|
let mut enabled = false;
|
||||||
let mut connected = false;
|
let mut connected = false;
|
||||||
|
|
||||||
for device in devices.values() {
|
for device in devices.values() {
|
||||||
if device.device_type().await? == DeviceType::Wifi {
|
if device.device_type()? == DeviceType::Wifi {
|
||||||
present = true;
|
present = true;
|
||||||
if device.state().await?.is_enabled() {
|
if device.state()?.is_enabled() {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
if device.state().await? == DeviceState::Activated {
|
if device.state()? == DeviceState::Activated {
|
||||||
connected = true;
|
connected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -117,19 +115,19 @@ pub(super) async fn determine_wifi_state(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn determine_cellular_state(
|
pub(super) fn determine_cellular_state(
|
||||||
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
|
devices: &PathMap<DeviceDbusProxyBlocking>,
|
||||||
) -> Result<CellularState> {
|
) -> Result<CellularState> {
|
||||||
let mut present = false;
|
let mut present = false;
|
||||||
let mut enabled = false;
|
let mut enabled = false;
|
||||||
let mut connected = false;
|
let mut connected = false;
|
||||||
|
|
||||||
for device in devices.values() {
|
for device in devices.values() {
|
||||||
if device.device_type().await? == DeviceType::Modem {
|
if device.device_type()? == DeviceType::Modem {
|
||||||
present = true;
|
present = true;
|
||||||
if device.state().await?.is_enabled() {
|
if device.state()?.is_enabled() {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
if device.state().await? == DeviceState::Activated {
|
if device.state()? == DeviceState::Activated {
|
||||||
connected = true;
|
connected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -148,11 +146,11 @@ pub(super) async fn determine_cellular_state(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn determine_vpn_state(
|
pub(super) fn determine_vpn_state(
|
||||||
active_connections: &PathMap<'_, ActiveConnectionDbusProxy<'_>>,
|
active_connections: &PathMap<ActiveConnectionDbusProxyBlocking>,
|
||||||
) -> Result<VpnState> {
|
) -> Result<VpnState> {
|
||||||
for connection in active_connections.values() {
|
for connection in active_connections.values() {
|
||||||
match connection.type_().await?.as_str() {
|
match connection.type_()?.as_str() {
|
||||||
"vpn" | "wireguard" => {
|
"vpn" | "wireguard" => {
|
||||||
return Ok(VpnState::Connected(VpnConnectedState {
|
return Ok(VpnState::Connected(VpnConnectedState {
|
||||||
name: "unknown".into(),
|
name: "unknown".into(),
|
||||||
|
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
use crate::spawn;
|
|
||||||
use color_eyre::{Report, Result};
|
|
||||||
use futures_lite::StreamExt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use swayipc_async::{Connection, Event, EventType};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use tracing::{info, trace};
|
|
||||||
|
|
||||||
type SyncFn<T> = dyn Fn(&T) + Sync + Send;
|
|
||||||
|
|
||||||
struct TaskState {
|
|
||||||
join_handle: Option<tokio::task::JoinHandle<Result<()>>>,
|
|
||||||
// could have been a `HashMap<EventType, Vec<Box<dyn Fn(&Event) + Sync + Send>>>`, but we don't
|
|
||||||
// expect enough listeners to justify the constant overhead of a hashmap.
|
|
||||||
listeners: Arc<Vec<(EventType, Box<SyncFn<Event>>)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Client {
|
|
||||||
connection: Arc<Mutex<Connection>>,
|
|
||||||
task_state: Mutex<TaskState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Client {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Client")
|
|
||||||
.field("client", &"Connection")
|
|
||||||
.field("task_state", &format_args!("<...>"))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub(crate) async fn new() -> Result<Self> {
|
|
||||||
// Avoid using `arc_mut!` here because we need tokio Mutex.
|
|
||||||
let client = Arc::new(Mutex::new(Connection::new().await?));
|
|
||||||
info!("Sway IPC subscription client connected");
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
connection: client,
|
|
||||||
task_state: Mutex::new(TaskState {
|
|
||||||
listeners: Arc::new(Vec::new()),
|
|
||||||
join_handle: None,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connection(&self) -> &Arc<Mutex<Connection>> {
|
|
||||||
&self.connection
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_listener<T: SwayIpcEvent>(
|
|
||||||
&self,
|
|
||||||
f: impl Fn(&T) + Sync + Send + 'static,
|
|
||||||
) -> Result<()> {
|
|
||||||
self.add_listener_type(
|
|
||||||
T::EVENT_TYPE,
|
|
||||||
Box::new(move |event| {
|
|
||||||
let event = T::from_event(event).expect("event type mismatch");
|
|
||||||
f(event);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_listener_type(
|
|
||||||
&self,
|
|
||||||
event_type: EventType,
|
|
||||||
f: Box<SyncFn<Event>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
// abort current running task
|
|
||||||
let TaskState {
|
|
||||||
join_handle,
|
|
||||||
listeners,
|
|
||||||
} = &mut *self.task_state.lock().await;
|
|
||||||
|
|
||||||
if let Some(handle) = join_handle.take() {
|
|
||||||
handle.abort();
|
|
||||||
let _ = handle.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only the task and self have a reference to listeners, and we just abort the task. This
|
|
||||||
// is the only reference to listeners, so we can safely get a mutable reference.
|
|
||||||
let listeners_mut = Arc::get_mut(listeners)
|
|
||||||
.ok_or_else(|| Report::msg("Failed to get mutable reference to listeners"))?;
|
|
||||||
|
|
||||||
listeners_mut.push((event_type, f));
|
|
||||||
|
|
||||||
// create new client as subscription takes ownership
|
|
||||||
let client = Connection::new().await?;
|
|
||||||
|
|
||||||
let event_types = listeners.iter().map(|(t, _)| *t).collect::<Vec<_>>();
|
|
||||||
let listeners = listeners.clone();
|
|
||||||
|
|
||||||
let handle = spawn(async move {
|
|
||||||
let mut events = client.subscribe(&event_types).await?;
|
|
||||||
|
|
||||||
while let Some(event) = events.next().await {
|
|
||||||
trace!("event: {:?}", event);
|
|
||||||
let event = event?;
|
|
||||||
let ty = sway_event_to_event_type(&event);
|
|
||||||
for (t, f) in listeners.iter() {
|
|
||||||
if *t == ty {
|
|
||||||
f(&event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<(), Report>(())
|
|
||||||
});
|
|
||||||
|
|
||||||
*join_handle = Some(handle);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sway_event_to_event_type(event: &Event) -> EventType {
|
|
||||||
match event {
|
|
||||||
Event::Workspace(_) => EventType::Workspace,
|
|
||||||
Event::Mode(_) => EventType::Mode,
|
|
||||||
Event::Window(_) => EventType::Window,
|
|
||||||
Event::BarConfigUpdate(_) => EventType::BarConfigUpdate,
|
|
||||||
Event::Binding(_) => EventType::Binding,
|
|
||||||
Event::Shutdown(_) => EventType::Shutdown,
|
|
||||||
Event::Tick(_) => EventType::Tick,
|
|
||||||
Event::BarStateUpdate(_) => EventType::BarStateUpdate,
|
|
||||||
Event::Input(_) => EventType::Input,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SwayIpcEvent {
|
|
||||||
const EVENT_TYPE: EventType;
|
|
||||||
fn from_event(e: &Event) -> Option<&Self>;
|
|
||||||
}
|
|
||||||
macro_rules! sway_ipc_event_impl {
|
|
||||||
(@ $($t:tt)*) => { $($t)* };
|
|
||||||
($t:ty, $v:expr, $($m:tt)*) => {
|
|
||||||
sway_ipc_event_impl! {@
|
|
||||||
impl SwayIpcEvent for $t {
|
|
||||||
const EVENT_TYPE: EventType = $v;
|
|
||||||
fn from_event(e: &Event) -> Option<&Self> {
|
|
||||||
match e {
|
|
||||||
$($m)* (x) => Some(x),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sway_ipc_event_impl!(
|
|
||||||
swayipc_async::WorkspaceEvent,
|
|
||||||
EventType::Workspace,
|
|
||||||
Event::Workspace
|
|
||||||
);
|
|
||||||
sway_ipc_event_impl!(swayipc_async::ModeEvent, EventType::Mode, Event::Mode);
|
|
||||||
sway_ipc_event_impl!(swayipc_async::WindowEvent, EventType::Window, Event::Window);
|
|
||||||
sway_ipc_event_impl!(
|
|
||||||
swayipc_async::BarConfig,
|
|
||||||
EventType::BarConfigUpdate,
|
|
||||||
Event::BarConfigUpdate
|
|
||||||
);
|
|
||||||
sway_ipc_event_impl!(
|
|
||||||
swayipc_async::BindingEvent,
|
|
||||||
EventType::Binding,
|
|
||||||
Event::Binding
|
|
||||||
);
|
|
||||||
sway_ipc_event_impl!(
|
|
||||||
swayipc_async::ShutdownEvent,
|
|
||||||
EventType::Shutdown,
|
|
||||||
Event::Shutdown
|
|
||||||
);
|
|
||||||
sway_ipc_event_impl!(swayipc_async::TickEvent, EventType::Tick, Event::Tick);
|
|
||||||
sway_ipc_event_impl!(
|
|
||||||
swayipc_async::BarStateUpdateEvent,
|
|
||||||
EventType::BarStateUpdate,
|
|
||||||
Event::BarStateUpdate
|
|
||||||
);
|
|
||||||
sway_ipc_event_impl!(swayipc_async::InputEvent, EventType::Input, Event::Input);
|
|
||||||
|
|
@ -20,14 +20,12 @@
|
||||||
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
|
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
|
||||||
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
|
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
|
||||||
|
|
||||||
use zbus::proxy;
|
#[zbus::dbus_proxy(
|
||||||
|
|
||||||
#[proxy(
|
|
||||||
interface = "org.erikreider.swaync.cc",
|
interface = "org.erikreider.swaync.cc",
|
||||||
default_service = "org.erikreider.swaync.cc",
|
default_service = "org.erikreider.swaync.cc",
|
||||||
default_path = "/org/erikreider/swaync/cc"
|
default_path = "/org/erikreider/swaync/cc"
|
||||||
)]
|
)]
|
||||||
pub trait SwayNc {
|
trait SwayNc {
|
||||||
/// AddInhibitor method
|
/// AddInhibitor method
|
||||||
fn add_inhibitor(&self, application_id: &str) -> zbus::Result<bool>;
|
fn add_inhibitor(&self, application_id: &str) -> zbus::Result<bool>;
|
||||||
|
|
||||||
|
|
@ -92,11 +90,11 @@ pub trait SwayNc {
|
||||||
fn toggle_visibility(&self) -> zbus::Result<()>;
|
fn toggle_visibility(&self) -> zbus::Result<()>;
|
||||||
|
|
||||||
/// Subscribe signal
|
/// Subscribe signal
|
||||||
#[zbus(signal)]
|
#[dbus_proxy(signal)]
|
||||||
fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>;
|
fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>;
|
||||||
|
|
||||||
/// SubscribeV2 signal
|
/// SubscribeV2 signal
|
||||||
#[zbus(signal)]
|
#[dbus_proxy(signal)]
|
||||||
fn subscribe_v2(
|
fn subscribe_v2(
|
||||||
&self,
|
&self,
|
||||||
count: u32,
|
count: u32,
|
||||||
|
|
@ -106,8 +104,8 @@ pub trait SwayNc {
|
||||||
) -> zbus::Result<()>;
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
/// Inhibited property
|
/// Inhibited property
|
||||||
#[zbus(property)]
|
#[dbus_proxy(property)]
|
||||||
fn inhibited(&self) -> zbus::Result<bool>;
|
fn inhibited(&self) -> zbus::Result<bool>;
|
||||||
#[zbus(property)]
|
#[dbus_proxy(property)]
|
||||||
fn set_inhibited(&self, value: bool) -> zbus::Result<()>;
|
fn set_inhibited(&self, value: bool) -> zbus::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
mod dbus;
|
mod dbus;
|
||||||
|
|
||||||
use crate::channels::SyncSenderExt;
|
use crate::{register_fallible_client, send, spawn};
|
||||||
use crate::{register_fallible_client, spawn};
|
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use dbus::SwayNcProxy;
|
use dbus::SwayNcProxy;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
@ -55,13 +54,9 @@ impl Client {
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Some(ev) = stream.next().await {
|
while let Some(ev) = stream.next().await {
|
||||||
let ev = ev
|
let ev = ev.body::<Event>().expect("to deserialize");
|
||||||
.message()
|
|
||||||
.body()
|
|
||||||
.deserialize::<Event>()
|
|
||||||
.expect("to deserialize");
|
|
||||||
debug!("Received event: {ev:?}");
|
debug!("Received event: {ev:?}");
|
||||||
tx.send_expect(ev);
|
send!(tx, ev);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,621 +0,0 @@
|
||||||
use crate::modules::sysinfo::Interval;
|
|
||||||
use crate::{lock, register_client};
|
|
||||||
use color_eyre::{Report, Result};
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use sysinfo::{Components, Disks, LoadAvg, Networks, RefreshKind, System};
|
|
||||||
|
|
||||||
#[repr(u64)]
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
|
||||||
pub enum Prefix {
|
|
||||||
#[default]
|
|
||||||
None = 1,
|
|
||||||
|
|
||||||
Kilo = 1000,
|
|
||||||
Mega = Prefix::Kilo as u64 * 1000,
|
|
||||||
Giga = Prefix::Mega as u64 * 1000,
|
|
||||||
Tera = Prefix::Giga as u64 * 1000,
|
|
||||||
Peta = Prefix::Tera as u64 * 1000,
|
|
||||||
|
|
||||||
Kibi = 1024,
|
|
||||||
Mebi = Prefix::Kibi as u64 * 1024,
|
|
||||||
Gibi = Prefix::Mebi as u64 * 1024,
|
|
||||||
Tebi = Prefix::Gibi as u64 * 1024,
|
|
||||||
Pebi = Prefix::Tebi as u64 * 1024,
|
|
||||||
|
|
||||||
// # Units
|
|
||||||
// These are special cases
|
|
||||||
// where you'd actually want to do slightly more than a prefix alone.
|
|
||||||
// Included as part of the prefix system for simplicity.
|
|
||||||
KiloBit = 128,
|
|
||||||
MegaBit = Prefix::KiloBit as u64 * 1024,
|
|
||||||
GigaBit = Prefix::MegaBit as u64 * 1024,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Function {
|
|
||||||
None,
|
|
||||||
Sum,
|
|
||||||
Min,
|
|
||||||
Max,
|
|
||||||
Mean,
|
|
||||||
Name(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Function {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().as_str() {
|
|
||||||
"sum" => Ok(Self::Sum),
|
|
||||||
"min" => Ok(Self::Min),
|
|
||||||
"max" => Ok(Self::Max),
|
|
||||||
"mean" => Ok(Self::Mean),
|
|
||||||
"" => Err(()),
|
|
||||||
_ => Ok(Self::Name(s.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ValueSet {
|
|
||||||
values: HashMap<Box<str>, Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromIterator<(Box<str>, Value)> for ValueSet {
|
|
||||||
fn from_iter<T: IntoIterator<Item = (Box<str>, Value)>>(iter: T) -> Self {
|
|
||||||
Self {
|
|
||||||
values: iter.into_iter().collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueSet {
|
|
||||||
fn values(&self, prefix: Prefix) -> impl Iterator<Item = f64> + use<'_> {
|
|
||||||
self.values
|
|
||||||
.values()
|
|
||||||
.map(move |v| v.get(prefix))
|
|
||||||
.filter(|v| !v.is_nan())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, function: &Function, prefix: Prefix) -> f64 {
|
|
||||||
match function {
|
|
||||||
Function::None => 0.0,
|
|
||||||
Function::Sum => self.sum(prefix),
|
|
||||||
Function::Min => self.min(prefix),
|
|
||||||
Function::Max => self.max(prefix),
|
|
||||||
Function::Mean => self.mean(prefix),
|
|
||||||
Function::Name(name) => self
|
|
||||||
.values
|
|
||||||
.get(&Box::from(name.as_str()))
|
|
||||||
.map(|v| v.get(prefix))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sum(&self, prefix: Prefix) -> f64 {
|
|
||||||
self.values(prefix).sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min(&self, prefix: Prefix) -> f64 {
|
|
||||||
self.values(prefix)
|
|
||||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max(&self, prefix: Prefix) -> f64 {
|
|
||||||
self.values(prefix)
|
|
||||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mean(&self, prefix: Prefix) -> f64 {
|
|
||||||
self.sum(prefix) / self.values.len() as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Value {
|
|
||||||
value: f64,
|
|
||||||
prefix: Prefix,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Value {
|
|
||||||
pub fn new(value: f64) -> Self {
|
|
||||||
Self::new_with_prefix(value, Prefix::None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_prefix(value: f64, prefix: Prefix) -> Self {
|
|
||||||
Self { value, prefix }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self, prefix: Prefix) -> f64 {
|
|
||||||
if prefix == self.prefix {
|
|
||||||
self.value
|
|
||||||
} else {
|
|
||||||
let scale = self.prefix as u64 as f64 / prefix as u64 as f64;
|
|
||||||
self.value * scale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client {
|
|
||||||
system: Mutex<System>,
|
|
||||||
disks: Mutex<Disks>,
|
|
||||||
components: Mutex<Components>,
|
|
||||||
networks: Mutex<Networks>,
|
|
||||||
load_average: Mutex<LoadAvg>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let refresh_kind = RefreshKind::everything().without_processes();
|
|
||||||
|
|
||||||
let system = System::new_with_specifics(refresh_kind);
|
|
||||||
let disks = Disks::new_with_refreshed_list();
|
|
||||||
let components = Components::new_with_refreshed_list();
|
|
||||||
let networks = Networks::new_with_refreshed_list();
|
|
||||||
let load_average = System::load_average();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
system: Mutex::new(system),
|
|
||||||
disks: Mutex::new(disks),
|
|
||||||
components: Mutex::new(components),
|
|
||||||
networks: Mutex::new(networks),
|
|
||||||
load_average: Mutex::new(load_average),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_cpu(&self) {
|
|
||||||
lock!(self.system).refresh_cpu_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_memory(&self) {
|
|
||||||
lock!(self.system).refresh_memory();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_network(&self) {
|
|
||||||
lock!(self.networks).refresh(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_temps(&self) {
|
|
||||||
lock!(self.components).refresh(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_disks(&self) {
|
|
||||||
lock!(self.disks).refresh(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_load_average(&self) {
|
|
||||||
*lock!(self.load_average) = System::load_average();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cpu_frequency(&self) -> ValueSet {
|
|
||||||
lock!(self.system)
|
|
||||||
.cpus()
|
|
||||||
.iter()
|
|
||||||
.map(|cpu| {
|
|
||||||
(
|
|
||||||
cpu.name().into(),
|
|
||||||
Value::new_with_prefix(cpu.frequency() as f64, Prefix::Mega),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cpu_percent(&self) -> ValueSet {
|
|
||||||
lock!(self.system)
|
|
||||||
.cpus()
|
|
||||||
.iter()
|
|
||||||
.map(|cpu| (cpu.name().into(), Value::new(cpu.cpu_usage() as f64)))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memory_free(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).free_memory() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memory_available(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).available_memory() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memory_total(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).total_memory() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memory_used(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).used_memory() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memory_percent(&self) -> Value {
|
|
||||||
let total = lock!(self.system).total_memory() as f64;
|
|
||||||
let used = lock!(self.system).used_memory() as f64;
|
|
||||||
|
|
||||||
Value::new(used / total * 100.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap_free(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).free_swap() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap_total(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).total_swap() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap_used(&self) -> Value {
|
|
||||||
Value::new(lock!(self.system).used_swap() as f64)
|
|
||||||
}
|
|
||||||
pub fn swap_percent(&self) -> Value {
|
|
||||||
let total = lock!(self.system).total_swap() as f64;
|
|
||||||
let used = lock!(self.system).used_swap() as f64;
|
|
||||||
|
|
||||||
Value::new(used / total * 100.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn temp_c(&self) -> ValueSet {
|
|
||||||
lock!(self.components)
|
|
||||||
.iter()
|
|
||||||
.map(|comp| {
|
|
||||||
(
|
|
||||||
comp.label().into(),
|
|
||||||
Value::new(comp.temperature().unwrap_or_default() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn temp_f(&self) -> ValueSet {
|
|
||||||
lock!(self.components)
|
|
||||||
.iter()
|
|
||||||
.map(|comp| {
|
|
||||||
(
|
|
||||||
comp.label().into(),
|
|
||||||
Value::new(c_to_f(comp.temperature().unwrap_or_default() as f64)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_free(&self) -> ValueSet {
|
|
||||||
lock!(self.disks)
|
|
||||||
.iter()
|
|
||||||
.map(|disk| {
|
|
||||||
(
|
|
||||||
disk.mount_point().to_string_lossy().into(),
|
|
||||||
Value::new(disk.available_space() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_total(&self) -> ValueSet {
|
|
||||||
lock!(self.disks)
|
|
||||||
.iter()
|
|
||||||
.map(|disk| {
|
|
||||||
(
|
|
||||||
disk.mount_point().to_string_lossy().into(),
|
|
||||||
Value::new(disk.total_space() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_used(&self) -> ValueSet {
|
|
||||||
lock!(self.disks)
|
|
||||||
.iter()
|
|
||||||
.map(|disk| {
|
|
||||||
(
|
|
||||||
disk.mount_point().to_string_lossy().into(),
|
|
||||||
Value::new((disk.total_space() - disk.available_space()) as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_percent(&self) -> ValueSet {
|
|
||||||
lock!(self.disks)
|
|
||||||
.iter()
|
|
||||||
.map(|disk| {
|
|
||||||
(
|
|
||||||
disk.mount_point().to_string_lossy().into(),
|
|
||||||
Value::new(
|
|
||||||
(disk.total_space() - disk.available_space()) as f64
|
|
||||||
/ disk.total_space() as f64
|
|
||||||
* 100.0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_read(&self, interval: Interval) -> ValueSet {
|
|
||||||
lock!(self.disks)
|
|
||||||
.iter()
|
|
||||||
.map(|disk| {
|
|
||||||
(
|
|
||||||
disk.mount_point().to_string_lossy().into(),
|
|
||||||
Value::new(disk.usage().read_bytes as f64 / interval.disks() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_write(&self, interval: Interval) -> ValueSet {
|
|
||||||
lock!(self.disks)
|
|
||||||
.iter()
|
|
||||||
.map(|disk| {
|
|
||||||
(
|
|
||||||
disk.mount_point().to_string_lossy().into(),
|
|
||||||
Value::new(disk.usage().written_bytes as f64 / interval.disks() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn net_down(&self, interval: Interval) -> ValueSet {
|
|
||||||
lock!(self.networks)
|
|
||||||
.iter()
|
|
||||||
.map(|(name, net)| {
|
|
||||||
(
|
|
||||||
name.as_str().into(),
|
|
||||||
Value::new(net.received() as f64 / interval.networks() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn net_up(&self, interval: Interval) -> ValueSet {
|
|
||||||
lock!(self.networks)
|
|
||||||
.iter()
|
|
||||||
.map(|(name, net)| {
|
|
||||||
(
|
|
||||||
name.as_str().into(),
|
|
||||||
Value::new(net.transmitted() as f64 / interval.networks() as f64),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_average_1(&self) -> Value {
|
|
||||||
Value::new(lock!(self.load_average).one)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_average_5(&self) -> Value {
|
|
||||||
Value::new(lock!(self.load_average).five)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_average_15(&self) -> Value {
|
|
||||||
Value::new(lock!(self.load_average).fifteen)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets system uptime formatted as `HH:mm`.
|
|
||||||
pub fn uptime() -> String {
|
|
||||||
let uptime = System::uptime();
|
|
||||||
let hours = uptime / 3600;
|
|
||||||
format!("{:0>2}:{:0>2}", hours, (uptime % 3600) / 60)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register_client!(Client, sys_info);
|
|
||||||
|
|
||||||
const fn c_to_f(c: f64) -> f64 {
|
|
||||||
c / 5.0 * 9.0 + 32.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum TokenType {
|
|
||||||
CpuFrequency,
|
|
||||||
CpuPercent,
|
|
||||||
|
|
||||||
MemoryFree,
|
|
||||||
MemoryAvailable,
|
|
||||||
MemoryTotal,
|
|
||||||
MemoryUsed,
|
|
||||||
MemoryPercent,
|
|
||||||
|
|
||||||
SwapFree,
|
|
||||||
SwapTotal,
|
|
||||||
SwapUsed,
|
|
||||||
SwapPercent,
|
|
||||||
|
|
||||||
TempC,
|
|
||||||
TempF,
|
|
||||||
|
|
||||||
DiskFree,
|
|
||||||
DiskTotal,
|
|
||||||
DiskUsed,
|
|
||||||
DiskPercent,
|
|
||||||
DiskRead,
|
|
||||||
DiskWrite,
|
|
||||||
|
|
||||||
NetDown,
|
|
||||||
NetUp,
|
|
||||||
|
|
||||||
LoadAverage1,
|
|
||||||
LoadAverage5,
|
|
||||||
LoadAverage15,
|
|
||||||
Uptime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for TokenType {
|
|
||||||
type Err = Report;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
match s {
|
|
||||||
"cpu_frequency" => Ok(Self::CpuFrequency),
|
|
||||||
"cpu_percent" => Ok(Self::CpuPercent),
|
|
||||||
|
|
||||||
"memory_free" => Ok(Self::MemoryFree),
|
|
||||||
"memory_available" => Ok(Self::MemoryAvailable),
|
|
||||||
"memory_total" => Ok(Self::MemoryTotal),
|
|
||||||
"memory_used" => Ok(Self::MemoryUsed),
|
|
||||||
"memory_percent" => Ok(Self::MemoryPercent),
|
|
||||||
|
|
||||||
"swap_free" => Ok(Self::SwapFree),
|
|
||||||
"swap_total" => Ok(Self::SwapTotal),
|
|
||||||
"swap_used" => Ok(Self::SwapUsed),
|
|
||||||
"swap_percent" => Ok(Self::SwapPercent),
|
|
||||||
|
|
||||||
"temp_c" => Ok(Self::TempC),
|
|
||||||
"temp_f" => Ok(Self::TempF),
|
|
||||||
|
|
||||||
"disk_free" => Ok(Self::DiskFree),
|
|
||||||
"disk_total" => Ok(Self::DiskTotal),
|
|
||||||
"disk_used" => Ok(Self::DiskUsed),
|
|
||||||
"disk_percent" => Ok(Self::DiskPercent),
|
|
||||||
"disk_read" => Ok(Self::DiskRead),
|
|
||||||
"disk_write" => Ok(Self::DiskWrite),
|
|
||||||
|
|
||||||
"net_down" => Ok(Self::NetDown),
|
|
||||||
"net_up" => Ok(Self::NetUp),
|
|
||||||
|
|
||||||
"load_average_1" => Ok(Self::LoadAverage1),
|
|
||||||
"load_average_5" => Ok(Self::LoadAverage5),
|
|
||||||
"load_average_15" => Ok(Self::LoadAverage15),
|
|
||||||
"uptime" => Ok(Self::Uptime),
|
|
||||||
_ => Err(Report::msg(format!("invalid token type: '{s}'"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
use crate::ironvar::Namespace;
|
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
impl Namespace for Client {
|
|
||||||
fn get(&self, key: &str) -> Option<String> {
|
|
||||||
let get = |value: Value| Some(value.get(Prefix::None).to_string());
|
|
||||||
|
|
||||||
let token = TokenType::from_str(key).ok()?;
|
|
||||||
match token {
|
|
||||||
TokenType::CpuFrequency => None,
|
|
||||||
TokenType::CpuPercent => None,
|
|
||||||
TokenType::MemoryFree => get(self.memory_free()),
|
|
||||||
TokenType::MemoryAvailable => get(self.memory_available()),
|
|
||||||
TokenType::MemoryTotal => get(self.memory_total()),
|
|
||||||
TokenType::MemoryUsed => get(self.memory_used()),
|
|
||||||
TokenType::MemoryPercent => get(self.memory_percent()),
|
|
||||||
TokenType::SwapFree => get(self.swap_free()),
|
|
||||||
TokenType::SwapTotal => get(self.swap_total()),
|
|
||||||
TokenType::SwapUsed => get(self.swap_used()),
|
|
||||||
TokenType::SwapPercent => get(self.swap_percent()),
|
|
||||||
TokenType::TempC => None,
|
|
||||||
TokenType::TempF => None,
|
|
||||||
TokenType::DiskFree => None,
|
|
||||||
TokenType::DiskTotal => None,
|
|
||||||
TokenType::DiskUsed => None,
|
|
||||||
TokenType::DiskPercent => None,
|
|
||||||
TokenType::DiskRead => None,
|
|
||||||
TokenType::DiskWrite => None,
|
|
||||||
TokenType::NetDown => None,
|
|
||||||
TokenType::NetUp => None,
|
|
||||||
TokenType::LoadAverage1 => get(self.load_average_1()),
|
|
||||||
TokenType::LoadAverage5 => get(self.load_average_5()),
|
|
||||||
TokenType::LoadAverage15 => get(self.load_average_15()),
|
|
||||||
TokenType::Uptime => Some(Client::uptime()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list(&self) -> Vec<String> {
|
|
||||||
vec![
|
|
||||||
"memory_free",
|
|
||||||
"memory_available",
|
|
||||||
"memory_total",
|
|
||||||
"memory_used",
|
|
||||||
"memory_percent",
|
|
||||||
"swap_free",
|
|
||||||
"swap_total",
|
|
||||||
"swap_used",
|
|
||||||
"swap_percent",
|
|
||||||
"load_average_1",
|
|
||||||
"load_average_5",
|
|
||||||
"load_average_15",
|
|
||||||
"uptime",
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn namespaces(&self) -> Vec<String> {
|
|
||||||
vec![
|
|
||||||
"cpu_frequency",
|
|
||||||
"cpu_percent",
|
|
||||||
"temp_c",
|
|
||||||
"temp_f",
|
|
||||||
"disk_free",
|
|
||||||
"disk_total",
|
|
||||||
"disk_used",
|
|
||||||
"disk_percent",
|
|
||||||
"disk_read",
|
|
||||||
"disk_write",
|
|
||||||
"net_down",
|
|
||||||
"net_up",
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_namespace(&self, key: &str) -> Option<Arc<dyn Namespace + Sync + Send>> {
|
|
||||||
let token = TokenType::from_str(key).ok()?;
|
|
||||||
|
|
||||||
match token {
|
|
||||||
TokenType::CpuFrequency => Some(Arc::new(self.cpu_frequency())),
|
|
||||||
TokenType::CpuPercent => Some(Arc::new(self.cpu_percent())),
|
|
||||||
TokenType::MemoryFree => None,
|
|
||||||
TokenType::MemoryAvailable => None,
|
|
||||||
TokenType::MemoryTotal => None,
|
|
||||||
TokenType::MemoryUsed => None,
|
|
||||||
TokenType::MemoryPercent => None,
|
|
||||||
TokenType::SwapFree => None,
|
|
||||||
TokenType::SwapTotal => None,
|
|
||||||
TokenType::SwapUsed => None,
|
|
||||||
TokenType::SwapPercent => None,
|
|
||||||
TokenType::TempC => Some(Arc::new(self.temp_c())),
|
|
||||||
TokenType::TempF => Some(Arc::new(self.temp_f())),
|
|
||||||
TokenType::DiskFree => Some(Arc::new(self.disk_free())),
|
|
||||||
TokenType::DiskTotal => Some(Arc::new(self.disk_total())),
|
|
||||||
TokenType::DiskUsed => Some(Arc::new(self.disk_used())),
|
|
||||||
TokenType::DiskPercent => Some(Arc::new(self.disk_percent())),
|
|
||||||
TokenType::DiskRead => Some(Arc::new(self.disk_read(Interval::All(1)))),
|
|
||||||
TokenType::DiskWrite => Some(Arc::new(self.disk_write(Interval::All(1)))),
|
|
||||||
TokenType::NetDown => Some(Arc::new(self.net_down(Interval::All(1)))),
|
|
||||||
TokenType::NetUp => Some(Arc::new(self.net_up(Interval::All(1)))),
|
|
||||||
TokenType::LoadAverage1 => None,
|
|
||||||
TokenType::LoadAverage5 => None,
|
|
||||||
TokenType::LoadAverage15 => None,
|
|
||||||
TokenType::Uptime => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
impl Namespace for ValueSet {
|
|
||||||
fn get(&self, key: &str) -> Option<String> {
|
|
||||||
let function = Function::from_str(key).ok()?;
|
|
||||||
Some(self.apply(&function, Prefix::None).to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list(&self) -> Vec<String> {
|
|
||||||
let mut vec = vec!["sum", "min", "max", "mean"]
|
|
||||||
.into_iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
vec.extend(self.values.keys().map(ToString::to_string));
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn namespaces(&self) -> Vec<String> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_namespace(&self, _key: &str) -> Option<Arc<dyn Namespace + Sync + Send>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
src/clients/upower.rs
Normal file
35
src/clients/upower.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::register_client;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use upower_dbus::UPowerProxy;
|
||||||
|
use zbus::fdo::PropertiesProxy;
|
||||||
|
|
||||||
|
pub async fn create_display_proxy() -> Arc<PropertiesProxy<'static>> {
|
||||||
|
let dbus = Box::pin(zbus::Connection::system())
|
||||||
|
.await
|
||||||
|
.expect("failed to create connection to system bus");
|
||||||
|
|
||||||
|
let device_proxy = UPowerProxy::new(&dbus)
|
||||||
|
.await
|
||||||
|
.expect("failed to create upower proxy");
|
||||||
|
|
||||||
|
let display_device = device_proxy
|
||||||
|
.get_display_device()
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| panic!("failed to get display device for {device_proxy:?}"));
|
||||||
|
|
||||||
|
let path = display_device.path().to_owned();
|
||||||
|
|
||||||
|
let proxy = PropertiesProxy::builder(&dbus)
|
||||||
|
.destination("org.freedesktop.UPower")
|
||||||
|
.expect("failed to set proxy destination address")
|
||||||
|
.path(path)
|
||||||
|
.expect("failed to set proxy path")
|
||||||
|
.cache_properties(zbus::CacheProperties::No)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.expect("failed to build proxy");
|
||||||
|
|
||||||
|
Arc::new(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
register_client!(PropertiesProxy<'static>, upower);
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
/// Originally taken from `upower-dbus` crate
|
|
||||||
/// <https://github.com/pop-os/upower-dbus/blob/main/LICENSE>
|
|
||||||
// Copyright 2021 System76 <info@system76.com>
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
use zbus::proxy;
|
|
||||||
use zbus::zvariant::OwnedValue;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, OwnedValue)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum BatteryState {
|
|
||||||
Unknown = 0,
|
|
||||||
Charging = 1,
|
|
||||||
Discharging = 2,
|
|
||||||
Empty = 3,
|
|
||||||
FullyCharged = 4,
|
|
||||||
PendingCharge = 5,
|
|
||||||
PendingDischarge = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, OwnedValue)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum BatteryType {
|
|
||||||
Unknown = 0,
|
|
||||||
LinePower = 1,
|
|
||||||
Battery = 2,
|
|
||||||
Ups = 3,
|
|
||||||
Monitor = 4,
|
|
||||||
Mouse = 5,
|
|
||||||
Keyboard = 6,
|
|
||||||
Pda = 7,
|
|
||||||
Phone = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, OwnedValue)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum BatteryLevel {
|
|
||||||
Unknown = 0,
|
|
||||||
None = 1,
|
|
||||||
Low = 3,
|
|
||||||
Critical = 4,
|
|
||||||
Normal = 6,
|
|
||||||
High = 7,
|
|
||||||
Full = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proxy(
|
|
||||||
interface = "org.freedesktop.UPower.Device",
|
|
||||||
default_service = "org.freedesktop.UPower",
|
|
||||||
assume_defaults = false
|
|
||||||
)]
|
|
||||||
pub trait Device {
|
|
||||||
#[zbus(property)]
|
|
||||||
fn battery_level(&self) -> zbus::Result<BatteryLevel>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn capacity(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn energy(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn energy_empty(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn energy_full(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn energy_full_design(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn has_history(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn has_statistics(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn icon_name(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn is_present(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn is_rechargeable(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn luminosity(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn model(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn native_path(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn online(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn percentage(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn power_supply(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
fn refresh(&self) -> zbus::Result<()>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn serial(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn state(&self) -> zbus::Result<BatteryState>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn temperature(&self) -> zbus::Result<f64>;
|
|
||||||
|
|
||||||
#[zbus(property, name = "Type")]
|
|
||||||
fn type_(&self) -> zbus::Result<BatteryType>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn vendor(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
#[zbus(property)]
|
|
||||||
fn voltage(&self) -> zbus::Result<f64>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proxy(interface = "org.freedesktop.UPower", assume_defaults = true)]
|
|
||||||
pub trait UPower {
|
|
||||||
/// EnumerateDevices method
|
|
||||||
fn enumerate_devices(&self) -> zbus::Result<Vec<zbus::zvariant::OwnedObjectPath>>;
|
|
||||||
|
|
||||||
/// GetCriticalAction method
|
|
||||||
fn get_critical_action(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// GetDisplayDevice method
|
|
||||||
#[zbus(object = "Device")]
|
|
||||||
fn get_display_device(&self);
|
|
||||||
|
|
||||||
/// DeviceAdded signal
|
|
||||||
#[zbus(signal)]
|
|
||||||
fn device_added(&self, device: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>;
|
|
||||||
|
|
||||||
/// DeviceRemoved signal
|
|
||||||
#[zbus(signal)]
|
|
||||||
fn device_removed(&self, device: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>;
|
|
||||||
|
|
||||||
/// DaemonVersion property
|
|
||||||
#[zbus(property)]
|
|
||||||
fn daemon_version(&self) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// LidIsClosed property
|
|
||||||
#[zbus(property)]
|
|
||||||
fn lid_is_closed(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
/// LidIsPresent property
|
|
||||||
#[zbus(property)]
|
|
||||||
fn lid_is_present(&self) -> zbus::Result<bool>;
|
|
||||||
|
|
||||||
/// OnBattery property
|
|
||||||
#[zbus(property)]
|
|
||||||
fn on_battery(&self) -> zbus::Result<bool>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
mod dbus;
|
|
||||||
|
|
||||||
use crate::clients::ClientResult;
|
|
||||||
use crate::register_fallible_client;
|
|
||||||
use dbus::UPowerProxy;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use zbus::fdo::PropertiesProxy;
|
|
||||||
use zbus::proxy::CacheProperties;
|
|
||||||
|
|
||||||
pub use dbus::BatteryState;
|
|
||||||
|
|
||||||
pub async fn create_display_proxy() -> ClientResult<PropertiesProxy<'static>> {
|
|
||||||
let dbus = Box::pin(zbus::Connection::system()).await?;
|
|
||||||
|
|
||||||
let device_proxy = UPowerProxy::new(&dbus).await?;
|
|
||||||
|
|
||||||
let display_device = device_proxy.get_display_device().await?;
|
|
||||||
|
|
||||||
let path = display_device.inner().path();
|
|
||||||
|
|
||||||
let proxy = PropertiesProxy::builder(&dbus)
|
|
||||||
.destination("org.freedesktop.UPower")
|
|
||||||
.expect("failed to set proxy destination address")
|
|
||||||
.path(path)
|
|
||||||
.expect("failed to set proxy path")
|
|
||||||
.cache_properties(CacheProperties::No)
|
|
||||||
.build()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Arc::new(proxy))
|
|
||||||
}
|
|
||||||
|
|
||||||
register_fallible_client!(PropertiesProxy<'static>, upower);
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
mod sink;
|
mod sink;
|
||||||
mod sink_input;
|
mod sink_input;
|
||||||
|
|
||||||
use crate::{APP_ID, arc_mut, lock, register_client, spawn_blocking};
|
use crate::{arc_mut, lock, register_client, send, spawn_blocking, APP_ID};
|
||||||
use libpulse_binding::callbacks::ListResult;
|
use libpulse_binding::callbacks::ListResult;
|
||||||
use libpulse_binding::context::introspect::{Introspector, ServerInfo};
|
use libpulse_binding::context::introspect::{Introspector, ServerInfo};
|
||||||
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet, Operation};
|
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet, Operation};
|
||||||
|
|
@ -12,9 +12,8 @@ use libpulse_binding::volume::{ChannelVolumes, Volume};
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use crate::channels::SyncSenderExt;
|
|
||||||
pub use sink::Sink;
|
pub use sink::Sink;
|
||||||
pub use sink_input::SinkInput;
|
pub use sink_input::SinkInput;
|
||||||
|
|
||||||
|
|
@ -231,8 +230,6 @@ fn on_event(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("server event: {facility:?}, op: {op:?}, i: {i}");
|
|
||||||
|
|
||||||
match facility {
|
match facility {
|
||||||
Facility::Server => on_server_event(context, &data.sinks, &data.default_sink_name, tx),
|
Facility::Server => on_server_event(context, &data.sinks, &data.default_sink_name, tx),
|
||||||
Facility::Sink => sink::on_event(context, &data.sinks, &data.default_sink_name, tx, op, i),
|
Facility::Sink => sink::on_event(context, &data.sinks, &data.default_sink_name, tx, op, i),
|
||||||
|
|
@ -272,7 +269,7 @@ fn set_default_sink(
|
||||||
{
|
{
|
||||||
sink.active = true;
|
sink.active = true;
|
||||||
debug!("Set sink active: {}", sink.name);
|
debug!("Set sink active: {}", sink.name);
|
||||||
tx.send_expect(Event::UpdateSink(sink.clone()));
|
send!(tx, Event::UpdateSink(sink.clone()));
|
||||||
} else {
|
} else {
|
||||||
warn!("Couldn't find sink: {}", default_sink_name);
|
warn!("Couldn't find sink: {}", default_sink_name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use super::{ArcMutVec, Client, ConnectionState, Event, percent_to_volume, volume_to_percent};
|
use super::{percent_to_volume, volume_to_percent, ArcMutVec, Client, ConnectionState, Event};
|
||||||
use crate::channels::SyncSenderExt;
|
use crate::{lock, send};
|
||||||
use crate::lock;
|
|
||||||
use libpulse_binding::callbacks::ListResult;
|
use libpulse_binding::callbacks::ListResult;
|
||||||
use libpulse_binding::context::Context;
|
|
||||||
use libpulse_binding::context::introspect::SinkInfo;
|
use libpulse_binding::context::introspect::SinkInfo;
|
||||||
use libpulse_binding::context::subscribe::Operation;
|
use libpulse_binding::context::subscribe::Operation;
|
||||||
|
use libpulse_binding::context::Context;
|
||||||
use libpulse_binding::def::SinkState;
|
use libpulse_binding::def::SinkState;
|
||||||
use std::sync::{Arc, Mutex, mpsc};
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, instrument, trace};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sink {
|
pub struct Sink {
|
||||||
|
|
@ -42,19 +41,16 @@ impl From<&SinkInfo<'_>> for Sink {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn sinks(&self) -> Arc<Mutex<Vec<Sink>>> {
|
pub fn sinks(&self) -> Arc<Mutex<Vec<Sink>>> {
|
||||||
self.data.sinks.clone()
|
self.data.sinks.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn set_default_sink(&self, name: &str) {
|
pub fn set_default_sink(&self, name: &str) {
|
||||||
if let ConnectionState::Connected { context, .. } = &*lock!(self.connection) {
|
if let ConnectionState::Connected { context, .. } = &*lock!(self.connection) {
|
||||||
lock!(context).set_default_sink(name, |_| {});
|
lock!(context).set_default_sink(name, |_| {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn set_sink_volume(&self, name: &str, volume_percent: f64) {
|
pub fn set_sink_volume(&self, name: &str, volume_percent: f64) {
|
||||||
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
@ -63,7 +59,7 @@ impl Client {
|
||||||
let ListResult::Item(info) = info else {
|
let ListResult::Item(info) = info else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
tx.send_expect(info.volume);
|
send!(tx, info.volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
let new_volume = percent_to_volume(volume_percent);
|
let new_volume = percent_to_volume(volume_percent);
|
||||||
|
|
@ -77,7 +73,6 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn set_sink_muted(&self, name: &str, muted: bool) {
|
pub fn set_sink_muted(&self, name: &str, muted: bool) {
|
||||||
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
||||||
introspector.set_sink_mute_by_name(name, muted, None);
|
introspector.set_sink_mute_by_name(name, muted, None);
|
||||||
|
|
@ -127,10 +122,8 @@ pub fn add(info: ListResult<&SinkInfo>, sinks: &ArcMutVec<Sink>, tx: &broadcast:
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("adding {info:?}");
|
|
||||||
|
|
||||||
lock!(sinks).push(info.into());
|
lock!(sinks).push(info.into());
|
||||||
tx.send_expect(Event::AddSink(info.into()));
|
send!(tx, Event::AddSink(info.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -143,8 +136,6 @@ fn update(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("updating {info:?}");
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut sinks = lock!(sinks);
|
let mut sinks = lock!(sinks);
|
||||||
let Some(pos) = sinks.iter().position(|sink| sink.index == info.index) else {
|
let Some(pos) = sinks.iter().position(|sink| sink.index == info.index) else {
|
||||||
|
|
@ -171,16 +162,14 @@ fn update(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.send_expect(Event::UpdateSink(sink));
|
send!(tx, Event::UpdateSink(sink));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(index: u32, sinks: &ArcMutVec<Sink>, tx: &broadcast::Sender<Event>) {
|
fn remove(index: u32, sinks: &ArcMutVec<Sink>, tx: &broadcast::Sender<Event>) {
|
||||||
trace!("removing {index}");
|
|
||||||
|
|
||||||
let mut sinks = lock!(sinks);
|
let mut sinks = lock!(sinks);
|
||||||
|
|
||||||
if let Some(pos) = sinks.iter().position(|s| s.index == index) {
|
if let Some(pos) = sinks.iter().position(|s| s.index == index) {
|
||||||
let info = sinks.remove(pos);
|
let info = sinks.remove(pos);
|
||||||
tx.send_expect(Event::RemoveSink(info.name));
|
send!(tx, Event::RemoveSink(info.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use super::{ArcMutVec, Client, ConnectionState, Event, percent_to_volume, volume_to_percent};
|
use super::{percent_to_volume, volume_to_percent, ArcMutVec, Client, ConnectionState, Event};
|
||||||
use crate::channels::SyncSenderExt;
|
use crate::{lock, send};
|
||||||
use crate::lock;
|
|
||||||
use libpulse_binding::callbacks::ListResult;
|
use libpulse_binding::callbacks::ListResult;
|
||||||
use libpulse_binding::context::Context;
|
|
||||||
use libpulse_binding::context::introspect::SinkInputInfo;
|
use libpulse_binding::context::introspect::SinkInputInfo;
|
||||||
use libpulse_binding::context::subscribe::Operation;
|
use libpulse_binding::context::subscribe::Operation;
|
||||||
use std::sync::{Arc, Mutex, mpsc};
|
use libpulse_binding::context::Context;
|
||||||
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, instrument, trace};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SinkInput {
|
pub struct SinkInput {
|
||||||
|
|
@ -36,12 +35,10 @@ impl From<&SinkInputInfo<'_>> for SinkInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn sink_inputs(&self) -> Arc<Mutex<Vec<SinkInput>>> {
|
pub fn sink_inputs(&self) -> Arc<Mutex<Vec<SinkInput>>> {
|
||||||
self.data.sink_inputs.clone()
|
self.data.sink_inputs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn set_input_volume(&self, index: u32, volume_percent: f64) {
|
pub fn set_input_volume(&self, index: u32, volume_percent: f64) {
|
||||||
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
@ -50,7 +47,7 @@ impl Client {
|
||||||
let ListResult::Item(info) = info else {
|
let ListResult::Item(info) = info else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
tx.send_expect(info.volume);
|
send!(tx, info.volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
let new_volume = percent_to_volume(volume_percent);
|
let new_volume = percent_to_volume(volume_percent);
|
||||||
|
|
@ -64,7 +61,6 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace")]
|
|
||||||
pub fn set_input_muted(&self, index: u32, muted: bool) {
|
pub fn set_input_muted(&self, index: u32, muted: bool) {
|
||||||
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
|
||||||
introspector.set_sink_input_mute(index, muted, None);
|
introspector.set_sink_input_mute(index, muted, None);
|
||||||
|
|
@ -116,10 +112,8 @@ pub fn add(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("adding {info:?}");
|
|
||||||
|
|
||||||
lock!(inputs).push(info.into());
|
lock!(inputs).push(info.into());
|
||||||
tx.send_expect(Event::AddInput(info.into()));
|
send!(tx, Event::AddInput(info.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -131,8 +125,6 @@ fn update(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("updating {info:?}");
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut inputs = lock!(inputs);
|
let mut inputs = lock!(inputs);
|
||||||
let Some(pos) = inputs.iter().position(|input| input.index == info.index) else {
|
let Some(pos) = inputs.iter().position(|input| input.index == info.index) else {
|
||||||
|
|
@ -143,16 +135,14 @@ fn update(
|
||||||
inputs[pos] = info.into();
|
inputs[pos] = info.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.send_expect(Event::UpdateInput(info.into()));
|
send!(tx, Event::UpdateInput(info.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(index: u32, inputs: &ArcMutVec<SinkInput>, tx: &broadcast::Sender<Event>) {
|
fn remove(index: u32, inputs: &ArcMutVec<SinkInput>, tx: &broadcast::Sender<Event>) {
|
||||||
let mut inputs = lock!(inputs);
|
let mut inputs = lock!(inputs);
|
||||||
|
|
||||||
trace!("removing {index}");
|
|
||||||
|
|
||||||
if let Some(pos) = inputs.iter().position(|s| s.index == index) {
|
if let Some(pos) = inputs.iter().position(|s| s.index == index) {
|
||||||
let info = inputs.remove(pos);
|
let info = inputs.remove(pos);
|
||||||
tx.send_expect(Event::RemoveInput(info.index));
|
send!(tx, Event::RemoveInput(info.index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,17 @@ mod macros;
|
||||||
mod wl_output;
|
mod wl_output;
|
||||||
mod wl_seat;
|
mod wl_seat;
|
||||||
|
|
||||||
use crate::error::{ERR_CHANNEL_RECV, ExitCode};
|
use crate::error::{ExitCode, ERR_CHANNEL_RECV};
|
||||||
use crate::{arc_mut, lock, register_client, spawn, spawn_blocking};
|
use crate::{arc_mut, lock, register_client, send, spawn, spawn_blocking};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::channels::SyncSenderExt;
|
|
||||||
use calloop_channel::Event::Msg;
|
use calloop_channel::Event::Msg;
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::Report;
|
||||||
use smithay_client_toolkit::output::OutputState;
|
use smithay_client_toolkit::output::OutputState;
|
||||||
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
|
||||||
use smithay_client_toolkit::reexports::calloop::channel as calloop_channel;
|
use smithay_client_toolkit::reexports::calloop::channel as calloop_channel;
|
||||||
|
use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle};
|
||||||
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
|
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
|
||||||
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
||||||
use smithay_client_toolkit::seat::SeatState;
|
use smithay_client_toolkit::seat::SeatState;
|
||||||
|
|
@ -44,6 +43,7 @@ cfg_if! {
|
||||||
use self::wlr_data_control::device::DataControlDevice;
|
use self::wlr_data_control::device::DataControlDevice;
|
||||||
use self::wlr_data_control::manager::DataControlDeviceManagerState;
|
use self::wlr_data_control::manager::DataControlDeviceManagerState;
|
||||||
use self::wlr_data_control::source::CopyPasteSource;
|
use self::wlr_data_control::source::CopyPasteSource;
|
||||||
|
use self::wlr_data_control::SelectionOfferItem;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
|
|
||||||
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
||||||
|
|
@ -76,8 +76,6 @@ pub enum Request {
|
||||||
ToplevelInfoAll,
|
ToplevelInfoAll,
|
||||||
#[cfg(feature = "launcher")]
|
#[cfg(feature = "launcher")]
|
||||||
ToplevelFocus(usize),
|
ToplevelFocus(usize),
|
||||||
#[cfg(feature = "launcher")]
|
|
||||||
ToplevelMinimize(usize),
|
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
CopyToClipboard(ClipboardItem),
|
CopyToClipboard(ClipboardItem),
|
||||||
|
|
@ -152,12 +150,12 @@ impl Client {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Some(event) = event_rx.recv().await {
|
while let Some(event) = event_rx.recv().await {
|
||||||
match event {
|
match event {
|
||||||
Event::Output(event) => output_tx.send_expect(event),
|
Event::Output(event) => send!(output_tx, event),
|
||||||
#[cfg(any(feature = "focused", feature = "launcher"))]
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
||||||
Event::Toplevel(event) => toplevel_tx.send_expect(event),
|
Event::Toplevel(event) => send!(toplevel_tx, event),
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Event::Clipboard(item) => clipboard_tx.send_expect(item),
|
Event::Clipboard(item) => send!(clipboard_tx, item),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +175,7 @@ impl Client {
|
||||||
/// Sends a request to the environment event loop,
|
/// Sends a request to the environment event loop,
|
||||||
/// and returns the response.
|
/// and returns the response.
|
||||||
fn send_request(&self, request: Request) -> Response {
|
fn send_request(&self, request: Request) -> Response {
|
||||||
self.tx.send_expect(request);
|
send!(self.tx, request);
|
||||||
lock!(self.rx).recv().expect(ERR_CHANNEL_RECV)
|
lock!(self.rx).recv().expect(ERR_CHANNEL_RECV)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +193,7 @@ pub struct Environment {
|
||||||
seat_state: SeatState,
|
seat_state: SeatState,
|
||||||
|
|
||||||
queue_handle: QueueHandle<Self>,
|
queue_handle: QueueHandle<Self>,
|
||||||
|
loop_handle: LoopHandle<'static, Self>,
|
||||||
|
|
||||||
event_tx: mpsc::Sender<Event>,
|
event_tx: mpsc::Sender<Event>,
|
||||||
response_tx: std::sync::mpsc::Sender<Response>,
|
response_tx: std::sync::mpsc::Sender<Response>,
|
||||||
|
|
@ -205,12 +204,14 @@ pub struct Environment {
|
||||||
|
|
||||||
// -- clipboard --
|
// -- clipboard --
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
data_control_device_manager_state: Option<DataControlDeviceManagerState>,
|
data_control_device_manager_state: DataControlDeviceManagerState,
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
data_control_devices: Vec<DataControlDeviceEntry>,
|
data_control_devices: Vec<DataControlDeviceEntry>,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
copy_paste_sources: Vec<CopyPasteSource>,
|
copy_paste_sources: Vec<CopyPasteSource>,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
selection_offers: Vec<SelectionOfferItem>,
|
||||||
|
|
||||||
// local state
|
// local state
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
|
|
@ -264,30 +265,12 @@ impl Environment {
|
||||||
let output_state = OutputState::new(&globals, &qh);
|
let output_state = OutputState::new(&globals, &qh);
|
||||||
let seat_state = SeatState::new(&globals, &qh);
|
let seat_state = SeatState::new(&globals, &qh);
|
||||||
#[cfg(any(feature = "focused", feature = "launcher"))]
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
||||||
if let Err(err) = ToplevelManagerState::bind(&globals, &qh) {
|
ToplevelManagerState::bind(&globals, &qh)
|
||||||
error!("{:?}",
|
.expect("to bind to wlr_foreign_toplevel_manager global");
|
||||||
Report::new(err)
|
|
||||||
.wrap_err("Failed to bind to wlr_foreign_toplevel_manager global")
|
|
||||||
.note("This is likely a due to the current compositor not supporting the required protocol")
|
|
||||||
.note("launcher and focused modules will not work")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
let data_control_device_manager_state = match DataControlDeviceManagerState::bind(
|
let data_control_device_manager_state = DataControlDeviceManagerState::bind(&globals, &qh)
|
||||||
&globals, &qh,
|
.expect("to bind to wlr_data_control_device_manager global");
|
||||||
) {
|
|
||||||
Ok(state) => Some(state),
|
|
||||||
Err(err) => {
|
|
||||||
error!("{:?}",
|
|
||||||
Report::new(err)
|
|
||||||
.wrap_err("Failed to bind to wlr_data_control_device global")
|
|
||||||
.note("This is likely a due to the current compositor not supporting the required protocol")
|
|
||||||
.note("clipboard module will not work")
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut env = Self {
|
let mut env = Self {
|
||||||
registry_state,
|
registry_state,
|
||||||
|
|
@ -296,6 +279,7 @@ impl Environment {
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
data_control_device_manager_state,
|
data_control_device_manager_state,
|
||||||
queue_handle: qh,
|
queue_handle: qh,
|
||||||
|
loop_handle: loop_handle.clone(),
|
||||||
event_tx,
|
event_tx,
|
||||||
response_tx,
|
response_tx,
|
||||||
#[cfg(any(feature = "focused", feature = "launcher"))]
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
||||||
|
|
@ -306,6 +290,8 @@ impl Environment {
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
copy_paste_sources: vec![],
|
copy_paste_sources: vec![],
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
|
selection_offers: vec![],
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard: arc_mut!(None),
|
clipboard: arc_mut!(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -334,12 +320,12 @@ impl Environment {
|
||||||
match event {
|
match event {
|
||||||
Msg(Request::Roundtrip) => {
|
Msg(Request::Roundtrip) => {
|
||||||
debug!("received roundtrip request");
|
debug!("received roundtrip request");
|
||||||
env.response_tx.send_expect(Response::Ok);
|
send!(env.response_tx, Response::Ok);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
Msg(Request::OutputInfoAll) => {
|
Msg(Request::OutputInfoAll) => {
|
||||||
let infos = env.output_info_all();
|
let infos = env.output_info_all();
|
||||||
env.response_tx.send_expect(Response::OutputInfoAll(infos));
|
send!(env.response_tx, Response::OutputInfoAll(infos));
|
||||||
}
|
}
|
||||||
#[cfg(any(feature = "focused", feature = "launcher"))]
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
||||||
Msg(Request::ToplevelInfoAll) => {
|
Msg(Request::ToplevelInfoAll) => {
|
||||||
|
|
@ -348,46 +334,31 @@ impl Environment {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(ToplevelHandle::info)
|
.filter_map(ToplevelHandle::info)
|
||||||
.collect();
|
.collect();
|
||||||
|
send!(env.response_tx, Response::ToplevelInfoAll(infos));
|
||||||
env.response_tx
|
|
||||||
.send_expect(Response::ToplevelInfoAll(infos));
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "launcher")]
|
#[cfg(feature = "launcher")]
|
||||||
Msg(Request::ToplevelFocus(id)) => {
|
Msg(Request::ToplevelFocus(id)) => {
|
||||||
let handle = env
|
let handle = env
|
||||||
.handles
|
.handles
|
||||||
.iter()
|
.iter()
|
||||||
.find(|handle| handle.info().is_some_and(|info| info.id == id));
|
.find(|handle| handle.info().map_or(false, |info| info.id == id));
|
||||||
|
|
||||||
if let Some(handle) = handle {
|
if let Some(handle) = handle {
|
||||||
let seat = env.default_seat();
|
let seat = env.default_seat();
|
||||||
handle.focus(&seat);
|
handle.focus(&seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
env.response_tx.send_expect(Response::Ok);
|
send!(env.response_tx, Response::Ok);
|
||||||
}
|
|
||||||
#[cfg(feature = "launcher")]
|
|
||||||
Msg(Request::ToplevelMinimize(id)) => {
|
|
||||||
let handle = env
|
|
||||||
.handles
|
|
||||||
.iter()
|
|
||||||
.find(|handle| handle.info().is_some_and(|info| info.id == id));
|
|
||||||
|
|
||||||
if let Some(handle) = handle {
|
|
||||||
handle.minimize();
|
|
||||||
}
|
|
||||||
|
|
||||||
env.response_tx.send_expect(Response::Ok);
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Msg(Request::CopyToClipboard(item)) => {
|
Msg(Request::CopyToClipboard(item)) => {
|
||||||
env.copy_to_clipboard(item);
|
env.copy_to_clipboard(item);
|
||||||
env.response_tx.send_expect(Response::Ok);
|
send!(env.response_tx, Response::Ok);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Msg(Request::ClipboardItem) => {
|
Msg(Request::ClipboardItem) => {
|
||||||
let item = lock!(env.clipboard).clone();
|
let item = lock!(env.clipboard).clone();
|
||||||
env.response_tx.send_expect(Response::ClipboardItem(item));
|
send!(env.response_tx, Response::ClipboardItem(item));
|
||||||
}
|
}
|
||||||
calloop_channel::Event::Closed => error!("request channel unexpectedly closed"),
|
calloop_channel::Event::Closed => error!("request channel unexpectedly closed"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{Client, Environment, Event};
|
use super::{Client, Environment, Event};
|
||||||
use crate::channels::AsyncSenderExt;
|
use crate::try_send;
|
||||||
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct OutputEvent {
|
||||||
pub event_type: OutputEventType,
|
pub event_type: OutputEventType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum OutputEventType {
|
pub enum OutputEventType {
|
||||||
New,
|
New,
|
||||||
Update,
|
Update,
|
||||||
|
|
@ -63,10 +63,13 @@ impl OutputHandler for Environment {
|
||||||
fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
||||||
debug!("Handler received new output");
|
debug!("Handler received new output");
|
||||||
if let Some(info) = self.output_state.info(&output) {
|
if let Some(info) = self.output_state.info(&output) {
|
||||||
self.event_tx.send_spawn(Event::Output(OutputEvent {
|
try_send!(
|
||||||
|
self.event_tx,
|
||||||
|
Event::Output(OutputEvent {
|
||||||
output: info,
|
output: info,
|
||||||
event_type: OutputEventType::New,
|
event_type: OutputEventType::New
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
error!("Output is missing information!");
|
error!("Output is missing information!");
|
||||||
}
|
}
|
||||||
|
|
@ -75,10 +78,13 @@ impl OutputHandler for Environment {
|
||||||
fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
||||||
debug!("Handle received output update");
|
debug!("Handle received output update");
|
||||||
if let Some(info) = self.output_state.info(&output) {
|
if let Some(info) = self.output_state.info(&output) {
|
||||||
self.event_tx.send_spawn(Event::Output(OutputEvent {
|
try_send!(
|
||||||
|
self.event_tx,
|
||||||
|
Event::Output(OutputEvent {
|
||||||
output: info,
|
output: info,
|
||||||
event_type: OutputEventType::Update,
|
event_type: OutputEventType::Update
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
error!("Output is missing information!");
|
error!("Output is missing information!");
|
||||||
}
|
}
|
||||||
|
|
@ -87,10 +93,13 @@ impl OutputHandler for Environment {
|
||||||
fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
||||||
debug!("Handle received output destruction");
|
debug!("Handle received output destruction");
|
||||||
if let Some(info) = self.output_state.info(&output) {
|
if let Some(info) = self.output_state.info(&output) {
|
||||||
self.event_tx.send_spawn(Event::Output(OutputEvent {
|
try_send!(
|
||||||
|
self.event_tx,
|
||||||
|
Event::Output(OutputEvent {
|
||||||
output: info,
|
output: info,
|
||||||
event_type: OutputEventType::Destroyed,
|
event_type: OutputEventType::Destroyed
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
error!("Output is missing information!");
|
error!("Output is missing information!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::Environment;
|
use super::Environment;
|
||||||
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
|
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
|
||||||
use tracing::{debug, error};
|
use tracing::debug;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::{Connection, QueueHandle};
|
use wayland_client::{Connection, QueueHandle};
|
||||||
|
|
||||||
|
|
@ -37,11 +37,7 @@ impl SeatHandler for Environment {
|
||||||
{
|
{
|
||||||
debug!("Adding new data control device");
|
debug!("Adding new data control device");
|
||||||
// create the data device here for this seat
|
// create the data device here for this seat
|
||||||
let Some(data_control_device_manager) = &self.data_control_device_manager_state else {
|
let data_control_device_manager = &self.data_control_device_manager_state;
|
||||||
error!("data_control_device_manager not available, cannot copy");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let data_control_device = data_control_device_manager.get_data_device(qh, &seat);
|
let data_control_device = data_control_device_manager.get_data_device(qh, &seat);
|
||||||
self.data_control_devices
|
self.data_control_devices
|
||||||
.push(super::DataControlDeviceEntry {
|
.push(super::DataControlDeviceEntry {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::error::ERR_WAYLAND_DATA;
|
||||||
use crate::lock;
|
use crate::lock;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, event_created_child};
|
use wayland_client::{event_created_child, Connection, Dispatch, Proxy, QueueHandle};
|
||||||
use wayland_protocols_wlr::data_control::v1::client::{
|
use wayland_protocols_wlr::data_control::v1::client::{
|
||||||
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
||||||
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
||||||
|
|
@ -37,9 +37,7 @@ pub trait DataControlDeviceDataExt: Send + Sync {
|
||||||
|
|
||||||
fn selection_mime_types(&self) -> Vec<String> {
|
fn selection_mime_types(&self) -> Vec<String> {
|
||||||
let inner = self.data_control_device_data();
|
let inner = self.data_control_device_data();
|
||||||
let offer = &lock!(inner.inner).selection_offer;
|
lock!(lock!(inner.inner).selection_offer)
|
||||||
|
|
||||||
lock!(offer)
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|offer| {
|
.map(|offer| {
|
||||||
let data = offer
|
let data = offer
|
||||||
|
|
@ -53,9 +51,9 @@ pub trait DataControlDeviceDataExt: Send + Sync {
|
||||||
/// Get the active selection offer if it exists.
|
/// Get the active selection offer if it exists.
|
||||||
fn selection_offer(&self) -> Option<SelectionOffer> {
|
fn selection_offer(&self) -> Option<SelectionOffer> {
|
||||||
let inner = self.data_control_device_data();
|
let inner = self.data_control_device_data();
|
||||||
let offer = &lock!(inner.inner).selection_offer;
|
lock!(lock!(inner.inner).selection_offer)
|
||||||
|
.as_ref()
|
||||||
lock!(offer).as_ref().and_then(|offer| {
|
.and_then(|offer| {
|
||||||
let data = offer
|
let data = offer
|
||||||
.data::<Self::DataControlOfferInner>()
|
.data::<Self::DataControlOfferInner>()
|
||||||
.expect(ERR_WAYLAND_DATA);
|
.expect(ERR_WAYLAND_DATA);
|
||||||
|
|
@ -161,9 +159,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Finished => {
|
Event::Finished => {
|
||||||
warn!(
|
warn!("Data control offer is no longer valid, but has not been dropped by client. This could cause clipboard issues.");
|
||||||
"Data control offer is no longer valid, but has not been dropped by client. This could cause clipboard issues."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,23 @@ pub mod offer;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
|
|
||||||
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
||||||
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler};
|
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
||||||
use self::source::DataControlSourceHandler;
|
use self::source::DataControlSourceHandler;
|
||||||
use super::{Client, Environment, Event, Request, Response};
|
use super::{Client, Environment, Event, Request, Response};
|
||||||
use crate::channels::AsyncSenderExt;
|
use crate::{lock, try_send, Ironbar};
|
||||||
use crate::{Ironbar, lock, spawn};
|
|
||||||
use color_eyre::Result;
|
|
||||||
use device::DataControlDevice;
|
use device::DataControlDevice;
|
||||||
use glib::Bytes;
|
use glib::Bytes;
|
||||||
use rustix::buffer::spare_capacity;
|
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
|
||||||
use rustix::event::epoll;
|
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout};
|
||||||
use rustix::event::epoll::CreateFlags;
|
|
||||||
use rustix::fs::Timespec;
|
|
||||||
use rustix::pipe::{fcntl_getpipe_size, fcntl_setpipe_size};
|
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
|
use smithay_client_toolkit::reexports::calloop::{PostAction, RegistrationToken};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::{ErrorKind, Read, Write};
|
||||||
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
|
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
use wayland_client::{Connection, QueueHandle};
|
use wayland_client::{Connection, QueueHandle};
|
||||||
|
|
@ -34,6 +28,12 @@ use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1
|
||||||
|
|
||||||
const INTERNAL_MIME_TYPE: &str = "x-ironbar-internal";
|
const INTERNAL_MIME_TYPE: &str = "x-ironbar-internal";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SelectionOfferItem {
|
||||||
|
offer: SelectionOffer,
|
||||||
|
token: Option<RegistrationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a value which can be read/written
|
/// Represents a value which can be read/written
|
||||||
/// to/from the system clipboard and surrounding metadata.
|
/// to/from the system clipboard and surrounding metadata.
|
||||||
///
|
///
|
||||||
|
|
@ -148,11 +148,6 @@ impl Environment {
|
||||||
pub fn copy_to_clipboard(&mut self, item: ClipboardItem) {
|
pub fn copy_to_clipboard(&mut self, item: ClipboardItem) {
|
||||||
debug!("Copying item to clipboard: {item:?}");
|
debug!("Copying item to clipboard: {item:?}");
|
||||||
|
|
||||||
let Some(data_control_device_manager) = &self.data_control_device_manager_state else {
|
|
||||||
error!("data_control_device_manager not available, cannot copy");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let seat = self.default_seat();
|
let seat = self.default_seat();
|
||||||
let Some(device) = self
|
let Some(device) = self
|
||||||
.data_control_devices
|
.data_control_devices
|
||||||
|
|
@ -162,8 +157,9 @@ impl Environment {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = data_control_device_manager
|
let source = self
|
||||||
.create_copy_paste_source(&self.queue_handle, [&item.mime_type, INTERNAL_MIME_TYPE]);
|
.data_control_device_manager_state
|
||||||
|
.create_copy_paste_source(&self.queue_handle, [INTERNAL_MIME_TYPE, &item.mime_type]);
|
||||||
|
|
||||||
source.set_selection(&device.device);
|
source.set_selection(&device.device);
|
||||||
self.copy_paste_sources.push(source);
|
self.copy_paste_sources.push(source);
|
||||||
|
|
@ -172,20 +168,22 @@ impl Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an offer file handle into a new `ClipboardItem`.
|
/// Reads an offer file handle into a new `ClipboardItem`.
|
||||||
async fn read_file(
|
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||||
mime_type: &MimeType,
|
|
||||||
file: &mut tokio::net::unix::pipe::Receiver,
|
|
||||||
) -> io::Result<ClipboardItem> {
|
|
||||||
let mut buf = vec![];
|
|
||||||
file.read_to_end(&mut buf).await?;
|
|
||||||
|
|
||||||
let value = match mime_type.category {
|
let value = match mime_type.category {
|
||||||
MimeTypeCategory::Text => {
|
MimeTypeCategory::Text => {
|
||||||
let txt = String::from_utf8_lossy(&buf).to_string();
|
let mut txt = String::new();
|
||||||
|
file.read_to_string(&mut txt)?;
|
||||||
|
|
||||||
ClipboardValue::Text(txt)
|
ClipboardValue::Text(txt)
|
||||||
}
|
}
|
||||||
MimeTypeCategory::Image => {
|
MimeTypeCategory::Image => {
|
||||||
let bytes = Bytes::from(&buf);
|
let mut bytes = vec![];
|
||||||
|
file.read_to_end(&mut bytes)?;
|
||||||
|
|
||||||
|
debug!("Read bytes: {}", bytes.len());
|
||||||
|
|
||||||
|
let bytes = Bytes::from(&bytes);
|
||||||
|
|
||||||
ClipboardValue::Image(bytes)
|
ClipboardValue::Image(bytes)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -216,33 +214,68 @@ impl DataControlDeviceHandler for Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(offer) = data_device.selection_offer() {
|
if let Some(offer) = data_device.selection_offer() {
|
||||||
|
self.selection_offers
|
||||||
|
.push(SelectionOfferItem { offer, token: None });
|
||||||
|
|
||||||
|
let cur_offer = self
|
||||||
|
.selection_offers
|
||||||
|
.last_mut()
|
||||||
|
.expect("Failed to get current offer");
|
||||||
|
|
||||||
// clear prev
|
// clear prev
|
||||||
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
||||||
lock!(self.clipboard).take();
|
lock!(self.clipboard).take();
|
||||||
// send an event so the clipboard module is aware it's changed
|
// send an event so the clipboard module is aware it's changed
|
||||||
self.event_tx.send_spawn(Event::Clipboard(ClipboardItem {
|
try_send!(
|
||||||
|
self.event_tx,
|
||||||
|
Event::Clipboard(ClipboardItem {
|
||||||
id: usize::MAX,
|
id: usize::MAX,
|
||||||
mime_type: String::new().into(),
|
mime_type: String::new().into(),
|
||||||
value: Arc::new(ClipboardValue::Other),
|
value: Arc::new(ClipboardValue::Other)
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Receiving mime type: {}", mime_type.value);
|
debug!("Receiving mime type: {}", mime_type.value);
|
||||||
if let Ok(mut read_pipe) = offer.receive(mime_type.value.clone()) {
|
|
||||||
|
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
|
||||||
|
let offer_clone = cur_offer.offer.clone();
|
||||||
|
|
||||||
let tx = self.event_tx.clone();
|
let tx = self.event_tx.clone();
|
||||||
let clipboard = self.clipboard.clone();
|
let clipboard = self.clipboard.clone();
|
||||||
|
|
||||||
spawn(async move {
|
let token =
|
||||||
match Self::read_file(&mime_type, &mut read_pipe).await {
|
self.loop_handle
|
||||||
|
.insert_source(read_pipe, move |(), file, state| unsafe {
|
||||||
|
let item = state
|
||||||
|
.selection_offers
|
||||||
|
.iter()
|
||||||
|
.position(|o| o.offer == offer_clone)
|
||||||
|
.map(|p| state.selection_offers.remove(p))
|
||||||
|
.expect("Failed to find selection offer item");
|
||||||
|
|
||||||
|
match Self::read_file(&mime_type, file.get_mut()) {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
lock!(clipboard).replace(item.clone());
|
lock!(clipboard).replace(item.clone());
|
||||||
tx.send_spawn(Event::Clipboard(item));
|
try_send!(tx, Event::Clipboard(item));
|
||||||
}
|
}
|
||||||
Err(err) => error!("{err:?}"),
|
Err(err) => error!("{err:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.loop_handle
|
||||||
|
.remove(item.token.expect("Missing item token"));
|
||||||
|
|
||||||
|
PostAction::Remove
|
||||||
});
|
});
|
||||||
|
|
||||||
|
match token {
|
||||||
|
Ok(token) => {
|
||||||
|
cur_offer.token.replace(token);
|
||||||
|
}
|
||||||
|
Err(err) => error!("Failed to insert read pipe event: {err:?}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +313,7 @@ impl DataControlSourceHandler for Environment {
|
||||||
source: &ZwlrDataControlSourceV1,
|
source: &ZwlrDataControlSourceV1,
|
||||||
mime: String,
|
mime: String,
|
||||||
write_pipe: WritePipe,
|
write_pipe: WritePipe,
|
||||||
) -> Result<()> {
|
) {
|
||||||
debug!("Handler received source send request event ({mime})");
|
debug!("Handler received source send request event ({mime})");
|
||||||
|
|
||||||
if let Some(item) = lock!(self.clipboard).clone() {
|
if let Some(item) = lock!(self.clipboard).clone() {
|
||||||
|
|
@ -297,34 +330,32 @@ impl DataControlSourceHandler for Environment {
|
||||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||||
ClipboardValue::Other => panic!(
|
ClipboardValue::Other => panic!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
io::Error::other("Attempted to copy unsupported mime type")
|
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type")
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pipe_size =
|
let pipe_size = set_pipe_size(fd.as_raw_fd(), bytes.len())
|
||||||
set_pipe_size(fd.as_fd(), bytes.len()).expect("Failed to increase pipe size");
|
.expect("Failed to increase pipe size");
|
||||||
let mut file = File::from(fd.try_clone().expect("to be able to clone"));
|
let mut file = File::from(fd.try_clone().expect("to be able to clone"));
|
||||||
|
|
||||||
debug!("Writing {} bytes", bytes.len());
|
debug!("Writing {} bytes", bytes.len());
|
||||||
|
|
||||||
let epoll = epoll::create(CreateFlags::CLOEXEC)?;
|
let mut events = (0..16).map(|_| EpollEvent::empty()).collect::<Vec<_>>();
|
||||||
epoll::add(
|
let epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0);
|
||||||
&epoll,
|
|
||||||
fd,
|
|
||||||
epoll::EventData::new_u64(0),
|
|
||||||
epoll::EventFlags::OUT,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut events = Vec::with_capacity(16);
|
let epoll_fd =
|
||||||
|
Epoll::new(EpollCreateFlags::empty()).expect("to get valid file descriptor");
|
||||||
|
epoll_fd
|
||||||
|
.add(fd, epoll_event)
|
||||||
|
.expect("to send valid epoll operation");
|
||||||
|
|
||||||
|
let timeout = EpollTimeout::from(100u16);
|
||||||
while !bytes.is_empty() {
|
while !bytes.is_empty() {
|
||||||
let chunk = &bytes[..min(pipe_size, bytes.len())];
|
let chunk = &bytes[..min(pipe_size as usize, bytes.len())];
|
||||||
|
|
||||||
epoll::wait(
|
epoll_fd
|
||||||
&epoll,
|
.wait(&mut events, timeout)
|
||||||
spare_capacity(&mut events),
|
.expect("Failed to wait to epoll");
|
||||||
Some(&Timespec::try_from(Duration::from_millis(100))?),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match file.write(chunk) {
|
match file.write(chunk) {
|
||||||
Ok(written) => {
|
Ok(written) => {
|
||||||
|
|
@ -340,11 +371,9 @@ impl DataControlSourceHandler for Environment {
|
||||||
|
|
||||||
debug!("Done writing");
|
debug!("Done writing");
|
||||||
} else {
|
} else {
|
||||||
error!("Failed to find source (mime: '{mime}')");
|
error!("Failed to find source");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancelled(
|
fn cancelled(
|
||||||
|
|
@ -369,7 +398,7 @@ impl DataControlSourceHandler for Environment {
|
||||||
/// it will be clamped at this.
|
/// it will be clamped at this.
|
||||||
///
|
///
|
||||||
/// Returns the new size if succeeded.
|
/// Returns the new size if succeeded.
|
||||||
fn set_pipe_size(fd: BorrowedFd, size: usize) -> io::Result<usize> {
|
fn set_pipe_size(fd: RawFd, size: usize) -> io::Result<i32> {
|
||||||
// clamp size at kernel max
|
// clamp size at kernel max
|
||||||
let max_pipe_size = fs::read_to_string("/proc/sys/fs/pipe-max-size")
|
let max_pipe_size = fs::read_to_string("/proc/sys/fs/pipe-max-size")
|
||||||
.expect("Failed to find pipe-max-size virtual kernel file")
|
.expect("Failed to find pipe-max-size virtual kernel file")
|
||||||
|
|
@ -379,24 +408,23 @@ fn set_pipe_size(fd: BorrowedFd, size: usize) -> io::Result<usize> {
|
||||||
|
|
||||||
let size = min(size, max_pipe_size);
|
let size = min(size, max_pipe_size);
|
||||||
|
|
||||||
let curr_size = fcntl_getpipe_size(fd)?;
|
let curr_size = fcntl(fd, F_GETPIPE_SZ)? as usize;
|
||||||
|
|
||||||
trace!("Current pipe size: {curr_size}");
|
trace!("Current pipe size: {curr_size}");
|
||||||
|
|
||||||
let new_size = if size > curr_size {
|
let new_size = if size > curr_size {
|
||||||
trace!("Requesting pipe size increase to (at least): {size}");
|
trace!("Requesting pipe size increase to (at least): {size}");
|
||||||
|
|
||||||
fcntl_setpipe_size(fd, size)?;
|
let res = fcntl(fd, F_SETPIPE_SZ(size as i32))?;
|
||||||
let res = fcntl_getpipe_size(fd)?;
|
|
||||||
trace!("New pipe size: {res}");
|
trace!("New pipe size: {res}");
|
||||||
|
|
||||||
if res < size {
|
if res < size as i32 {
|
||||||
return Err(io::Error::last_os_error());
|
return Err(io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
size
|
size as i32
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(new_size)
|
Ok(new_size)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use super::manager::DataControlDeviceManagerState;
|
use super::manager::DataControlDeviceManagerState;
|
||||||
use crate::lock;
|
use crate::lock;
|
||||||
use rustix::pipe::{PipeFlags, pipe_with};
|
use nix::fcntl::OFlag;
|
||||||
|
use nix::unistd::pipe2;
|
||||||
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
||||||
|
use smithay_client_toolkit::data_device_manager::ReadPipe;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::os::fd::AsFd;
|
use std::os::fd::AsFd;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::net::unix::pipe::Receiver;
|
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
|
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
|
||||||
|
|
@ -35,8 +36,8 @@ impl PartialEq for SelectionOffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionOffer {
|
impl SelectionOffer {
|
||||||
pub fn receive(&self, mime_type: String) -> Result<Receiver, DataOfferError> {
|
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
|
||||||
receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
|
unsafe { receive(&self.data_offer, mime_type) }.map_err(DataOfferError::Io)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,11 +169,14 @@ where
|
||||||
///
|
///
|
||||||
/// Fails if too many file descriptors were already open and a pipe
|
/// Fails if too many file descriptors were already open and a pipe
|
||||||
/// could not be created.
|
/// could not be created.
|
||||||
pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<Receiver> {
|
pub unsafe fn receive(
|
||||||
|
offer: &ZwlrDataControlOfferV1,
|
||||||
|
mime_type: String,
|
||||||
|
) -> std::io::Result<ReadPipe> {
|
||||||
// create a pipe
|
// create a pipe
|
||||||
let (readfd, writefd) = pipe_with(PipeFlags::CLOEXEC)?;
|
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||||
|
|
||||||
offer.receive(mime_type, writefd.as_fd());
|
offer.receive(mime_type, writefd.as_fd());
|
||||||
|
|
||||||
Receiver::from_owned_fd(readfd)
|
Ok(ReadPipe::from(readfd))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use super::device::DataControlDevice;
|
use super::device::DataControlDevice;
|
||||||
use super::manager::DataControlDeviceManagerState;
|
use super::manager::DataControlDeviceManagerState;
|
||||||
use color_eyre::Result;
|
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use tracing::error;
|
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
||||||
Event, ZwlrDataControlSourceV1,
|
Event, ZwlrDataControlSourceV1,
|
||||||
|
|
@ -25,7 +23,7 @@ impl DataControlSourceDataExt for DataControlSourceData {
|
||||||
///
|
///
|
||||||
/// The functions defined in this trait are called as `DataSource` events are received from the compositor.
|
/// The functions defined in this trait are called as `DataSource` events are received from the compositor.
|
||||||
pub trait DataControlSourceHandler: Sized {
|
pub trait DataControlSourceHandler: Sized {
|
||||||
// /// This may be called multiple times, once for each accepted mime type from the destination, if any.
|
/// This may be called multiple times, once for each accepted mime type from the destination, if any.
|
||||||
// fn accept_mime(
|
// fn accept_mime(
|
||||||
// &mut self,
|
// &mut self,
|
||||||
// conn: &Connection,
|
// conn: &Connection,
|
||||||
|
|
@ -43,7 +41,7 @@ pub trait DataControlSourceHandler: Sized {
|
||||||
source: &ZwlrDataControlSourceV1,
|
source: &ZwlrDataControlSourceV1,
|
||||||
mime: String,
|
mime: String,
|
||||||
fd: WritePipe,
|
fd: WritePipe,
|
||||||
) -> Result<()>;
|
);
|
||||||
|
|
||||||
/// The data source is no longer valid
|
/// The data source is no longer valid
|
||||||
/// Cleanup & destroy this resource
|
/// Cleanup & destroy this resource
|
||||||
|
|
@ -70,9 +68,7 @@ where
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Send { mime_type, fd } => {
|
Event::Send { mime_type, fd } => {
|
||||||
if let Err(err) = state.send_request(conn, qh, source, mime_type, fd.into()) {
|
state.send_request(conn, qh, source, mime_type, fd.into());
|
||||||
error!("{err:#}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::Cancelled => {
|
Event::Cancelled => {
|
||||||
state.cancelled(conn, qh, source);
|
state.cancelled(conn, qh, source);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::manager::ToplevelManagerState;
|
use super::manager::ToplevelManagerState;
|
||||||
use crate::{Ironbar, lock};
|
use crate::{lock, Ironbar};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
@ -33,11 +33,6 @@ impl ToplevelHandle {
|
||||||
trace!("Activating handle");
|
trace!("Activating handle");
|
||||||
self.handle.activate(seat);
|
self.handle.activate(seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn minimize(&self) {
|
|
||||||
trace!("Minimizing handle");
|
|
||||||
self.handle.set_minimized();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -151,7 +146,7 @@ where
|
||||||
ToplevelHandle {
|
ToplevelHandle {
|
||||||
handle: handle.clone(),
|
handle: handle.clone(),
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
Event::Done if !lock!(data.inner).closed => {
|
Event::Done if !lock!(data.inner).closed => {
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use wayland_client::globals::{BindError, GlobalList};
|
use wayland_client::globals::{BindError, GlobalList};
|
||||||
use wayland_client::{Connection, Dispatch, QueueHandle, event_created_child};
|
use wayland_client::{event_created_child, Connection, Dispatch, QueueHandle};
|
||||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
||||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||||
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
|
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
|
||||||
|
|
@ -67,9 +67,7 @@ where
|
||||||
state.toplevel(conn, qhandle);
|
state.toplevel(conn, qhandle);
|
||||||
}
|
}
|
||||||
Event::Finished => {
|
Event::Finished => {
|
||||||
warn!(
|
warn!("Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues.");
|
||||||
"Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ pub mod manager;
|
||||||
use self::handle::ToplevelHandleHandler;
|
use self::handle::ToplevelHandleHandler;
|
||||||
use self::manager::ToplevelManagerHandler;
|
use self::manager::ToplevelManagerHandler;
|
||||||
use super::{Client, Environment, Event, Request, Response};
|
use super::{Client, Environment, Event, Request, Response};
|
||||||
|
use crate::try_send;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
use wayland_client::{Connection, QueueHandle};
|
use wayland_client::{Connection, QueueHandle};
|
||||||
|
|
||||||
use crate::channels::AsyncSenderExt;
|
|
||||||
pub use handle::{ToplevelHandle, ToplevelInfo};
|
pub use handle::{ToplevelHandle, ToplevelInfo};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -36,15 +36,6 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimizes the toplevel with the provided ID.
|
|
||||||
#[cfg(feature = "launcher")]
|
|
||||||
pub fn toplevel_minimize(&self, handle_id: usize) {
|
|
||||||
match self.send_request(Request::ToplevelMinimize(handle_id)) {
|
|
||||||
Response::Ok => (),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribes to events from toplevels.
|
/// Subscribes to events from toplevels.
|
||||||
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
|
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
|
||||||
self.toplevel_channel.0.subscribe()
|
self.toplevel_channel.0.subscribe()
|
||||||
|
|
@ -63,16 +54,10 @@ impl ToplevelHandleHandler for Environment {
|
||||||
|
|
||||||
match handle.info() {
|
match handle.info() {
|
||||||
Some(info) => {
|
Some(info) => {
|
||||||
if info.app_id.is_empty() {
|
|
||||||
trace!("ignoring xwayland dialog");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!("Adding new handle: {info:?}");
|
trace!("Adding new handle: {info:?}");
|
||||||
self.handles.push(handle.clone());
|
self.handles.push(handle.clone());
|
||||||
if let Some(info) = handle.info() {
|
if let Some(info) = handle.info() {
|
||||||
self.event_tx
|
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::New(info)));
|
||||||
.send_spawn(Event::Toplevel(ToplevelEvent::New(info)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -93,8 +78,7 @@ impl ToplevelHandleHandler for Environment {
|
||||||
Some(info) => {
|
Some(info) => {
|
||||||
trace!("Updating handle: {info:?}");
|
trace!("Updating handle: {info:?}");
|
||||||
if let Some(info) = handle.info() {
|
if let Some(info) = handle.info() {
|
||||||
self.event_tx
|
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Update(info)));
|
||||||
.send_spawn(Event::Toplevel(ToplevelEvent::Update(info)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -113,8 +97,7 @@ impl ToplevelHandleHandler for Environment {
|
||||||
|
|
||||||
self.handles.retain(|h| h != &handle);
|
self.handles.retain(|h| h != &handle);
|
||||||
if let Some(info) = handle.info() {
|
if let Some(info) = handle.info() {
|
||||||
self.event_tx
|
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Remove(info)));
|
||||||
.send_spawn(Event::Toplevel(ToplevelEvent::Remove(info)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::dynamic_value::{DynamicBool, dynamic_string};
|
use crate::dynamic_value::{dynamic_string, DynamicBool};
|
||||||
use crate::script::{Script, ScriptInput};
|
use crate::script::{Script, ScriptInput};
|
||||||
use glib::Propagation;
|
use glib::Propagation;
|
||||||
use gtk::gdk::ScrollDirection;
|
use gtk::gdk::ScrollDirection;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{EventBox, Justification, Orientation, Revealer, RevealerTransitionType};
|
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
|
|
@ -198,28 +198,6 @@ impl From<ModuleOrientation> for Orientation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone, Copy)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
|
||||||
pub enum ModuleJustification {
|
|
||||||
#[default]
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Center,
|
|
||||||
Fill,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ModuleJustification> for Justification {
|
|
||||||
fn from(o: ModuleJustification) -> Self {
|
|
||||||
match o {
|
|
||||||
ModuleJustification::Left => Self::Left,
|
|
||||||
ModuleJustification::Right => Self::Right,
|
|
||||||
ModuleJustification::Center => Self::Center,
|
|
||||||
ModuleJustification::Fill => Self::Fill,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransitionType {
|
impl TransitionType {
|
||||||
pub const fn to_revealer_transition_type(
|
pub const fn to_revealer_transition_type(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -268,13 +246,6 @@ impl CommonConfig {
|
||||||
let script = match event.direction() {
|
let script = match event.direction() {
|
||||||
ScrollDirection::Up => scroll_up_script.as_ref(),
|
ScrollDirection::Up => scroll_up_script.as_ref(),
|
||||||
ScrollDirection::Down => scroll_down_script.as_ref(),
|
ScrollDirection::Down => scroll_down_script.as_ref(),
|
||||||
ScrollDirection::Smooth => {
|
|
||||||
if event.scroll_deltas().unwrap_or_default().1 > 0.0 {
|
|
||||||
scroll_down_script.as_ref()
|
|
||||||
} else {
|
|
||||||
scroll_up_script.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -301,7 +272,8 @@ impl CommonConfig {
|
||||||
install_oneshot!(self.on_mouse_exit, connect_leave_notify_event);
|
install_oneshot!(self.on_mouse_exit, connect_leave_notify_event);
|
||||||
|
|
||||||
if let Some(tooltip) = self.tooltip {
|
if let Some(tooltip) = self.tooltip {
|
||||||
dynamic_string(&tooltip, container, move |container, string| {
|
let container = container.clone();
|
||||||
|
dynamic_string(&tooltip, move |string| {
|
||||||
container.set_tooltip_text(Some(&string));
|
container.set_tooltip_text(Some(&string));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -313,15 +285,19 @@ impl CommonConfig {
|
||||||
container.show_all();
|
container.show_all();
|
||||||
},
|
},
|
||||||
|show_if| {
|
|show_if| {
|
||||||
// need to keep clone here for the notify callback
|
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
|
|
||||||
show_if.subscribe((revealer, &container), |(revealer, container), success| {
|
{
|
||||||
|
let revealer = revealer.clone();
|
||||||
|
let container = container.clone();
|
||||||
|
|
||||||
|
show_if.subscribe(move |success| {
|
||||||
if success {
|
if success {
|
||||||
container.show_all();
|
container.show_all();
|
||||||
}
|
}
|
||||||
revealer.set_reveal_child(success);
|
revealer.set_reveal_child(success);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
revealer.connect_child_revealed_notify(move |revealer| {
|
revealer.connect_child_revealed_notify(move |revealer| {
|
||||||
if !revealer.reveals_child() {
|
if !revealer.reveals_child() {
|
||||||
|
|
|
||||||
|
|
@ -37,24 +37,26 @@ impl<'de> Deserialize<'de> for MonitorConfig {
|
||||||
|
|
||||||
pub fn deserialize_layer<'de, D>(deserializer: D) -> Result<gtk_layer_shell::Layer, D::Error>
|
pub fn deserialize_layer<'de, D>(deserializer: D) -> Result<gtk_layer_shell::Layer, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
use gtk_layer_shell::Layer;
|
use gtk_layer_shell::Layer;
|
||||||
|
|
||||||
let value = Option::<String>::deserialize(deserializer)?;
|
let value = Option::<String>::deserialize(deserializer)?;
|
||||||
value.map_or(Ok(Layer::Top), |v| match v.as_str() {
|
value
|
||||||
|
.map(|v| match v.as_str() {
|
||||||
"background" => Ok(Layer::Background),
|
"background" => Ok(Layer::Background),
|
||||||
"bottom" => Ok(Layer::Bottom),
|
"bottom" => Ok(Layer::Bottom),
|
||||||
"top" => Ok(Layer::Top),
|
"top" => Ok(Layer::Top),
|
||||||
"overlay" => Ok(Layer::Overlay),
|
"overlay" => Ok(Layer::Overlay),
|
||||||
_ => Err(serde::de::Error::custom("invalid value for orientation")),
|
_ => Err(serde::de::Error::custom("invalid value for orientation")),
|
||||||
})
|
})
|
||||||
|
.unwrap_or(Ok(Layer::Top))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "schema")]
|
#[cfg(feature = "schema")]
|
||||||
pub fn schema_layer(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
pub fn schema_layer(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
let mut schema: schemars::schema::SchemaObject = <String>::json_schema(generator).into();
|
let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into();
|
||||||
schema.enum_values = Some(vec![
|
schema.enum_values = Some(vec![
|
||||||
"background".into(),
|
"background".into(),
|
||||||
"bottom".into(),
|
"bottom".into(),
|
||||||
|
|
@ -77,7 +79,7 @@ impl BarPosition {
|
||||||
|
|
||||||
/// Gets the angle that label text should be displayed at
|
/// Gets the angle that label text should be displayed at
|
||||||
/// based on this position.
|
/// based on this position.
|
||||||
pub const fn angle(self) -> f64 {
|
pub const fn get_angle(self) -> f64 {
|
||||||
match self {
|
match self {
|
||||||
Self::Top | Self::Bottom => 0.0,
|
Self::Top | Self::Bottom => 0.0,
|
||||||
Self::Left => 90.0,
|
Self::Left => 90.0,
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
use crate::config::{ModuleJustification, ModuleOrientation};
|
|
||||||
use crate::modules::ModuleInfo;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
|
||||||
pub struct LayoutConfig {
|
|
||||||
/// The orientation to display the widget contents.
|
|
||||||
/// Setting to vertical will rotate text 90 degrees.
|
|
||||||
///
|
|
||||||
/// **Valid options**: `horizontal`, `vertical`
|
|
||||||
/// <br>
|
|
||||||
/// **Default**: `horizontal`
|
|
||||||
orientation: Option<ModuleOrientation>,
|
|
||||||
|
|
||||||
/// The justification (alignment) of the widget text shown on the bar.
|
|
||||||
///
|
|
||||||
/// **Valid options**: `left`, `right`, `center`, `fill`
|
|
||||||
/// <br>
|
|
||||||
/// **Default**: `left`
|
|
||||||
#[serde(default)]
|
|
||||||
pub justify: ModuleJustification,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutConfig {
|
|
||||||
pub fn orientation(&self, info: &ModuleInfo) -> gtk::Orientation {
|
|
||||||
self.orientation
|
|
||||||
.map_or(info.bar_position.orientation(), ModuleOrientation::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn angle(&self, info: &ModuleInfo) -> f64 {
|
|
||||||
self.orientation
|
|
||||||
.map_or(info.bar_position.angle(), ModuleOrientation::to_angle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +1,25 @@
|
||||||
mod common;
|
mod common;
|
||||||
mod r#impl;
|
mod r#impl;
|
||||||
mod layout;
|
|
||||||
mod truncate;
|
mod truncate;
|
||||||
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
use crate::modules::bindmode::Bindmode;
|
|
||||||
#[cfg(feature = "cairo")]
|
#[cfg(feature = "cairo")]
|
||||||
use crate::modules::cairo::CairoModule;
|
use crate::modules::cairo::CairoModule;
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
use crate::modules::clipboard::ClipboardModule;
|
use crate::modules::clipboard::ClipboardModule;
|
||||||
#[cfg(feature = "clock")]
|
#[cfg(feature = "clock")]
|
||||||
use crate::modules::clock::ClockModule;
|
use crate::modules::clock::ClockModule;
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
use crate::modules::custom::CustomModule;
|
use crate::modules::custom::CustomModule;
|
||||||
#[cfg(feature = "focused")]
|
#[cfg(feature = "focused")]
|
||||||
use crate::modules::focused::FocusedModule;
|
use crate::modules::focused::FocusedModule;
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
use crate::modules::keyboard::KeyboardModule;
|
|
||||||
#[cfg(feature = "label")]
|
|
||||||
use crate::modules::label::LabelModule;
|
use crate::modules::label::LabelModule;
|
||||||
#[cfg(feature = "launcher")]
|
#[cfg(feature = "launcher")]
|
||||||
use crate::modules::launcher::LauncherModule;
|
use crate::modules::launcher::LauncherModule;
|
||||||
#[cfg(feature = "menu")]
|
|
||||||
use crate::modules::menu::MenuModule;
|
|
||||||
#[cfg(feature = "music")]
|
#[cfg(feature = "music")]
|
||||||
use crate::modules::music::MusicModule;
|
use crate::modules::music::MusicModule;
|
||||||
#[cfg(feature = "network_manager")]
|
#[cfg(feature = "network_manager")]
|
||||||
use crate::modules::networkmanager::NetworkManagerModule;
|
use crate::modules::networkmanager::NetworkManagerModule;
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
use crate::modules::notifications::NotificationsModule;
|
use crate::modules::notifications::NotificationsModule;
|
||||||
#[cfg(feature = "script")]
|
|
||||||
use crate::modules::script::ScriptModule;
|
use crate::modules::script::ScriptModule;
|
||||||
#[cfg(feature = "sys_info")]
|
#[cfg(feature = "sys_info")]
|
||||||
use crate::modules::sysinfo::SysInfoModule;
|
use crate::modules::sysinfo::SysInfoModule;
|
||||||
|
|
@ -45,46 +35,37 @@ use crate::modules::workspaces::WorkspacesModule;
|
||||||
use crate::modules::{AnyModuleFactory, ModuleFactory, ModuleInfo};
|
use crate::modules::{AnyModuleFactory, ModuleFactory, ModuleInfo};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
#[cfg(feature = "schema")]
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub use self::common::{CommonConfig, ModuleJustification, ModuleOrientation, TransitionType};
|
#[cfg(feature = "schema")]
|
||||||
pub use self::layout::LayoutConfig;
|
use schemars::JsonSchema;
|
||||||
pub use self::truncate::{EllipsizeMode, TruncateMode};
|
|
||||||
|
pub use self::common::{CommonConfig, ModuleOrientation, TransitionType};
|
||||||
|
pub use self::truncate::TruncateMode;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||||
pub enum ModuleConfig {
|
pub enum ModuleConfig {
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
Bindmode(Box<Bindmode>),
|
|
||||||
#[cfg(feature = "cairo")]
|
#[cfg(feature = "cairo")]
|
||||||
Cairo(Box<CairoModule>),
|
Cairo(Box<CairoModule>),
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Clipboard(Box<ClipboardModule>),
|
Clipboard(Box<ClipboardModule>),
|
||||||
#[cfg(feature = "clock")]
|
#[cfg(feature = "clock")]
|
||||||
Clock(Box<ClockModule>),
|
Clock(Box<ClockModule>),
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Custom(Box<CustomModule>),
|
Custom(Box<CustomModule>),
|
||||||
#[cfg(feature = "focused")]
|
#[cfg(feature = "focused")]
|
||||||
Focused(Box<FocusedModule>),
|
Focused(Box<FocusedModule>),
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
Keyboard(Box<KeyboardModule>),
|
|
||||||
#[cfg(feature = "label")]
|
|
||||||
Label(Box<LabelModule>),
|
Label(Box<LabelModule>),
|
||||||
#[cfg(feature = "launcher")]
|
#[cfg(feature = "launcher")]
|
||||||
Launcher(Box<LauncherModule>),
|
Launcher(Box<LauncherModule>),
|
||||||
#[cfg(feature = "menu")]
|
|
||||||
Menu(Box<MenuModule>),
|
|
||||||
#[cfg(feature = "music")]
|
#[cfg(feature = "music")]
|
||||||
Music(Box<MusicModule>),
|
Music(Box<MusicModule>),
|
||||||
#[cfg(feature = "network_manager")]
|
#[cfg(feature = "network_manager")]
|
||||||
NetworkManager(Box<NetworkManagerModule>),
|
NetworkManager(Box<NetworkManagerModule>),
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
Notifications(Box<NotificationsModule>),
|
Notifications(Box<NotificationsModule>),
|
||||||
#[cfg(feature = "script")]
|
|
||||||
Script(Box<ScriptModule>),
|
Script(Box<ScriptModule>),
|
||||||
#[cfg(feature = "sys_info")]
|
#[cfg(feature = "sys_info")]
|
||||||
SysInfo(Box<SysInfoModule>),
|
SysInfo(Box<SysInfoModule>),
|
||||||
|
|
@ -112,33 +93,24 @@ impl ModuleConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
Self::Bindmode(module) => create!(module),
|
|
||||||
#[cfg(feature = "cairo")]
|
#[cfg(feature = "cairo")]
|
||||||
Self::Cairo(module) => create!(module),
|
Self::Cairo(module) => create!(module),
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Self::Clipboard(module) => create!(module),
|
Self::Clipboard(module) => create!(module),
|
||||||
#[cfg(feature = "clock")]
|
#[cfg(feature = "clock")]
|
||||||
Self::Clock(module) => create!(module),
|
Self::Clock(module) => create!(module),
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(module) => create!(module),
|
Self::Custom(module) => create!(module),
|
||||||
#[cfg(feature = "focused")]
|
#[cfg(feature = "focused")]
|
||||||
Self::Focused(module) => create!(module),
|
Self::Focused(module) => create!(module),
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
Self::Keyboard(module) => create!(module),
|
|
||||||
#[cfg(feature = "label")]
|
|
||||||
Self::Label(module) => create!(module),
|
Self::Label(module) => create!(module),
|
||||||
#[cfg(feature = "launcher")]
|
#[cfg(feature = "launcher")]
|
||||||
Self::Launcher(module) => create!(module),
|
Self::Launcher(module) => create!(module),
|
||||||
#[cfg(feature = "menu")]
|
|
||||||
Self::Menu(module) => create!(module),
|
|
||||||
#[cfg(feature = "music")]
|
#[cfg(feature = "music")]
|
||||||
Self::Music(module) => create!(module),
|
Self::Music(module) => create!(module),
|
||||||
#[cfg(feature = "network_manager")]
|
#[cfg(feature = "network_manager")]
|
||||||
Self::NetworkManager(module) => create!(module),
|
Self::NetworkManager(module) => create!(module),
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
Self::Notifications(module) => create!(module),
|
Self::Notifications(module) => create!(module),
|
||||||
#[cfg(feature = "script")]
|
|
||||||
Self::Script(module) => create!(module),
|
Self::Script(module) => create!(module),
|
||||||
#[cfg(feature = "sys_info")]
|
#[cfg(feature = "sys_info")]
|
||||||
Self::SysInfo(module) => create!(module),
|
Self::SysInfo(module) => create!(module),
|
||||||
|
|
@ -152,53 +124,6 @@ impl ModuleConfig {
|
||||||
Self::Workspaces(module) => create!(module),
|
Self::Workspaces(module) => create!(module),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> String {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "bindmode")]
|
|
||||||
ModuleConfig::Bindmode(_) => "Bindmode",
|
|
||||||
#[cfg(feature = "cairo")]
|
|
||||||
ModuleConfig::Cairo(_) => "Cario",
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
ModuleConfig::Clipboard(_) => "Clipboard",
|
|
||||||
#[cfg(feature = "clock")]
|
|
||||||
ModuleConfig::Clock(_) => "Clock",
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
ModuleConfig::Custom(_) => "Custom",
|
|
||||||
#[cfg(feature = "focused")]
|
|
||||||
ModuleConfig::Focused(_) => "Focused",
|
|
||||||
#[cfg(feature = "keyboard")]
|
|
||||||
ModuleConfig::Keyboard(_) => "Keyboard",
|
|
||||||
#[cfg(feature = "label")]
|
|
||||||
ModuleConfig::Label(_) => "Label",
|
|
||||||
#[cfg(feature = "launcher")]
|
|
||||||
ModuleConfig::Launcher(_) => "Launcher",
|
|
||||||
#[cfg(feature = "menu")]
|
|
||||||
ModuleConfig::Menu(_) => "Menu",
|
|
||||||
#[cfg(feature = "music")]
|
|
||||||
ModuleConfig::Music(_) => "Music",
|
|
||||||
#[cfg(feature = "network_manager")]
|
|
||||||
ModuleConfig::NetworkManager(_) => "NetworkManager",
|
|
||||||
#[cfg(feature = "notifications")]
|
|
||||||
ModuleConfig::Notifications(_) => "Notifications",
|
|
||||||
#[cfg(feature = "script")]
|
|
||||||
ModuleConfig::Script(_) => "Script",
|
|
||||||
#[cfg(feature = "sys_info")]
|
|
||||||
ModuleConfig::SysInfo(_) => "SysInfo",
|
|
||||||
#[cfg(feature = "tray")]
|
|
||||||
ModuleConfig::Tray(_) => "Tray",
|
|
||||||
#[cfg(feature = "upower")]
|
|
||||||
ModuleConfig::Upower(_) => "UPower",
|
|
||||||
#[cfg(feature = "volume")]
|
|
||||||
ModuleConfig::Volume(_) => "Volume",
|
|
||||||
#[cfg(feature = "workspaces")]
|
|
||||||
ModuleConfig::Workspaces(_) => "Workspaces",
|
|
||||||
// in case no modules are compiled
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => "",
|
|
||||||
}
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -347,6 +272,12 @@ pub struct BarConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub autohide: Option<u64>,
|
pub autohide: Option<u64>,
|
||||||
|
|
||||||
|
/// The name of the GTK icon theme to use.
|
||||||
|
/// Leave unset to use the default Adwaita theme.
|
||||||
|
///
|
||||||
|
/// **Default**: `null`
|
||||||
|
pub icon_theme: Option<String>,
|
||||||
|
|
||||||
/// An array of modules to append to the start of the bar.
|
/// An array of modules to append to the start of the bar.
|
||||||
/// Depending on the orientation, this is either the top of the left edge.
|
/// Depending on the orientation, this is either the top of the left edge.
|
||||||
///
|
///
|
||||||
|
|
@ -394,12 +325,10 @@ impl Default for BarConfig {
|
||||||
height: default_bar_height(),
|
height: default_bar_height(),
|
||||||
start_hidden: None,
|
start_hidden: None,
|
||||||
autohide: None,
|
autohide: None,
|
||||||
#[cfg(feature = "label")]
|
icon_theme: None,
|
||||||
start: Some(vec![ModuleConfig::Label(
|
start: Some(vec![ModuleConfig::Label(
|
||||||
LabelModule::new("ℹ️ Using default config".to_string()).into(),
|
LabelModule::new("ℹ️ Using default config".to_string()).into(),
|
||||||
)]),
|
)]),
|
||||||
#[cfg(not(feature = "label"))]
|
|
||||||
start: None,
|
|
||||||
center,
|
center,
|
||||||
end,
|
end,
|
||||||
anchor_to_edges: default_true(),
|
anchor_to_edges: default_true(),
|
||||||
|
|
@ -447,19 +376,6 @@ pub struct Config {
|
||||||
///
|
///
|
||||||
/// Providing this option overrides the single, global `bar` option.
|
/// Providing this option overrides the single, global `bar` option.
|
||||||
pub monitors: Option<HashMap<String, MonitorConfig>>,
|
pub monitors: Option<HashMap<String, MonitorConfig>>,
|
||||||
|
|
||||||
/// The name of the GTK icon theme to use.
|
|
||||||
/// Leave unset to use the default Adwaita theme.
|
|
||||||
///
|
|
||||||
/// **Default**: `null`
|
|
||||||
pub icon_theme: Option<String>,
|
|
||||||
|
|
||||||
/// Map of app IDs (or classes) to icon names,
|
|
||||||
/// overriding the app's default icon.
|
|
||||||
///
|
|
||||||
/// **Default**: `{}`
|
|
||||||
#[serde(default)]
|
|
||||||
pub icon_overrides: HashMap<String, String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_layer() -> gtk_layer_shell::Layer {
|
const fn default_layer() -> gtk_layer_shell::Layer {
|
||||||
|
|
@ -481,7 +397,3 @@ pub const fn default_false() -> bool {
|
||||||
pub const fn default_true() -> bool {
|
pub const fn default_true() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_launch_command() -> String {
|
|
||||||
String::from("gtk-launch {app_name}")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
use gtk::pango::EllipsizeMode as GtkEllipsizeMode;
|
use gtk::pango::EllipsizeMode as GtkEllipsizeMode;
|
||||||
|
use gtk::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Copy, Default)]
|
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
pub enum EllipsizeMode {
|
pub enum EllipsizeMode {
|
||||||
None,
|
|
||||||
Start,
|
Start,
|
||||||
Middle,
|
Middle,
|
||||||
#[default]
|
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EllipsizeMode> for GtkEllipsizeMode {
|
impl From<EllipsizeMode> for GtkEllipsizeMode {
|
||||||
fn from(value: EllipsizeMode) -> Self {
|
fn from(value: EllipsizeMode) -> Self {
|
||||||
match value {
|
match value {
|
||||||
EllipsizeMode::None => Self::None,
|
|
||||||
EllipsizeMode::Start => Self::Start,
|
EllipsizeMode::Start => Self::Start,
|
||||||
EllipsizeMode::Middle => Self::Middle,
|
EllipsizeMode::Middle => Self::Middle,
|
||||||
EllipsizeMode::End => Self::End,
|
EllipsizeMode::End => Self::End,
|
||||||
|
|
@ -29,23 +27,10 @@ impl From<EllipsizeMode> for GtkEllipsizeMode {
|
||||||
///
|
///
|
||||||
/// The option can be configured in one of two modes.
|
/// The option can be configured in one of two modes.
|
||||||
///
|
///
|
||||||
/// **Default**: `Auto (end)`
|
|
||||||
///
|
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
pub enum TruncateMode {
|
pub enum TruncateMode {
|
||||||
/// Do not truncate content.
|
|
||||||
///
|
|
||||||
/// Setting this option may cause excessively long content to overflow other widgets,
|
|
||||||
/// shifting them off-screen.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```corn
|
|
||||||
/// { truncate = "off" }
|
|
||||||
Off,
|
|
||||||
|
|
||||||
/// Auto mode lets GTK decide when to ellipsize.
|
/// Auto mode lets GTK decide when to ellipsize.
|
||||||
///
|
///
|
||||||
/// To use this mode, set the truncate option to a string
|
/// To use this mode, set the truncate option to a string
|
||||||
|
|
@ -59,7 +44,7 @@ pub enum TruncateMode {
|
||||||
///
|
///
|
||||||
/// **Valid options**: `start`, `middle`, `end`
|
/// **Valid options**: `start`, `middle`, `end`
|
||||||
/// <br>
|
/// <br>
|
||||||
/// **Default**: `end`
|
/// **Default**: `null`
|
||||||
Auto(EllipsizeMode),
|
Auto(EllipsizeMode),
|
||||||
|
|
||||||
/// Length mode defines a fixed point at which to ellipsize.
|
/// Length mode defines a fixed point at which to ellipsize.
|
||||||
|
|
@ -103,34 +88,36 @@ pub enum TruncateMode {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TruncateMode {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Auto(EllipsizeMode::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TruncateMode {
|
impl TruncateMode {
|
||||||
pub const fn length(&self) -> Option<i32> {
|
const fn mode(&self) -> EllipsizeMode {
|
||||||
match self {
|
match self {
|
||||||
Self::Auto(_) | Self::Off => None,
|
Self::Length { mode, .. } | Self::Auto(mode) => *mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn length(&self) -> Option<i32> {
|
||||||
|
match self {
|
||||||
|
Self::Auto(_) => None,
|
||||||
Self::Length { length, .. } => *length,
|
Self::Length { length, .. } => *length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn max_length(&self) -> Option<i32> {
|
const fn max_length(&self) -> Option<i32> {
|
||||||
match self {
|
match self {
|
||||||
Self::Auto(_) | Self::Off => None,
|
Self::Auto(_) => None,
|
||||||
Self::Length { max_length, .. } => *max_length,
|
Self::Length { max_length, .. } => *max_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TruncateMode> for GtkEllipsizeMode {
|
pub fn truncate_label(&self, label: >k::Label) {
|
||||||
fn from(value: TruncateMode) -> Self {
|
label.set_ellipsize(self.mode().into());
|
||||||
let mode = match value {
|
|
||||||
TruncateMode::Off => EllipsizeMode::None,
|
if let Some(length) = self.length() {
|
||||||
TruncateMode::Length { mode, .. } | TruncateMode::Auto(mode) => mode,
|
label.set_width_chars(length);
|
||||||
};
|
}
|
||||||
mode.into()
|
|
||||||
|
if let Some(length) = self.max_length() {
|
||||||
|
label.set_max_width_chars(length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue