commit a213b10a887fe93b9b5708e44d417e8fc0598418 Author: pyrschtjag Date: Tue Aug 11 10:30:07 2026 +0400 Initial commit: sway + uwsm desktop dotfiles Nord-themed Wayland setup on sway (via uwsm), including: - sway/swayidle/swaylock/swaddle — WM, idle/lock/inhibit - waybar, rofi — bar and launcher - dunst — notifications - foot — terminal - zsh (+fzf, syntax highlighting, autosuggestions) — shell - mpd/rmpc/mpc, mpv — music and video - nvim — editor (lazy.nvim) - ly — login manager - rbw + pinentry-rofi wrapper — Bitwarden/Vaultwarden CLI - per-window-layout, clipvault — helper systemd services diff --git a/.zshrc b/.zshrc new file mode 100644 index 0000000..639ce63 --- /dev/null +++ b/.zshrc @@ -0,0 +1,164 @@ +HISTFILE="$HOME/.zsh_history" +HISTSIZE=10000 +SAVEHIST=10000 + +bindkey -v +KEYTIMEOUT=1 + +setopt APPEND_HISTORY +setopt SHARE_HISTORY +setopt PROMPT_SUBST + +autoload -U compinit +compinit +source <(fzf --zsh) + +if [[ -f /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh ]]; then + source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh +fi + +if [[ -f /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]]; then + source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh +fi + +if [[ -f ~/.zsh/plugins/fzf-tab/fzf-tab.plugin.zsh ]]; then + source ~/.zsh/plugins/fzf-tab/fzf-tab.plugin.zsh +fi + +if [[ -f ~/.zsh/plugins/fzf-tab-completion/zsh/fzf-tab-completion.plugin.zsh ]]; then + source ~/.zsh/plugins/fzf-tab-completion/zsh/fzf-tab-completion.plugin.zsh +fi + +typeset -A ZSH_HIGHLIGHT_STYLES +ZSH_HIGHLIGHT_STYLES[default]='fg=#eceff4' +ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#bf616a' +ZSH_HIGHLIGHT_STYLES[command]='fg=#88c0d0' +ZSH_HIGHLIGHT_STYLES[builtin]='fg=#88c0d0' +ZSH_HIGHLIGHT_STYLES[alias]='fg=#8fbcbb' +ZSH_HIGHLIGHT_STYLES[function]='fg=#88c0d0' +ZSH_HIGHLIGHT_STYLES[precommand]='fg=#81a1c1,bold' +ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=#81a1c1' +ZSH_HIGHLIGHT_STYLES[commandseparator]='fg=#d08770' +ZSH_HIGHLIGHT_STYLES[path]='fg=#eceff4,underline' +ZSH_HIGHLIGHT_STYLES[path_prefix]='fg=#eceff4,underline' +ZSH_HIGHLIGHT_STYLES[optstring]='fg=#ebcb8b' +ZSH_HIGHLIGHT_STYLES[argument-base]='fg=#eceff4' +ZSH_HIGHLIGHT_STYLES[dollar-parameter]='fg=#b48ead' +ZSH_HIGHLIGHT_STYLES[dollar-parameter_extension]='fg=#b48ead' +ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#a3be8c' +ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#a3be8c' +ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#a3be8c' +ZSH_HIGHLIGHT_STYLES[comment]='fg=#4c566a,italic' +ZSH_HIGHLIGHT_STYLES[globbing]='fg=#ebcb8b' +ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=#d08770' + +zstyle ':fzf-tab:*' fzf-flags \ + --color=fg:#eceff4,bg:#2e3440,fg+:#eceff4,bg+:#3b4252 \ + --color=hl:#88c0d0,hl+:#88c0d0,info:#b48ead,prompt:#81a1c1 \ + --color=pointer:#88c0d0,marker:#a3be8c,spinner:#88c0d0,header:#5e81ac \ + --color=border:#4c566a,label:#8fbcbb,query:#eceff4 \ + --color=preview-fg:#eceff4,preview-bg:#2e3440,preview-border:#4c566a,preview-label:#8fbcbb \ + --color=gutter:#3b4252,separator:#434c5e,scrollbar:#5e81ac,preview-scrollbar:#5e81ac \ + --color=disabled:#4c566a \ + --prompt='❯ ' + +export FZF_DEFAULT_OPTS=" + --color=fg:#eceff4,bg:#2e3440,fg+:#eceff4,bg+:#3b4252 + --color=hl:#88c0d0,hl+:#88c0d0,info:#b48ead,prompt:#81a1c1 + --color=pointer:#88c0d0,marker:#a3be8c,spinner:#88c0d0,header:#5e81ac + --color=border:#4c566a,label:#8fbcbb,query:#eceff4 + --color=preview-fg:#eceff4,preview-bg:#2e3440,preview-border:#4c566a,preview-label:#8fbcbb + --color=gutter:#3b4252,separator:#434c5e,scrollbar:#5e81ac,preview-scrollbar:#5e81ac + --color=disabled:#4c566a + --prompt='❯ ' +" + +prompt_symbol() { + if [[ "$KEYMAP" == "vicmd" ]]; then + print -r -- "%F{#ebcb8b}❮%f" + else + print -r -- "%F{#88c0d0}❯%f" + fi +} + +update_vim_prompt() { + zle reset-prompt +} + +zle-keymap-select() { + update_vim_prompt +} + +zle-line-init() { + zle -K viins + update_vim_prompt +} + +git_prompt_info() { + local branch + local staged + local modified + local untracked + local deleted + local conflicts + local git_state="" + + branch=$(git branch --show-current 2>/dev/null) + + # Не Git-репозиторий + if [[ -z "$branch" ]]; then + return + fi + + staged=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ') + modified=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ') + untracked=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ') + deleted=$(git diff --name-only --diff-filter=D 2>/dev/null | wc -l | tr -d ' ') + conflicts=$(git diff --name-only --diff-filter=U 2>/dev/null | wc -l | tr -d ' ') + + if (( staged > 0 )); then + git_state+=" %F{#b48ead} ${staged}%f" + fi + if (( modified > 0 )); then + git_state+=" %F{#ebcb8b} ${modified}%f" + fi + if (( untracked > 0 )); then + git_state+=" %F{#a3be8c} ${untracked}%f" + fi + if (( deleted > 0 )); then + git_state+=" %F{#d08770} ${deleted}%f" + fi + if (( conflicts > 0 )); then + git_state+=" %F{#bf616a}󱈸 ${conflicts}%f" + fi + if [[ -n "$git_state" ]]; then + git_state=" %F{#88c0d0}❯%f${git_state}" + fi + + print -r -- " %F{#88c0d0}❯%f %F{#8fbcbb}󰘬 ${branch}%f${git_state}" +} + +precmd() { + local exit_code=$? + + if (( exit_code != 0 )); then + PROMPT_EXIT="%F{#941703}✗${exit_code}%f " + else + PROMPT_EXIT="" + fi +} + +nvim() { + printf '\033]0;nvim\007' + command nvim "$@" + printf '\033]0;foot\007' +} + +zle -N zle-keymap-select +zle -N zle-line-init + +PROMPT='%F{#eceff4}%~%f$(git_prompt_info) $(prompt_symbol) ' + +vi() { nvim "$@"; } +vim() { nvim "$@"; } +alias rmpc='printf "\033]0;rmpc\007"; command rmpc; printf "\033]0;foot\007"' diff --git a/README.md b/README.md new file mode 100644 index 0000000..b21d57a --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# dotfiles-sway + +Конфиги для Sway. + +## Стек + +|Компонент|Технология|Описание| +|---|---|---| +|Тайлинговый менеджер|sway|i3-совместимый Wayland-компоузер. Раскладка окон, workspaces, бинды, запуск через uwsm| +||swayidle|Демон бездействия: блокирует экран, гасит мониторы (dpms off) и уходит в suspend по таймаутам; блокирует перед сном и включает мониторы после пробуждения| +||swaylock|Экран блокировки для Wayland/sway, с Nord-темой и индикатором ввода пароля| +||swaddle|MPRIS-инхибитор автоблокировки/сна — не даёт экрану гаснуть во время прослушивания музыки или просмотра видео| +||waybar|Панель статуса: workspaces, clock, tray, idle-inhibitor, CPU/RAM/temp/disk, аудио-виджеты (wireplumber), кастомный лаунчер rofi| +||rofi|Лаунчер приложений/меню: drun, кастомные моды по категориям (internet/dev/games/system/media), буфер обмена, история уведомлений, Bitwarden (rofi-rbw)| +|Музыка|mpd|Music Player Daemon — бэкенд воспроизведения музыки (вывод через pipewire, replaygain по альбому)| +||mpc|Консольный клиент-обёртка для MPD (control через CLI/скрипты — быстрые команды типа play/pause/next без запуска TUI)| +||rmpc|TUI-клиент для MPD: очередь, браузер библиотеки, поиск, кастомный статус-бар (артист/альбом/трек/битрейт), Nord-тема| +|Видео|mpv|Плеер с рендером через Vulkan (gpu-context=waylandvk, gpu-api=vulkan) и аппаратным декодированием на nvidia (hwdec=nvdec)| +|Редактор|nvim|Neovim на lazy.nvim: цветовая схема nord, colorizer, render-markdown, nvim-tree, treesitter, gitsigns, bufferline| +|Терминал|foot|Лёгкий Wayland-нативный терминал; шрифт Noto Mono, тёмная тема| +|Уведомления|dunst|Демон уведомлений: позиция top-right, прогресс-бары, история (sticky), Nord-цвета по уровню важности| +|Шелл|zsh|Интерактивный shell: vi-режим (bindkey -v), общая история между сессиями (SHARE_HISTORY), автодополнение через compinit, fzf (включая fzf-tab и fzf-tab-completion), подсветка синтаксиса (zsh-syntax-highlighting) и автоподсказки (zsh-autosuggestions) — всё в Nord-палитре. Кастомный promt с vi-режимом (❯/❮ в зависимости от normal/insert), git-статус в промпте (staged/modified/untracked/deleted/conflicts с иконками) и код выхода последней команды. Обёртки nvim/vi/vim и алиас rmpc меняют заголовок окна терминала (\033]0;...\007) на время работы программы| +|Сессия|uwsm|Universal Wayland Session Manager — запускает sway как systemd-managed сессию, пробрасывает переменные окружения (WAYLAND_DISPLAY, SWAYSOCK, Qt-тема, ssh-agent через rbw)| +|Логин-менеджер|ly|TUI display manager: анимация matrix, большие часы (bigclock=en), пустой пароль запрещён (allow_empty_password=false). Хоткеи: F1 shutdown, F2 restart, F3 suspend (systemctl suspend), F7 показать пароль; brightness/hibernate скрыты (*_key = null)| + +## Зависимости + +```bash +sudo pacman -S \ + zsh \ + zsh-syntax-highlighting \ + zsh-autosuggestions \ + pipewire \ + wireplumber \ + wl-clipboard \ + noto-fonts \ + noto-fonts-cjk \ + noto-fonts-emoji \ + ttf-nerd-fonts-symbols-mono \ + sway \ + swaylock \ + swayidle \ + swaybg \ + slurp \ + grim \ + rbw \ + rofi \ + rofi-rbw \ + python-systemd \ + python-i3ipc \ + rmpc \ + neovim \ + mpv \ + mpc \ + mpd \ + mpd-mpris \ + playerctl \ + xorg-xwayland \ + xdg-utils \ + xdg-desktop-portal-gtk \ + xdg-desktop-portal-wlr \ + xorg-xwayland \ + xorg-xrandr \ + polkit \ + ly \ + jq \ + git \ + qt6ct \ + fzf \ + foot \ + dunst \ + uwsm \ + mc \ + imv +``` + +```bash +yay -S \ + swaddle \ + clipvault \ + pinentry-rofi +``` + +## Деплой + +```bash +ln -s ./.zsh ~/.config/.zsh +ln -s ./.zshrc ~/.zshrc +ln -s ./dunst ~/.config/dunst +ln -s ./foot ~/.config/foot +ln -s ./uwsm ~/.config/uwsm +ln -s ./systemd/user/* ~/.config/systemd/user/ +ln -s ./sway ~/.config/sway +ln -s ./swaylock ~/.config/swaylock +ln -s ./swaddle ~/.config/swaddle +ln -s ./rofi ~/.config/rofi +ln -s ./rmpc ~/.config/rmpc +ln -s ./nvim ~/.config/nvim +ln -s ./mpv ~/.config/mpv +ln -s ./mpd ~/.config/mpd +``` + +```bash +mkdir -p ~/.zsh/plugins +git clone https://github.com/Aloxaf/fzf-tab ~/.zsh/plugins/fzf-tab +git clone https://github.com/lincheney/fzf-tab-completion ~/.zsh/plugins/fzf-tab-completion +``` + +```bash +chsh -s /usr/bin/zsh +``` + +```bash +sudo cp ./other/sway-uwsm.desktop /usr/share/wayland-sessions/ +sudo cp ./other/ly.ini /etc/ly/config.ini +``` + +```bash +mkdir -p ~/.local/bin/ +ln -s ./other/pinentry-rofi-wrapper ~/.local/bin/pinentry-rofi-wrapper +``` + +```bash +rbw config set email $email +rbw config set base_url $url +rbw config set pinentry $pinentry_rofi_wrapper +``` + +## Скриншоты + +

