diff --git a/.gitignore b/.gitignore index f0c1dd5..21fe5f8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ mutt/.config/mutt/.mailsynclastrun gtk/.config/gtk-3.0/bookmarks mpd/.config/mpd/pid +/emacs/.config/doom/configuration.el diff --git a/LICENSE.md b/LICENSE.org similarity index 95% rename from LICENSE.md rename to LICENSE.org index d53ed8d..b04255d 100644 --- a/LICENSE.md +++ b/LICENSE.org @@ -1,5 +1,4 @@ -The MIT License (MIT) -===================== +* The MIT License (MIT) Copyright (c) `2016` `Ryan Kes` @@ -19,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 8b47ea2..0000000 --- a/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Dotfiles - -A repository of my personal configuration files. - -# Requirements - -[stow](http://www.gnu.org/software/stow/) - -# Installation - -```shell -./install.sh -``` - -# Find & delete orphaned symlinks -```shell -./clean-dead-symlinks -``` - -# Packages configured - -* [dunst](https://dunst-project.org/) -* [firejail](https://firejail.wordpress.com/) -* [git](https://git-scm.com/) -* [gnupg](https://gnupg.org/) -* [gtk](https://www.gtk.org/) -* [lf](https://github.com/gokcehan/lf) -* [mpd](https://www.musicpd.org/) -* [neomutt](https://neomutt.org/) -* [neovim](https://neovim.io/) -* [redshift](http://jonls.dk/redshift/) -* [sxhkd](https://github.com/baskerville/sxhkd) -* [spacemacs](http://spacemacs.org/) -* [tmux](https://tmux.github.io/) -* [vifm](https://vifm.info/) -* [xcompmgr](https://github.com/freedesktop/xcompmgr) -* [xwallpaper](https://github.com/stoeckmann/xwallpaper) -* [weechat](https://weechat.org/) -* [zsh](https://www.zsh.org/) -* [spaceship zsh theme](https://github.com/denysdovhan/spaceship-prompt) - -# Custom packages configured - -These are custom packages I use (mostly [suckless](https://suckless.org/)). - -* [dwm](https://github.com/alrayyes/dwm) -* [slstatus](https://github.com/alrayyes/slstatus) - -# Caveats - -rcirc doesn't work peroperly with znc - -# License - -This theme is released under the MIT License. For more information read the [license][license]. - -[license]: LICENSE.md diff --git a/README.org b/README.org new file mode 100644 index 0000000..d4e5ec9 --- /dev/null +++ b/README.org @@ -0,0 +1,37 @@ +* Dotfiles +A repository of my personal configuration files. + +** Requirements +- [[http://www.gnu.org/software/stow/][stow]] +** Installation +#+BEGIN_SRC shell +./install.sh +#+END_SRC +** Find & delete orphaned symlinks +#+BEGIN_SRC shell +./clean-dead-symlinks +#+END_SRC +** Configured Packages +- [[https://github.com/baskerville/bspwm][bspwm]] +- [[https://dunst-project.org/][dunst]] +- [[https://firejail.wordpress.com/][firejail]] +- [[https://git-scm.com/][git]] +- [[https://gnupg.org/][gnupg]] +- [[https://www.gtk.org/][gtk]] +- [[https://github.com/gokcehan/lf][lf]] +- [[https://www.musicpd.org/][mpd]] +- [[https://neomutt.org/][neomutt]] +- [[https://neovim.io/][neovim]] +- [[http://jonls.dk/redshift/][redshift]] +- [[https://github.com/baskerville/sxhkd][sxhkd]] +- [[https://github.com/hlissner/doom-emacs][Doom Emacs]] +- [[https://tmux.github.io/][tmux]] +- [[https://vifm.info/][vifm]] +- [[https://github.com/freedesktop/xcompmgr][xcompmgr]] +- [[https://github.com/stoeckmann/xwallpaper][xwallpaper]] +- [[https://weechat.org/][weechat]] +- [[https://www.zsh.org/][zsh]] +- [[https://github.com/denysdovhan/spaceship-prompt][spaceship zsh + theme]] +** Custom packages +- [[https://github.com/alrayyes/st][st]] diff --git a/dwm/.Xresources.desktop b/bspwm/.Xresources.desktop similarity index 100% rename from dwm/.Xresources.desktop rename to bspwm/.Xresources.desktop diff --git a/dwm/.Xresources.thinkpad b/bspwm/.Xresources.thinkpad similarity index 100% rename from dwm/.Xresources.thinkpad rename to bspwm/.Xresources.thinkpad diff --git a/bspwm/.config/bspwm/bspwmrc b/bspwm/.config/bspwm/bspwmrc new file mode 100755 index 0000000..9aab0d7 --- /dev/null +++ b/bspwm/.config/bspwm/bspwmrc @@ -0,0 +1,78 @@ +#! /bin/sh + +sxhkd & + +# Set screen orientation if second monitor is connected +SCREENCOUNT=$(xrandr | grep -c "\*") + +# If multi screen add special mode for monitor so it supports 1440p over HDMI +if [ "$SCREENCOUNT" -eq 2 ]; then + if [ -f "$HOME/.local/bin/screen_desktop" ]; then + screen_desktop + # Make sure mouse is on main screen so windows are launched there + # xdotool mousemove 3360 1080 + bspc monitor DP-1 -d 2 3 4 5 6 7 8 9 10 + bspc monitor HDMI-2 -d 1 + fi + if [ -f "$HOME/.local/bin/polybar-desktop" ]; then + polybar-desktop + fi +else + if [ -f "$HOME/.local/bin/screen" ]; then + screen + fi + if [ -f "$HOME/.local/bin/polybar-laptop" ]; then + bspc monitor -d 1 2 3 4 5 6 7 8 9 0 + polybar-laptop + fi +fi + +# lock screen after x minutes and on laptop close lid +xautolock -time 10 -locker ~/.local/bin/lock & +xss-lock -- ~/.local/bin/lock & +xcompmgr & + +if [ -e ~/.cache/wall1.png ] && [ -e ~/.cache/wall2.png ]; then + xwallpaper --output HDMI-2 --zoom ~/.cache/wall2.png --output DP-1 --zoom ~/.cache/wall1.png & +elif [ -e ~/.cache/wall1.png ]; then + xwallpaper --output eDP1 --zoom ~/.cache/wall1.png & +fi + +# switch off microphones +amixer -c 2 set Mic nocap +amixer -c 3 set Mic nocap + +unclutter & +dunst & +slstatus & + +redshift-gtk & +nm-applet & +syncthing-gtk & +gpodder & + +spotify & + +if [ -e /usr/bin/firefox ]; then + firefox & +elif [ -e /usr/bin/iceweasel ]; then + iceweasel & +fi + +bspc config border_width 2 +bspc config window_gap 12 + +bspc config split_ratio 0.52 +bspc config borderless_monocle true +bspc config gapless_monocle true + +bspc rule -a "Syncthing GTK" state=floating +bspc rule -a Gimp state=floating +bspc rule -a Gpodder desktop='^4' +bspc rule -a Spotify desktop='^4' +bspc rule -a Slack desktop='^4' +bspc rule -a Emacs state=tiled +bspc rule -a iceweasel desktop='^9' +bspc rule -a Firefox desktop='^9' +bspc rule -a scratchpad sticky=on state=floating +bspc rule -a scratchmacs sticky=on state=floating diff --git a/dwm/.local/bin/lock b/bspwm/.local/bin/lock similarity index 100% rename from dwm/.local/bin/lock rename to bspwm/.local/bin/lock diff --git a/dwm/.local/bin/maimpick b/bspwm/.local/bin/maimpick similarity index 100% rename from dwm/.local/bin/maimpick rename to bspwm/.local/bin/maimpick diff --git a/bspwm/.local/bin/scratch b/bspwm/.local/bin/scratch new file mode 100755 index 0000000..890b3f4 --- /dev/null +++ b/bspwm/.local/bin/scratch @@ -0,0 +1,2 @@ +#!/usr/bin/sh +st -c scratchpad diff --git a/dwm/.local/bin/screen_desktop b/bspwm/.local/bin/screen_desktop similarity index 100% rename from dwm/.local/bin/screen_desktop rename to bspwm/.local/bin/screen_desktop diff --git a/dwm/.xinitrc b/bspwm/.xinitrc similarity index 95% rename from dwm/.xinitrc rename to bspwm/.xinitrc index a2d37ff..e726a86 100644 --- a/dwm/.xinitrc +++ b/bspwm/.xinitrc @@ -11,4 +11,4 @@ export _JAVA_AWT_WM_NONREPARENTING=1 export AWT_TOOLKIT=MToolkit wmname LG3D -exec dwm +exec bspwm diff --git a/dwm/.dwm/autostart.sh b/dwm/.dwm/autostart.sh deleted file mode 100755 index 8bfe46e..0000000 --- a/dwm/.dwm/autostart.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh - -# Set working directory to home -cd ~ - -# Set screen orientation if second monitor is connected -SCREENCOUNT=$(xrandr | grep -c "\*") - -# If multi screen add special mode for monitor so it supports 1440p over HDMI -if [ "$SCREENCOUNT" -eq 2 ] -then - if [ -f "$HOME/.local/bin/screen_desktop" ] - then - screen_desktop - # Make sure mouse is on main screen so windows are launched there - xdotool mousemove 3360 1080 - fi -else - if [ -f "$HOME/.local/bin/screen" ] - then - screen - fi -fi - -# lock screen after x minutes and on laptop close lid -xautolock -time 10 -locker ~/.local/bin/lock & -xss-lock -- ~/.local/bin/lock & -xcompmgr & -sxhkd & - -if [ -e ~/.cache/wall1.png ] && [ -e ~/.cache/wall2.png ] -then - xwallpaper --output HDMI-2 --zoom ~/.cache/wall2.png --output DP-1 --zoom ~/.cache/wall1.png & -elif [ -e ~/.cache/wall1.png ] -then - xwallpaper --output eDP1 --zoom ~/.cache/wall1.png & -fi - -# switch off microphones -amixer -c 2 set Mic nocap -amixer -c 3 set Mic nocap - -unclutter & -dunst & -slstatus & - -redshift-gtk & -nm-applet & -syncthing-gtk & -gpodder & - -exec st -c tmux -e tmux & -exec spotify & - -if [ -e /usr/bin/firefox ] -then - exec firefox & -elif [ -e /usr/bin/iceweasel ] -then - exec iceweasel & -fi diff --git a/emacs/.config/doom/config.el b/emacs/.config/doom/config.el index 2c132e8..edbac4e 100644 --- a/emacs/.config/doom/config.el +++ b/emacs/.config/doom/config.el @@ -1,56 +1,5 @@ ;;; .doom.d/config.el -*- lexical-binding: t; -*- ;; Place your private configuration here -;; General settings -(setq doom-font (font-spec :family "FuraCode Nerd Font Mono" :size 12) - doom-theme 'doom-molokai) -;; projectile -(use-package! projectile - :config - (setq projectile-project-search-path '("~/devel/personal/" "~/devel/andthensome/" "~/Documents/"))) - -;; irc -(use-package! circe - :config - (setq circe-network-options - `( - ("znc-freenode" - :host "irc.higherlearning.eu" - :port "5000" - :tls t - :user "l0rd/freenode" - :server-buffer-name "⇄ Freenode (ZNC)" - :pass (lambda (&rest _) (+pass-get-secret "controlpanel/irc.higherlearning.eu")) - :channels ("#emacs")) - ("znc-snoonet" - :host "irc.higherlearning.eu" - :port "5000" - :tls t - :user "l0rd/snoonet" - :server-buffer-name "⇄ Snoonet (ZNC)" - :pass (lambda (&rest _) (+pass-get-secret "controlpanel/irc.higherlearning.eu")) - :channels ("#islam")) - )) - :hook (circe-channel-mode . enable-lui-autopaste) - ) - -;; notmuch -(use-package! notmuch - :config - (setq +notmuch-sync-backend 'mbsync - +notmuch-sync-command "mailsync" - sendmail-program "/usr/bin/msmtp" - message-sendmail-f-is-evil t - message-sendmail-extra-arguments '("--read-envelope-from") - message-send-mail-function 'message-send-mail-with-sendmail) - ) - -;; org -(after! org - (map! :map org-mode-map - :n "M-j" #'org-metadown - :n "M-k" #'org-metaup) - ) -(setq org-directory "~/Documents/org/" - org-log-done 'note) +(org-babel-load-file "~/.config/doom/configuration.org") diff --git a/emacs/.config/doom/configuration.org b/emacs/.config/doom/configuration.org new file mode 100644 index 0000000..5641660 --- /dev/null +++ b/emacs/.config/doom/configuration.org @@ -0,0 +1,87 @@ +* General settings +Set fonts to a [[https://www.nerdfonts.com/][Nerd Fonts]] patched version of [[https://github.com/tonsky/FiraCode][Fira Code]]. Theme is set to +doom-molokai from [[https://github.com/hlissner/emacs-doom-themes][doom-themes]] +[[https://raw.githubusercontent.com/hlissner/emacs-doom-themes/screenshots/doom-molokai.png][doom-molokai screenshot]] +#+BEGIN_SRC emacs-lisp +(setq doom-font (font-spec :family "FuraCode Nerd Font Mono" :size 12) ; Set font + doom-theme 'doom-molokai) ; Set theme +#+END_SRC + +* [[https://www.projectile.mx/en/latest/][Projectile]] +Set default paths [[https://www.projectile.mx/en/latest/][projectile]] should search + +#+BEGIN_SRC emacs-lisp +(use-package! projectile + :config + (setq projectile-project-search-path '("~/devel/personal/" "~/devel/andthensome/" "~/Documents/"))) ; Default paths +#+END_SRC + +* [[https://github.com/jorgenschaefer/circe][Circe]] +Connect to [[https://wiki.znc.in/ZNC][ZNC]]. Passwords are gotten from [[https://www.passwordstore.org/][Password Store]]. + +#+BEGIN_SRC emacs-lisp +(use-package! circe + :config + (setq circe-network-options + `( + ("znc-freenode" + :host "irc.higherlearning.eu" + :port "5000" + :tls t ;; Enable tls + :user "l0rd/freenode" + :server-buffer-name "⇄ Freenode (ZNC)" + :pass (lambda (&rest _) (+pass-get-secret "controlpanel/irc.higherlearning.eu")) ;; Get password from pass + :channels ("#emacs")) + ("znc-snoonet" + :host "irc.higherlearning.eu" + :port "5000" + :tls t + :user "l0rd/snoonet" + :server-buffer-name "⇄ Snoonet (ZNC)" + :pass (lambda (&rest _) (+pass-get-secret "controlpanel/irc.higherlearning.eu")) + :channels ("#islam")) + )) + :hook (circe-channel-mode . enable-lui-autopaste) + ) +#+END_SRC + +* [[https://notmuchmail.org/notmuch-emacs/][Notmuch Emacs]] +Play nice with [[https://marlam.de/msmtp/][msmtp]] and fetch mail via custom [[https://github.com/alrayyes/dotfiles/blob/master/mutt/.local/bin/mailsync][mailsync]] shell script. + +#+BEGIN_SRC emacs-lisp +(use-package! notmuch + :config + (setq +notmuch-sync-backend nil ; Needed to make sure notmuch-sync-command below is run when notmmuch is loaded + +notmuch-sync-command "mailsync" ; Command to fetch email + sendmail-program "/usr/bin/msmtp" ; Use msmtp to send mail + message-sendmail-f-is-evil t; Non-nil means don't add "-f username" to the sendmail command line. + message-sendmail-extra-arguments '("--read-envelope-from") ; Additional arguments to sendmail-program. + message-send-mail-function 'message-send-mail-with-sendmail) ; Function to call to send the current buffer as mail. + ) +#+END_SRC + +* [[https://orgmode.org/][Org mode]] +When moving entries up and down in the buffer use `j` and `k` instead of `↑` and `↓` +#+BEGIN_SRC emacs-lisp +(after! org + (map! :map org-mode-map + :n "M-j" #'org-metadown + :n "M-k" #'org-metaup) + ) +#+END_SRC + +- Set default org directory +- When task is set to `DONE` [[https://orgmode.org/manual/Closing-items.html][add timestamp and give the opportunity to type in a note]] +- Show images in buffers as default +#+BEGIN_SRC emacs-lisp +(setq org-directory "~/Documents/org" + org-agenda-files '("~/Documents/org") + org-log-done 'note + org-startup-with-inline-images t) +#+END_SRC +* Spotify +Control Spotify with [[https://github.com/Lautaro-Garcia/counsel-spotify][Counsel Spotify]] +#+BEGIN_SRC emacs-lisp +(setq counsel-spotify-client-id "03f9817b1b6946febf9a0573d28e3831" + counsel-spotify-client-secret "d03c172f1497466a953026f001f1daeb") +#+END_SRC diff --git a/emacs/.config/doom/packages.el b/emacs/.config/doom/packages.el index c018f75..b5ba6ee 100644 --- a/emacs/.config/doom/packages.el +++ b/emacs/.config/doom/packages.el @@ -5,3 +5,4 @@ ;; (package! some-package) ;; (package! another-package :recipe (:host github :repo "username/repo")) ;; (package! builtin-package :disable t) +(package! counsel-spotify) diff --git a/polybar/.config/polybar/bin/isactive-bluetooth/README.md b/polybar/.config/polybar/bin/isactive-bluetooth/README.md new file mode 100644 index 0000000..a97abb0 --- /dev/null +++ b/polybar/.config/polybar/bin/isactive-bluetooth/README.md @@ -0,0 +1,13 @@ +# Script: isactive-bluetooth + +A script that shows if bluetooth is on or off. + + +## Module + +```ini +[module/isactive-bluetooth] +type = custom/script +exec = ~/polybar-scripts/isactive-bluetooth.sh +interval = 10 +``` diff --git a/polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh b/polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh new file mode 100755 index 0000000..f16935f --- /dev/null +++ b/polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$(systemctl is-active bluetooth.service)" = "active" ]; then + echo "" +else + echo "" +fi diff --git a/polybar/.config/polybar/bin/player-mpris-tail/README.md b/polybar/.config/polybar/bin/player-mpris-tail/README.md new file mode 100644 index 0000000..dc1fe25 --- /dev/null +++ b/polybar/.config/polybar/bin/player-mpris-tail/README.md @@ -0,0 +1,164 @@ +# Script: player-mpris-tail + +This script displays the current track and the play-pause status without polling. Information is obtained by listening to MPRIS events, so it is updated instantaneously on change. + +![player-mpris-tail](screenshots/1.png) ![player-mpris-tail](screenshots/2.png) + + +## Dependencies + +* `python-dbus` +* `python-gobject` +* `python-gi` + + +## Configuration + +The format of the output can be defined by passing an `-f` or `--format` argument. This argument supports metadata replacement using `{tag}` (e.g. `{title}`) as well as more advanced formatting, described below. + +Players can be blacklisted by passing a `-b` or `--blacklist` argument. As an example, VLC can be blacklisted by passing `-b vlc`. To get a list of the current running players (and their status), run the script as `player-mpris-tail.py list`. + + +### Commands + +The current player can be controlled by passing one of the following commands: + +Command | Description +---|--- +play | Play the current track +pause | Pause the currently playing track +play-pause | Play the current track or unpause it if currently paused +stop | Stop playback +previous | Move to the previous track +next | Move to the next track +raise | Tell the current player to focus its window + +General information about the current state can be printed using the following commands: + +Command | Description +---|--- +status | Print the normal output and exit immediately +current | Print the currently detected player and its status +list | List the detected players and their status +metadata | Print the metadata object for the current track + + +### Arguments + +The following arguments are supported: + +Argument | Description | Default +---|---|--- +-b, --blacklist | Blacklist / Ignore the given player +-f, --format | Use the given `format` string | `{icon} {artist} - {title}` +--truncate-text | Use the given string as the end of truncated text | `…` +--icon-playing | Use the given text as the playing icon | `⏵` +--icon-paused | Use the given text as the paused icon | `⏸` +--icon-stopped | Use the given text as the stopped icon | `⏹` +--icon-none | Use the given text as the icon for when no player is active | `` + + +### Formatting + +Tags can be printed by surrounding them with `{` and `}`. Polybar formatting can also be given and will be passed through, including substituted tags and formatters. + + +### Tags + +The supported tags are: + +Tag | Description +---|--- +artist | The artist of the current track +album | The album of the current track +title | The title of the current track +track | The track number of the current track +length | The length of the current track +genre | The genre of the current track +disc | The disc number of the current track +date | The date of the current track +year | The year of the current track +cover | The URL of the cover of the current track +icon | The icon for the current status (playing / paused / stopped / none) +icon-reversed | The pause icon when playing, else the play icon + + +### String formatters + +Parts of the `format` string can be manipulated by surrounding them with `{:` and `:}` and prepending a formatter followed by a `:` (e.g. `{:t20:by {artist}:}`) + +The following formatters are supported: + +Formatter | Argument | Description | Example | Output +---|---|---|---|--- +`tag` | | Only print the string if `tag` exists | `{:album: on {album}:}` | ` on Album Name` +w | Number | Limit the width of the string to `number` | `{:w3:Hello:}` | `Hel` +t | Number | Truncate width of the string to `number`. If the string is shorter than or equal to `number` it is printed as given, else the string is truncated and appended a truncator text | `{:t3:Hello:}` | `He…` + + +## Module + +### Basic output + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {artist} - {title}' +tail = true +``` + +Example: `⏵ Artist - Title` + + +### Basic output and mouse controls + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {artist} - {title}' +tail = true +click-left = ~/polybar-scripts/player-mpris-tail.py previous & +click-right = ~/polybar-scripts/player-mpris-tail.py next & +click-middle = ~/polybar-scripts/player-mpris-tail.py play-pause & +``` + +Example: `⏵ Artist - Title` + + +### Output using formatters + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {:artist:t5:{artist}:}{:artist: - :}{:t4:{title}:}' +tail = true +click-left = ~/polybar-scripts/player-mpris-tail.py previous & +click-right = ~/polybar-scripts/player-mpris-tail.py next & +click-middle = ~/polybar-scripts/player-mpris-tail.py play-pause & +``` + +Example: `⏵ Artis… - Titl…` or `⏵ Titl…` + + +### Output using formatters and Polybar action handlers + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {:artist:t18:{artist}:}{:artist: - :}{:t20:{title}:} %{A1:~/polybar-scripts/player-mpris-tail.py previous:} ⏮ %{A} %{A1:~/polybar-scripts/player-mpris-tail.py play-pause:} {icon-reversed} %{A} %{A1:~/polybar-scripts/player-mpris-tail.py next:} ⏭ %{A}' +tail = true +``` + +Example: `⏵ Artis… - Titl… ⏮ ⏸ ⏭ ` or `⏵ Titl… ⏮ ⏸ ⏭ ` or `⏸ Titl… ⏮ ⏵ ⏭ ` + + +### Output using formatters, Polybar action handlers and blacklisting + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {:artist:t18:{artist}:}{:artist: - :}{:t20:{title}:} %{A1:~/polybar-scripts/player-mpris-tail.py previous -b vlc -b plasma-browser-integration:} ⏮ %{A} %{A1:~/polybar-scripts/player-mpris-tail.py play-pause -b vlc -b plasma-browser-integration:} {icon-reversed} %{A} %{A1:~/polybar-scripts/player-mpris-tail.py next -b vlc -b plasma-browser-integration:} ⏭ %{A}' -b vlc -b plasma-browser-integration +tail = true +``` + +Example: `⏵ Artis… - Titl… ⏮ ⏸ ⏭ ` or `⏵ Titl… ⏮ ⏸ ⏭ ` or `⏸ Titl… ⏮ ⏵ ⏭ ` diff --git a/polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py b/polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py new file mode 100755 index 0000000..3f6a2c4 --- /dev/null +++ b/polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 + +import sys +import dbus +import os +from operator import itemgetter +import argparse +import re +from urllib.parse import unquote +import time +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib +DBusGMainLoop(set_as_default=True) + + +FORMAT_STRING = '{icon} {artist} - {title}' +FORMAT_REGEX = re.compile(r'(\{:(?P.*?)(:(?P[wt])(?P\d+))?:(?P.*?):\})', re.I) +FORMAT_TAG_REGEX = re.compile(r'(?P[wt])(?P\d+)') +SAFE_TAG_REGEX = re.compile(r'[{}]') + +class PlayerManager: + def __init__(self, blacklist = [], connect = True): + self.blacklist = blacklist + self._connect = connect + self._session_bus = dbus.SessionBus() + self.players = {} + + self.print_queue = [] + self.connected = False + self.player_states = {} + + self.refreshPlayerList() + + if self._connect: + self.connect() + loop = GLib.MainLoop() + try: + loop.run() + except KeyboardInterrupt: + print("interrupt received, stopping…") + + def connect(self): + self._session_bus.add_signal_receiver(self.onOwnerChangedName, 'NameOwnerChanged') + self._session_bus.add_signal_receiver(self.onChangedProperties, 'PropertiesChanged', + path = '/org/mpris/MediaPlayer2', + sender_keyword='sender') + + def onChangedProperties(self, interface, properties, signature, sender = None): + if sender in self.players: + player = self.players[sender] + # If we know this player, but haven't been able to set up a signal handler + if 'properties_changed' not in player._signals: + # Then trigger the signal handler manually + player.onPropertiesChanged(interface, properties, signature) + else: + # If we don't know this player, get its name and add it + bus_name = self.getBusNameFromOwner(sender) + self.addPlayer(bus_name, sender) + player = self.players[sender] + player.onPropertiesChanged(interface, properties, signature) + + def onOwnerChangedName(self, bus_name, old_owner, new_owner): + if self.busNameIsAPlayer(bus_name): + if new_owner and not old_owner: + self.addPlayer(bus_name, new_owner) + elif old_owner and not new_owner: + self.removePlayer(old_owner) + else: + self.changePlayerOwner(bus_name, old_owner, new_owner) + + def getBusNameFromOwner(self, owner): + player_bus_names = [ bus_name for bus_name in self._session_bus.list_names() if self.busNameIsAPlayer(bus_name) ] + for player_bus_name in player_bus_names: + player_bus_owner = self._session_bus.get_name_owner(player_bus_name) + if owner == player_bus_owner: + return player_bus_name + + def busNameIsAPlayer(self, bus_name): + return bus_name.startswith('org.mpris.MediaPlayer2') and bus_name.split('.')[3] not in self.blacklist + + def refreshPlayerList(self): + player_bus_names = [ bus_name for bus_name in self._session_bus.list_names() if self.busNameIsAPlayer(bus_name) ] + for player_bus_name in player_bus_names: + self.addPlayer(player_bus_name) + if self.connected != True: + self.connected = True + self.printQueue() + + def addPlayer(self, bus_name, owner = None): + player = Player(self._session_bus, bus_name, owner = owner, connect = self._connect, _print = self.print) + self.players[player.owner] = player + + def removePlayer(self, owner): + if owner in self.players: + self.players[owner].disconnect() + del self.players[owner] + # If there are no more players, clear the output + if len(self.players) == 0: + _printFlush(ICON_NONE) + # Else, print the output of the next active player + else: + players = self.getSortedPlayerOwnerList() + if len(players) > 0: + self.players[players[0]].printStatus() + + def changePlayerOwner(self, bus_name, old_owner, new_owner): + player = Player(self._session_bus, bus_name, owner = new_owner, connect = self._connect, _print = self.print) + self.players[new_owner] = player + del self.players[old_owner] + + # Get a list of player owners sorted by current status and age + def getSortedPlayerOwnerList(self): + players = [ + { + 'number': int(owner.split('.')[-1]), + 'status': 2 if player.status == 'playing' else 1 if player.status == 'paused' else 0, + 'owner': owner + } + for owner, player in self.players.items() + ] + return [ info['owner'] for info in reversed(sorted(players, key=itemgetter('status', 'number'))) ] + + # Get latest player that's currently playing + def getCurrentPlayer(self): + playing_players = [ + player_owner for player_owner in self.getSortedPlayerOwnerList() + if + self.players[player_owner].status == 'playing' or + self.players[player_owner].status == 'paused' + ] + return self.players[playing_players[0]] if playing_players else None + + def print(self, status, player): + self.player_states[player.bus_name] = status + + if self.connected: + current_player = self.getCurrentPlayer() + if current_player != None: + _printFlush(self.player_states[current_player.bus_name]) + else: + _printFlush(ICON_STOPPED) + else: + self.print_queue.append([status, player]) + + def printQueue(self): + for args in self.print_queue: + self.print(args[0], args[1]) + self.print_queue.clear() + + +class Player: + def __init__(self, session_bus, bus_name, owner = None, connect = True, _print = None): + self._session_bus = session_bus + self.bus_name = bus_name + self._disconnecting = False + self.__print = _print + + self.metadata = { + 'artist' : '', + 'album' : '', + 'title' : '', + 'track' : 0 + } + + self._rate = 1. + self._positionAtLastUpdate = 0. + self._timeAtLastUpdate = time.time() + self._positionTimerRunning = False + + self._metadata = None + self.status = 'stopped' + self.icon = ICON_NONE + self.icon_reversed = ICON_PLAYING + if owner is not None: + self.owner = owner + else: + self.owner = self._session_bus.get_name_owner(bus_name) + self._obj = self._session_bus.get_object(self.bus_name, '/org/mpris/MediaPlayer2') + self._properties_interface = dbus.Interface(self._obj, dbus_interface='org.freedesktop.DBus.Properties') + self._introspect_interface = dbus.Interface(self._obj, dbus_interface='org.freedesktop.DBus.Introspectable') + self._media_interface = dbus.Interface(self._obj, dbus_interface='org.mpris.MediaPlayer2') + self._player_interface = dbus.Interface(self._obj, dbus_interface='org.mpris.MediaPlayer2.Player') + self._introspect = self._introspect_interface.get_dbus_method('Introspect', dbus_interface=None) + self._getProperty = self._properties_interface.get_dbus_method('Get', dbus_interface=None) + self._playerPlay = self._player_interface.get_dbus_method('Play', dbus_interface=None) + self._playerPause = self._player_interface.get_dbus_method('Pause', dbus_interface=None) + self._playerPlayPause = self._player_interface.get_dbus_method('PlayPause', dbus_interface=None) + self._playerStop = self._player_interface.get_dbus_method('Stop', dbus_interface=None) + self._playerPrevious = self._player_interface.get_dbus_method('Previous', dbus_interface=None) + self._playerNext = self._player_interface.get_dbus_method('Next', dbus_interface=None) + self._playerRaise = self._media_interface.get_dbus_method('Raise', dbus_interface=None) + self._signals = {} + + self.refreshPosition() + self.refreshStatus() + self.refreshMetadata() + + if connect: + self.printStatus() + self.connect() + + def play(self): + self._playerPlay() + def pause(self): + self._playerPause() + def playpause(self): + self._playerPlayPause() + def stop(self): + self._playerStop() + def previous(self): + self._playerPrevious() + def next(self): + self._playerNext() + def raisePlayer(self): + self._playerRaise() + + def connect(self): + if self._disconnecting is not True: + introspect_xml = self._introspect(self.bus_name, '/') + if 'TrackMetadataChanged' in introspect_xml: + self._signals['track_metadata_changed'] = self._session_bus.add_signal_receiver(self.onMetadataChanged, 'TrackMetadataChanged', self.bus_name) + self._signals['seeked'] = self._player_interface.connect_to_signal('Seeked', self.onSeeked) + self._signals['properties_changed'] = self._properties_interface.connect_to_signal('PropertiesChanged', self.onPropertiesChanged) + + def disconnect(self): + self._disconnecting = True + for signal_name, signal_handler in list(self._signals.items()): + signal_handler.remove() + del self._signals[signal_name] + + def refreshStatus(self): + # Some clients (VLC) will momentarily create a new player before removing it again + # so we can't be sure the interface still exists + try: + self.status = str(self._getProperty('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')).lower() + self.updateIcon() + self.checkPositionTimer() + except dbus.exceptions.DBusException: + self.disconnect() + + def refreshMetadata(self): + # Some clients (VLC) will momentarily create a new player before removing it again + # so we can't be sure the interface still exists + try: + self._metadata = self._getProperty('org.mpris.MediaPlayer2.Player', 'Metadata') + self._parseMetadata() + except dbus.exceptions.DBusException: + self.disconnect() + + def updateIcon(self): + self.icon = ( + ICON_PLAYING if self.status == 'playing' else + ICON_PAUSED if self.status == 'paused' else + ICON_STOPPED if self.status == 'stopped' else + ICON_NONE + ) + self.icon_reversed = ( + ICON_PAUSED if self.status == 'playing' else + ICON_PLAYING + ) + + def _print(self, status): + self.__print(status, self) + + def _parseMetadata(self): + if self._metadata != None: + artist = _getProperty(self._metadata, 'xesam:artist', ['']) + if artist != None and len(artist): + self.metadata['artist'] = re.sub(SAFE_TAG_REGEX, """\1\1""", artist[0]) + else: + artists = _getProperty(self._metadata, 'xesam:artists', ['']) + if artists != None and len(artists): + # Note: This only grabs the first artist + self.metadata['artist'] = re.sub(SAFE_TAG_REGEX, """\1\1""", artists[0]) + else: + self.metadata['artist'] = ''; + self.metadata['album'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _getProperty(self._metadata, 'xesam:album', '')) + self.metadata['title'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _getProperty(self._metadata, 'xesam:title', '')) + self.metadata['track'] = _getProperty(self._metadata, 'xesam:trackNumber', '') + length = str(_getProperty(self._metadata, 'xesam:length', '')) + if not len(length): + length = str(_getProperty(self._metadata, 'mpris:length', '')) + if len(length): + self.metadata['length'] = int(length) + else: + self.metadata['length'] = 0 + self.metadata['genre'] = _getProperty(self._metadata, 'xesam:genre', '') + self.metadata['disc'] = _getProperty(self._metadata, 'xesam:discNumber', '') + self.metadata['date'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _getProperty(self._metadata, 'xesam:contentCreated', '')) + self.metadata['year'] = re.sub(SAFE_TAG_REGEX, """\1\1""", self.metadata['date'][0:4]) + self.metadata['url'] = _getProperty(self._metadata, 'xesam:url', '') + self.metadata['filename'] = os.path.basename(self.metadata['url']) + cover = _getProperty(self._metadata, 'xesam:artUrl', '') + if not len(cover): + cover = _getProperty(self._metadata, 'mpris:artUrl', '') + if len(cover): + self.metadata['cover'] = re.sub(SAFE_TAG_REGEX, """\1\1""", cover) + else: + self.metadata['cover'] = '' + + self.metadata['duration'] = _getDuration(self.metadata['length']) + + def onMetadataChanged(self, track_id, metadata): + self.refreshMetadata() + self.printStatus() + + def onPropertiesChanged(self, interface, properties, signature): + updated = False + if dbus.String('Metadata') in properties: + _metadata = properties[dbus.String('Metadata')] + if _metadata != self._metadata: + self._metadata = _metadata + self._parseMetadata() + updated = True + if dbus.String('PlaybackStatus') in properties: + status = str(properties[dbus.String('PlaybackStatus')]).lower() + if status != self.status: + self.status = status + self.checkPositionTimer() + self.updateIcon() + updated = True + if dbus.String('Rate') in properties and dbus.String('PlaybackStatus') not in properties: + self.refreshStatus() + if NEEDS_POSITION and dbus.String('Rate') in properties: + rate = properties[dbus.String('Rate')] + if rate != self._rate: + self._rate = rate + self.refreshPosition() + + if updated: + self.refreshPosition() + self.printStatus() + + def checkPositionTimer(self): + if NEEDS_POSITION and self.status == 'playing' and not self._positionTimerRunning: + self._positionTimerRunning = True + GLib.timeout_add_seconds(1, self._positionTimer) + + def onSeeked(self, position): + self.refreshPosition() + self.printStatus() + + def _positionTimer(self): + self.printStatus() + self._positionTimerRunning = self.status == 'playing' + return self._positionTimerRunning + + def refreshPosition(self): + try: + time_us = self._getProperty('org.mpris.MediaPlayer2.Player', 'Position') + except dbus.exceptions.DBusException: + time_us = 0 + + self._timeAtLastUpdate = time.time() + self._positionAtLastUpdate = time_us / 1000000 + + def _getPosition(self): + if self.status == 'playing': + return self._positionAtLastUpdate + self._rate * (time.time() - self._timeAtLastUpdate) + else: + return self._positionAtLastUpdate + + def _statusReplace(self, match, metadata): + tag = match.group('tag') + format = match.group('format') + formatlen = match.group('formatlen') + text = match.group('text') + tag_found = False + reversed_tag = False + + if tag.startswith('-'): + tag = tag[1:] + reversed_tag = True + + if format is None: + tag_is_format_match = re.match(FORMAT_TAG_REGEX, tag) + if tag_is_format_match: + format = tag_is_format_match.group('format') + formatlen = tag_is_format_match.group('formatlen') + tag_found = True + if format is not None: + text = text.format_map(CleanSafeDict(**metadata)) + if format == 'w': + formatlen = int(formatlen) + text = text[:formatlen] + elif format == 't': + formatlen = int(formatlen) + if len(text) > formatlen: + text = text[:max(formatlen - len(TRUNCATE_STRING), 0)] + TRUNCATE_STRING + if tag_found is False and tag in metadata and len(metadata[tag]): + tag_found = True + + if reversed_tag: + tag_found = not tag_found + + if tag_found: + return text + else: + return '' + + def printStatus(self): + if self.status in [ 'playing', 'paused' ]: + metadata = { **self.metadata, 'icon': self.icon, 'icon-reversed': self.icon_reversed } + if NEEDS_POSITION: + metadata['position'] = time.strftime("%M:%S", time.gmtime(self._getPosition())) + # replace metadata tags in text + text = re.sub(FORMAT_REGEX, lambda match: self._statusReplace(match, metadata), FORMAT_STRING) + # restore polybar tag formatting and replace any remaining metadata tags after that + try: + text = re.sub(r'􏿿p􏿿(.*?)􏿿p􏿿(.*?)􏿿p􏿿(.*?)􏿿p􏿿', r'%{\1}\2%{\3}', text.format_map(CleanSafeDict(**metadata))) + except: + print("Invalid format string") + self._print(text) + else: + self._print(ICON_STOPPED) + + +def _dbusValueToPython(value): + if isinstance(value, dbus.Dictionary): + return {_dbusValueToPython(key): _dbusValueToPython(value) for key, value in value.items()} + elif isinstance(value, dbus.Array): + return [ _dbusValueToPython(item) for item in value ] + elif isinstance(value, dbus.Boolean): + return int(value) == 1 + elif ( + isinstance(value, dbus.Byte) or + isinstance(value, dbus.Int16) or + isinstance(value, dbus.UInt16) or + isinstance(value, dbus.Int32) or + isinstance(value, dbus.UInt32) or + isinstance(value, dbus.Int64) or + isinstance(value, dbus.UInt64) + ): + return int(value) + elif isinstance(value, dbus.Double): + return float(value) + elif ( + isinstance(value, dbus.ObjectPath) or + isinstance(value, dbus.Signature) or + isinstance(value, dbus.String) + ): + return unquote(str(value)) + +def _getProperty(properties, property, default = None): + value = default + if not isinstance(property, dbus.String): + property = dbus.String(property) + if property in properties: + value = properties[property] + return _dbusValueToPython(value) + else: + return value + +def _getDuration(t: int): + seconds = t / 1000000 + return time.strftime("%M:%S", time.gmtime(seconds)) + + +class CleanSafeDict(dict): + def __missing__(self, key): + return '{{{}}}'.format(key) + + +""" +Seems to assure print() actually prints when no terminal is connected +""" + +_last_status = '' +def _printFlush(status, **kwargs): + global _last_status + if status != _last_status: + print(status, **kwargs) + sys.stdout.flush() + _last_status = status + + + +parser = argparse.ArgumentParser() +parser.add_argument('command', help="send the given command to the active player", + choices=[ 'play', 'pause', 'play-pause', 'stop', 'previous', 'next', 'status', 'list', 'current', 'metadata', 'raise' ], + default=None, + nargs='?') +parser.add_argument('-b', '--blacklist', help="ignore a player by it's bus name. Can be be given multiple times (e.g. -b vlc -b audacious)", + action='append', + metavar="BUS_NAME", + default=[]) +parser.add_argument('-f', '--format', default='{icon} {:artist:{artist} - :}{:title:{title}:}{:-title:{filename}:}') +parser.add_argument('--truncate-text', default='…') +parser.add_argument('--icon-playing', default='⏵') +parser.add_argument('--icon-paused', default='⏸') +parser.add_argument('--icon-stopped', default='⏹') +parser.add_argument('--icon-none', default='') +args = parser.parse_args() + +FORMAT_STRING = re.sub(r'%\{(.*?)\}(.*?)%\{(.*?)\}', r'􏿿p􏿿\1􏿿p􏿿\2􏿿p􏿿\3􏿿p􏿿', args.format) +NEEDS_POSITION = "{position}" in FORMAT_STRING + +TRUNCATE_STRING = args.truncate_text +ICON_PLAYING = args.icon_playing +ICON_PAUSED = args.icon_paused +ICON_STOPPED = args.icon_stopped +ICON_NONE = args.icon_none + +if args.command is None: + PlayerManager(blacklist = args.blacklist) +else: + player_manager = PlayerManager(blacklist = args.blacklist, connect = False) + current_player = player_manager.getCurrentPlayer() + if args.command == 'play' and current_player: + current_player.play() + elif args.command == 'pause' and current_player: + current_player.pause() + elif args.command == 'play-pause' and current_player: + current_player.playpause() + elif args.command == 'stop' and current_player: + current_player.stop() + elif args.command == 'previous' and current_player: + current_player.previous() + elif args.command == 'next' and current_player: + current_player.next() + elif args.command == 'status' and current_player: + current_player.printStatus() + elif args.command == 'list': + print("\n".join(sorted([ + "{} : {}".format(player.bus_name.split('.')[3], player.status) + for player in player_manager.players.values() ]))) + elif args.command == 'current' and current_player: + print("{} : {}".format(current_player.bus_name.split('.')[3], current_player.status)) + elif args.command == 'metadata' and current_player: + print(_dbusValueToPython(current_player._metadata)) + elif args.command == 'raise' and current_player: + current_player.raisePlayer() diff --git a/polybar/.config/polybar/bin/player-mpris-tail/screenshots/1.png b/polybar/.config/polybar/bin/player-mpris-tail/screenshots/1.png new file mode 100644 index 0000000..686c7d6 Binary files /dev/null and b/polybar/.config/polybar/bin/player-mpris-tail/screenshots/1.png differ diff --git a/polybar/.config/polybar/bin/player-mpris-tail/screenshots/2.png b/polybar/.config/polybar/bin/player-mpris-tail/screenshots/2.png new file mode 100644 index 0000000..7aeabf3 Binary files /dev/null and b/polybar/.config/polybar/bin/player-mpris-tail/screenshots/2.png differ diff --git a/polybar/.config/polybar/bin/updates-arch-combined/README.md b/polybar/.config/polybar/bin/updates-arch-combined/README.md new file mode 100644 index 0000000..499bee7 --- /dev/null +++ b/polybar/.config/polybar/bin/updates-arch-combined/README.md @@ -0,0 +1,24 @@ +# Script: updates-arch-combined + +A script that shows if there are updates for Arch Linux and AUR updates. + +See also [updates-arch](../updates-arch) and [updates-arch-aur](../updates-arch-aur). + +![updates-arch-combined](screenshots/1.png) + + +## Dependencies + +The possibilities depend on your AUR helper. Not all helpers can report the pending updates. + +At the moment `trizen` and `cower` are documented. Take a look at the script to see how it works. + + +## Module + +```ini +[module/updates-arch-combined] +type = custom/script +exec = ~/polybar-scripts/updates-arch-combined.sh +interval = 600 +``` diff --git a/polybar/.config/polybar/bin/updates-arch-combined/screenshots/1.png b/polybar/.config/polybar/bin/updates-arch-combined/screenshots/1.png new file mode 100644 index 0000000..b69bbc3 Binary files /dev/null and b/polybar/.config/polybar/bin/updates-arch-combined/screenshots/1.png differ diff --git a/polybar/.config/polybar/bin/updates-arch-combined/updates-arch-combined.sh b/polybar/.config/polybar/bin/updates-arch-combined/updates-arch-combined.sh new file mode 100755 index 0000000..00b6c79 --- /dev/null +++ b/polybar/.config/polybar/bin/updates-arch-combined/updates-arch-combined.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +updates_arch=$(checkupdates | wc -l) + +# if ! updates_aur=$(cower -u 2> /dev/null | wc -l); then +if ! updates_aur=$(trizen -Su --aur --quiet | wc -l); then + updates_aur=0 +fi + +updates=$(("$updates_arch" + "$updates_aur")) + +if [ "$updates" -gt 0 ]; then + echo " $updates" +else + echo "" +fi diff --git a/polybar/.config/polybar/config b/polybar/.config/polybar/config new file mode 100644 index 0000000..ad9025d --- /dev/null +++ b/polybar/.config/polybar/config @@ -0,0 +1,422 @@ +;===================================================== +; +; To learn more about how to configure Polybar +; go to https://github.com/jaagr/polybar +; +; The README contains alot of information +; +;===================================================== + +[colors] +;background = ${xrdb:color0:#222} +background = #222 +background-alt = #444 +;foreground = ${xrdb:color7:#222} +foreground = #dfdfdf +foreground-alt = #555 +primary = #ffb52a +secondary = #e60053 +alert = #bd2c40 + +[bar/base] +width = 100% + +height = 27 +radius = 6.0 +fixed-center = false + +background = ${colors.background} +foreground = ${colors.foreground} + +line-size = 3 +line-color = #f00 + +border-size = 0 +border-color = #00000000 + +padding-left = 0 +padding-right = 2 + +module-margin-left = 1 +module-margin-right = 2 + +scroll-up = bspwm-desknext +scroll-down = bspwm-deskprev + +wm-restack = bspwm + +[bar/base-laptop] +inherit = "bar/base" +font-0 = FuraCode Nerd Font:pixelsize=14 + +[bar/base-desktop] +inherit = "bar/base" +font-0 = FuraCode Nerd Font:pixelsize=10 + +[bar/laptop-bottom] +inherit = bar/base-laptop +bottom = true +monitor = ${env:MONITOR:e-DP1} + +modules-center = mpd player-mpris-tail + +[bar/laptop-top] +inherit = bar/base-laptop +monitor = ${env:MONITOR:e-DP1} + +modules-left = bspwm +modules-center = tun0 tun1 wlan bluetooth +modules-right = github updates-arch-combined battery xbacklight filesystem volume memory cpu temperature date + +tray-position = right +tray-padding = 0 + +[bar/monitor1] +inherit = bar/base-desktop +monitor = ${env:MONITOR:DP-1} + +modules-left = bspwm +modules-center = mpd player-mpris-tail +modules-right = github updates-arch-combined bluetooth eth filesystem volume memory cpu temperature date + +tray-position = right +tray-padding = 0 + +[bar/monitor2] +inherit = bar/base-desktop +monitor = ${env:MONITOR:HDMI-2} +modules-left = bspwm + +[module/xwindow] +type = internal/xwindow +label = %title:0:30:...% + +[module/xkeyboard] +type = internal/xkeyboard +blacklist-0 = num lock + +format-prefix = " " +format-prefix-foreground = ${colors.foreground-alt} +format-prefix-underline = ${colors.secondary} + +label-layout = %layout% +label-layout-underline = ${colors.secondary} + +label-indicator-padding = 2 +label-indicator-margin = 1 +label-indicator-background = ${colors.secondary} +label-indicator-underline = ${colors.secondary} + +[module/filesystem] +type = internal/fs +interval = 25 + +mount-0 = / +mount-1 = /home + +label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%% +label-unmounted = %mountpoint% not mounted +label-unmounted-foreground = ${colors.foreground-alt} + +[module/bspwm] +type = internal/bspwm + +label-focused-background = ${colors.background-alt} +label-focused-underline= ${colors.primary} +label-focused-padding = 2 + +label-occupied-padding = 2 + +label-urgent-background = ${colors.alert} +label-urgent-padding = 2 + +label-empty-foreground = ${colors.foreground-alt} +label-empty-padding = 2 + +pin-workspaces = true + +[module/mpd] +type = internal/mpd +format-online = + +icon-prev =  +icon-stop =  +icon-play =  +icon-pause =  +icon-next =  + +label-song-maxlen = 25 +label-song-ellipsis = true + +[module/xbacklight] +type = internal/xbacklight + +format =