diff --git a/private_dot_config/waybar/colors.css b/private_dot_config/waybar/colors.css
new file mode 100644
index 0000000..92b58bb
--- /dev/null
+++ b/private_dot_config/waybar/colors.css
@@ -0,0 +1,28 @@
+/* @define-color bar-bg rgba(43, 48, 59, 0.5); */
+@define-color bar-bg transparent;
+@define-color bar-fg #ffffff;
+
+@define-color module-bg rgba(50, 50, 50, 0.7);
+@define-color module-fg #ffffff;
+@define-color module-fg-dim #9f9f9f;
+
+@define-color module-info-bg rgba(30, 102, 236, 0.7);
+@define-color module-succ-bg rgba(26, 238, 114, 0.74);
+@define-color module-warn-bg rgba(236, 164, 30, 0.7);
+@define-color module-err-bg rgba(224, 20, 20, 0.7);
+
+@define-color module-idle-bg rgba(80, 80,80, 0.7);
+@define-color module-active-bg rgba(255, 255, 255, 0.7);
+@define-color module-active-fg #2d3436;
+/* @define-color bar-bg rgba(0, 0, 0, 0); */
+
+@define-color main-bg #1A1916;
+@define-color main-fg #FFFFFF;
+
+@define-color wb-act-bg #B7CACF;
+@define-color wb-act-fg #FFFFFF;
+
+@define-color bright-accent #9A4192;
+
+@define-color wb-hvr-bg #ECE7D8;
+@define-color wb-hvr-fg #FFFFFF;
\ No newline at end of file
diff --git a/private_dot_config/waybar/config.jsonc b/private_dot_config/waybar/config.jsonc
new file mode 100644
index 0000000..c4f9b61
--- /dev/null
+++ b/private_dot_config/waybar/config.jsonc
@@ -0,0 +1,250 @@
+// -*- mode: jsonc -*-
+{
+ // ROOT: Root waybar configuration
+
+ // "layer": "top", // Waybar at top layer
+ //"position": "bottom", // Waybar position (top|bottom|left|right)
+ "height": 28, // Waybar height (to be removed for auto height)
+ // "width": 1280, // Waybar width
+ "spacing": 5, // Gaps between modules (5px)
+ "margin-top": 4, // Margin above bar (4px)
+ "margin-bottom": 2, // Margin below bar (2px)
+ "margin-right": 5, // Margin to the right of bar (5px)
+ "margin-left": 5, // Margin to the left of bar (5px)
+
+ // LAYOUT: Choose the order of the modules
+
+ "modules-left": [
+ "power-profiles-daemon",
+ "hyprland/workspaces",
+ "clock",
+ "custom/media"
+ ],
+ "modules-center": [
+ "hyprland/window"
+ ],
+ "modules-right": [
+ // "mpd",
+ "idle_inhibitor",
+ "pulseaudio",
+ "network",
+ "cpu",
+ "memory",
+ "temperature",
+ "backlight",
+ "keyboard-state",
+ "sway/language",
+ "battery",
+ "battery#bat2",
+ "tray"
+ //"custom/power"
+ ],
+
+
+ // CONFIG: Modules configuration
+
+ "hyprland/workspaces": {
+ "all-outputs": false,
+ "active-only": false,
+ "on-click": "activate",
+ "disable-scroll": false,
+ "on-scroll-up": "hyprctl dispatch workspace -1",
+ "on-scroll-down": "hyprctl dispatch workspace +1",
+ "persistent-workspaces": {
+ "1": [],
+ "2": [],
+ "3": [],
+ "4": [],
+ "5": [],
+ "6": [],
+ "7": [],
+ "8": [],
+ "9": [],
+ "10": []
+ },
+ "sort-by-number": true
+ },
+
+ "hyprland/window": {
+ "format": "{}",
+ "separate-outputs": true,
+ "icon": true,
+ "icon-size": 20,
+ "rewrite": {
+ "Discord": " Discord",
+ "GNU Image Manipulation Program": " GNU Image Manipulation Program",
+ "OBS(.*)": " OBS Studio",
+ "VLC media player": " VLC Media Player",
+ "ONLYOFFICE Desktop Editors": " OnlyOffice Desktop",
+ "(.*).jpg": " $1.jpg",
+ "(.*).png": " $1.png",
+ "(.*).svg": " $1.svg",
+ "/": " File Manager"
+ },
+ "min-length": 5,
+ "max-length": 45
+ },
+
+ "keyboard-state": {
+ "numlock": true,
+ "capslock": true,
+ "format": "{name} {icon}",
+ "format-icons": {
+ "locked": "",
+ "unlocked": ""
+ }
+ },
+ // "sway/mode": {
+ // "format": "{}"
+ // },
+ // "sway/scratchpad": {
+ // "format": "{icon} {count}",
+ // "show-empty": false,
+ // "format-icons": ["", ""],
+ // "tooltip": true,
+ // "tooltip-format": "{app}: {title}"
+ // },
+ // "mpd": {
+ // "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
+ // "format-disconnected": "Disconnected ",
+ // "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
+ // "unknown-tag": "N/A",
+ // "interval": 5,
+ // "consume-icons": {
+ // "on": " "
+ // },
+ // "random-icons": {
+ // "off": " ",
+ // "on": " "
+ // },
+ // "repeat-icons": {
+ // "on": " "
+ // },
+ // "single-icons": {
+ // "on": "1 "
+ // },
+ // "state-icons": {
+ // "paused": "",
+ // "playing": ""
+ // },
+ // "tooltip-format": "MPD (connected)",
+ // "tooltip-format-disconnected": "MPD (disconnected)"
+ // },
+ "idle_inhibitor": {
+ "format": "{icon}",
+ "format-icons": {
+ "activated": "",
+ "deactivated": ""
+ }
+ },
+ "tray": {
+ // "icon-size": 21,
+ "spacing": 10
+ },
+ "clock": {
+ // "timezone": "America/New_York",
+ "tooltip-format": "{:%Y %B}\n{calendar}",
+ "format-alt": "{:%Y-%m-%d}"
+ },
+ "cpu": {
+ "format": "{usage}% ",
+ "tooltip": true
+ },
+ "memory": {
+ "format": "{}% "
+ },
+ "temperature": {
+ // "thermal-zone": 2,
+ // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
+ "critical-threshold": 80,
+ // "format-critical": "{temperatureC}°C {icon}",
+ "format": "{temperatureC}°C {icon}",
+ "format-icons": ["", "", ""]
+ },
+ "backlight": {
+ // "device": "acpi_video1",
+ "format": "{percent}% {icon}",
+ "format-icons": ["", "", "", "", "", "", "", "", ""]
+ },
+ "battery": {
+ "states": {
+ // "good": 95,
+ "warning": 30,
+ "critical": 15
+ },
+ "format": "{capacity}% {icon}",
+ "format-full": "{capacity}% {icon}",
+ "format-charging": "{capacity}% ",
+ "format-plugged": "{capacity}% ",
+ "format-alt": "{time} {icon}",
+ // "format-good": "", // An empty format will hide the module
+ // "format-full": "",
+ "format-icons": [" ", " ", " ", " ", " "]
+ },
+ "battery#bat2": {
+ "bat": "BAT2"
+ },
+ "power-profiles-daemon": {
+ "format": "{icon}",
+ "tooltip-format": "Power profile: {profile}\nDriver: {driver}",
+ "tooltip": true,
+ "format-icons": {
+ "default": "",
+ "performance": "",
+ "balanced": "",
+ "power-saver": ""
+ }
+ },
+ "network": {
+ // "interface": "wlp2*", // (Optional) To force the use of this interface
+ "format-wifi": "{essid} ({signalStrength}%) ",
+ "format-ethernet": "{ipaddr}/{cidr} ",
+ "tooltip-format": "{ifname} via {gwaddr} ",
+ "format-linked": "{ifname} (No IP) ",
+ "format-disconnected": "Disconnected ⚠",
+ "format-alt": "{ifname}: {ipaddr}/{cidr}"
+ },
+ "pulseaudio": {
+ "scroll-step": 1, // %, can be a float
+ "format": "{volume}% {icon} {format_source}",
+ "format-bluetooth": "{volume}% {icon} {format_source}",
+ "format-bluetooth-muted": " {icon} {format_source}",
+ "format-muted": " {format_source}",
+ "format-source": "{volume}% ",
+ "format-source-muted": "",
+ "format-icons": {
+ "headphone": "",
+ "hands-free": "",
+ "headset": "",
+ "phone": "",
+ "portable": "",
+ "car": "",
+ "default": ["", "", ""]
+ },
+ "on-click": "pavucontrol"
+ },
+ "custom/media": {
+ "format": "{icon} {text}",
+ "return-type": "json",
+ "max-length": 40,
+ "format-icons": {
+ "spotify": "",
+ "default": "🎜"
+ },
+ "escape": true,
+ "exec": "python $HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
+ // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
+ },
+ "custom/power": {
+ "format" : "⏻ ",
+ "tooltip": false,
+ "menu": "on-click",
+ "menu-file": "$HOME/.config/waybar/power_menu.xml", // Menu file in resources folder
+ "menu-actions": {
+ "shutdown": "shutdown",
+ "reboot": "reboot",
+ "suspend": "systemctl suspend",
+ "hibernate": "systemctl hibernate"
+ }
+ }
+}
diff --git a/private_dot_config/waybar/mediaplayer.py b/private_dot_config/waybar/mediaplayer.py
new file mode 100644
index 0000000..4275df2
--- /dev/null
+++ b/private_dot_config/waybar/mediaplayer.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+import gi
+gi.require_version("Playerctl", "2.0")
+from gi.repository import Playerctl, GLib
+from gi.repository.Playerctl import Player
+import argparse
+import logging
+import sys
+import signal
+import gi
+import json
+import os
+from typing import List
+
+logger = logging.getLogger(__name__)
+
+def signal_handler(sig, frame):
+ logger.info("Received signal to stop, exiting")
+ sys.stdout.write("\n")
+ sys.stdout.flush()
+ # loop.quit()
+ sys.exit(0)
+
+
+class PlayerManager:
+ def __init__(self, selected_player=None, excluded_player=[]):
+ self.manager = Playerctl.PlayerManager()
+ self.loop = GLib.MainLoop()
+ self.manager.connect(
+ "name-appeared", lambda *args: self.on_player_appeared(*args))
+ self.manager.connect(
+ "player-vanished", lambda *args: self.on_player_vanished(*args))
+
+ signal.signal(signal.SIGINT, signal_handler)
+ signal.signal(signal.SIGTERM, signal_handler)
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ self.selected_player = selected_player
+ self.excluded_player = excluded_player.split(',') if excluded_player else []
+
+ self.init_players()
+
+ def init_players(self):
+ for player in self.manager.props.player_names:
+ if player.name in self.excluded_player:
+ continue
+ if self.selected_player is not None and self.selected_player != player.name:
+ logger.debug(f"{player.name} is not the filtered player, skipping it")
+ continue
+ self.init_player(player)
+
+ def run(self):
+ logger.info("Starting main loop")
+ self.loop.run()
+
+ def init_player(self, player):
+ logger.info(f"Initialize new player: {player.name}")
+ player = Playerctl.Player.new_from_name(player)
+ player.connect("playback-status",
+ self.on_playback_status_changed, None)
+ player.connect("metadata", self.on_metadata_changed, None)
+ self.manager.manage_player(player)
+ self.on_metadata_changed(player, player.props.metadata)
+
+ def get_players(self) -> List[Player]:
+ return self.manager.props.players
+
+ def write_output(self, text, player):
+ logger.debug(f"Writing output: {text}")
+
+ output = {"text": text,
+ "class": "custom-" + player.props.player_name,
+ "alt": player.props.player_name}
+
+ sys.stdout.write(json.dumps(output) + "\n")
+ sys.stdout.flush()
+
+ def clear_output(self):
+ sys.stdout.write("\n")
+ sys.stdout.flush()
+
+ def on_playback_status_changed(self, player, status, _=None):
+ logger.debug(f"Playback status changed for player {player.props.player_name}: {status}")
+ self.on_metadata_changed(player, player.props.metadata)
+
+ def get_first_playing_player(self):
+ players = self.get_players()
+ logger.debug(f"Getting first playing player from {len(players)} players")
+ if len(players) > 0:
+ # if any are playing, show the first one that is playing
+ # reverse order, so that the most recently added ones are preferred
+ for player in players[::-1]:
+ if player.props.status == "Playing":
+ return player
+ # if none are playing, show the first one
+ return players[0]
+ else:
+ logger.debug("No players found")
+ return None
+
+ def show_most_important_player(self):
+ logger.debug("Showing most important player")
+ # show the currently playing player
+ # or else show the first paused player
+ # or else show nothing
+ current_player = self.get_first_playing_player()
+ if current_player is not None:
+ self.on_metadata_changed(current_player, current_player.props.metadata)
+ else:
+ self.clear_output()
+
+ def on_metadata_changed(self, player, metadata, _=None):
+ logger.debug(f"Metadata changed for player {player.props.player_name}")
+ player_name = player.props.player_name
+ artist = player.get_artist()
+ title = player.get_title()
+ title = title.replace("&", "&")
+
+ track_info = ""
+ if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]:
+ track_info = "Advertisement"
+ elif artist is not None and title is not None:
+ track_info = f"{artist} - {title}"
+ else:
+ track_info = title
+
+ if track_info:
+ if player.props.status == "Playing":
+ track_info = " " + track_info
+ else:
+ track_info = " " + track_info
+ # only print output if no other player is playing
+ current_playing = self.get_first_playing_player()
+ if current_playing is None or current_playing.props.player_name == player.props.player_name:
+ self.write_output(track_info, player)
+ else:
+ logger.debug(f"Other player {current_playing.props.player_name} is playing, skipping")
+
+ def on_player_appeared(self, _, player):
+ logger.info(f"Player has appeared: {player.name}")
+ if player.name in self.excluded_player:
+ logger.debug(
+ "New player appeared, but it's in exclude player list, skipping")
+ return
+ if player is not None and (self.selected_player is None or player.name == self.selected_player):
+ self.init_player(player)
+ else:
+ logger.debug(
+ "New player appeared, but it's not the selected player, skipping")
+
+ def on_player_vanished(self, _, player):
+ logger.info(f"Player {player.props.player_name} has vanished")
+ self.show_most_important_player()
+
+def parse_arguments():
+ parser = argparse.ArgumentParser()
+
+ # Increase verbosity with every occurrence of -v
+ parser.add_argument("-v", "--verbose", action="count", default=0)
+
+ parser.add_argument("-x", "--exclude", "- Comma-separated list of excluded player")
+
+ # Define for which player we"re listening
+ parser.add_argument("--player")
+
+ parser.add_argument("--enable-logging", action="store_true")
+
+ return parser.parse_args()
+
+
+def main():
+ arguments = parse_arguments()
+
+ # Initialize logging
+ if arguments.enable_logging:
+ logfile = os.path.join(os.path.dirname(
+ os.path.realpath(__file__)), "media-player.log")
+ logging.basicConfig(filename=logfile, level=logging.DEBUG,
+ format="%(asctime)s %(name)s %(levelname)s:%(lineno)d %(message)s")
+
+ # Logging is set by default to WARN and higher.
+ # With every occurrence of -v it's lowered by one
+ logger.setLevel(max((3 - arguments.verbose) * 10, 0))
+
+ logger.info("Creating player manager")
+ if arguments.player:
+ logger.info(f"Filtering for player: {arguments.player}")
+ if arguments.exclude:
+ logger.info(f"Exclude player {arguments.exclude}")
+
+ player = PlayerManager(arguments.player, arguments.exclude)
+ player.run()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/private_dot_config/waybar/style.css b/private_dot_config/waybar/style.css
new file mode 100644
index 0000000..66cff32
--- /dev/null
+++ b/private_dot_config/waybar/style.css
@@ -0,0 +1,349 @@
+* {
+ border: none;
+ /* `otf-font-awesome` is required to be installed for icons */
+ font-family: "JetBrainsMono Nerd Font";
+ /* font-weight: bold; */
+ font-size: 13px;
+ min-height: 11px;
+}
+
+@import "colors.css";
+
+window#waybar {
+ background-color: @bar-bg;
+ color: @bar-fg;
+ transition-property: background-color;
+ transition-duration: .5s;
+}
+
+window#waybar.hidden {
+ opacity: 0.2;
+}
+
+/*
+window#waybar.empty {
+ background-color: transparent;
+}
+window#waybar.solo {
+ background-color: #FFFFFF;
+}
+*/
+
+window#waybar.termite {
+ background-color: #3F3F3F;
+}
+
+window#waybar.chromium {
+ background-color: #000000;
+ border: none;
+}
+
+button {
+ /* Use box-shadow instead of border so the text isn't offset */
+ box-shadow: inset 0 -3px transparent;
+ /* Avoid rounded borders under each button name */
+ border: none;
+ border-radius: 0;
+}
+
+/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
+button:hover {
+ background: inherit;
+ box-shadow: inset 0 -3px #ffffff;
+}
+
+/* you can set a style on hover for any module like this */
+#pulseaudio:hover {
+ background-color: #a37800;
+}
+
+#workspaces button {
+ padding: 0 3px;
+ margin: 3px 0;
+ border-radius: 5px;
+ background-color: transparent;
+ color: @module-fg;
+}
+
+#workspaces button:hover {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+#workspaces button.empty {
+ color: @module-fg-dim;
+}
+
+#workspaces button.active {
+ background-color: @module-idle-bg;
+}
+
+#workspaces button.focused {
+ background-color: #64727D;
+ /* box-shadow: inset 0 -3px #ffffff; */
+}
+
+#workspaces button.urgent {
+ background-color: @module-info-bg;
+}
+
+#mode {
+ background-color: #64727D;
+ box-shadow: inset 0 -3px #ffffff;
+}
+
+#clock,
+#battery,
+#cpu,
+#memory,
+#disk,
+#temperature,
+#backlight,
+#network,
+#pulseaudio,
+#wireplumber,
+#custom-media,
+#tray,
+#mode,
+#idle_inhibitor,
+#scratchpad,
+#power-profiles-daemon,
+#workspaces,
+#window,
+#mpd {
+ padding: 0 10px;
+ color: @module-fg;
+ background-color: @module-bg;
+ border-radius: 8px;
+ border: 1px solid @module-idle-bg;
+}
+
+/* #window,
+#workspaces {
+ margin: 0 4px;
+} */
+
+/* If workspaces is the leftmost module, override left margin */
+.modules-left > widget:first-child > #workspaces {
+ margin-left: 5px;
+}
+
+/* If workspaces is the rightmost module, override right margin */
+.modules-right > widget:last-child > #workspaces {
+ margin-right: 5px;
+}
+
+/* #workspaces {
+ background-color: #1ca000;
+} */
+
+/* #clock {
+ background-color: #64727D;
+} */
+
+/* #battery {
+ background-color: #ffffff;
+ color: #000000;
+} */
+
+#battery.charging, #battery.plugged {
+ background-color: @module-succ-bg;
+}
+
+@keyframes blink {
+ to {
+ background-color: @module-bg;
+ color: @module-fg;
+ }
+}
+
+/* Using steps() instead of linear as a timing function to limit cpu usage */
+#battery.critical:not(.charging) {
+ background-color: @module-err-bg;
+ color: #ffffff;
+ animation-name: blink;
+ animation-duration: 0.5s;
+ animation-timing-function: steps(12);
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+}
+
+#power-profiles-daemon {
+ padding-right: 15px;
+}
+
+#power-profiles-daemon.performance {
+ background-color: @module-warn-bg;
+}
+
+#power-profiles-daemon.balanced {
+ background-color: @module-info-bg;
+ color: #ffffff;
+}
+
+#power-profiles-daemon.power-saver {
+ background-color: @module-succ-bg;
+ color: #000000;
+}
+
+label:focus {
+ background-color: #000000;
+}
+
+/* #cpu {
+ background-color: #2ecc71;
+ color: #000000;
+} */
+
+/* #memory {
+ background-color: #9b59b6;
+} */
+
+/* #disk {
+ background-color: #964B00;
+} */
+
+/* #backlight {
+ background-color: #90b1b1;
+} */
+
+/* #network {
+ background-color: #2980b9;
+} */
+
+/* #network.disconnected {
+ background-color: #f53c3c;
+} */
+
+/* #pulseaudio {
+ background-color: #f1c40f;
+ color: #000000;
+} */
+
+#pulseaudio.muted {
+ background-color: @module-warn-bg;
+}
+
+/* #wireplumber {
+ background-color: #fff0f5;
+ color: #000000;
+} */
+
+/* #wireplumber.muted {
+ background-color: #f53c3c;
+} */
+
+/* #custom-media {
+ background-color: #66cc99;
+ color: #2a5c45;
+ min-width: 100px;
+} */
+
+#custom-media.custom-spotify {
+ background: rgb(26,248,114);
+ background: linear-gradient(50deg, rgba(26,248,114,0.7) 0%, rgba(50,50,50,0.7) 100%);
+}
+
+#custom-media.custom-vlc {
+ background: rgb(255,160,60);
+ background: linear-gradient(50deg, rgba(255,160,60,0.7) 0%, rgba(50,50,50,0.7) 100%);
+}
+
+/* #temperature {
+ background-color: #f0932b;
+} */
+
+#temperature.critical {
+ background-color: @module-err-bg;
+}
+
+/* #tray {
+ background-color: #2980b9;
+} */
+
+#tray > .passive {
+ -gtk-icon-effect: dim;
+}
+
+#tray > .needs-attention {
+ -gtk-icon-effect: highlight;
+ background-color: @module-info-bg;
+}
+
+#idle_inhibitor {
+ background-color: @module-bg;
+}
+
+#idle_inhibitor.activated {
+ background-color: @module-active-bg;
+ color: @module-active-fg;
+}
+
+/* #mpd {
+ background-color: #66cc99;
+ color: #2a5c45;
+} */
+
+#mpd.disconnected {
+ background-color: @module-err-bg;
+}
+
+#mpd.stopped {
+ background-color: @module-idle-bg;
+}
+
+#mpd.paused {
+ background-color: @module-idle-bg;
+}
+
+#language {
+ /* background: #00b093;
+ color: #740864; */
+ padding: 0 5px;
+ margin: 0 5px;
+ min-width: 16px;
+}
+
+#keyboard-state {
+ /* background: #97e1ad;
+ color: #000000; */
+ padding: 0 0px;
+ margin: 0 5px;
+ min-width: 16px;
+}
+
+#keyboard-state > label {
+ padding: 0 5px;
+}
+
+#keyboard-state > label.locked {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+#scratchpad {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+#scratchpad.empty {
+ background-color: transparent;
+}
+
+#privacy {
+ padding: 0;
+}
+
+#privacy-item {
+ padding: 0 5px;
+ color: white;
+}
+
+#privacy-item.screenshare {
+ background-color: #cf5700;
+}
+
+#privacy-item.audio-in {
+ background-color: #1ca000;
+}
+
+#privacy-item.audio-out {
+ background-color: #0069d4;
+}