+ waybar/rofi/dunst +
+ waybar / rofi / dunst +

+ +

+ rmpc +
+ rmpc +

+ +

+ nvim +
+ nvim +

+ +

+ zsh/fzf +
+ zsh / fzf +

\ No newline at end of file diff --git a/assets/photo_2026-08-11_08-01-47.jpg b/assets/photo_2026-08-11_08-01-47.jpg new file mode 100644 index 0000000..06d53a9 Binary files /dev/null and b/assets/photo_2026-08-11_08-01-47.jpg differ diff --git a/assets/photo_2026-08-11_08-01-57.jpg b/assets/photo_2026-08-11_08-01-57.jpg new file mode 100644 index 0000000..cbbb5d7 Binary files /dev/null and b/assets/photo_2026-08-11_08-01-57.jpg differ diff --git a/assets/photo_2026-08-11_08-02-00.jpg b/assets/photo_2026-08-11_08-02-00.jpg new file mode 100644 index 0000000..99264a5 Binary files /dev/null and b/assets/photo_2026-08-11_08-02-00.jpg differ diff --git a/assets/photo_2026-08-11_08-02-03.jpg b/assets/photo_2026-08-11_08-02-03.jpg new file mode 100644 index 0000000..bc03ce7 Binary files /dev/null and b/assets/photo_2026-08-11_08-02-03.jpg differ diff --git a/dunst/dunstrc b/dunst/dunstrc new file mode 100644 index 0000000..3f7a3cd --- /dev/null +++ b/dunst/dunstrc @@ -0,0 +1,82 @@ +[global] + monitor = 0 + follow = mouse + origin = top-right + offset = 5x5 + scale = 0 + + width = 340 + height = 300 + notification_limit = 5 + + progress_bar = true + progress_bar_height = 10 + progress_bar_frame_width = 0 + progress_bar_min_width = 150 + progress_bar_max_width = 300 + progress_bar_corner_radius = 0 + + indicate_hidden = yes + separator_height = 0 + padding = 10 + horizontal_padding = 10 + text_icon_padding = 10 + frame_width = 0 + separator_color = frame + sort = yes + idle_threshold = 120 + gap_size = 5 + + font = Noto Mono 10 + line_height = 0 + markup = full + format = "%s\n%b" + alignment = left + vertical_alignment = center + show_age_threshold = 60 + ellipsize = middle + ignore_newline = no + stack_duplicates = true + hide_duplicate_count = false + show_indicators = yes + + icon_position = left + min_icon_size = 32 + max_icon_size = 48 + + sticky_history = yes + history_length = 20 + + dmenu = rofi -dmenu -p dunst + browser = xdg-open + + always_run_script = true + title = Dunst + class = Dunst + corner_radius = 0 + ignore_dbusclose = false + + force_xwayland = false + force_xinerama = false + + mouse_left_click = close_current + mouse_middle_click = do_action, close_current + mouse_right_click = close_all + +[experimental] + per_monitor_dpi = false + +[urgency_low] + background = "#2e3440" + foreground = "#eceff4" + timeout = 2 + +[urgency_normal] + background = "#2e3440" + foreground = "#eceff4" + timeout = 8 + +[urgency_critical] + background = "#2e3440" + foreground = "#bf616a" + timeout = 0 diff --git a/foot/foot.ini b/foot/foot.ini new file mode 100644 index 0000000..6d3c920 --- /dev/null +++ b/foot/foot.ini @@ -0,0 +1,11 @@ +[main] +font=Noto Mono:size=10 +dpi-aware=no +pad=0x0 + +[colors-dark] +background=2e3440 + +[key-bindings] +clipboard-copy=Control+Shift+c +clipboard-paste=Control+Shift+v diff --git a/mpd/mpd.conf b/mpd/mpd.conf new file mode 100644 index 0000000..4010ec8 --- /dev/null +++ b/mpd/mpd.conf @@ -0,0 +1,16 @@ +music_directory "/mnt/media1/music" +playlist_directory "~/.config/mpd/playlists" +db_file "~/.config/mpd/database" +log_file "~/.config/mpd/log" +pid_file "~/.config/mpd/pid" +state_file "~/.config/mpd/state" +sticker_file "~/.config/mpd/sticker.sql" +bind_to_address "~/.config/mpd/socket" +bind_to_address "127.0.0.1" +port "6600" +auto_update "yes" +audio_output { + type "pipewire" + name "MPD PipeWire Output" +} +replaygain "album" diff --git a/mpv/mpv.conf b/mpv/mpv.conf new file mode 100644 index 0000000..1acc522 --- /dev/null +++ b/mpv/mpv.conf @@ -0,0 +1,3 @@ +gpu-context=waylandvk +gpu-api=vulkan +hwdec=nvdec diff --git a/nvim/init.lua b/nvim/init.lua new file mode 100644 index 0000000..18edd0f --- /dev/null +++ b/nvim/init.lua @@ -0,0 +1,141 @@ +require('core.configs') + +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", + lazypath, + }) +end +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + { + "shaunsingh/nord.nvim", + lazy = false, + priority = 1000, + config = function() + vim.cmd("colorscheme nord") + end, + }, + { + "NvChad/nvim-colorizer.lua", + opts = { + filetypes = { "*" }, + user_default_options = { + RGB = true, + RRGGBB = true, + names = false, + RRGGBBAA = true, + css = true, + mode = "background", + }, + }, + }, + { + "MeanderingProgrammer/render-markdown.nvim", + dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-tree/nvim-web-devicons" }, + ft = { "markdown" }, + opts = {}, + }, + { + "nvim-tree/nvim-tree.lua", + version = "*", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("nvim-tree").setup({ + view = { side = "left", width = 30 }, + }) + end, + }, + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + config = function() + local status_ok, configs = pcall(require, "nvim-treesitter.configs") + if not status_ok then + return + end + + configs.setup({ + ensure_installed = { "python", "bash", "lua", "vim", "markdown" }, + highlight = { enable = true }, + }) + end, + }, + { + "lewis6991/gitsigns.nvim", + opts = {}, + }, + { + "akinsho/bufferline.nvim", + version = "*", + dependencies = "nvim-tree/nvim-web-devicons", + config = function() + require("bufferline").setup({ + options = { + mode = "buffers", + separator_style = "thick", + diagnostics = "nvim_lsp", + enforce_regular_tabs = true, + tab_size = 14, + }, + highlights = { + fill = { + bg = "#2e3440", + }, + background = { + bg = "#2e3440", + fg = "#eceff4", + }, + buffer_selected = { + bg = "#3b4252", + fg = "#eceff4", + bold = false, + italic = false, + }, + close_button_selected = { + bg = "#3b4252", + fg = "#eceff4", + }, + close_button = { + bg = "#2e3440", + fg = "#4c566a", + }, + close_button_visible = { + bg = "#2e3440", + fg = "#4c566a", + }, + separator = { + bg = "#2e3440", + fg = "#2e3440", + }, + separator_selected = { + bg = "#3b4252", + fg = "#3b4252", + }, + indicator_selected = { + bg = "#3b4252", + fg = "#88c0d0", + }, + modified_selected = { + bg = "#3b4252", + fg = "#a3be8c", + }, + modified = { + bg = "#2e3440", + fg = "#4c566a", + }, + modified_visible = { + bg = "#2e3440", + fg = "#4c566a", + }, + }, + }) + end, + }, +}) diff --git a/nvim/lazy-lock.json b/nvim/lazy-lock.json new file mode 100644 index 0000000..0bec64a --- /dev/null +++ b/nvim/lazy-lock.json @@ -0,0 +1,11 @@ +{ + "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, + "gitsigns.nvim": { "branch": "main", "commit": "31d6fb2d618bca1482b9f274751ead5f03461408" }, + "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, + "nord.nvim": { "branch": "master", "commit": "80c1e5321505aeb22b7a9f23eb82f1e193c12470" }, + "nvim-colorizer.lua": { "branch": "master", "commit": "72a05f62c52241bc7441c820eb53946f92b2e6a4" }, + "nvim-tree.lua": { "branch": "master", "commit": "531b807b8f0d6f75016a0ee1e0cd5ce2086e9d95" }, + "nvim-treesitter": { "branch": "main", "commit": "c9f9ed6c1892f629ea399f4ee7905f2686fa13f2" }, + "nvim-web-devicons": { "branch": "master", "commit": "2ae6958df7ced50baac5035cec0c15799eedfbf7" }, + "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" } +} diff --git a/nvim/lua/core/configs.lua b/nvim/lua/core/configs.lua new file mode 100644 index 0000000..d992355 --- /dev/null +++ b/nvim/lua/core/configs.lua @@ -0,0 +1,6 @@ +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.number = true +vim.opt.relativenumber = true +vim.o.termguicolors = true +vim.cmd("syntax enable") diff --git a/nvim/lua/core/keymaps.lua b/nvim/lua/core/keymaps.lua new file mode 100644 index 0000000..0a717de --- /dev/null +++ b/nvim/lua/core/keymaps.lua @@ -0,0 +1,4 @@ +vim.keymap.set('n', 'e', ':NvimTreeToggle') +vim.keymap.set('n', '', ':BufferLineCycleNext') +vim.keymap.set('n', '', ':BufferLineCyclePrev') +vim.keymap.set('n', 'x', ':bdelete') diff --git a/other/ly.ini b/other/ly.ini new file mode 100644 index 0000000..40a631c --- /dev/null +++ b/other/ly.ini @@ -0,0 +1,422 @@ +# Ly supports 24-bit true color with styling, which means each color is a 32-bit value. +# The format is 0xSSRRGGBB, where SS is the styling, RR is red, GG is green, and BB is blue. +# Here are the possible styling options: +# TB_BOLD 0x01000000 +# TB_UNDERLINE 0x02000000 +# TB_REVERSE 0x04000000 +# TB_ITALIC 0x08000000 +# TB_BLINK 0x10000000 +# TB_HI_BLACK 0x20000000 +# TB_BRIGHT 0x40000000 +# TB_DIM 0x80000000 +# Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's +# configuration doesn't support using it, you have to manually compute the color value. +# Note that, if you want to use the default color value of the terminal, you can use the +# special value 0x00000000. This means that, if you want to use black, you *must* use +# the styling option TB_HI_BLACK (the RGB values are ignored when using this option). + +# Allow empty password or not when authenticating +allow_empty_password = false + +# The active animation +# none -> Nothing +# doom -> PSX DOOM fire +# matrix -> CMatrix +# colormix -> Color mixing shader +# gameoflife -> John Conway's Game of Life +# dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master) +animation = matrix + +# Delay between each animation frame in milliseconds +animation_frame_delay = 5 + +# Stop the animation after some time +# 0 -> Run forever +# 1..2e12 -> Stop the animation after this many seconds +animation_timeout_sec = 0 + +# The character used to mask the password +# You can either type it directly as a UTF-8 character (like *), or use a UTF-32 +# codepoint (for example 0x2022 for a bullet point) +# If null, the password will be hidden +# Note: you can use a # by escaping it like so: \# +asterisk = * + +# The number of failed authentications before a special animation is played... ;) +# If set to 0, the animation will never be played +auth_fails = 10 + +# Identifier for battery whose charge to display at top left +# Primary battery is usually BAT0 or BAT1 +# If set to null, battery status won't be shown +battery_id = null + +# Automatic login configuration +# This feature allows Ly to automatically log in a user without password prompt. +# IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work. +# Autologin only happens once at startup - it won't re-trigger after logout. + +# PAM service name to use for automatic login +# The default service (ly-autologin) uses pam_permit to allow login without password +# The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically +auto_login_service = ly-autologin + +# Session name to launch automatically +# To find available session names, check the .desktop files in: +# - /usr/share/xsessions/ (for X11 sessions) +# - /usr/share/wayland-sessions/ (for Wayland sessions) +# Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field +# Examples: "i3", "sway", "gnome", "plasma", "xfce" +# If null, automatic login is disabled +auto_login_session = null + +# Username to automatically log in +# Must be a valid user on the system +# If null, automatic login is disabled +auto_login_user = null + +# Background color id +bg = 0x00000000 + +# Change the state and language of the big clock +# none -> Disabled (default) +# en -> English +# fa -> Farsi +bigclock = en + +# Set bigclock to 12-hour notation. +bigclock_12hr = false + +# Set bigclock to show the seconds. +bigclock_seconds = false + +# Blank main box background +# Setting to false will make it transparent +blank_box = true + +# Border foreground color id +border_fg = 0x00FFFFFF + +# Title to show at the top of the main box +# If set to null, none will be shown +box_title = null + +# Brightness decrease command +brightness_down_cmd = /usr/bin/brightnessctl -q -n s 10%- + +# Brightness decrease key combination, or null to disable +brightness_down_key = null + +# Brightness increase command +brightness_up_cmd = /usr/bin/brightnessctl -q -n s +10% + +# Brightness increase key combination, or null to disable +brightness_up_key = null + +# Erase password input on failure +clear_password = false + +# Format string for clock in top right corner (see strftime specification). Example: %c +# If null, the clock won't be shown +clock = null + +# CMatrix animation foreground color id +cmatrix_fg = 0x0000FF00 + +# CMatrix animation character string head color id +cmatrix_head_col = 0x01FFFFFF + +# CMatrix animation minimum codepoint. It uses a 16-bit integer +# For Japanese characters for example, you can use 0x3000 here +cmatrix_min_codepoint = 0x21 + +# CMatrix animation maximum codepoint. It uses a 16-bit integer +# For Japanese characters for example, you can use 0x30FF here +cmatrix_max_codepoint = 0x7B + +# Color mixing animation first color id +colormix_col1 = 0x00FF0000 + +# Color mixing animation second color id +colormix_col2 = 0x000000FF + +# Color mixing animation third color id +colormix_col3 = 0x20000000 + +# For custom binds: the horizontal limit in characters for each +# line of custom binds before moving on to the next. +# If null, defaults to the width of the terminal instead. +custom_bind_width = null + +# Custom sessions directory +# You can specify multiple directories, +# e.g. /etc/ly/custom-sessions:/usr/share/custom-sessions +custom_sessions = /etc/ly/custom-sessions + +# Input box active by default on startup +# Available inputs: info_line, session, login, password +default_input = login + +# DOOM animation fire height (1 thru 9) +doom_fire_height = 6 + +# DOOM animation fire spread (0 thru 4) +doom_fire_spread = 2 + +# DOOM animation custom top color (low intensity flames) +doom_top_color = 0x009F2707 + +# DOOM animation custom middle color (medium intensity flames) +doom_middle_color = 0x00C78F17 + +# DOOM animation custom bottom color (high intensity flames) +doom_bottom_color = 0x00FFFFFF + +# Dur file path +dur_file_path = /etc/ly/example.dur + +# Dur file alignment +# The dur file can be aligned with a direction and centered easily with the flags below +# Available inputs: topleft, topcenter, topright, centerleft, center, centerright, bottomleft, bottomcenter, bottomright +dur_offset_alignment = center + +# Dur offset x direction (value is added to the current position determined by alignment, negatives are supported) +dur_x_offset = 0 + +# Dur offset y direction (value is added to the current position determined by alignment, negatives are supported) +dur_y_offset = 0 + +# Set margin to the edges of the DM (useful for curved monitors) +edge_margin = 0 + +# Error background color id +error_bg = 0x00000000 + +# Error foreground color id +# Default is red and bold +error_fg = 0x01FF0000 + +# Foreground color id +fg = 0x00FFFFFF + +# Render true colors (if supported) +# If false, output will be in eight-color mode +# All eight-color mode color codes: +# TB_DEFAULT 0x0000 +# TB_BLACK 0x0001 +# TB_RED 0x0002 +# TB_GREEN 0x0003 +# TB_YELLOW 0x0004 +# TB_BLUE 0x0005 +# TB_MAGENTA 0x0006 +# TB_CYAN 0x0007 +# TB_WHITE 0x0008 +# If full color is off, the styling options still work. The colors are +# always 32-bit values with the styling in the most significant byte. +# Note: If using the dur_file animation option and the dur file's color range +# is saved as 256 with this option disabled, the file will not be drawn. +full_color = true + +# Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations) +# 0 -> Pure Conway's Game of Life (will eventually stabilize) +# 10 -> Add entropy every 10 generations (recommended for continuous activity) +# 50+ -> Less frequent entropy for more natural evolution +gameoflife_entropy_interval = 10 + +# Game of Life animation foreground color id +gameoflife_fg = 0x0000FF00 + +# Game of Life frame delay (lower = faster animation, higher = slower) +# 1-3 -> Very fast animation +# 6 -> Default smooth animation speed +# 10+ -> Slower, more contemplative speed +gameoflife_frame_delay = 6 + +# Game of Life initial cell density (0.0 to 1.0) +# 0.1 -> Sparse, minimal activity +# 0.4 -> Balanced activity (recommended) +# 0.7+ -> Dense, chaotic patterns +gameoflife_initial_density = 0.4 + +# Command executed when pressing hibernate key (can be null) +hibernate_cmd = null + +# Specifies the key combination used for hibernate +hibernate_key = null + +# Remove main box borders +hide_borders = false + +# Remove power management command hints +hide_key_hints = false + +# Remove keyboard lock states from the top right corner +hide_keyboard_locks = false + +# Remove version number from the top left corner +hide_version_string = false + +# Command executed when no input is detected for a certain time +# If null, no command will be executed +inactivity_cmd = null + +# Executes a command after a certain amount of seconds +inactivity_delay = 0 + +# Initial text to show on the info line +# If set to null, the info line defaults to the hostname +initial_info_text = null + +# Input boxes length +input_len = 34 + +# Active language +# Available languages are found in /etc/ly/lang/ +lang = en + +# Command executed when logging in +# If null, no command will be executed +# Important: the code itself must end with `exec "$@"` in order to launch the session! +# You can also set environment variables in there, they'll persist until logout +login_cmd = null + +# Path for login.defs file (used for listing all local users on the system on +# Linux) +login_defs_path = /etc/login.defs + +# Command executed when logging out +# If null, no command will be executed +# Important: the session will already be terminated when this command is executed, so +# no need to add `exec "$@"` at the end +logout_cmd = null + +# General log file path +ly_log = /var/log/ly.log + +# Main box horizontal margin +margin_box_h = 2 + +# Main box vertical margin +margin_box_v = 1 + +# Set numlock on/off at startup +numlock = false + +# Default path +# If null, ly doesn't set a path +path = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +# Command executed when pressing restart_key +restart_cmd = /sbin/shutdown -r now + +# Specifies the key combination used for restart +restart_key = F2 + +# Save the current desktop and login as defaults, and load them on startup +save = true + +# Service name (set to ly to use the provided pam config file) +service_name = ly + +# Session log file path +# This will contain stdout and stderr of Wayland sessions +# By default it's saved in the user's home directory +# Important: due to technical limitations, X11, shell sessions as well as +# launching session via KMSCON aren't supported, which means you won't get any +# logs from those sessions. +# If null, no session log will be created +session_log = .local/state/ly-session.log + +# Setup command +setup_cmd = /etc/ly/setup.sh + +# Show the shell session in the session list +# If false, the shell session will be hidden +shell = true + +# Specifies the key combination used for showing the password +show_password_key = F7 + +# Display the active TTY number (e.g. tty3) to the right of the clock in the top right corner +# If the clock is disabled, the TTY label occupies the top right corner on its own +# If false, the TTY number will not be shown +show_tty = false + +# Command executed when pressing shutdown_key +shutdown_cmd = /sbin/shutdown -a now + +# Specifies the key combination used for shutdown +shutdown_key = F1 + +# Command executed when pressing sleep key (can be null) +sleep_cmd = systemctl suspend + +# Specifies the key combination used for sleep +sleep_key = F3 + +# Command executed when starting Ly (before the TTY is taken control of) +# See file at path below for an example of changing the default TTY colors +start_cmd = /etc/ly/startup.sh + +# Center the session name. +text_in_center = false + +# Default vi mode +# normal -> normal mode +# insert -> insert mode +vi_default_mode = normal + +# Enable vi keybindings +vi_mode = false + +# Wayland desktop environments +# You can specify multiple directories, +# e.g. /usr/share/wayland-sessions:/usr/local/share/wayland-sessions +# If null, Wayland sessions will not be shown +waylandsessions = /usr/share/wayland-sessions + +# Xorg server command +# Add the -quiet argument to hide startup logs from the server +x_cmd = /usr/bin/X + +# Xorg virtual terminal number +# Mostly useful for FreeBSD where choosing the current TTY causes issues +# If null, the current TTY will be chosen +x_vt = null + +# Xorg xauthority edition tool +xauth_cmd = /usr/bin/xauth + +# xinitrc +# If null, the xinitrc session will be hidden +xinitrc = ~/.xinitrc + +# Xorg desktop environments +# You can specify multiple directories, +# e.g. /usr/share/xsessions:/usr/local/share/xsessions +# If null, X11 sessions will not be shown +xsessions = /usr/share/xsessions + +# Custom Commands and Labels: +# The following examples below give an outline for setting up custom commands and labels. +# Unless specified as optional, an option is mandatory. + +# Comments preceding with '##' are for documentation. +# Comments preceding with '#' comment out the example INI. + +## Declare a command with the F8 binding. +#[cmd:F8] +## The name of the command to show up in Ly. +## Note: "$" in "$brightness_up" fetches the appropriate string from the specified locale file +## and is replaced with the value representing "brightness_up". +## You can see the list of keys in any locale file in /etc/ly/lang. +#cmd = touch /tmp/ly.gaming +#name = custom command $brightness_up + +## Declare a label with an ID. This ID should be unique across all labels. +#[lbl:kernel] +#cmd = uname -srn +## Optional, defaulting to 0. +## In frames, the time to re-run the command and update the label. +## If 0, only run once and do not refresh afterwards +#refresh = 0 diff --git a/other/pinentry-rofi-wrapper b/other/pinentry-rofi-wrapper new file mode 100755 index 0000000..150dcd7 --- /dev/null +++ b/other/pinentry-rofi-wrapper @@ -0,0 +1,4 @@ +#!/bin/sh +exec pinentry-rofi -- \ + -theme "$HOME/.config/rofi/password.rasi" \ + "$@" diff --git a/other/sway-uwsm.desktop b/other/sway-uwsm.desktop new file mode 100644 index 0000000..93f4e9a --- /dev/null +++ b/other/sway-uwsm.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Sway (uwsm-managed) +Comment=An i3-compatible Wayland compositor +Exec=uwsm start -e -D sway sway.desktop +TryExec=uwsm +DesktopNames=sway +Type=Application diff --git a/rmpc/config.ron b/rmpc/config.ron new file mode 100644 index 0000000..0c80760 --- /dev/null +++ b/rmpc/config.ron @@ -0,0 +1,253 @@ +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +( + address: "127.0.0.1:6600", + password: None, + theme: "def", + cache_dir: None, + on_song_change: None, + volume_step: 5, + max_fps: 30, + scrolloff: 0, + wrap_navigation: false, + enable_mouse: true, + scroll_amount: 1, + enable_config_hot_reload: true, + enable_lyrics_hot_reload: false, + status_update_interval_ms: 1000, + rewind_to_start_sec: None, + keep_state_on_song_change: true, + reflect_changes_to_playlist: false, + select_current_song_on_change: false, + ignore_leading_the: true, + browser_song_sort: [Disc, Track, Artist, Title], + directories_sort: SortFormat(group_by_type: true, reverse: false), + auto_open_downloads: true, + album_art: ( + method: Auto, + max_size_px: (width: 1200, height: 1200), + disabled_protocols: ["http://", "https://"], + vertical_align: Center, + horizontal_align: Center, + ), + keybinds: ( + global: { + "q": Quit, + "?": ShowHelp, + ":": CommandMode, + "oI": ShowCurrentSongInfo, + "oo": ShowOutputs, + "op": ShowDecoders, + "od": ShowDownloads, + "oP": Partition(), + "z": ToggleRepeat, + "x": ToggleRandom, + "c": ToggleConsume, + "v": ToggleSingle, + "p": TogglePause, + "s": Stop, + ">": NextTrack, + "<": PreviousTrack, + "f": SeekForward, + "b": SeekBack, + ".": VolumeUp, + ",": VolumeDown, + "": NextTab, + "gt": NextTab, + "": PreviousTab, + "gT": PreviousTab, + "1": SwitchToTab("Queue"), + "2": SwitchToTab("Directories"), + //"3": SwitchToTab("Artists"), + "3": SwitchToTab("Album Artists"), + //"5": SwitchToTab("Albums"), + "4": SwitchToTab("Playlists"), + "5": SwitchToTab("Search"), + "": Update, + "": Rescan, + "R": AddRandom, + }, + navigation: { + "": Close, + "": Close, + "": Confirm, + "k": Up, + "": Up, + "j": Down, + "": Down, + "h": Left, + "": Left, + "l": Right, + "": Right, + "k": PaneUp, + "": PaneUp, + "j": PaneDown, + "": PaneDown, + "h": PaneLeft, + "": PaneLeft, + "l": PaneRight, + "": PaneRight, + "K": MoveUp, + "J": MoveDown, + "": UpHalf, + "": DownHalf, + "": PageUp, + "": PageUp, + "": PageDown, + "": PageDown, + "gg": Top, + "G": Bottom, + "": Select, + "": InvertSelection, + "/": EnterSearch, + "n": NextResult, + "N": PreviousResult, + "a": Add, + "A": AddAll, + "D": Delete, + "": Rename, + "i": FocusInput, + "oi": ShowInfo, + "": ContextMenu(), + "s": Save(kind: Modal(all: false, duplicates_strategy: Ask)), + "a": Save(kind: Modal(all: true, duplicates_strategy: Ask)), + "r": Rate(), + }, + queue: { + "d": Delete, + "D": DeleteAll, + "": Play, + "C": JumpToCurrent, + "X": Shuffle, + }, + ), + search: ( + case_sensitive: false, + ignore_diacritics: false, + search_button: true, + mode: Contains, + tags: [ + (value: "any", label: "any tag"), + (value: "artist", label: "artist"), + (value: "album", label: "album"), + (value: "albumartist", label: "album artist"), + (value: "title", label: "title"), + (value: "filename", label: "filename"), + (value: "genre", label: "genre"), + ], + ), + artists: ( + album_display_mode: SplitByDate, + album_sort_by: Date, + album_date_tags: [Date], + ), + tabs: [ + ( + name: "queue", + pane: Split( + direction: Vertical, + panes: [ + ( + size: "100%", + pane: Split( + direction: Vertical, + panes: [ + ( + size: "1", + //borders: "ALL", + //border_symbols: Inherited(parent: Rounded, bottom_left: "├", bottom_right: "┤",), + pane: Split( + direction: Horizontal, + panes: [ + /*( + size: "1", + pane: Pane(Empty()) + ),*/ + ( + size: "100%", + pane: Pane(QueueHeader()) + ), + ] + ) + ), + ( + size: "100%", + //borders: "LEFT | RIGHT | BOTTOM", + //border_symbols: Rounded, + pane: Split( + direction: Horizontal, + panes: [ + /*( + size: "1", + pane: Pane(Empty()) + ),*/ + ( + size: "100%", + pane: Pane(Queue) + ), + ] + ) + ), + ], + ) + ), + ( + size: "8", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "19", + //borders: "", //LEFT | RIGHT | TOP", + //border_symbols: Rounded, + pane: Pane(AlbumArt) + ), + ( + size: "100%", + //borders: "", //ALL", + //border_symbols: Inherited(parent: Rounded, top_left: "├", top_right: "┤",), + //border_title: [(kind: Text(" Lyrics "))], + //border_title_alignment: Right, + pane: Component("column1"), + ), + ], + ), + ), +], + ), + ), + ( + name: "directories", + pane: Split( + size: "100%", + direction: Vertical, + panes: [(pane: Pane(Directories), size: "100%")] //, borders: "ALL", border_symbols: Rounded)], + ) + ), + ( + name: "album artists", + pane: Split( + size: "100%", + direction: Vertical, + panes: [(pane: Pane(AlbumArtists), size: "100%")] //, borders: "ALL", border_symbols: Rounded)], + ) + ), + ( + name: "playlists", + pane: Split( + size: "100%", + direction: Vertical, + panes: [(pane: Pane(Playlists), size: "100%")] //, borders: "ALL", border_symbols: Rounded)], + ) + ), + ( + name: "search", + pane: Split( + size: "100%", + direction: Vertical, + panes: [(pane: Pane(Search), size: "100%")] //, borders: "ALL", border_symbols: Rounded)], + ) + ), + ], +) diff --git a/rmpc/def.ron b/rmpc/def.ron new file mode 100644 index 0000000..af1850b --- /dev/null +++ b/rmpc/def.ron @@ -0,0 +1,311 @@ +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +( + default_album_art_path: None, + format_tag_separator: " | ", + browser_column_widths: [0,30,70], + background_color: None, + text_color: None, + header_background_color: None, + modal_background_color: None, + modal_backdrop: false, + preview_label_style: (fg: "#88c0d0"), + preview_metadata_group_style: (fg: "#88c0d0", modifiers: "Bold"), + highlighted_item_style: (fg: "#88c0d0", modifiers: "Bold"), + current_item_style: (fg: "#2e3440", bg: "#88c0d0", modifiers: "Bold"), + borders_style: (fg: "#88c0d0"), + highlight_border_style: (fg: "#88c0d0"), + symbols: ( + song: "S", + dir: "D", + playlist: "P", + marker: "M", + ellipsis: "...", + song_style: (fg: "#88c0d0"), + dir_style: (fg: "#ebcb8b"), + playlist_style: (fg: "#b48ead"), + ), + level_styles: ( + info: (fg: "blue", bg: "black"), + warn: (fg: "yellow", bg: "black"), + error: (fg: "red", bg: "black"), + debug: (fg: "light_green", bg: "black"), + trace: (fg: "magenta", bg: "black"), + ), + progress_bar: ( + symbols: ["█", "█", "█", "█", "█"], + track_style: (fg: "#4c566a"), + elapsed_style: (fg: "#88c0d0"), + thumb_style: (fg: "#5e81ac"), + use_track_when_empty: true, + ), + scrollbar: ( + symbols: ["│", "█", "", ""], + track_style: (), + ends_style: (), + thumb_style: (fg: "#88c0d0"), + ), + tab_bar: ( + active_style: (fg: "#2e3440", bg: "#88c0d0"), + inactive_style: (fg: "#eceff4", bg: "#2e3440"),), + lyrics: ( + timestamp: false + ), + browser_song_format: [ + ( + kind: Group([ + (kind: Property(Track)), + (kind: Text(" ")), + ]) + ), + ( + kind: Group([ + (kind: Property(Artist)), + (kind: Text(" - ")), + (kind: Property(Title)), + ]), + default: (kind: Property(Filename)) + ), + ], + song_table_format: [ + ( + prop: (kind: Property(Artist), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4")) + ), + label_prop: (kind: Text("artist"), style: (fg: "#8fbcbb", modifiers: "Bold")), + width: "20%", + ), + ( + prop: (kind: Property(Title), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4")) + ), + label_prop: (kind: Text("title"), style: (fg: "#a3be8c", modifiers: "Bold")), + width: "35%", + ), + ( + prop: (kind: Property(Album), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4")) + ), + label_prop: (kind: Text("album"), style: (fg: "#b48ead", modifiers: "Bold")), + width: "30%", + ), + ( + prop: (kind: Property(Duration), style: (fg: "#eceff4"), + default: (kind: Text("-"), style: (fg: "#eceff4")) + ), + label_prop: (kind: Text("duration"), style: (fg: "#ebcb8b", modifiers: "Bold")), + width: "15%", + alignment: Right, + ), + ], + layout: Split( + direction: Vertical, + panes: [ + ( + size: "1", + pane: Split( + direction: Horizontal, + panes: [ + ( pane: Pane(Tabs), size: "70" ), + ( pane: Pane(Empty()), size: "100%" ), + ], + ), + ), + ( + pane: Pane(TabContent), + size: "100%", + ), + ( + size: "1", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "8", + pane: Component("input_mode") + ), + ( + size: "100%", + pane: Component("progress_bar"), + ), + ( + size: "20", + pane: Component("volume_and_state"), + ) + ] + ), + ), + ], + ), + components: { + "volume_and_state": Split( + direction: Horizontal, + panes: [ + (size: "100%", pane: Component("volume")), + (size: "7", pane: Component("states")), + ] + ), + "column1": Split( + direction: Vertical, + panes: [ + (size: "1", pane: Component("state")), + (size: "1", pane: Component("artist")), + (size: "1", pane: Component("album")), + (size: "1", pane: Component("track")), + (size: "1", pane: Component("title")), + (size: "1", pane: Component("date")), + (size: "1", pane: Component("elapsed")), + (size: "1", pane: Component("bitrate")), + ] + ), + "state": Pane(Property( + content: [ + (kind: Text("state: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Status(StateV2( ))), style: (fg: "#eceff4")), + ], align: Left, scroll_speed: 1 + )), + "artist": Pane(Property( + content: [ + (kind: Text("artist: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Song(Artist)), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4"))), + ], align: Left, scroll_speed: 1 + )), + "album": Pane(Property( + content: [ + (kind: Text("album: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Song(Album)), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4"))), + ], align: Left, scroll_speed: 1 + )), + "track": Pane(Property( + content: [ + (kind: Text("track: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Song(Track)), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4"))), + ], align: Left, scroll_speed: 1 + )), + "title": Pane(Property( + content: [ + (kind: Text("title: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Song(Title)), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4"))), + ], align: Left, scroll_speed: 1 + )), + "date": Pane(Property( + content: [ + (kind: Text("date: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Song(Other("date"))), style: (fg: "#eceff4"), + default: (kind: Text("unknown"), style: (fg: "#eceff4"))), + ], align: Left, scroll_speed: 1 + )), + "elapsed": Pane(Property( + content: [ + (kind: Text("elapsed: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Status(Elapsed)), style: (fg: "#eceff4")), + (kind: Text(" / ")), + (kind: Property(Status(Duration)), style: (fg: "#eceff4")), + ], + align: Left, scroll_speed: 1 + )), + "bitrate": Pane(Property( + content: [ + (kind: Text("bitrate: "), style: (fg: "#88c0d0", modifiers: "Bold")), + (kind: Property(Status(Bitrate)), style: (fg: "#eceff4")), + (kind: Text(" kbps"), style: (fg: "#eceff4")), + ], + align: Left, scroll_speed: 1 + )), + "volume": Split( + direction: Horizontal, + panes: [ + (size: "100%", pane: Pane(Volume(kind: Slider( + symbols: (filled: "█", thumb: "█", track: "█"), + filled_style: (fg: "#d08770"), + track_style: (fg: "#4c566a"), + thumb_style: (fg: "#bf616a"), + )))), + ], align: Right + ), + "states": Split( + direction: Horizontal, + panes: [ + ( + size: "1", + pane: Pane(Empty()) + ), + ( + size: "100%", + pane: Pane(Property(content: [(kind: Property(Status(InputBuffer())), style: (fg: "blue"), align: Right)])) + ), + ( + size: "1", + pane: Pane(Empty()) + ), + ( + size: "6", + pane: Pane(Property(content: [ + (kind: Text(" "), style: (bg: "#4c566a")), + (kind: Property(Status(RepeatV2( + on_label: "z", + off_label: "z", + on_style: (fg: "#ebcb8b", bg: "#4c566a", modifiers: "Bold"), + off_style: (fg: "#eceff4", bg: "#4c566a"), + )))), + (kind: Property(Status(RandomV2( + on_label: "x", + off_label: "x", + on_style: (fg: "#ebcb8b", bg: "#4c566a", modifiers: "Bold"), + off_style: (fg: "#eceff4", bg: "#4c566a"), + )))), + (kind: Property(Status(ConsumeV2( + on_label: "c", + off_label: "c", + oneshot_label: "c", + on_style: (fg: "#ebcb8b", bg: "#4c566a", modifiers: "Bold"), + off_style: (fg: "#eceff4", bg: "#4c566a"), + oneshot_style: (fg: "#d08770", bg: "#4c566a", modifiers: "Bold"), + )))), + (kind: Property(Status(SingleV2( + on_label: "v", + off_label: "v", + oneshot_label: "v", + on_style: (fg: "#ebcb8b", bg: "#4c566a", modifiers: "Bold"), + off_style: (fg: "#eceff4", bg: "#4c566a"), + oneshot_style: (fg: "#d08770", bg: "#4c566a", modifiers: "Bold"), + )))), + (kind: Text(" "), style: (bg: "#4c566a")), + ], + align: Right + )) + ), + ] + ), + "input_mode": Pane(Property( + content: [ + (kind: Transform(Replace(content: (kind: Property(Status(InputMode()))), replacements: [ + (match: "Normal", replace: (kind: Text(" normal "), style: (fg: "#eceff4", bg: "#4c566a"))), + (match: "Insert", replace: (kind: Text(" insert "), style: (fg: "#2e3440", bg: "#a3be8c"))), + ]))) + ], align: Left + )), + "progress_bar": Split( + direction: Horizontal, + panes: [ + ( + size: "1", + pane: Pane(Empty()) + ), + ( + size: "100%", + pane: Pane(ProgressBar) + ), + ( + size: "1", + pane: Pane(Empty()) + ), + ] + ) + }, +) diff --git a/rofi/bitwarden.rasi b/rofi/bitwarden.rasi new file mode 100644 index 0000000..fdd51ff --- /dev/null +++ b/rofi/bitwarden.rasi @@ -0,0 +1,29 @@ +@import "config.rasi" + +window { + children: [ mainbox ]; +} + +mainbox { + background-color: transparent; + children: [ inputbar, listview, message ]; +} + +element { + children: [ element-icon, element-text ]; +} + +element-icon { + size: 0px; + margin: 0; +} + +message { + background-color: @NORD1; + padding: 6px 0 12px 12px; +} + +textbox { + text-color: @NORD6; + background-color: transparent; +} diff --git a/rofi/config.rasi b/rofi/config.rasi new file mode 100644 index 0000000..2f12fbe --- /dev/null +++ b/rofi/config.rasi @@ -0,0 +1,166 @@ +configuration { + me-select-entry: "MouseSecondary"; + me-accept-entry: "MousePrimary"; + scroll-method: 1; + show-icons: true; + sidebar-mode: true; + modi: "drun,internet:~/.config/rofi/rofi-filter internet,dev:~/.config/rofi/rofi-filter dev,games:~/.config/rofi/rofi-filter games,system:~/.config/rofi/rofi-filter system,media:~/.config/rofi/rofi-filter media,notifications:~/.config/rofi/rofi-filter notifications,clipboard:~/.config/rofi/rofi-filter clipboard"; + display-drun: "ALL"; + display-internet: "NET"; + display-dev: "DEV"; + display-games: "GMS"; + display-system: "SYS"; + display-media: "MED"; + display-notifications: "NTF"; + display-clipboard: "CLP"; + kb-remove-to-eol: ""; + kb-mode-complete: ""; + kb-remove-char-back: "BackSpace,Shift+BackSpace"; + kb-row-down: "Down,Control+j"; + kb-row-up: "Up,Control+k"; + kb-accept-entry: "Control+m,Return,KP_Enter"; +} + +* { + NORD0: #2e3440; + NORD1: #3b4252; + NORD2: #434c5e; + NORD3: #4c566a; + NORD4: #d8dee9; + NORD5: #e5e9f0; + NORD6: #eceff4; + NORD7: #8fbcbb; + NORD8: #88c0d0; + NORD9: #81a1c1; + NORD10: #5e81ac; + NORD11: #bf616a; + NORD12: #d08770; + NORD13: #ebcb8b; + NORD14: #a3be8c; + NORD15: #b48ead; + font: "Noto Sans Mono 10"; +} + +window { + background-color: @NORD0; + location: north west; + anchor: north west; + width: 540px; + height: 550px; + x-offset: 5px; + y-offset: 5px; + padding: 0px; + border: 0px; + border-radius: 0px; + children: [ sb-mainbox ]; +} + +sb-mainbox { + orientation: horizontal; + spacing: 0px; + children: [ left-bar-container, vertibox ]; +} + +left-bar-container { + background-color: @NORD0; + orientation: vertical; + width: 40px; + expand: false; + children: [ mode-switcher ]; +} + +mode-switcher { + orientation: vertical; + spacing: 0px; + padding: 0px; +} + +button { + text-color: @NORD4; + background-color: transparent; + border-color: transparent; + horizontal-align: 0.5; + vertical-align: 0.5; + padding: 6px 0px; + border: 0px 0px 0px 2px; + font: "Noto Sans Mono Bold 8"; +} + +button selected { + text-color: @NORD6; + background-color: @NORD1; + border-color: @NORD8; +} + +vertibox { + background-color: @NORD0; + orientation: vertical; + spacing: 0px; + padding: 0px; + children: [ inputbar, listview ]; +} + +inputbar { + background-color: transparent; + children: [ entry ]; +} + +entry { + text-color: @NORD6; + background-color: transparent; + placeholder-color: @NORD4; + padding: 6px; + placeholder: " [search...]"; +} + +listview { + background-color: transparent; + margin: 0px; + spacing: 0px; + columns: 1; + lines: 10; + border: 0px; + cycle: false; + dynamic: true; + scrollbar: false; +} + +element { + background-color: transparent; + padding: 8px; + children: [ element-icon, element-text ]; +} + +element normal.normal, +element normal.urgent, +element normal.active { + text-color: @NORD6; + background-color: transparent; +} + +element alternate.normal, +element alternate.urgent, +element alternate.active { + text-color: @NORD6; + background-color: transparent; +} + +element selected.normal, +element selected.urgent, +element selected.active { + text-color: @NORD4; + background-color: @NORD1; + border: 0px 0px 0px 2px; + border-color: @NORD8; +} + +element-icon { + background-color: transparent; + size: 14px; + margin: 0 8px 0 6px; +} + +element-text { + text-color: inherit; + background-color: transparent; +} diff --git a/rofi/password.rasi b/rofi/password.rasi new file mode 100644 index 0000000..2260705 --- /dev/null +++ b/rofi/password.rasi @@ -0,0 +1,28 @@ +@import "config.rasi" + +window { + width: 540px; + height: 60px; + children: [ vertibox ]; +} + +vertibox { + children: [ label-box, inputbar ]; +} + +label-box { + background-color: @NORD1; + children: [ prompt ]; +} + +prompt { + text-color: @NORD4; + background-color: @NORD1; + border-color: @NORD8; + padding: 6px; + border: 0px 0px 0px 2px; +} + +entry { + placeholder: " [password...]"; +} diff --git a/rofi/rofi-filter b/rofi/rofi-filter new file mode 100755 index 0000000..5d13d6c --- /dev/null +++ b/rofi/rofi-filter @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -uo pipefail +tmp_dir="/tmp/cliphist" +mkdir -p "$tmp_dir" +mode="${1:-}" +selection="${2:-}" + +if [[ -n "$selection" ]]; then + if [[ "$mode" == "clipboard" ]]; then + id="${selection%% *}" + printf '%s' "$id" | clipvault get | wl-copy + elif [[ "$mode" == "notifications" ]]; then + body="${selection#*— }" + printf '%s' "$body" | wl-copy + else + target_app=$(grep -rl "^Name=${selection}\$" /usr/share/applications/*.desktop 2>/dev/null | head -n1) + if [[ -n "$target_app" ]]; then + exec_cmd=$(awk -F= '/^Exec=/{print $2; exit}' "$target_app" | sed 's/%[uUfFck]//g') + setsid $exec_cmd >/dev/null 2>&1 & + disown + fi + fi + exit 0 +fi + +case "$mode" in + clipboard) + find "$tmp_dir" -type f -mmin +30 -delete 2>/dev/null + while IFS=$'\t' read -r id rest; do + [[ "$id" =~ ^[0-9]+$ ]] || continue + [[ "$rest" == *"meta http-equiv"* ]] && continue + if [[ "$rest" == *"binary data"* ]]; then + ext="" + case "$rest" in + *jpeg*|*jpg*) ext="jpg" ;; + *png*) ext="png" ;; + *bmp*) ext="bmp" ;; + *) continue ;; + esac + img="$tmp_dir/$id.$ext" + [[ -s "$img" ]] || printf '%s\n' "$id" | clipvault get > "$img" 2>/dev/null + [[ -s "$img" ]] && printf '%s [IMG]\0icon\x1f%s\n' "$id" "$img" + else + printf '%s %s\n' "$id" "$rest" + fi + done < <(clipvault list) + ;; + notifications) + dunstctl history | jq -r ' + .data[0][] | + "\(.appname.data): \(.summary.data) — \(.body.data)" + ' + ;; + internet|dev|media|games|system) + case "$mode" in + internet) category="Network" ;; + dev) category="Development" ;; + media) category="AudioVideo" ;; + games) category="Game" ;; + system) category="System" ;; + esac + awk -v cat="$category" ' + BEGINFILE { name=""; icon=""; match_cat=0 } + /^Categories=/ { if (index($0, cat)) match_cat=1 } + /^Name=/ && name=="" { sub(/^Name=/, ""); name=$0 } + /^Icon=/ && icon=="" { sub(/^Icon=/, ""); icon=$0 } + ENDFILE { + if (match_cat && name != "") { + if (icon != "") printf "%s\x00icon\x1f%s\n", name, icon + else printf "%s\n", name + } + } + ' /usr/share/applications/*.desktop 2>/dev/null + ;; +esac diff --git a/swaddle/config.toml b/swaddle/config.toml new file mode 100644 index 0000000..e401661 --- /dev/null +++ b/swaddle/config.toml @@ -0,0 +1,5 @@ +debug = false + +[server] +inhibit_duration = 25 +sleep_duration = 5 diff --git a/sway/config b/sway/config new file mode 100644 index 0000000..0b211b9 --- /dev/null +++ b/sway/config @@ -0,0 +1,126 @@ +client.focused #88c0d0 #88c0d0 #eceff4 #88c0d0 #88c0d0 +client.focused_inactive #00000000 #00000000 #81a1c1 #00000000 #00000000 +client.unfocused #00000000 #00000000 #81a1c1 #00000000 #00000000 +client.urgent #d08770 #d08770 #eceff4 #d08770 #d08770 +font pango:Noto Sans Mono 10 +smart_gaps off +smart_borders off +gaps inner 3 +gaps outer 1 +default_border pixel 1 +default_floating_border pixel 1 +titlebar_border_thickness 0 +default_orientation auto +input type:keyboard { + xkb_layout "us,ru" + xkb_options "grp:win_space_toggle" +} +exec uwsm finalize WAYLAND_DISPLAY SWAYSOCK QT_QPA_PLATFORMTHEME QT_QPA_PLATFORMTHEME_QT6 +exec_always xrandr --output HDMI-A-2 --primary +set $mod Mod4 +set $left h +set $down j +set $up k +set $right l +set $term foot +set $menu rofi -show drun +set $file foot mc +workspace 1 output HDMI-A-2 +workspace 2 output HDMI-A-2 +workspace 3 output HDMI-A-2 +workspace 4 output HDMI-A-2 +workspace 5 output HDMI-A-2 +workspace 6 output HDMI-A-2 +workspace 7 output DP-1 +workspace 8 output DP-1 +workspace 9 output DP-2 +workspace 0 output DP-2 +workspace 1 +output DP-1 bg ~/Pictures/wallhaven-43qlkd.jpg fill +output HDMI-A-2 bg ~/Pictures/wallhaven-478kjo.jpg fill +output DP-2 bg ~/Pictures/wallhaven-4gx713.jpg fill +output DP-1 resolution 1280x1024 position 0,220 +output HDMI-A-2 resolution 1920x1080 position 1280,0 +output DP-2 resolution 1280x102 position 3200,220 +floating_modifier $mod normal +bindsym --to-code $mod+$left focus left +bindsym --to-code $mod+$down focus down +bindsym --to-code $mod+$up focus up +bindsym --to-code $mod+$right focus right +bindsym --to-code $mod+Shift+$left move left +bindsym --to-code $mod+Shift+$down move down +bindsym --to-code $mod+Shift+$up move up +bindsym --to-code $mod+Shift+$right move right +bindsym --to-code $mod+1 workspace number 1 +bindsym --to-code $mod+2 workspace number 2 +bindsym --to-code $mod+3 workspace number 3 +bindsym --to-code $mod+4 workspace number 4 +bindsym --to-code $mod+5 workspace number 5 +bindsym --to-code $mod+6 workspace number 6 +bindsym --to-code $mod+7 workspace number 7 +bindsym --to-code $mod+8 workspace number 8 +bindsym --to-code $mod+9 workspace number 9 +bindsym --to-code $mod+0 workspace number 10 +bindsym --to-code $mod+Shift+1 move container to workspace number 1 +bindsym --to-code $mod+Shift+2 move container to workspace number 2 +bindsym --to-code $mod+Shift+3 move container to workspace number 3 +bindsym --to-code $mod+Shift+4 move container to workspace number 4 +bindsym --to-code $mod+Shift+5 move container to workspace number 5 +bindsym --to-code $mod+Shift+6 move container to workspace number 6 +bindsym --to-code $mod+Shift+7 move container to workspace number 7 +bindsym --to-code $mod+Shift+8 move container to workspace number 8 +bindsym --to-code $mod+Shift+9 move container to workspace number 9 +bindsym --to-code $mod+Shift+0 move container to workspace number 10 +bindsym --to-code $mod+d exec $menu +bindsym --to-code $mod+v exec rofi -show clipboard +bindsym --to-code $mod+e exec $file +bindsym --to-code $mod+Return exec $term +bindsym --to-code $mod+c exec pkill -SIGRTMIN+13 waybar +bindsym --to-code $mod+Shift+q kill +bindsym --to-code $mod+f fullscreen +bindsym --to-code $mod+m focus mode_toggle +bindsym --to-code $mod+Shift+m floating toggle +bindsym --to-code $mod+s scratchpad show +bindsym --to-code $mod+Shift+s move scratchpad +bindsym --to-code --locked XF86AudioRaiseVolume exec wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ +bindsym --to-code --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- +bindsym --to-code --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle +bindsym --to-code --locked $mod+F12 exec wpctl set-volume -l 1 @DEFAULT_AUDIO_SOURCE@ 5%+ +bindsym --to-code --locked $mod+F11 exec wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%- +bindsym --to-code --locked $mod+F10 exec wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle +bindsym --to-code --locked XF86AudioNext exec playerctl next +bindsym --to-code --locked XF86AudioPause exec playerctl play-pause +bindsym --to-code --locked XF86AudioPlay exec playerctl play-pause +bindsym --to-code --locked XF86AudioPrev exec playerctl previous +bindsym --to-code Print exec area=$(slurp) && grim -g "$area" - | wl-copy && notify-send -u low "Screenshot" "Captured: $area" +bindsym --to-code $mod+Print exec active_class=$(swaymsg -t get_tree | jq -r '.. | select(.focused? == true) | .app_id // .window_properties.class // "unknown"') && geom=$(swaymsg -t get_tree | jq -r '.. | select(.focused? == true) | "\(.rect.x),\(.rect.y) \(.rect.width)x\(.rect.height)"') && grim -g "$geom" - | wl-copy && notify-send -u low "Screenshot" "Captured: $active_class" +bindsym --to-code ctrl+Print exec monitor=$(swaymsg -t get_outputs | jq -r '.[] | select(.focused==true) | .name') && grim -o "$monitor" - | wl-copy && notify-send -u low "Screenshot" "Captured: $monitor" +bindsym --to-code $mod+u exec sh -c 'rbw get ed-ssh | jq -r ".fields[]? | select(.name==\"privateKey\") | .text" | ssh-add -' +bindsym --to-code $mod+b exec rofi-rbw --selector rofi --selector-args="-theme ~/.config/rofi/bitwarden.rasi -mesg 'alt+1: type username/password\nalt+2: type username\talt+3: type password\talt+4: type totp\talt+u: copy username\talt+c: copy password\talt+t: copy totp\talt+m: menu\t\talt+s: sync logins'" +mode "resize" { + bindsym --to-code $left resize shrink width 10px + bindsym --to-code $down resize grow height 10px + bindsym --to-code $up resize shrink height 10px + bindsym --to-code $right resize grow width 10px + bindsym --to-code Return mode "default" + bindsym --to-code Escape mode "default" +} +mode "reload sway? [y/n]" { + bindsym --to-code y reload; mode "default" + bindsym --to-code n mode "default" + bindsym Escape mode "default" +} +mode "lock sway? [y/n]" { + bindsym --to-code y exec swaylock; mode "default" + bindsym --to-code n mode "default" + bindsym Escape mode "default" +} +mode "exit sway? [y/n]" { + bindsym --to-code y exec swaymsg exit; mode "default" + bindsym --to-code n mode "default" + bindsym Escape mode "default" +} +bindsym --to-code $mod+r mode "resize" +bindsym --to-code $mod+Shift+r mode "reload sway? [y/n]" +bindsym --to-code $mod+x mode "lock sway? [y/n]" +bindsym --to-code $mod+Shift+x mode "exit sway? [y/n]" diff --git a/sway/scripts/per-window-layout.py b/sway/scripts/per-window-layout.py new file mode 100755 index 0000000..138efef --- /dev/null +++ b/sway/scripts/per-window-layout.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import asyncio +import logging +import os +import signal +from dataclasses import dataclass, field +from typing import Dict, Optional + +try: + from systemd import daemon +except ImportError: + daemon = None + +import i3ipc +from i3ipc.aio import Connection + +DEFAULT_MODE = os.getenv("PWL_DEFAULT_MODE", "1") == "1" +DEBUG = os.getenv("PWL_DEBUG", "0") == "1" +INFO = os.getenv("PWL_INFO", "1") == "1" + +logging.basicConfig( + level=logging.DEBUG if DEBUG else (logging.INFO if INFO else logging.WARNING), + format="[pwl] %(asctime)s %(message)s", + datefmt="%H:%M:%S", +) +log = logging.getLogger("pwl") + + +def notify_systemd(msg: str) -> None: + if daemon: + try: + daemon.notify(msg) + except Exception as e: + log.debug("Failed to notify systemd: %s", e) + + +def notify_ready(status: str) -> None: + notify_systemd(f"READY=1\nSTATUS={status}") + + +def notify_status(status: str) -> None: + notify_systemd(f"STATUS={status}") + + +def notify_watchdog() -> None: + notify_systemd("WATCHDOG=1") + + +@dataclass +class Keyboard: + identifier: str + default_index: int + current_index: int + layout_map: Dict[str, int] = field(default_factory=dict) + + +class PerWindowLayout: + def __init__(self) -> None: + self.conn: Optional[Connection] = None + self.keyboard: Optional[Keyboard] = None + + self.window_layouts: Dict[int, int] = {} + self.current_window: Optional[int] = None + + self.stop_event = asyncio.Event() + self._layout_map_built = False + + async def connect(self) -> None: + self.conn = await Connection(auto_reconnect=True).connect() + + def _find_main_keyboard(self, inputs) -> Optional[object]: + keyboards = [i for i in inputs if i.type == "keyboard"] + if not keyboards: + return None + + real = [k for k in keyboards if "virtual" not in (k.identifier or "").lower()] + return (real or keyboards)[0] + + async def wait_until_ready(self) -> None: + notify_status("Waiting for sway...") + while not self.stop_event.is_set(): + try: + inputs = await self.conn.get_inputs() + except Exception: + await asyncio.sleep(0.2) + continue + + main = self._find_main_keyboard(inputs) + if main is None: + await asyncio.sleep(0.2) + continue + + idx = main.xkb_active_layout_index or 0 + self.keyboard = Keyboard( + identifier=main.identifier, + default_index=idx, + current_index=idx, + ) + return + + async def build_layout_map(self, force: bool = False) -> None: + assert self.keyboard is not None + inputs = await self.conn.get_inputs() + current = self.keyboard.current_index + + kbd = next((i for i in inputs if i.identifier == self.keyboard.identifier), None) + if not kbd: + return + + layouts = kbd.xkb_layout_names or [] + count = len(layouts) + + if self._layout_map_built and not force and len(self.keyboard.layout_map) == count: + return + + notify_status("Building layout map...") + self.keyboard.layout_map.clear() + + for i in range(count): + await self.conn.command(f'input "{self.keyboard.identifier}" xkb_switch_layout {i}') + inputs = await self.conn.get_inputs() + kbd = next((k for k in inputs if k.identifier == self.keyboard.identifier), kbd) + name = kbd.xkb_active_layout_name or str(i) + self.keyboard.layout_map[name] = i + + await self.conn.command(f'input "{self.keyboard.identifier}" xkb_switch_layout {current}') + self.keyboard.current_index = current + self._layout_map_built = True + log.info("Layout map initialized (%d layouts)", len(self.keyboard.layout_map)) + + async def init_windows(self) -> None: + assert self.keyboard is not None + inputs = await self.conn.get_inputs() + kbd = next((i for i in inputs if i.identifier == self.keyboard.identifier), None) + idx = kbd.xkb_active_layout_index if kbd else self.keyboard.default_index + + tree = await self.conn.get_tree() + active_ids = set() + + for con in tree.leaves(): + active_ids.add(con.id) + if con.id not in self.window_layouts: + self.window_layouts[con.id] = idx + + stale = [wid for wid in self.window_layouts if wid not in active_ids] + for wid in stale: + self.window_layouts.pop(wid, None) + + notify_status(f"Tracking {len(self.window_layouts)} windows") + + async def switch_layout(self, idx: int) -> None: + assert self.keyboard is not None + if idx == self.keyboard.current_index: + return + await self.conn.command(f'input "{self.keyboard.identifier}" xkb_switch_layout {idx}') + self.keyboard.current_index = idx + + async def handle_active_window(self, con_id: int) -> None: + assert self.keyboard is not None + self.current_window = con_id + + if con_id in self.window_layouts: + idx = self.window_layouts[con_id] + else: + idx = self.keyboard.default_index if DEFAULT_MODE else self.keyboard.current_index + self.window_layouts[con_id] = idx + notify_status(f"Tracking {len(self.window_layouts)} windows") + + await self.switch_layout(idx) + + def handle_active_layout(self, identifier: str, layout_name: Optional[str]) -> None: + assert self.keyboard is not None + if identifier != self.keyboard.identifier or not layout_name: + return + + idx = self.keyboard.layout_map.get(layout_name) + if idx is None: + return + + self.keyboard.current_index = idx + if self.current_window is not None: + self.window_layouts[self.current_window] = idx + + def handle_close_window(self, con_id: int) -> None: + if self.window_layouts.pop(con_id, None) is not None: + notify_status(f"Tracking {len(self.window_layouts)} windows") + + async def handle_device_change(self) -> None: + inputs = await self.conn.get_inputs() + main = self._find_main_keyboard(inputs) + if main and self.keyboard and main.identifier != self.keyboard.identifier: + log.info("Main keyboard changed: %s -> %s", self.keyboard.identifier, main.identifier) + idx = main.xkb_active_layout_index or 0 + self.keyboard.identifier = main.identifier + self.keyboard.default_index = idx + self.keyboard.current_index = idx + await self.build_layout_map(force=True) + await self.init_windows() + + def _register_handlers(self) -> None: + assert self.conn is not None + + async def on_window(_conn, event): + change = event.change + if change == "focus" and event.container is not None: + await self.handle_active_window(event.container.id) + elif change == "close" and event.container is not None: + self.handle_close_window(event.container.id) + + async def on_input(_conn, event): + change = event.change + if change == "xkb_layout" and event.input is not None: + self.handle_active_layout( + event.input.identifier, event.input.xkb_active_layout_name + ) + elif change in ("added", "removed"): + await self.handle_device_change() + + self.conn.on(i3ipc.Event.WINDOW, on_window) + self.conn.on(i3ipc.Event.INPUT, on_input) + + async def watchdog_loop(self) -> None: + interval = 10.0 + if daemon: + try: + wd = daemon.watchdog_enabled() + if isinstance(wd, (int, float)) and wd > 0: + interval = wd / 2.0 + except Exception: + pass + + while not self.stop_event.is_set(): + notify_watchdog() + await asyncio.sleep(interval) + + async def run(self) -> None: + await self.connect() + await self.wait_until_ready() + await self.build_layout_map() + await self.init_windows() + self._register_handlers() + + notify_ready(f"Tracking {len(self.window_layouts)} windows") + + async with asyncio.TaskGroup() as tg: + tg.create_task(self.conn.main()) + tg.create_task(self.watchdog_loop()) + await self.stop_event.wait() + await self.conn.main_quit() + + +async def amain() -> None: + pwl = PerWindowLayout() + loop = asyncio.get_running_loop() + + def shutdown_handler() -> None: + pwl.stop_event.set() + + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(sig, shutdown_handler) + + try: + await pwl.run() + except asyncio.CancelledError: + raise + except Exception: + log.exception("Fatal error") + raise + + +def main() -> None: + try: + asyncio.run(amain()) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/swaylock/config b/swaylock/config new file mode 100644 index 0000000..a2f92aa --- /dev/null +++ b/swaylock/config @@ -0,0 +1,39 @@ +ignore-empty-password +show-failed-attempts +daemonize +color=2e3440 +indicator-idle-visible +indicator-caps-lock +disable-caps-lock-text +hide-keyboard-layout +indicator-radius=42 +indicator-thickness=2 +font=Noto Sans Mono +font-size=10 +inside-color=00000000 +inside-clear-color=00000000 +inside-ver-color=00000000 +inside-wrong-color=00000000 +inside-caps-lock-color=00000000 +ring-color=4c566a +ring-clear-color=ebcb8b +ring-ver-color=a3be8c +ring-wrong-color=bf616a +ring-caps-lock-color=d08770 +line-color=00000000 +line-clear-color=00000000 +line-ver-color=00000000 +line-wrong-color=00000000 +line-caps-lock-color=00000000 +text-color=eceff4 +text-clear-color=ebcb8bff +text-ver-color=a3be8cff +text-wrong-color=bf616aff +text-caps-lock-color=d08770ff +separator-color=00000000 +key-hl-color=88c0d0ff +caps-lock-key-hl-color=88c0d0ff +bs-hl-color=ebcb8bff +layout-bg-color=00000000 +layout-border-color=00000000 +layout-text-color=d8dee9ff diff --git a/systemd/user/clipvault.service b/systemd/user/clipvault.service new file mode 100644 index 0000000..55967a6 --- /dev/null +++ b/systemd/user/clipvault.service @@ -0,0 +1,12 @@ +[Unit] +Description=clipvault +PartOf=graphical-session.target +After=graphical-session.target + +[Service] +ExecStart=/usr/bin/wl-paste --watch clipvault store --max-entries 5000 +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=graphical-session.target diff --git a/systemd/user/gtk-settings.service b/systemd/user/gtk-settings.service new file mode 100644 index 0000000..b13baf3 --- /dev/null +++ b/systemd/user/gtk-settings.service @@ -0,0 +1,12 @@ +[Unit] +Description=GTK dark mode settings +After=graphical-session.target +PartOf=graphical-session.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark +ExecStart=/usr/bin/gsettings set org.gnome.desktop.interface color-scheme prefer-dark + +[Install] +WantedBy=graphical-session.target diff --git a/systemd/user/per-window-layout.service b/systemd/user/per-window-layout.service new file mode 100644 index 0000000..8707473 --- /dev/null +++ b/systemd/user/per-window-layout.service @@ -0,0 +1,24 @@ +[Unit] +Description=Per window keyboard layout (sway) +Documentation=https://github.com/pyrschtjag +PartOf=graphical-session.target +After=graphical-session.target +BindsTo=graphical-session.target + +[Service] +Type=notify +NotifyAccess=main +ExecStart=%h/.config/sway/scripts/per-window-layout.py +Environment=PWL_DEFAULT_MODE=1 +Environment=PWL_INFO=1 +Environment=PWL_DEBUG=0 +Restart=always +RestartSec=1 +NoNewPrivileges=yes +StandardOutput=journal +StandardError=journal +TimeoutStartSec=30 +TimeoutStopSec=5 + +[Install] +WantedBy=graphical-session.target diff --git a/systemd/user/swaddle.service b/systemd/user/swaddle.service new file mode 100644 index 0000000..5f3107d --- /dev/null +++ b/systemd/user/swaddle.service @@ -0,0 +1,12 @@ +[Unit] +Description=Swaddle + +[Service] +Type=simple +ExecStart=/usr/sbin/swaddle +NoNewPrivileges=yes +Restart=on-failure +RestartSec=3 + +[Install] +WantedBy=graphical-session.target diff --git a/systemd/user/swayidle.service b/systemd/user/swayidle.service new file mode 100644 index 0000000..a60240d --- /dev/null +++ b/systemd/user/swayidle.service @@ -0,0 +1,21 @@ +[Unit] +Description=Sway idle daemon +PartOf=graphical-session.target +After=graphical-session.target +BindsTo=graphical-session.target + +[Service] +Type=simple +ExecStart=/usr/bin/swayidle -w \ + timeout 300 'pidof swaylock || swaylock -f' \ + timeout 360 'swaymsg "output * dpms off"' \ + timeout 420 'systemctl suspend' \ + before-sleep 'pidof swaylock || swaylock -f' \ + after-resume 'swaymsg "output * dpms on"' +Restart=always +RestartSec=1 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=graphical-session.target diff --git a/uwsm/env-sway b/uwsm/env-sway new file mode 100644 index 0000000..5b5d0b6 --- /dev/null +++ b/uwsm/env-sway @@ -0,0 +1,3 @@ +export QT_QPA_PLATFORMTHEME=qt6ct +export QT_QPA_PLATFORMTHEME_QT6=qt6ct +export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/rbw/ssh-agent-socket" diff --git a/waybar/config b/waybar/config new file mode 100644 index 0000000..513854c --- /dev/null +++ b/waybar/config @@ -0,0 +1,197 @@ +{ + "layer": "top", + "position": "top", + + "margin-top": 5, + "margin-left": 5, + "margin-right": 5, + + "height": 34, + + "modules-left": [ + "custom/rofi", + "sway/workspaces", + "sway/mode" + ], + + "modules-center": [ + "clock#time" + ], + + "modules-right": [ + "tray", + "idle_inhibitor", + "group/keyboard", + "disk" , + "group/hardware", + "group/temp", + "group/audio" + ], + + "group/keyboard": { + "orientation": "horizontal", + /*"modules": [ + "keyboard-state", + "sway/language" + ]*/ + "modules": ["sway/language"] + }, + + "group/hardware": { + "orientation": "horizontal", + "modules": [ + "cpu", + "memory" + ] + }, + + "group/temp": { + "orientation": "horizontal", + "modules": [ + "temperature#cpu", + "custom/gpu_temperature" + ] + }, + + "group/audio": { + "orientation": "horizontal", + "modules": [ + "wireplumber#source", + "wireplumber#sink" + ] + }, + + "custom/rofi": { + "exec-if": "[ \"$USER\" != \"pyrschtjag\" ]", + "exec": "echo '{\"text\": \"\", \"tooltip\": \"\"}'", + "return-type": "json", + "format": "{icon}{text}", + "format-icons": ["󰀻"], + "interval": "once", + "on-click": "rofi -show drun", + "tooltip": false + }, + + "sway/workspaces": { + "format": "{icon}{windows}", + "window-rewrite": { + "class": " 󰈹", + "class": " ", + "class": " 󰙯", + "class": " ", + "class": " 󰓓", + "class": " 󰷕", + "class": " ", + "title": " ", + "title": " 󰎈", + "title": " ", + "": " " + }, + "tooltip": false + }, + + "sway/mode": { + "format": "{}", + "max-length": 18, + "tooltip": false + }, + + "clock#time": { + "format": "{:%H:%M}", + "tooltip": false + }, + + "tray": { + "icon-size": 12, + "spacing": 10, + "tooltip": false + }, + + "idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", +// "deactivated": "" + "deactivated": "󱎴" + }, + "signal": 13, + "tooltip": false, + "cursor": "" + }, + + "sway/language": { +// "format": "{}", + "format": "󰌌 {}", + "format-en": "EN", + "format-ru": "RU", + "tooltip": false + }, + + "keyboard-state": { + "format": "{name}{icon}", + "numlock": true, + "capslock": true, + "scrolllock": false, + "format-icons": { + "numlock": { + "locked": "󰎠 ", + "unlocked": "" + }, + "capslock": { + "locked": "󰌌 󰬴 ", + "unlocked": "󰌌 " + } + } + }, + + "disk": { +// enable when release waybar 0.16+ +// "format": " {free:5G}G", + "format": " {free:7}", + "paths": ["/"], + "interval": 120, + "tooltip": false + }, + + "cpu": { + "format": " {usage:03}%", + "interval": 5, + "tooltip": false + }, + + "memory": { + "format": " {percentage:03}%", + "interval": 5, + "tooltip": false + }, + + "temperature#cpu": { + "format": " {temperatureC:03}°C", + "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", + "interval": 5, + "tooltip": false + }, + + "custom/gpu_temperature": { + "format": "󰢮 {text}°C", + "exec": "nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits | xargs printf '%03d'", + "interval": 5, + "tooltip": false + }, + + "wireplumber#source": { + "format": " {volume:03}%", + "format-muted": " 000%", + "node-type": "Audio/Source", + "scroll-step": 5, + "tooltip": false + }, + + "wireplumber#sink": { + "format": "{icon} {volume:03}%", + "format-muted": " 000%", + "format-icons": ["", "", ""], + "scroll-step": 5, + "tooltip": false + } +} diff --git a/waybar/style.css b/waybar/style.css new file mode 100644 index 0000000..909829d --- /dev/null +++ b/waybar/style.css @@ -0,0 +1,156 @@ +@define-color nord0 #2e3440; +@define-color nord1 #3b4252; +@define-color nord2 #434c5e; +@define-color nord3 #4c566a; +@define-color nord4 #d8dee9; +@define-color nord5 #e5e9f0; +@define-color nord6 #eceff4; +@define-color nord7 #8fbcbb; +@define-color nord8 #88c0d0; +@define-color nord9 #81a1c1; +@define-color nord10 #5e81ac; +@define-color nord11 #bf616a; +@define-color nord12 #d08770; +@define-color nord13 #ebcb8b; +@define-color nord14 #a3be8c; +@define-color nord15 #b48ead; + +* { + font-family: "Noto Sans Mono"; + font-size: 13px; + border: 0px; + border-radius: 0px; + box-shadow: none; + text-shadow: none; + transition: none; +} + +window#waybar { + color: @nord6; + background-color: @nord0; +} + +#custom-rofi { + margin: 0 10px 0 10px; +} + +#workspaces button { + color: @nord4; + padding: 0px 10px; + border-bottom: 2px solid transparent; +} + +#workspaces button.visible { + color: @nord5; + background-color: transparent; + border-bottom: 2px solid @nord9; +} + +#workspaces button.focused { + color: @nord6; + background-color: @nord1; + border-bottom: 2px solid @nord8; +} + +#workspaces button.urgent { + color: @nord8; +} + +#workspaces button:hover { + background: transparent; + border-bottom: 2px solid transparent; +} + +#workspaces button:hover.active, +#workspaces button:hover.visible { + background-color: @nord1; + border-bottom: 2px solid @nord8; +} + +#workspaces button.urgent { + border-bottom: 2px solid @nord12; +} + +#mode { + margin: 0 0 0 10px; +} + +#tray menu { + background-color: @nord0; + padding: 4px 0; +} + +#tray menu menuitem { + color: @nord4; + padding: 4px 0px; + border-left: 2px solid transparent; +} + +#tray menu menuitem:hover { + background-color: @nord1; + color: @nord6; + border-left: 2px solid @nord8; +} + +#tray menu separator { + background-color: @nord3; + margin: 4px 0; + min-height: 1px; +} + +#tray > .passive { opacity: 0.6; } +#tray > .needs-attention { color: @nord8; } + +/* delete when release waybar 0.16+ */ +#idle_inhibitor { + margin: 0 10px 0 0; + opacity: 1; +} + +/* enable when release waybar 0.16+ +#idle_inhibitor { + margin: 0; + opacity: 0; +} + +#idle_inhibitor.activated { + margin: 0 10px 0 0; + opacity: 1; +} +*/ + +#tray, +#keyboard, +#disk, +#hardware, +#temp, +#audio { + margin: 0 10px 0 0; +} + +#wireplumber.source-muted, +#wireplumber.sink-muted { + color: @nord3; + color: @nord3; +} + +#cpu, +#temperature.cpu, +#wireplumber.source { + padding: 0 5px 0 0; +} + +#memory, +#custom-gpu_temperature, +#wireplumber.sink { + padding: 0 0 0 5px; +} + +tooltip { + background-color: @nord0; +} + +tooltip label { + background-color: @nord0; + color: @nord6; +}