Reorganize Fedora dotfiles and remove legacy profile
@@ -1,90 +0,0 @@
|
||||
# Конфиги рабочего стола Fedora
|
||||
|
||||
Эта папка содержит профиль Fedora KDE/Workstation, где основной рабочей
|
||||
Wayland-сессией становится Niri. Профиль получен из старого `bazzite/`, но
|
||||
адаптирован под обычную mutable Fedora: пакеты ставятся через `dnf`, Steam
|
||||
запускается обычным `/usr/bin/steam`, а локальные Bazzite helpers не требуются.
|
||||
|
||||
## Что здесь настроено
|
||||
|
||||
| Путь | Назначение |
|
||||
|---|---|
|
||||
| `niri/config.kdl` | Конфиг Niri: монитор `DP-1`, ввод, раскладка окон, автозапуск, правила окон, хоткеи. |
|
||||
| `waybar/` | Верхняя панель: конфиг, CSS в стиле Catppuccin и custom-модули. |
|
||||
| `waybar/scripts/gpu.sh` | Модуль Waybar для загрузки и температуры NVIDIA GPU через `nvidia-smi`. |
|
||||
| `waybar/scripts/weather.sh` | Модуль Waybar с погодой через `wttr.in`. |
|
||||
| `khal-calendar/` | Rust-версия календарного модуля Waybar. |
|
||||
| `mail-counter/` | Rust-модуль Waybar для счетчика непрочитанных писем Thunderbird. |
|
||||
| `calendar-mail-popup-notes.md` | Набросок UX и архитектуры для собственного calendar/mail popup из Waybar. |
|
||||
| `fuzzel/fuzzel.ini` | Тема лаунчера Fuzzel на цветах Catppuccin Latte. |
|
||||
| `btop/btop.conf` | Настройки системного монитора btop. |
|
||||
| `glow/glow.yml` | Настройки терминального Markdown-viewer Glow. |
|
||||
| `gtk-3.0/`, `gtk-4.0/` | GTK-настройки и цветовые файлы Breeze/Catppuccin. |
|
||||
| `xsettingsd/xsettingsd.conf` | Настройки GTK, курсора, иконок, шрифта и DPI для X11/XWayland-приложений. |
|
||||
| `environment.d/intel-gtk-fix.conf` | Переменная окружения сессии: `GSK_RENDERER=gl`. |
|
||||
| `autostart/steam.desktop` | Автозапуск Steam через обычный Fedora Steam. |
|
||||
| `wallpapers/raspberry.png` | Текущая картинка рабочего стола для `swaybg`. |
|
||||
|
||||
## Ожидаемые программы
|
||||
|
||||
```bash
|
||||
sudo dnf install -y \
|
||||
niri waybar fuzzel alacritty swaybg swaylock \
|
||||
btop glow xsettingsd pavucontrol xsel mpv nautilus \
|
||||
khal vdirsyncer thunderbird rustup cargo
|
||||
```
|
||||
|
||||
Также ожидается уже настроенный NVIDIA-драйвер с рабочим `nvidia-smi`.
|
||||
|
||||
## Установка
|
||||
|
||||
Подключить директории в `~/.config`:
|
||||
|
||||
```bash
|
||||
for dir in niri fuzzel waybar btop glow gtk-3.0 gtk-4.0 xsettingsd environment.d autostart; do
|
||||
ln -sfn ~/dotfiles/fedora/$dir ~/.config/$dir
|
||||
done
|
||||
mkdir -p ~/Pictures/Wallpapers
|
||||
ln -sfn ~/dotfiles/fedora/wallpapers/raspberry.png ~/Pictures/Wallpapers/raspberry.png
|
||||
```
|
||||
|
||||
Если репозиторий лежит не в `~/dotfiles`, путь нужно поправить.
|
||||
|
||||
## Сборка календарного модуля
|
||||
|
||||
```bash
|
||||
cd ~/dotfiles/fedora/khal-calendar
|
||||
cargo build --release
|
||||
install -m 755 target/release/khal-calendar ~/.config/waybar/scripts/khal-calendar
|
||||
```
|
||||
|
||||
## Сборка почтового модуля
|
||||
|
||||
```bash
|
||||
cd ~/dotfiles/fedora/mail-counter
|
||||
cargo build --release
|
||||
install -m 755 target/release/mail-counter ~/.config/waybar/scripts/mail
|
||||
```
|
||||
|
||||
Модуль читает `global-messages-db.sqlite` из профиля Thunderbird и печатает JSON
|
||||
для `custom/mail` в Waybar.
|
||||
|
||||
## Fedora-specific отличия от Bazzite
|
||||
|
||||
- Steam запускается через `/usr/bin/steam`, не через `/usr/bin/bazzite-steam`.
|
||||
- Wallpaper запускается через `swaybg` из `$PATH`, не через `~/.local/bin/swaybg`.
|
||||
- Hotkeys на `refresh.sh` отключены, потому что этого локального helper пока нет.
|
||||
- Clipboard hotkeys больше не зависят от локальной команды `t`.
|
||||
- Firefox/Telegram правила Niri отправляют окна на `DP-1`, текущий подключенный
|
||||
выход на этой Fedora-машине.
|
||||
|
||||
## Перезагрузка
|
||||
|
||||
После изменения конфига или CSS Waybar:
|
||||
|
||||
```bash
|
||||
pkill -SIGUSR2 waybar
|
||||
```
|
||||
|
||||
Если Waybar не подхватил изменения, нужно перезапустить Waybar вручную или
|
||||
перезапустить Niri-сессию.
|
||||
6
fedora/alacritty/alacritty.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[window]
|
||||
# Let the wallpaper show through without making terminal text hard to read.
|
||||
opacity = 0.8
|
||||
|
||||
[font]
|
||||
normal = { family = "JetBrainsMono Nerd Font Mono" }
|
||||
@@ -1,191 +0,0 @@
|
||||
# Calendar + Mail Popup Notes
|
||||
|
||||
Заметка по идее компактного календарно-почтового инструмента для Fedora + Niri +
|
||||
Waybar.
|
||||
|
||||
## Цель
|
||||
|
||||
Нужны две связанные поверхности:
|
||||
|
||||
1. Понятное решение в `waybar`, откуда можно быстро открыть календарь и увидеть
|
||||
нужную информацию.
|
||||
2. Отдельное компактное `tool-window` / popup-окно с календарем и мини-почтой.
|
||||
|
||||
Это не должен быть полноценный тяжелый PIM-комбайн на первом этапе. Основной
|
||||
приоритет — быстрый обзор и быстрые действия.
|
||||
|
||||
## Что нужно уметь
|
||||
|
||||
### В Waybar
|
||||
|
||||
- Кликнуть по модулю.
|
||||
- Увидеть календарную информацию без переключения в большой клиент.
|
||||
- Быстро понять:
|
||||
- что сегодня;
|
||||
- что дальше по неделе;
|
||||
- есть ли ближайшие события;
|
||||
- есть ли непрочитанная почта.
|
||||
|
||||
### В popup/tool-window
|
||||
|
||||
- Посмотреть календарь на неделю и месяц.
|
||||
- Увидеть список ближайших событий.
|
||||
- Создать событие.
|
||||
- Иметь мини-почту как обзор, а не как полноценный mail client.
|
||||
|
||||
## Ограничения текущего Waybar
|
||||
|
||||
Встроенный `clock` модуль Waybar умеет:
|
||||
|
||||
- показывать месяц/год;
|
||||
- переключать режимы;
|
||||
- листать календарь;
|
||||
- запускать команды по клику.
|
||||
|
||||
Но встроенный календарь Waybar — только display layer:
|
||||
|
||||
- он не показывает реальные CalDAV / Nextcloud / Google события;
|
||||
- он не умеет создавать события;
|
||||
- он не является полноценным event-aware календарем.
|
||||
|
||||
Следствие: Waybar должен быть entry point, а не местом, где живет вся
|
||||
календарная логика.
|
||||
|
||||
## Вывод по архитектуре
|
||||
|
||||
Нужна связка из двух частей:
|
||||
|
||||
1. `waybar` как обзор и кнопка входа;
|
||||
2. отдельное маленькое приложение/окно как рабочая поверхность.
|
||||
|
||||
Рекомендованный подход:
|
||||
|
||||
- hover в `waybar` показывает компактный календарный overview;
|
||||
- click открывает собственный popup/tool-window;
|
||||
- popup уже работает с реальными данными календаря и почты.
|
||||
|
||||
## Предлагаемый UX
|
||||
|
||||
### 1. Waybar popover / overview
|
||||
|
||||
Минимальный состав:
|
||||
|
||||
- верх: дата, `Today`, кнопка `+ Event`;
|
||||
- слева: мини-месяц;
|
||||
- справа или снизу: блок `Today / Tomorrow / This Week`;
|
||||
- отдельный короткий блок `Unread mail`.
|
||||
|
||||
Роль этого слоя:
|
||||
|
||||
- быстрое чтение;
|
||||
- быстрый переход;
|
||||
- минимум действий;
|
||||
- без тяжелой навигации.
|
||||
|
||||
### 2. Tool-window / popup
|
||||
|
||||
Форма окна:
|
||||
|
||||
- не полноценное большое desktop app окно;
|
||||
- не полноразмерный почтовый клиент;
|
||||
- компактное floating window, открываемое по клику из `waybar`.
|
||||
|
||||
Предпочтительная компоновка:
|
||||
|
||||
- левая колонка: мини-месяц + список календарей;
|
||||
- центральная зона: week view или month-agenda;
|
||||
- правая колонка: `Upcoming`, `Reminders`, `Unread mail`.
|
||||
|
||||
Верхняя строка:
|
||||
|
||||
- `Today`
|
||||
- `New event`
|
||||
- `Search`
|
||||
- переключение `Week / Month`
|
||||
|
||||
## Мини-почта: что это значит
|
||||
|
||||
Почта в этом окне должна быть не отдельным MUA, а summary layer:
|
||||
|
||||
- unread messages;
|
||||
- flagged / important;
|
||||
- письма за сегодня;
|
||||
- клик по письму открывает внешний клиент.
|
||||
|
||||
Нежелательно на первом этапе:
|
||||
|
||||
- полноценный composing;
|
||||
- threading;
|
||||
- archive/reply прямо в popup;
|
||||
- попытка заменить Thunderbird или другой почтовик.
|
||||
|
||||
Идея: calendar-first окно с mail summary, а не равноправный split mail/calendar
|
||||
комбайн.
|
||||
|
||||
## Референсы по форме и UX
|
||||
|
||||
### Основной референс по форме
|
||||
|
||||
`Fantastical Mini Window`
|
||||
|
||||
- маленькое окно поверх рабочего контекста;
|
||||
- быстрый обзор событий;
|
||||
- быстрый add/search;
|
||||
- ощущается как utility window, а не как полноразмерное приложение.
|
||||
|
||||
Ссылка:
|
||||
|
||||
- https://flexibits.com/blog/2022/04/mini-window-big-productivity-getting-the-most-from-fantastical-for-macs-mini-window/
|
||||
|
||||
### Референс по базовым календарным экранам
|
||||
|
||||
`GNOME Calendar`
|
||||
|
||||
- чистый month/week view;
|
||||
- отдельный event editor;
|
||||
- понятная структура desktop calendar без перегруза.
|
||||
|
||||
Ссылка:
|
||||
|
||||
- https://apps.gnome.org/Calendar/
|
||||
|
||||
### Референс по плотной рабочей компоновке
|
||||
|
||||
`Notion Calendar`
|
||||
|
||||
- маленький месяц + основной agenda/week layout;
|
||||
- показывает, как совместить overview и рабочую область.
|
||||
|
||||
Ссылка:
|
||||
|
||||
- https://habr.com/ru/news/787144/
|
||||
|
||||
### Референс по идее mail+calendar продукта
|
||||
|
||||
`Thunderbird`
|
||||
|
||||
- полезен не как popup reference, а как пример того, что почтовый summary и
|
||||
календарь могут жить рядом;
|
||||
- для нашего UX слишком тяжел как direct reference.
|
||||
|
||||
Ссылка:
|
||||
|
||||
- https://www.thunderbird.net/en-US/
|
||||
|
||||
## Практический вывод
|
||||
|
||||
Лучшее направление для реализации:
|
||||
|
||||
- брать `Fantastical Mini Window` как референс формы;
|
||||
- брать `GNOME Calendar` как референс календарных экранов;
|
||||
- делать calendar-first popup;
|
||||
- оставлять почту в формате короткого summary.
|
||||
|
||||
## Следующий логический шаг
|
||||
|
||||
Перед реализацией нужно решить:
|
||||
|
||||
1. popup должен быть очень маленьким overview-окном или уже почти полноценным
|
||||
mini-organizer;
|
||||
2. mini-mail нужен только для чтения summary или уже с действиями вроде
|
||||
`mark read`, `archive`, `open`.
|
||||
@@ -1,18 +0,0 @@
|
||||
# khal-calendar
|
||||
|
||||
Waybar custom calendar module that prints JSON with the current time and a Pango
|
||||
tooltip. Event days and upcoming events are read from `khal`.
|
||||
|
||||
## Build On Bazzite
|
||||
|
||||
```bash
|
||||
cd ~/dotfiles/bazzite/khal-calendar
|
||||
cargo build --release
|
||||
install -m 755 target/release/khal-calendar ~/.config/waybar/scripts/khal-calendar
|
||||
```
|
||||
|
||||
Waybar config should call the compiled binary:
|
||||
|
||||
```jsonc
|
||||
"exec": "~/.config/waybar/scripts/khal-calendar"
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
# mail-counter
|
||||
|
||||
Waybar custom mail module for Thunderbird. It reads Thunderbird's
|
||||
`global-messages-db.sqlite` and prints JSON for Waybar.
|
||||
|
||||
## Build On Bazzite
|
||||
|
||||
```bash
|
||||
cd ~/dotfiles/bazzite/mail-counter
|
||||
cargo build --release
|
||||
install -m 755 target/release/mail-counter ~/.config/waybar/scripts/mail
|
||||
```
|
||||
|
||||
Waybar config should call the compiled binary:
|
||||
|
||||
```jsonc
|
||||
"exec": "~/.config/waybar/scripts/mail"
|
||||
```
|
||||
@@ -70,7 +70,7 @@ fn unread_count(db_path: &Path) -> i64 {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn waybar_json(count: i64) -> Value {
|
||||
fn mail_json(count: i64) -> Value {
|
||||
if count > 0 {
|
||||
json!({
|
||||
"text": format!("{MAIL_ICON} {count}"),
|
||||
@@ -109,5 +109,5 @@ fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("{}", waybar_json(unread_count(&db_path)));
|
||||
println!("{}", mail_json(unread_count(&db_path)));
|
||||
}
|
||||
|
||||
@@ -145,8 +145,8 @@ environment {
|
||||
}
|
||||
|
||||
// screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
|
||||
spawn-at-startup "sh" "-c" "exec swaybg -i ~/Pictures/Wallpapers/raspberry.png -m fill"
|
||||
spawn-at-startup "waybar"
|
||||
spawn-at-startup "sh" "-c" "exec swaybg -i \"$HOME/.config/wallpaper.png\" -m fill"
|
||||
spawn-at-startup "systemctl" "--user" "start" "quickshell-control-center.service"
|
||||
hotkey-overlay {
|
||||
// skip-at-startup
|
||||
}
|
||||
@@ -322,9 +322,9 @@ window-rule {
|
||||
}
|
||||
}
|
||||
|
||||
/-window-rule {
|
||||
window-rule {
|
||||
match app-id="code"
|
||||
// opacity 0.98
|
||||
opacity 0.8
|
||||
border {
|
||||
// off
|
||||
}
|
||||
@@ -450,8 +450,8 @@ binds {
|
||||
Mod+Shift+V { spawn "sh" "-c" "wl-paste -n | env DISPLAY=:0 xsel -ib"; }
|
||||
|
||||
// Mod+T { spawn "sh" "-c" "alacritty msg create-window -e fish -C t || alacritty -e fish -C t"; }
|
||||
Mod+T { spawn "sh" "-c" "alacritty msg create-window || alacritty"; }
|
||||
Mod+Shift+T { spawn "alacritty"; }
|
||||
Mod+T { spawn "sh" "-c" "alacritty msg create-window -o window.opacity=0.5 || alacritty --option window.opacity=0.5"; }
|
||||
Mod+Shift+T { spawn "alacritty" "--option" "window.opacity=0.5"; }
|
||||
// Mod+Shift+T { spawn "sh" "-c" "alacritty msg create-window || alacritty"; }
|
||||
// Mod+Return { spawn "~/arst"; }
|
||||
// Mod+T { spawn "flatpak" "run" "org.gnome.Ptyxis.Devel" "-s"; }
|
||||
@@ -463,14 +463,16 @@ binds {
|
||||
Super+Alt+L allow-when-locked=true { spawn "swaylock"; }
|
||||
|
||||
Mod+W { spawn "sh" "-c" "mpv --pause \"$(wl-paste)\""; }
|
||||
// refresh.sh was a local Bazzite helper; keep these disabled until a Fedora helper exists.
|
||||
// These refresh bindings stay disabled until a local Fedora helper exists.
|
||||
// Super+Alt+H { spawn "refresh.sh" "144"; }
|
||||
// Super+Alt+Comma { spawn "refresh.sh" "120"; }
|
||||
|
||||
XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.1+"; }
|
||||
XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.1-"; }
|
||||
XF86AudioMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
|
||||
XF86AudioMicMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; }
|
||||
XF86AudioRaiseVolume allow-when-locked=true { spawn "sh" "-c" "qs ipc -p \"$HOME/.config/quickshell/control-center\" call osd volume_up"; }
|
||||
XF86AudioLowerVolume allow-when-locked=true { spawn "sh" "-c" "qs ipc -p \"$HOME/.config/quickshell/control-center\" call osd volume_down"; }
|
||||
XF86AudioMute allow-when-locked=true { spawn "sh" "-c" "qs ipc -p \"$HOME/.config/quickshell/control-center\" call osd volume_mute"; }
|
||||
XF86AudioMicMute allow-when-locked=true { spawn "sh" "-c" "qs ipc -p \"$HOME/.config/quickshell/control-center\" call osd mic_mute"; }
|
||||
XF86MonBrightnessUp allow-when-locked=true { spawn "sh" "-c" "qs ipc -p \"$HOME/.config/quickshell/control-center\" call osd brightness_up"; }
|
||||
XF86MonBrightnessDown allow-when-locked=true { spawn "sh" "-c" "qs ipc -p \"$HOME/.config/quickshell/control-center\" call osd brightness_down"; }
|
||||
|
||||
Mod+Q { close-window; }
|
||||
Mod+BackSpace { close-window; }
|
||||
|
||||
21
fedora/packages.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
niri
|
||||
quickshell
|
||||
fuzzel
|
||||
alacritty
|
||||
swaybg
|
||||
swaylock
|
||||
btop
|
||||
glow
|
||||
xsettingsd
|
||||
pavucontrol
|
||||
xsel
|
||||
wl-clipboard
|
||||
mpv
|
||||
nautilus
|
||||
khal
|
||||
vdirsyncer
|
||||
thunderbird
|
||||
rustup
|
||||
cargo
|
||||
ddcutil
|
||||
curl
|
||||
517
fedora/quickshell/control-center/Bar.qml
Normal file
@@ -0,0 +1,517 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
|
||||
Item {
|
||||
id: bar
|
||||
|
||||
property date currentTime: new Date()
|
||||
|
||||
property var controlCenter
|
||||
property var settings
|
||||
property var notificationCenter
|
||||
property var mediaPopup
|
||||
property var toggleMedia: null
|
||||
property var workspaces: []
|
||||
property string windowTitle: "Desktop"
|
||||
property string language: "EN"
|
||||
property string weather: "N/A"
|
||||
property string gpu: "GPU N/A"
|
||||
property string gpuTemperature: "--"
|
||||
property string mail: ""
|
||||
property int cpuUsage: 0
|
||||
property int memoryUsage: 0
|
||||
property string diskUsage: "--"
|
||||
property string temperature: "--"
|
||||
property var audioSink: Pipewire.defaultAudioSink
|
||||
property var mediaPlayer: Mpris.players.values.find(player => player.playbackState === MprisPlaybackState.Playing)
|
||||
|| Mpris.players.values[0]
|
||||
|
||||
function start(process) {
|
||||
if (!process.running) process.running = true;
|
||||
}
|
||||
|
||||
function focusWorkspace(index) {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", String(index)]);
|
||||
}
|
||||
|
||||
function parseJson(collector, fallback) {
|
||||
try {
|
||||
const value = JSON.parse(collector.text);
|
||||
return value === null ? fallback : value;
|
||||
} catch (error) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: barWindow
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
implicitHeight: 40
|
||||
exclusiveZone: 40
|
||||
color: "transparent"
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-bar"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 4
|
||||
anchors.rightMargin: 4
|
||||
anchors.bottomMargin: 0
|
||||
radius: 14
|
||||
color: "#1e1e2e"
|
||||
border.color: "#313244"
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 6
|
||||
anchors.rightMargin: 6
|
||||
spacing: 6
|
||||
|
||||
RowLayout {
|
||||
id: workspaceRow
|
||||
spacing: 3
|
||||
|
||||
Repeater {
|
||||
model: bar.workspaces
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
implicitWidth: workspaceLabel.implicitWidth + 16
|
||||
implicitHeight: 26
|
||||
radius: 10
|
||||
color: modelData.is_focused ? "#89b4fa" : (modelData.is_active ? "#45475a" : "#313244")
|
||||
|
||||
Label {
|
||||
id: workspaceLabel
|
||||
anchors.centerIn: parent
|
||||
text: modelData.name || String(modelData.idx)
|
||||
color: modelData.is_focused ? "#1e1e2e" : "#cdd6f4"
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: bar.focusWorkspace(modelData.idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: bar.windowTitle
|
||||
color: "#cdd6f4"
|
||||
font.pixelSize: 15
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.maximumWidth: 420
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
text: bar.weather
|
||||
color: "#89dceb"
|
||||
font.pixelSize: 15
|
||||
ToolTip.visible: weatherMouse.containsMouse
|
||||
ToolTip.text: "Weather"
|
||||
MouseArea {
|
||||
id: weatherMouse
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: bar.mail.length > 0
|
||||
text: bar.mail
|
||||
color: "#b4befe"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: mailMouse.containsMouse
|
||||
ToolTip.text: "Unread mail"
|
||||
MouseArea {
|
||||
id: mailMouse
|
||||
anchors.fill: parent
|
||||
onClicked: Quickshell.execDetached(["thunderbird"])
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: bar.mediaPlayer !== null
|
||||
text: bar.mediaPlayer ? (bar.mediaPlayer.trackTitle || bar.mediaPlayer.identity) : ""
|
||||
color: "#f5c2e7"
|
||||
leftPadding: 19
|
||||
elide: Text.ElideRight
|
||||
Layout.maximumWidth: 190
|
||||
BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: mediaMouse.containsMouse
|
||||
ToolTip.text: "Open media controls"
|
||||
MouseArea {
|
||||
id: mediaMouse
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (bar.toggleMedia) bar.toggleMedia();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: !!bar.notificationCenter && bar.notificationCenter.count > 0
|
||||
text: bar.notificationCenter ? String(bar.notificationCenter.count) : ""
|
||||
color: "#f38ba8"
|
||||
leftPadding: 19
|
||||
BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: notificationMouse.containsMouse
|
||||
ToolTip.text: "Open notifications"
|
||||
MouseArea {
|
||||
id: notificationMouse
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (bar.notificationCenter) bar.notificationCenter.open = !bar.notificationCenter.open;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: bar.gpu
|
||||
color: "#a6e3a1"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "gpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: gpuMouse.containsMouse
|
||||
ToolTip.text: "GPU usage"
|
||||
MouseArea {
|
||||
id: gpuMouse
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: bar.cpuUsage + "%"
|
||||
color: "#a6e3a1"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "cpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: cpuMouse.containsMouse
|
||||
ToolTip.text: "CPU usage"
|
||||
MouseArea { id: cpuMouse; anchors.fill: parent }
|
||||
}
|
||||
|
||||
Label {
|
||||
text: bar.memoryUsage + "%"
|
||||
color: "#cba6f7"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "memory"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: memoryMouse.containsMouse
|
||||
ToolTip.text: "Memory usage"
|
||||
MouseArea { id: memoryMouse; anchors.fill: parent }
|
||||
}
|
||||
|
||||
Label {
|
||||
text: bar.diskUsage
|
||||
color: "#f38ba8"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "disk"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: diskMouse.containsMouse
|
||||
ToolTip.text: "Root filesystem"
|
||||
MouseArea { id: diskMouse; anchors.fill: parent }
|
||||
}
|
||||
|
||||
Label {
|
||||
text: bar.language
|
||||
color: "#94e2d5"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "language"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: languageMouse.containsMouse
|
||||
ToolTip.text: "Keyboard layout"
|
||||
MouseArea { id: languageMouse; anchors.fill: parent }
|
||||
}
|
||||
|
||||
Label {
|
||||
text: bar.audioSink && bar.audioSink.audio
|
||||
? Math.round(bar.audioSink.audio.volume * 100) + "%"
|
||||
: "--"
|
||||
color: "#f9e2af"
|
||||
font.pixelSize: 15
|
||||
leftPadding: 19
|
||||
BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||
ToolTip.visible: audioMouse.containsMouse
|
||||
ToolTip.text: "Audio output"
|
||||
MouseArea {
|
||||
id: audioMouse
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (bar.audioSink && bar.audioSink.audio) bar.audioSink.audio.muted = !bar.audioSink.audio.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Qt.formatDateTime(bar.currentTime, "dd.MM HH:mm")
|
||||
color: "#cdd6f4"
|
||||
font.pixelSize: 15
|
||||
ToolTip.visible: clockMouse.containsMouse
|
||||
ToolTip.text: "Open calendar"
|
||||
MouseArea {
|
||||
id: clockMouse
|
||||
anchors.fill: parent
|
||||
onClicked: Quickshell.execDetached(["sh", "-c", "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'"])
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: ""
|
||||
color: "#89b4fa"
|
||||
Layout.preferredWidth: 18
|
||||
Layout.minimumWidth: 18
|
||||
Layout.maximumWidth: 18
|
||||
BarIcon { name: "gear"; size: 18; anchors.centerIn: parent }
|
||||
ToolTip.visible: settingsMouse.containsMouse
|
||||
ToolTip.text: "Quick settings"
|
||||
MouseArea {
|
||||
id: settingsMouse
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (bar.controlCenter) bar.controlCenter.togglePanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
delegate: Item {
|
||||
required property var modelData
|
||||
property bool hiddenTrayItem: !modelData
|
||||
|| /wayland to x11 video bridge|screen sharing|screen share/i.test(String(modelData.title || ""))
|
||||
|| /video-display|screen-share|screen-sharing/i.test(String(modelData.icon || ""))
|
||||
visible: !hiddenTrayItem
|
||||
implicitWidth: hiddenTrayItem ? 0 : 20
|
||||
implicitHeight: 22
|
||||
|
||||
Loader {
|
||||
id: trayIcon
|
||||
anchors.fill: parent
|
||||
active: !hiddenTrayItem && modelData
|
||||
sourceComponent: Component {
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
implicitSize: 18
|
||||
source: modelData && modelData.icon && modelData.icon.startsWith("image://")
|
||||
? modelData.icon
|
||||
: (modelData ? Quickshell.iconPath(modelData.icon) : "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BarIcon {
|
||||
visible: !hiddenTrayItem && !trayIcon.active
|
||||
anchors.fill: parent
|
||||
enabled: !hiddenTrayItem
|
||||
name: "video"
|
||||
size: 15
|
||||
anchors.margins: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: function(mouse) {
|
||||
if (!modelData) return;
|
||||
if (mouse.button === Qt.RightButton && modelData.hasMenu) {
|
||||
modelData.display(barWindow, mouse.x, mouse.y + 20);
|
||||
} else {
|
||||
modelData.activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [bar.audioSink]
|
||||
}
|
||||
|
||||
Process {
|
||||
id: workspaceReader
|
||||
command: ["niri", "msg", "-j", "workspaces"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const value = bar.parseJson(this, []);
|
||||
if (Array.isArray(value)) {
|
||||
value.sort((left, right) => left.idx - right.idx);
|
||||
bar.workspaces = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: windowReader
|
||||
command: ["niri", "msg", "-j", "focused-window"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const value = bar.parseJson(this, {});
|
||||
bar.windowTitle = value.title || "Desktop";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: languageReader
|
||||
command: ["niri", "msg", "-j", "keyboard-layouts"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const value = bar.parseJson(this, {});
|
||||
const index = Number(value.current_idx);
|
||||
const name = Array.isArray(value.names) && index >= 0 ? String(value.names[index] || "") : "";
|
||||
const normalized = name.toLowerCase();
|
||||
if (normalized.includes("russian") || normalized.includes("рус")) bar.language = "RU";
|
||||
else if (normalized.includes("english") || normalized.includes("англ")) bar.language = "EN";
|
||||
else if (name.length > 0) bar.language = name.slice(0, 2).toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cpuReader
|
||||
command: ["sh", "-c", "awk '/^cpu / { idle=$5; total=0; for (i=2; i<=NF; i++) total += $i; print int((total-idle)*100/total) }' /proc/stat"]
|
||||
stdout: StdioCollector { onStreamFinished: bar.cpuUsage = parseInt(text.trim()) || 0 }
|
||||
}
|
||||
|
||||
Process {
|
||||
id: memoryReader
|
||||
command: ["sh", "-c", "free | awk '/^Mem:/ { print int($3*100/$2) }'"]
|
||||
stdout: StdioCollector { onStreamFinished: bar.memoryUsage = parseInt(text.trim()) || 0 }
|
||||
}
|
||||
|
||||
Process {
|
||||
id: diskReader
|
||||
command: ["sh", "-c", "df -P / | awk 'NR==2 { print $5 }'"]
|
||||
stdout: StdioCollector { onStreamFinished: bar.diskUsage = text.trim() || "--" }
|
||||
}
|
||||
|
||||
Process {
|
||||
id: temperatureReader
|
||||
command: ["sh", "-c", "max=0; for file in /sys/class/thermal/thermal_zone*/temp; do value=$(cat $file 2>/dev/null); [ $value -gt $max ] 2>/dev/null && max=$value; done; if [ $max -gt 0 ]; then echo $((max / 1000))C; else echo --; fi"]
|
||||
stdout: StdioCollector { onStreamFinished: bar.temperature = text.trim() || "--" }
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gpuReader
|
||||
command: ["sh", "-c", "nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const parts = text.trim().split(",");
|
||||
const usage = parseInt((parts[0] || "").trim());
|
||||
const temperature = parseInt((parts[1] || "").trim());
|
||||
bar.gpu = isNaN(usage) ? "N/A" : usage + "%";
|
||||
bar.gpuTemperature = isNaN(temperature) ? "--" : temperature + "C";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: weatherReader
|
||||
command: ["bash", Quickshell.shellPath("../scripts/weather.sh")]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const value = bar.parseJson(this, {});
|
||||
bar.weather = value.text || "N/A";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: mailReader
|
||||
command: ["sh", "-c", "if [ -x $HOME/.local/bin/mail-counter ]; then $HOME/.local/bin/mail-counter; else printf '{}'; fi"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const value = bar.parseJson(this, {});
|
||||
bar.mail = value.text || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: bar.settings ? bar.settings.temperatureInterval : 2000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
bar.currentTime = new Date();
|
||||
bar.start(windowReader);
|
||||
bar.start(cpuReader);
|
||||
bar.start(memoryReader);
|
||||
bar.start(diskReader);
|
||||
bar.start(temperatureReader);
|
||||
bar.start(gpuReader);
|
||||
bar.start(mailReader);
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: weatherTimer
|
||||
interval: 900000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: bar.start(weatherReader)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: languageTimer
|
||||
interval: 200
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: bar.start(languageReader)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: workspaceTimer
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: bar.start(workspaceReader)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
bar.start(workspaceReader);
|
||||
bar.start(windowReader);
|
||||
bar.start(languageReader);
|
||||
bar.start(cpuReader);
|
||||
bar.start(memoryReader);
|
||||
bar.start(diskReader);
|
||||
bar.start(temperatureReader);
|
||||
bar.start(gpuReader);
|
||||
bar.start(weatherReader);
|
||||
bar.start(mailReader);
|
||||
}
|
||||
}
|
||||
16
fedora/quickshell/control-center/BarIcon.qml
Normal file
@@ -0,0 +1,16 @@
|
||||
import QtQuick
|
||||
|
||||
Image {
|
||||
id: root
|
||||
|
||||
property string name
|
||||
property real size: 15
|
||||
|
||||
width: size
|
||||
height: size
|
||||
source: Qt.resolvedUrl("icons/" + name + ".svg")
|
||||
sourceSize.width: size * 2
|
||||
sourceSize.height: size * 2
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
209
fedora/quickshell/control-center/MediaPopup.qml
Normal file
@@ -0,0 +1,209 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Wayland
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool open: false
|
||||
property var selectedPlayer: null
|
||||
property var player: selectedPlayer
|
||||
|| Mpris.players.values.find(candidate => candidate.playbackState === MprisPlaybackState.Playing)
|
||||
|| Mpris.players.values[0]
|
||||
|| null
|
||||
|
||||
signal dismissed()
|
||||
|
||||
function formatTime(value) {
|
||||
if (!value || value < 0) return "0:00";
|
||||
const seconds = Math.floor(value);
|
||||
return Math.floor(seconds / 60) + ":" + String(seconds % 60).padStart(2, "0");
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
visible: root.open
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: root.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-media-popup"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.dismissed()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: Math.min(410, parent.width - 32)
|
||||
height: root.player ? 330 : 150
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 52
|
||||
radius: 20
|
||||
color: "#f5f2ea"
|
||||
border.color: "#d8d0c2"
|
||||
border.width: 1
|
||||
z: 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: function(mouse) { mouse.accepted = true; }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Media"
|
||||
color: "#252321"
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.player ? root.player.identity : "No player"
|
||||
color: "#7c756c"
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Rectangle {
|
||||
width: 26
|
||||
height: 26
|
||||
radius: 13
|
||||
color: "#e5e0d7"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "x"
|
||||
color: "#6e675f"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.dismissed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.player !== null
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 14
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 132
|
||||
Layout.preferredHeight: 132
|
||||
radius: 14
|
||||
color: "#e5e0d7"
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
id: cover
|
||||
anchors.fill: parent
|
||||
source: root.player ? root.player.trackArtUrl : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
}
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
visible: cover.status !== Image.Ready
|
||||
text: "♪"
|
||||
color: "#8b8176"
|
||||
font.pixelSize: 42
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
text: root.player ? (root.player.trackTitle || "Unknown track") : ""
|
||||
color: "#252321"
|
||||
font.pixelSize: 17
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.player ? (root.player.trackArtist || root.player.trackAlbum || "Unknown artist") : ""
|
||||
color: "#7c756c"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
Label {
|
||||
text: root.player ? root.formatTime(root.player.position) + " / " + root.formatTime(root.player.length) : ""
|
||||
color: "#7c756c"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slider {
|
||||
visible: root.player && root.player.length > 0
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: root.player ? root.player.length : 1
|
||||
value: root.player ? root.player.position : 0
|
||||
onMoved: {
|
||||
if (root.player && root.player.canSeek) root.player.position = value;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.player !== null
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 14
|
||||
|
||||
Button {
|
||||
text: "Prev"
|
||||
enabled: !!(root.player && root.player.canGoPrevious)
|
||||
onClicked: root.player.previous()
|
||||
}
|
||||
Button {
|
||||
text: root.player && root.player.playbackState === MprisPlaybackState.Playing ? "Pause" : "Play"
|
||||
enabled: !!(root.player && root.player.canTogglePlaying)
|
||||
onClicked: root.player.togglePlaying()
|
||||
}
|
||||
Button {
|
||||
text: "Next"
|
||||
enabled: !!(root.player && root.player.canGoNext)
|
||||
onClicked: root.player.next()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: Mpris.players.values.length > 1
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Players"
|
||||
color: "#7c756c"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Repeater {
|
||||
model: Mpris.players
|
||||
delegate: Button {
|
||||
required property var modelData
|
||||
text: modelData.identity
|
||||
onClicked: root.selectedPlayer = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
fedora/quickshell/control-center/Notifications.qml
Normal file
@@ -0,0 +1,220 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Wayland
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool open: false
|
||||
property bool dnd: false
|
||||
property bool toastOpen: false
|
||||
property var latest: null
|
||||
readonly property int count: server.trackedNotifications ? server.trackedNotifications.values.length : 0
|
||||
|
||||
signal dismissed()
|
||||
|
||||
NotificationServer {
|
||||
id: server
|
||||
keepOnReload: true
|
||||
onNotification: function(notification) {
|
||||
notification.tracked = true;
|
||||
root.latest = notification;
|
||||
if (!root.dnd) {
|
||||
root.toastOpen = true;
|
||||
toastTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: toastTimer
|
||||
interval: 5000
|
||||
repeat: false
|
||||
onTriggered: root.toastOpen = false
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
visible: root.toastOpen && !root.open && root.latest !== null
|
||||
color: "transparent"
|
||||
anchors { top: true; bottom: true; left: true; right: true }
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-notification-toast"
|
||||
|
||||
Rectangle {
|
||||
width: Math.min(390, parent.width - 32)
|
||||
height: 100
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 16
|
||||
radius: 16
|
||||
color: "#f5f2ea"
|
||||
border.color: "#d8d0c2"
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.toastOpen = false;
|
||||
root.open = true;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 3
|
||||
Label {
|
||||
text: root.latest ? root.latest.appName : ""
|
||||
color: "#7c756c"
|
||||
font.pixelSize: 11
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
text: root.latest ? root.latest.summary : ""
|
||||
color: "#252321"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
text: root.latest ? root.latest.body : ""
|
||||
color: "#5f5952"
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
visible: root.open
|
||||
color: "transparent"
|
||||
anchors { top: true; bottom: true; left: true; right: true }
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: root.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-notification-center"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.dismissed()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.min(430, parent.width - 32)
|
||||
height: Math.min(parent.height - 32, 700)
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 16
|
||||
radius: 20
|
||||
color: "#f5f2ea"
|
||||
border.color: "#d8d0c2"
|
||||
border.width: 1
|
||||
z: 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: function(mouse) { mouse.accepted = true }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Notifications"
|
||||
color: "#252321"
|
||||
font.pixelSize: 22
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
text: root.dnd ? "DND on" : "DND off"
|
||||
onClicked: root.dnd = !root.dnd
|
||||
}
|
||||
Button {
|
||||
text: "Close"
|
||||
onClicked: root.dismissed()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: root.count === 0
|
||||
text: "No notifications"
|
||||
color: "#7c756c"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 18
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: server.trackedNotifications
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: notificationColumn.implicitHeight + 22
|
||||
radius: 14
|
||||
color: "#e8e3da"
|
||||
|
||||
ColumnLayout {
|
||||
id: notificationColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 11
|
||||
spacing: 3
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: modelData.appName
|
||||
color: "#7c756c"
|
||||
font.pixelSize: 11
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Button {
|
||||
text: "x"
|
||||
onClicked: modelData.dismiss()
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: modelData.summary
|
||||
color: "#252321"
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Label {
|
||||
text: modelData.body
|
||||
color: "#5f5952"
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
fedora/quickshell/control-center/Osd.qml
Normal file
@@ -0,0 +1,76 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool open: false
|
||||
property string kind: "volume"
|
||||
property real value: 0
|
||||
property bool muted: false
|
||||
|
||||
PanelWindow {
|
||||
visible: root.open
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-osd"
|
||||
|
||||
Rectangle {
|
||||
width: 300
|
||||
height: 78
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 110
|
||||
radius: 20
|
||||
color: "#1e1e2e"
|
||||
border.color: "#45475a"
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
text: root.kind === "brightness" ? "Display" : (root.muted ? "Muted" : "Volume")
|
||||
color: "#cdd6f4"
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 8
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * Math.max(0, Math.min(1, root.value))
|
||||
height: parent.height
|
||||
radius: 4
|
||||
color: root.kind === "brightness" ? "#f9e2af" : "#89b4fa"
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Math.round(root.value * 100) + "%"
|
||||
color: "#cdd6f4"
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
fedora/quickshell/control-center/Settings.qml
Normal file
@@ -0,0 +1,36 @@
|
||||
import QtQuick
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property alias temperatureInterval: values.temperatureInterval
|
||||
property alias temperatureWarning: values.temperatureWarning
|
||||
property alias temperatureCritical: values.temperatureCritical
|
||||
|
||||
function setTemperatureWarning(value) {
|
||||
root.temperatureWarning = Math.min(value, root.temperatureCritical - 1);
|
||||
}
|
||||
|
||||
function setTemperatureCritical(value) {
|
||||
root.temperatureCritical = Math.max(value, root.temperatureWarning + 1);
|
||||
}
|
||||
|
||||
property FileView settingsFile: FileView {
|
||||
path: Quickshell.statePath("control-center.json")
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: writeAdapter()
|
||||
|
||||
JsonAdapter {
|
||||
id: values
|
||||
|
||||
property int temperatureInterval: 2000
|
||||
property int temperatureWarning: 75
|
||||
property int temperatureCritical: 90
|
||||
}
|
||||
}
|
||||
}
|
||||
1
fedora/quickshell/control-center/icons/audio.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#f9e2af" d="M3 9v6h4l5 4V5L7 9H3Zm13.5 3a4.5 4.5 0 0 0-2.5-4.03v8.06A4.5 4.5 0 0 0 16.5 12Zm0-8v2.12A6.98 6.98 0 0 1 20 12a6.98 6.98 0 0 1-3.5 5.88V20a9 9 0 0 0 0-16Z"/></svg>
|
||||
|
After Width: | Height: | Size: 248 B |
1
fedora/quickshell/control-center/icons/cpu.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#a6e3a1" d="M4 4h16v16H4V4Zm3 3v10h10V7H7Zm2 2h6v6H9V9Zm-7 2H0v2h2v-2Zm20 0h2v2h-2v-2ZM11 0h2v2h-2V0Zm0 22h2v2h-2v-2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 199 B |
1
fedora/quickshell/control-center/icons/disk.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#f38ba8" d="M3 5h18v14H3V5Zm3 3v8h12V8H6Zm2 2h8v4H8v-4Z"/></svg>
|
||||
|
After Width: | Height: | Size: 137 B |
1
fedora/quickshell/control-center/icons/gear.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#89b4fa" d="m19.43 12.98.04-.98-.04-.98 2.11-1.65-2-3.46-2.49 1a7.7 7.7 0 0 0-1.7-.98L15 3h-4l-.35 2.93c-.6.25-1.17.58-1.7.98l-2.49-1-2 3.46 2.11 1.65-.04.98.04.98-2.11 1.65 2 3.46 2.49-1c.53.4 1.1.73 1.7.98L11 21h4l.35-2.93a7.7 7.7 0 0 0 1.7-.98l2.49 1 2-3.46-2.11-1.65ZM13 15.5A3.5 3.5 0 1 1 13 8a3.5 3.5 0 0 1 0 7.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
1
fedora/quickshell/control-center/icons/gpu.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#a6e3a1" d="M2 5h20v14H2V5Zm2 2v10h16V7H4Zm2 2h6v6H6V9Zm2 2v2h2v-2H8Zm8-2h2v2h-2V9Zm0 4h2v2h-2v-2ZM0 9h2v2H0V9Zm0 4h2v2H0v-2Zm22-4h2v2h-2V9Zm0 4h2v2h-2v-2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 237 B |
1
fedora/quickshell/control-center/icons/language.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#94e2d5" d="M4 20 9 4h2l5 16h-2l-1.25-4h-5.5L6 20H4Zm4.88-6h3.24L10.5 8.8 8.88 14ZM15 6h7v2h-2.5c-.38 3-1.42 5.47-3.12 7.4.9.8 1.94 1.5 3.12 2.1v2.2a16.2 16.2 0 0 1-4.45-2.86A16 16 0 0 1 12 19v-2.2c.98-.58 1.88-1.3 2.7-2.18A14.3 14.3 0 0 1 12.8 11h2.1c.27.7.65 1.4 1.15 2.08A12.7 12.7 0 0 0 17.4 8H15V6Z"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
1
fedora/quickshell/control-center/icons/mail.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#b4befe" d="M3 5h18a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2Zm0 3v1l9 5 9-5V8l-9 5-9-5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 191 B |
1
fedora/quickshell/control-center/icons/memory.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#cba6f7" d="M3 3h18v4H3V3Zm0 7h18v4H3v-4Zm0 7h18v4H3v-4Z"/></svg>
|
||||
|
After Width: | Height: | Size: 138 B |
1
fedora/quickshell/control-center/icons/temperature.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fab387" d="M14 14.76V5a2 2 0 0 0-4 0v9.76a5 5 0 1 0 4 0ZM12 20a3 3 0 0 1-1-5.83V5a1 1 0 0 1 2 0v9.17A3 3 0 0 1 12 20Z"/></svg>
|
||||
|
After Width: | Height: | Size: 200 B |
1
fedora/quickshell/control-center/icons/video.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#cdd6f4" d="M3 5h13a2 2 0 0 1 2 2v2l4-2v10l-4-2v2a2 2 0 0 1-2 2H3V5Zm2 3v8h11V8H5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 164 B |
748
fedora/quickshell/control-center/shell.qml
Normal file
@@ -0,0 +1,748 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Networking
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Wayland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
property bool open: false
|
||||
property bool panelVisible: false
|
||||
property bool mediaOpen: false
|
||||
property bool notificationsOpen: false
|
||||
property bool osdOpen: false
|
||||
property string osdKind: "volume"
|
||||
property int brightness: 0
|
||||
property var pendingNetwork: null
|
||||
property string wifiPassword: ""
|
||||
property var wifiDevice: Networking.devices.values.find(device => device.type === DeviceType.Wifi)
|
||||
property var bluetoothAdapter: Bluetooth.defaultAdapter
|
||||
property var audioSink: Pipewire.defaultAudioSink
|
||||
property var settings: statusSettings
|
||||
property alias cpuTemperature: statusBar.temperature
|
||||
property alias gpuTemperature: statusBar.gpuTemperature
|
||||
|
||||
Settings {
|
||||
id: statusSettings
|
||||
}
|
||||
|
||||
Bar {
|
||||
id: statusBar
|
||||
controlCenter: root
|
||||
settings: root.settings
|
||||
mediaPopup: mediaPopup
|
||||
toggleMedia: function() { root.mediaOpen = !root.mediaOpen }
|
||||
notificationCenter: notificationCenter
|
||||
}
|
||||
|
||||
MediaPopup {
|
||||
id: mediaPopup
|
||||
open: root.mediaOpen
|
||||
onDismissed: root.mediaOpen = false
|
||||
}
|
||||
|
||||
Notifications {
|
||||
id: notificationCenter
|
||||
open: root.notificationsOpen
|
||||
onDismissed: root.notificationsOpen = false
|
||||
}
|
||||
|
||||
Osd {
|
||||
open: root.osdOpen
|
||||
kind: root.osdKind
|
||||
value: root.osdKind === "brightness"
|
||||
? root.brightness / 100
|
||||
: (root.audioSink && root.audioSink.audio ? root.audioSink.audio.volume : 0)
|
||||
muted: root.audioSink && root.audioSink.audio ? root.audioSink.audio.muted : false
|
||||
}
|
||||
|
||||
function setBrightness(value) {
|
||||
const level = Math.max(0, Math.min(100, Math.round(value)));
|
||||
Quickshell.execDetached(["ddcutil", "setvcp", "10", String(level)]);
|
||||
root.brightness = level;
|
||||
}
|
||||
|
||||
function connectWifi(network) {
|
||||
if (network.security === WifiSecurityType.Open) {
|
||||
network.connect();
|
||||
return;
|
||||
}
|
||||
|
||||
root.pendingNetwork = network;
|
||||
root.wifiPassword = "";
|
||||
}
|
||||
|
||||
function submitWifiPassword() {
|
||||
if (root.pendingNetwork && root.wifiPassword.length > 0) {
|
||||
root.pendingNetwork.connectWithPsk(root.wifiPassword);
|
||||
root.pendingNetwork = null;
|
||||
root.wifiPassword = "";
|
||||
}
|
||||
}
|
||||
|
||||
function showOsd(kind) {
|
||||
root.osdKind = kind;
|
||||
root.osdOpen = true;
|
||||
osdTimer.restart();
|
||||
}
|
||||
|
||||
function adjustBrightness(delta) {
|
||||
const current = root.brightness > 0 ? root.brightness : 50;
|
||||
root.setBrightness(current + delta);
|
||||
root.showOsd("brightness");
|
||||
}
|
||||
|
||||
function temperatureColor(value) {
|
||||
const match = String(value).match(/(\d+)C\s*$/);
|
||||
const temperature = match ? Number(match[1]) : -1;
|
||||
if (temperature < 0) return "#806d95";
|
||||
if (temperature >= root.settings.temperatureCritical) return "#c94f6d";
|
||||
if (temperature >= root.settings.temperatureWarning) return "#996315";
|
||||
return "#4d7c38";
|
||||
}
|
||||
|
||||
function openPanel() {
|
||||
closePanelTimer.stop();
|
||||
root.panelVisible = true;
|
||||
openPanelTimer.restart();
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
openPanelTimer.stop();
|
||||
root.open = false;
|
||||
closePanelTimer.restart();
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
if (root.open || root.panelVisible) root.closePanel();
|
||||
else root.openPanel();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: openPanelTimer
|
||||
interval: 1
|
||||
repeat: false
|
||||
onTriggered: root.open = true
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closePanelTimer
|
||||
interval: 240
|
||||
repeat: false
|
||||
onTriggered: root.panelVisible = false
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "controlcenter"
|
||||
|
||||
function toggle() {
|
||||
root.togglePanel();
|
||||
}
|
||||
|
||||
function open() {
|
||||
root.openPanel();
|
||||
}
|
||||
|
||||
function close() {
|
||||
root.closePanel();
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "osd"
|
||||
|
||||
function volume_up() {
|
||||
Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%+"]);
|
||||
root.showOsd("volume");
|
||||
}
|
||||
|
||||
function volume_down() {
|
||||
Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"]);
|
||||
root.showOsd("volume");
|
||||
}
|
||||
|
||||
function volume_mute() {
|
||||
Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]);
|
||||
root.showOsd("volume");
|
||||
}
|
||||
|
||||
function mic_mute() {
|
||||
Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", "toggle"]);
|
||||
root.showOsd("mic");
|
||||
}
|
||||
|
||||
function brightness_up() {
|
||||
root.adjustBrightness(5);
|
||||
}
|
||||
|
||||
function brightness_down() {
|
||||
root.adjustBrightness(-5);
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: osdTimer
|
||||
interval: 1400
|
||||
repeat: false
|
||||
onTriggered: root.osdOpen = false
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [root.audioSink]
|
||||
}
|
||||
|
||||
Process {
|
||||
id: brightnessReader
|
||||
command: ["sh", "-c", "ddcutil getvcp 10 2>/dev/null | sed -n 's/.*current value = *\\([0-9]*\\).*/\\1/p'"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const value = parseInt(text.trim());
|
||||
if (!isNaN(value)) root.brightness = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: brightnessPoll
|
||||
interval: 3000
|
||||
repeat: true
|
||||
running: root.open
|
||||
onTriggered: {
|
||||
if (!brightnessReader.running) brightnessReader.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
visible: root.panelVisible
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: root.panelVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-control-center"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.closePanel()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: Math.min(470, parent.width - 32)
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
x: root.open ? parent.width - width - 16 : parent.width + 24
|
||||
radius: 20
|
||||
color: "#f5f2ea"
|
||||
border.color: "#d8d0c2"
|
||||
border.width: 1
|
||||
z: 1
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 220
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: mouse.accepted = true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
text: "Control center"
|
||||
color: "#252321"
|
||||
font.pixelSize: 22
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: "Niri / QuickShell"
|
||||
color: "#7c756c"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "✕"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Close"
|
||||
onClicked: root.closePanel()
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: wifiColumn.implicitHeight + 24
|
||||
radius: 14
|
||||
color: "#e5f0ef"
|
||||
|
||||
ColumnLayout {
|
||||
id: wifiColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Wi-Fi"
|
||||
color: "#1c666d"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.wifiDevice ? (root.wifiDevice.name || "Available") : "Unavailable"
|
||||
color: "#52777a"
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Switch {
|
||||
checked: Networking.wifiEnabled
|
||||
enabled: Networking.wifiHardwareEnabled
|
||||
onToggled: Networking.wifiEnabled = checked
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.wifiDevice !== null
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: root.wifiDevice && root.wifiDevice.networks.values.find(network => network.connected)
|
||||
? root.wifiDevice.networks.values.find(network => network.connected).name
|
||||
: "Not connected"
|
||||
color: "#252321"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
text: "Scan"
|
||||
onClicked: {
|
||||
if (root.wifiDevice) root.wifiDevice.scannerEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.wifiDevice ? root.wifiDevice.networks : null
|
||||
delegate: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: modelData.name
|
||||
color: "#252321"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: modelData.connected ? "Connected" : Math.round(modelData.signalStrength) + "%"
|
||||
color: modelData.connected ? "#1c666d" : "#7c756c"
|
||||
}
|
||||
Button {
|
||||
text: modelData.connected ? "Disconnect" : "Connect"
|
||||
enabled: !modelData.stateChanging
|
||||
onClicked: modelData.connected ? modelData.disconnect() : root.connectWifi(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.pendingNetwork !== null
|
||||
Layout.fillWidth: true
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Password for " + (root.pendingNetwork ? root.pendingNetwork.name : "network")
|
||||
echoMode: TextInput.Password
|
||||
text: root.wifiPassword
|
||||
onTextChanged: root.wifiPassword = text
|
||||
onAccepted: root.submitWifiPassword()
|
||||
}
|
||||
Button {
|
||||
text: "Join"
|
||||
enabled: root.wifiPassword.length > 0
|
||||
onClicked: root.submitWifiPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: bluetoothColumn.implicitHeight + 24
|
||||
radius: 14
|
||||
color: "#eceafd"
|
||||
|
||||
ColumnLayout {
|
||||
id: bluetoothColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Bluetooth"
|
||||
color: "#55499c"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Switch {
|
||||
checked: root.bluetoothAdapter ? root.bluetoothAdapter.enabled : false
|
||||
enabled: root.bluetoothAdapter !== null
|
||||
onToggled: {
|
||||
if (root.bluetoothAdapter) root.bluetoothAdapter.enabled = checked;
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: root.bluetoothAdapter && root.bluetoothAdapter.discovering ? "Stop scan" : "Scan"
|
||||
enabled: root.bluetoothAdapter && root.bluetoothAdapter.enabled
|
||||
onClicked: {
|
||||
if (root.bluetoothAdapter) root.bluetoothAdapter.discovering = !root.bluetoothAdapter.discovering;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.bluetoothAdapter ? root.bluetoothAdapter.devices : null
|
||||
delegate: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
Label {
|
||||
text: modelData.name || modelData.deviceName || "Unknown device"
|
||||
color: "#252321"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "Not paired")
|
||||
color: "#7c756c"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
Label {
|
||||
visible: modelData.batteryAvailable
|
||||
text: Math.round(modelData.battery * 100) + "%"
|
||||
color: "#7c756c"
|
||||
}
|
||||
Button {
|
||||
text: modelData.connected ? "Disconnect" : (modelData.paired ? "Connect" : "Pair")
|
||||
onClicked: {
|
||||
if (modelData.connected) modelData.disconnect();
|
||||
else if (modelData.paired) modelData.connect();
|
||||
else modelData.pair();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: audioColumn.implicitHeight + 24
|
||||
radius: 14
|
||||
color: "#fff1d7"
|
||||
|
||||
ColumnLayout {
|
||||
id: audioColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Audio"
|
||||
color: "#996315"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.audioSink ? (root.audioSink.description || "Default output") : "Unavailable"
|
||||
color: "#806a48"
|
||||
elide: Text.ElideRight
|
||||
Layout.maximumWidth: 230
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Button {
|
||||
text: root.audioSink && root.audioSink.audio && root.audioSink.audio.muted ? "Unmute" : "Mute"
|
||||
enabled: root.audioSink && root.audioSink.audio
|
||||
onClicked: {
|
||||
if (root.audioSink && root.audioSink.audio) root.audioSink.audio.muted = !root.audioSink.audio.muted;
|
||||
}
|
||||
}
|
||||
Slider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
value: root.audioSink && root.audioSink.audio ? root.audioSink.audio.volume : 0
|
||||
enabled: root.audioSink && root.audioSink.audio
|
||||
onMoved: {
|
||||
if (root.audioSink && root.audioSink.audio) root.audioSink.audio.volume = value;
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: root.audioSink && root.audioSink.audio ? Math.round(root.audioSink.audio.volume * 100) + "%" : "--"
|
||||
color: "#806a48"
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Pipewire.nodes
|
||||
delegate: RowLayout {
|
||||
visible: modelData.isSink && !modelData.isStream
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: modelData.description || modelData.name
|
||||
color: "#252321"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
text: modelData === root.audioSink ? "Default" : "Use"
|
||||
enabled: modelData !== root.audioSink
|
||||
onClicked: Pipewire.preferredDefaultAudioSink = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: displayColumn.implicitHeight + 24
|
||||
radius: 14
|
||||
color: "#f8e4dc"
|
||||
|
||||
ColumnLayout {
|
||||
id: displayColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Display brightness"
|
||||
color: "#9e4d39"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.brightness > 0 ? root.brightness + "%" : "Unavailable"
|
||||
color: "#8b665b"
|
||||
}
|
||||
}
|
||||
Slider {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 100
|
||||
value: root.brightness
|
||||
enabled: root.brightness > 0
|
||||
onPressedChanged: {
|
||||
if (!pressed) root.setBrightness(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: statusColumn.implicitHeight + 24
|
||||
radius: 14
|
||||
color: "#eee8f7"
|
||||
|
||||
ColumnLayout {
|
||||
id: statusColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Status bar"
|
||||
color: "#684b8f"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: "Temperature"
|
||||
color: "#806d95"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "CPU temperature"
|
||||
color: "#252321"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.cpuTemperature
|
||||
color: root.temperatureColor(root.cpuTemperature)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "GPU temperature"
|
||||
color: "#252321"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: root.gpuTemperature
|
||||
color: root.temperatureColor(root.gpuTemperature)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Refresh interval"
|
||||
color: "#252321"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ComboBox {
|
||||
model: ["1 second", "2 seconds", "5 seconds"]
|
||||
currentIndex: [1000, 2000, 5000].indexOf(root.settings.temperatureInterval)
|
||||
onActivated: root.settings.temperatureInterval = [1000, 2000, 5000][currentIndex]
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Warning / critical"
|
||||
color: "#252321"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
SpinBox {
|
||||
from: 40
|
||||
to: 99
|
||||
value: root.settings.temperatureWarning
|
||||
editable: true
|
||||
onValueModified: root.settings.setTemperatureWarning(value)
|
||||
}
|
||||
Label {
|
||||
text: "/"
|
||||
color: "#7c756c"
|
||||
}
|
||||
SpinBox {
|
||||
from: 41
|
||||
to: 110
|
||||
value: root.settings.temperatureCritical
|
||||
editable: true
|
||||
onValueModified: root.settings.setTemperatureCritical(value)
|
||||
}
|
||||
Label {
|
||||
text: "°C"
|
||||
color: "#7c756c"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: powerColumn.implicitHeight + 24
|
||||
radius: 14
|
||||
color: "#e8f1df"
|
||||
|
||||
ColumnLayout {
|
||||
id: powerColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: "Power profile"
|
||||
color: "#4d7c38"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: PowerProfile.toString(PowerProfiles.profile)
|
||||
color: "#66825a"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Button {
|
||||
text: "Saver"
|
||||
Layout.fillWidth: true
|
||||
onClicked: PowerProfiles.profile = PowerProfile.PowerSaver
|
||||
}
|
||||
Button {
|
||||
text: "Balanced"
|
||||
Layout.fillWidth: true
|
||||
onClicked: PowerProfiles.profile = PowerProfile.Balanced
|
||||
}
|
||||
Button {
|
||||
text: "Performance"
|
||||
enabled: PowerProfiles.hasPerformanceProfile
|
||||
Layout.fillWidth: true
|
||||
onClicked: PowerProfiles.profile = PowerProfile.Performance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
fedora/quickshell/scripts/gpu.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
data=$(nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null)
|
||||
if [ -z "$data" ]; then
|
||||
printf '%s\n' '{"text":"N/A"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
usage=$(printf '%s\n' "$data" | cut -d',' -f1 | tr -d ' ')
|
||||
temp=$(printf '%s\n' "$data" | cut -d',' -f2 | tr -d ' ')
|
||||
printf '{"text":"%s%% %sC"}\n' "$usage" "$temp"
|
||||
9
fedora/quickshell/scripts/weather.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
weather=$(curl -4 -sS --connect-timeout 3 --max-time 8 "https://wttr.in/?format=%c%t" 2>/dev/null)
|
||||
if [ -z "$weather" ] || printf '%s' "$weather" | grep -q "Unknown"; then
|
||||
printf '%s\n' '{"text":"N/A"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '{"text":"%s"}\n' "$weather"
|
||||
10
fedora/systemd/user/quickshell-control-center.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=QuickShell control center
|
||||
PartOf=niri.service
|
||||
After=niri.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/qs -p %h/.config/quickshell/control-center
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
Environment=QT_QPA_PLATFORM=wayland
|
||||
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
@@ -1,179 +0,0 @@
|
||||
// -*- mode: jsonc -*-
|
||||
{
|
||||
"layer": "top",
|
||||
"height": 32,
|
||||
"spacing": 8,
|
||||
|
||||
"modules-left": [
|
||||
"niri/workspaces"
|
||||
],
|
||||
"modules-center": [
|
||||
"niri/window"
|
||||
],
|
||||
"modules-right": [
|
||||
"privacy",
|
||||
"custom/mail",
|
||||
"custom/weather",
|
||||
"pulseaudio",
|
||||
"network",
|
||||
"niri/language",
|
||||
"power-profiles-daemon",
|
||||
"cpu",
|
||||
"memory",
|
||||
"temperature",
|
||||
"custom/gpu",
|
||||
"disk",
|
||||
"custom/calendar",
|
||||
"tray"
|
||||
],
|
||||
|
||||
"niri/workspaces": {
|
||||
"format": "{index}"
|
||||
},
|
||||
|
||||
"niri/window": {
|
||||
"format": "{}",
|
||||
"max-length": 50,
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"niri/language": {
|
||||
"format": "{short}",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"privacy": {
|
||||
"icon-spacing": 4,
|
||||
"icon-size": 16,
|
||||
"transition-duration": 250,
|
||||
"modules": [
|
||||
{ "type": "screenshare" },
|
||||
{ "type": "audio-in" },
|
||||
{ "type": "audio-out" }
|
||||
]
|
||||
},
|
||||
|
||||
"custom/mail": {
|
||||
"format": "<span font='Symbols Nerd Font Mono'>{}</span>",
|
||||
"return-type": "json",
|
||||
"exec": "~/.config/waybar/scripts/mail",
|
||||
"interval": 60,
|
||||
"tooltip": true,
|
||||
"on-click": "thunderbird",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"custom/weather": {
|
||||
"format": "{}",
|
||||
"return-type": "json",
|
||||
"exec": "~/.config/waybar/scripts/weather.sh",
|
||||
"interval": 900,
|
||||
"tooltip": true,
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"custom/gpu": {
|
||||
"format": "{}",
|
||||
"return-type": "json",
|
||||
"exec": "~/.config/waybar/scripts/gpu.sh",
|
||||
"interval": 5,
|
||||
"tooltip": true,
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"disk": {
|
||||
"format": "<span font='Font Awesome 6 Free'></span> {percentage_used}%",
|
||||
"path": "/",
|
||||
"tooltip-format": "{used} / {total} ({percentage_used}%)",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"tray": {
|
||||
"spacing": 10
|
||||
},
|
||||
|
||||
"custom/calendar": {
|
||||
"format": "<span font='Font Awesome 6 Free'></span> {}",
|
||||
"return-type": "json",
|
||||
"exec": "~/.config/waybar/scripts/khal-calendar",
|
||||
"interval": 30,
|
||||
"tooltip": true,
|
||||
"on-click": "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"cpu": {
|
||||
"format": "<span font='Font Awesome 6 Free Solid'></span> {usage}%",
|
||||
"tooltip": false,
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"memory": {
|
||||
"format": "<span font='Font Awesome 6 Free Solid'></span> {}%",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"temperature": {
|
||||
"critical-threshold": 80,
|
||||
"format": "<span font='Font Awesome 6 Free Solid'></span> {temperatureC}°C",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"network": {
|
||||
"format-wifi": "<span font='Symbols Nerd Font Mono'>{icon}</span>",
|
||||
"format-ethernet": "",
|
||||
"format-icons": ["", "", "", "", ""],
|
||||
"tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ipaddr}/{cidr}",
|
||||
"tooltip-format-ethernet": "{ifname}\n{ipaddr}/{cidr}",
|
||||
"format-linked": "{ifname} (No IP) ",
|
||||
"format-disconnected": "<span font='Symbols Nerd Font Mono'></span>",
|
||||
"on-click": "",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"pulseaudio": {
|
||||
"format": "<span font='Font Awesome 6 Free Solid'>{icon}</span> {volume}% <span font='Font Awesome 6 Free Solid'></span> {format_source}",
|
||||
"format-bluetooth": "<span font='Font Awesome 6 Free Solid'>{icon}</span> {volume}% <span font='Font Awesome 6 Brands'></span> <span font='Font Awesome 6 Free Solid'></span> {format_source}",
|
||||
"format-bluetooth-muted": "<span font='Font Awesome 6 Free Solid'></span> <span font='Font Awesome 6 Brands'></span> <span font='Font Awesome 6 Free Solid'></span> {format_source}",
|
||||
"format-muted": "<span font='Font Awesome 6 Free Solid'></span> <span font='Font Awesome 6 Free Solid'></span> {format_source}",
|
||||
"format-source": "{volume}%",
|
||||
"format-source-muted": "<span font='Font Awesome 6 Free Solid'></span>",
|
||||
"format-icons": {
|
||||
"headphone": "",
|
||||
"hands-free": "",
|
||||
"headset": "",
|
||||
"phone": "",
|
||||
"portable": "",
|
||||
"car": "",
|
||||
"default": ["", "", ""]
|
||||
},
|
||||
"on-click": "pavucontrol",
|
||||
"align": 0.5,
|
||||
"justify": "center"
|
||||
},
|
||||
|
||||
"power-profiles-daemon": {
|
||||
"format": "<span font='Font Awesome 6 Free Solid'>{icon}</span>",
|
||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
||||
"tooltip": true,
|
||||
"align": 0.5,
|
||||
"justify": "center",
|
||||
"format-icons": {
|
||||
"default": "",
|
||||
"performance": "",
|
||||
"balanced": "",
|
||||
"power-saver": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
data=$(nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null)
|
||||
if [ -z "$data" ]; then
|
||||
echo '{"text": "GPU N/A", "tooltip": "nvidia-smi not available"}'
|
||||
exit 0
|
||||
fi
|
||||
usage=$(echo "$data" | cut -d',' -f1 | tr -d ' ')
|
||||
temp=$(echo "$data" | cut -d',' -f2 | tr -d ' ')
|
||||
echo "{\"text\": \"GPU ${usage}% ${temp}°C\", \"tooltip\": \"GPU: ${usage}%\\nTemp: ${temp}°C\", \"class\": \"\"}"
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
weather=$(curl -s "wttr.in/?format=%c%t" 2>/dev/null)
|
||||
if [ -z "$weather" ] || echo "$weather" | grep -q "Unknown"; then
|
||||
echo '{"text": "N/A", "tooltip": "Weather unavailable"}'
|
||||
exit 0
|
||||
fi
|
||||
tooltip=$(curl -s "wttr.in/?format=%l:+%C+%t+%w+%h" 2>/dev/null)
|
||||
echo "{\"text\": \"${weather}\", \"tooltip\": \"${tooltip}\"}"
|
||||
@@ -1,262 +0,0 @@
|
||||
/* Catppuccin Latte */
|
||||
@define-color rosewater #dc8a78;
|
||||
@define-color flamingo #dd7878;
|
||||
@define-color pink #ea76cb;
|
||||
@define-color mauve #8839ef;
|
||||
@define-color red #d20f39;
|
||||
@define-color maroon #e64553;
|
||||
@define-color peach #fe640b;
|
||||
@define-color yellow #df8e1d;
|
||||
@define-color green #40a02b;
|
||||
@define-color teal #179299;
|
||||
@define-color sky #04a5e5;
|
||||
@define-color sapphire #209fb5;
|
||||
@define-color blue #1e66f5;
|
||||
@define-color lavender #7287fd;
|
||||
@define-color text #4c4f69;
|
||||
@define-color subtext1 #5c5f77;
|
||||
@define-color subtext0 #6c6f85;
|
||||
@define-color overlay2 #7c7f93;
|
||||
@define-color overlay1 #8c8fa1;
|
||||
@define-color overlay0 #9ca0b0;
|
||||
@define-color surface2 #acb0be;
|
||||
@define-color surface1 #bcc0cc;
|
||||
@define-color surface0 #ccd0da;
|
||||
@define-color base #eff1f5;
|
||||
@define-color mantle #e6e9ef;
|
||||
@define-color crust #dce0e8;
|
||||
|
||||
* {
|
||||
font-family: 'Noto Sans Mono', 'Symbols Nerd Font Mono', 'Symbols Nerd Font', 'Font Awesome 6 Free', monospace;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
window#waybar {
|
||||
background-color: rgba(30, 30, 46, 0.72);
|
||||
color: @text;
|
||||
transition-property: background-color;
|
||||
transition-duration: .5s;
|
||||
}
|
||||
|
||||
window#waybar.hidden {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
button {
|
||||
box-shadow: inset 0 -3px transparent;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: inherit;
|
||||
box-shadow: inset 0 -2px @blue;
|
||||
}
|
||||
|
||||
/* Workspaces */
|
||||
#workspaces button {
|
||||
min-height: 24px;
|
||||
padding: 4px 8px;
|
||||
background-color: #000000;
|
||||
color: @overlay2;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
#workspaces button:hover {
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
#workspaces button.active {
|
||||
color: @blue;
|
||||
box-shadow: inset 0 -2px @blue;
|
||||
}
|
||||
|
||||
#workspaces button.urgent {
|
||||
background-color: #000000;
|
||||
color: @base;
|
||||
}
|
||||
|
||||
/* Common module styling */
|
||||
#window,
|
||||
#custom-calendar,
|
||||
#cpu,
|
||||
#memory,
|
||||
#disk,
|
||||
#temperature,
|
||||
#network,
|
||||
#pulseaudio,
|
||||
#wireplumber,
|
||||
#tray,
|
||||
#power-profiles-daemon,
|
||||
#language,
|
||||
#custom-gpu,
|
||||
#custom-weather,
|
||||
#privacy {
|
||||
min-height: 24px;
|
||||
padding: 4px 8px;
|
||||
background-color: #000000;
|
||||
border-radius: 16px;
|
||||
color: @text;
|
||||
}
|
||||
|
||||
#workspaces button label,
|
||||
#window label,
|
||||
#custom-calendar label,
|
||||
#cpu label,
|
||||
#memory label,
|
||||
#disk label,
|
||||
#temperature label,
|
||||
#network label,
|
||||
#pulseaudio label,
|
||||
#wireplumber label,
|
||||
#tray label,
|
||||
#power-profiles-daemon label,
|
||||
#language label,
|
||||
#custom-gpu label,
|
||||
#custom-weather label,
|
||||
#privacy label {
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#window,
|
||||
#workspaces {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
#window {
|
||||
color: @text;
|
||||
}
|
||||
|
||||
/* Calendar/Clock */
|
||||
#custom-calendar {
|
||||
color: @blue;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* CPU */
|
||||
#cpu {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
/* Memory */
|
||||
#memory {
|
||||
color: @mauve;
|
||||
}
|
||||
|
||||
/* Temperature */
|
||||
#temperature {
|
||||
color: @peach;
|
||||
}
|
||||
|
||||
#temperature.critical {
|
||||
color: @red;
|
||||
}
|
||||
|
||||
/* Network */
|
||||
#network {
|
||||
color: @sapphire;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#network.disconnected {
|
||||
color: @red;
|
||||
}
|
||||
|
||||
/* Pulseaudio */
|
||||
#pulseaudio {
|
||||
color: @yellow;
|
||||
}
|
||||
|
||||
#pulseaudio.muted {
|
||||
color: @overlay1;
|
||||
}
|
||||
|
||||
/* Language */
|
||||
#language {
|
||||
color: @teal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Power profiles */
|
||||
#power-profiles-daemon {
|
||||
color: @subtext0;
|
||||
}
|
||||
|
||||
#power-profiles-daemon.performance {
|
||||
color: @red;
|
||||
}
|
||||
|
||||
#power-profiles-daemon.balanced {
|
||||
color: @blue;
|
||||
}
|
||||
|
||||
#power-profiles-daemon.power-saver {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
/* Mail */
|
||||
#custom-mail {
|
||||
color: @lavender;
|
||||
}
|
||||
|
||||
#custom-mail.unread {
|
||||
color: @red;
|
||||
}
|
||||
|
||||
/* Disk */
|
||||
#disk {
|
||||
color: @flamingo;
|
||||
}
|
||||
|
||||
/* GPU */
|
||||
#custom-gpu {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
/* Weather */
|
||||
#custom-weather {
|
||||
color: @sky;
|
||||
}
|
||||
|
||||
/* Privacy */
|
||||
#privacy {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#privacy-item {
|
||||
min-height: 24px;
|
||||
padding: 4px 8px;
|
||||
color: @base;
|
||||
background-color: #000000;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
#privacy-item.screenshare {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
#privacy-item.audio-in {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
#privacy-item.audio-out {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* Tray */
|
||||
#tray {
|
||||
color: @text;
|
||||
}
|
||||
|
||||
#tray > .passive {
|
||||
-gtk-icon-effect: dim;
|
||||
}
|
||||
|
||||
#tray > .needs-attention {
|
||||
-gtk-icon-effect: highlight;
|
||||
color: @red;
|
||||
}
|
||||