mirror of
https://github.com/dcarrillo/dotfiles.git
synced 2025-10-01 04:59:09 +00:00
[polybar] Add polytasks module
This commit is contained in:
@@ -60,7 +60,7 @@ font-4 = "NotoSans-Regular:size=28:weight=bold:antialias=true;-11
|
|||||||
font-5 = "FontAwesome:size=21:antialias=true;4"
|
font-5 = "FontAwesome:size=21:antialias=true;4"
|
||||||
font-6 = "Font Awesome 6 Brands-Regular-400:size=32:antialias=true;1"
|
font-6 = "Font Awesome 6 Brands-Regular-400:size=32:antialias=true;1"
|
||||||
|
|
||||||
modules-left = polywins
|
modules-left = polytasks
|
||||||
modules-center = custom_date
|
modules-center = custom_date
|
||||||
modules-right = updates cpu_bar memory_bar docker vpn network_status network_usage syncthing_status pulseaudio tray
|
modules-right = updates cpu_bar memory_bar docker vpn network_status network_usage syncthing_status pulseaudio tray
|
||||||
|
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
[ -f ~/.config/polybar/bar.env ] && . ~/.config/polybar/bar.env
|
POLYBAR_PATH=~/.config/polybar
|
||||||
|
|
||||||
|
[ -f $POLYBAR_PATH/bar.env ] && . $POLYBAR_PATH/bar.env
|
||||||
|
|
||||||
export TERMINAL_CMD=${TERMINAL_CMD:-"kitty --class=info --override='foreground=#c69026' "}
|
export TERMINAL_CMD=${TERMINAL_CMD:-"kitty --class=info --override='foreground=#c69026' "}
|
||||||
export BROWSER_CMD=${BROWSER_CMD:-"firefox"}
|
export BROWSER_CMD=${BROWSER_CMD:-"firefox"}
|
||||||
export WM_CONTROL=${WM_CONTROL:-"~/.config/polybar/scripts/switch_window_state"}
|
export WM_CONTROL=${WM_CONTROL:-"$POLYBAR_PATH/scripts/switch_window_state"}
|
||||||
export ROFI_THEME=${ROFI_THEME:-orange}
|
export ROFI_THEME=${ROFI_THEME:-orange}
|
||||||
|
|
||||||
function wait_for_polybar
|
function wait_for_polybar
|
||||||
@@ -25,12 +27,21 @@ function kill_polybar
|
|||||||
wait_for_polybar stopped
|
wait_for_polybar stopped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compile_src
|
||||||
|
{
|
||||||
|
pushd $POLYBAR_PATH/scripts/src/polytasks || return
|
||||||
|
echo "Compiling polytasks..."
|
||||||
|
go build -buildvcs=false -ldflags="-s -w" -o $POLYBAR_PATH/scripts/polytasks .
|
||||||
|
popd || return
|
||||||
|
}
|
||||||
|
|
||||||
function launch_polybar
|
function launch_polybar
|
||||||
{
|
{
|
||||||
for monitor in $(polybar --list-monitors | cut -d":" -f1); do
|
for monitor in $(polybar --list-monitors | cut -d":" -f1); do
|
||||||
export MONITOR=$monitor
|
export MONITOR=$monitor
|
||||||
polybar top -c ~/.config/polybar/bar.ini >/dev/null &
|
polybar top -c $POLYBAR_PATH/bar.ini >/dev/null &
|
||||||
done
|
done
|
||||||
|
compile_src
|
||||||
wait_for_polybar started
|
wait_for_polybar started
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -147,9 +147,9 @@ tail = true
|
|||||||
interval = 5
|
interval = 5
|
||||||
click-left = $TERMINAL_CMD yay -Suy &
|
click-left = $TERMINAL_CMD yay -Suy &
|
||||||
|
|
||||||
[module/polywins]
|
[module/polytasks]
|
||||||
type = custom/script
|
type = custom/script
|
||||||
exec = ~/.config/polybar/scripts/polywins 2>/dev/null
|
exec = ~/.config/polybar/scripts/polytasks
|
||||||
format = <label>
|
format = <label>
|
||||||
label = "%output%"
|
label = "%output%"
|
||||||
label-padding = 0
|
label-padding = 0
|
||||||
|
5
.config/polybar/scripts/src/polytasks/go.mod
Normal file
5
.config/polybar/scripts/src/polytasks/go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require github.com/godbus/dbus/v5 v5.1.0
|
2
.config/polybar/scripts/src/polytasks/go.sum
Normal file
2
.config/polybar/scripts/src/polytasks/go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
308
.config/polybar/scripts/src/polytasks/main.go
Normal file
308
.config/polybar/scripts/src/polytasks/main.go
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var iconMap = map[string]string{
|
||||||
|
"brave-browser": "",
|
||||||
|
"chromium": "",
|
||||||
|
"code": "",
|
||||||
|
"firefox": "",
|
||||||
|
"glrnvim": "",
|
||||||
|
"keepassxc": "",
|
||||||
|
"info": "",
|
||||||
|
"kitty": "",
|
||||||
|
"nuclear": "",
|
||||||
|
"nvim": "",
|
||||||
|
"org.gnome.calculator": "",
|
||||||
|
"org.gnome.calendar": "",
|
||||||
|
"org.gnome.console": "",
|
||||||
|
"org.gnome.eog": "",
|
||||||
|
"org.gnome.evince": "",
|
||||||
|
"org.gnome.fileroller": "",
|
||||||
|
"org.gnome.nautilus": "",
|
||||||
|
"org.gnome.settings": "",
|
||||||
|
"org.telegram.desktop": "",
|
||||||
|
"seahorse": "",
|
||||||
|
"slack": "",
|
||||||
|
"spotube": "",
|
||||||
|
"steam": "",
|
||||||
|
"thunderbird": "",
|
||||||
|
"vpn": "",
|
||||||
|
"tor-browser": "",
|
||||||
|
"vivaldi-stable": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var blacklistedClasses = map[string]bool{
|
||||||
|
"": true,
|
||||||
|
"polybar": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var whatIsMyPath string
|
||||||
|
|
||||||
|
const (
|
||||||
|
activeTextColor = "#F5A70A"
|
||||||
|
activeLeft = "%{F" + activeTextColor + "}"
|
||||||
|
activeRight = "%{F-}"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WindowInfo struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Class string `json:"class"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalJS(conn *dbus.Conn, js string) (bool, string, error) {
|
||||||
|
obj := conn.Object("org.gnome.Shell", "/org/gnome/Shell")
|
||||||
|
|
||||||
|
call := obj.Call("org.gnome.Shell.Eval", 0, js)
|
||||||
|
if call.Err != nil {
|
||||||
|
return false, "", call.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
var success bool
|
||||||
|
var value string
|
||||||
|
if err := call.Store(&success, &value); err != nil {
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return success, value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkUnsafeMode(conn *dbus.Conn) (bool, error) {
|
||||||
|
js := `global.context ? global.context.unsafe_mode : false`
|
||||||
|
|
||||||
|
success, value, err := evalJS(conn, js)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to check unsafe mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
var unsafeMode bool
|
||||||
|
if err := json.Unmarshal([]byte(value), &unsafeMode); err != nil {
|
||||||
|
return false, fmt.Errorf("failed to parse unsafe mode value: %w", err)
|
||||||
|
}
|
||||||
|
return unsafeMode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("failed to execute unsafe mode check")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWindowList(conn *dbus.Conn) ([]WindowInfo, error) {
|
||||||
|
js := `
|
||||||
|
global
|
||||||
|
.get_window_actors()
|
||||||
|
.map(a => a.meta_window)
|
||||||
|
.map(w => ({id: w.get_id(), class: w.get_wm_class()}))
|
||||||
|
`
|
||||||
|
|
||||||
|
success, value, err := evalJS(conn, js)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get window list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allWindows []WindowInfo
|
||||||
|
if success {
|
||||||
|
if err := json.Unmarshal([]byte(value), &allWindows); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse window list: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []WindowInfo
|
||||||
|
for _, win := range allWindows {
|
||||||
|
if win.Class != "" && !blacklistedClasses[strings.ToLower(win.Class)] {
|
||||||
|
result = append(result, win)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
return result[i].Class < result[j].Class
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveWindow(conn *dbus.Conn) (int, error) {
|
||||||
|
js := `global.get_window_actors().find(a => a.meta_window.has_focus())?.meta_window.get_id()`
|
||||||
|
|
||||||
|
success, value, err := evalJS(conn, js)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get active window: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
var id int
|
||||||
|
if err := json.Unmarshal([]byte(value), &id); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to parse active window ID: %w", err)
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeWindow(conn *dbus.Conn, windowID int) error {
|
||||||
|
js := fmt.Sprintf(`
|
||||||
|
const window = global.get_window_actors()
|
||||||
|
.map(a => a.meta_window)
|
||||||
|
.find(w => w.get_id() === %d);
|
||||||
|
if (window) {
|
||||||
|
window.delete(global.get_current_time());
|
||||||
|
}
|
||||||
|
`, windowID)
|
||||||
|
|
||||||
|
success, _, err := evalJS(conn, js)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute close window script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("failed to close window with ID %d", windowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func focusOrMinimize(conn *dbus.Conn, windowID int) error {
|
||||||
|
js := fmt.Sprintf(`
|
||||||
|
const window = global.get_window_actors()
|
||||||
|
.map(a => a.meta_window)
|
||||||
|
.find(w => w.get_id() === %d);
|
||||||
|
|
||||||
|
if (window) {
|
||||||
|
if (window.has_focus()) {
|
||||||
|
window.minimize();
|
||||||
|
} else {
|
||||||
|
window.activate(global.get_current_time());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, windowID)
|
||||||
|
|
||||||
|
success, _, err := evalJS(conn, js)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute raise or minimize window script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("failed to raise or minimize window with ID %d", windowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func polyPrintWindowList(conn *dbus.Conn) error {
|
||||||
|
windows, err := getWindowList(conn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get window list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(windows) > 0 {
|
||||||
|
fmt.Print("%{T4}Tasks: %{T-}")
|
||||||
|
}
|
||||||
|
for _, win := range windows {
|
||||||
|
activeID, err := getActiveWindow(conn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get active window: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
icon := getIcon(win.Class)
|
||||||
|
if win.ID == activeID {
|
||||||
|
icon = fmt.Sprintf("%s%s%s", activeLeft, icon, activeRight)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %%{A1:%s focus_or_minimize %d:}", whatIsMyPath, win.ID)
|
||||||
|
fmt.Printf("%%{A2:%s close %d:}", whatIsMyPath, win.ID)
|
||||||
|
fmt.Printf("%%{T6}%s%%{T-}%%{A}%%{A}", icon)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCLICommands(conn *dbus.Conn, args []string) error {
|
||||||
|
if len(args) != 3 {
|
||||||
|
return fmt.Errorf("invalid number of arguments, expected: <command> <window_id>")
|
||||||
|
}
|
||||||
|
|
||||||
|
command := args[1]
|
||||||
|
windowIDStr := args[2]
|
||||||
|
|
||||||
|
windowID, err := strconv.Atoi(windowIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid window ID '%s': %w", windowIDStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case "close":
|
||||||
|
return closeWindow(conn, windowID)
|
||||||
|
case "focus_or_minimize":
|
||||||
|
return focusOrMinimize(conn, windowID)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown command '%s', supported commands: close, focus", command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIcon(class string) string {
|
||||||
|
if icon, exists := iconMap[strings.ReplaceAll(strings.ToLower(class), " ", "-")]; exists {
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
return strings.ToUpper(class[:1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
whatIsMyPath, err = os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to get executable path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dbus.SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
if err := handleCLICommands(conn, os.Args); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
var lastWindowCount int
|
||||||
|
var lastActiveWindow int
|
||||||
|
for range ticker.C {
|
||||||
|
if safe, err := checkUnsafeMode(conn); err != nil || !safe {
|
||||||
|
fmt.Println("GNOME Shell unsafe mode is not enabled.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
windows, err := getWindowList(conn)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
activeID, err := getActiveWindow(conn)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(windows) != lastWindowCount || activeID != lastActiveWindow {
|
||||||
|
if err := polyPrintWindowList(conn); err == nil {
|
||||||
|
lastWindowCount = len(windows)
|
||||||
|
lastActiveWindow = activeID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user