tenseleyflow/ndotfiles / 6bcadd8

Browse files

waybar

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6bcadd8e781b87f44d14b1ab5cfccb0cef075696
Parents
76a2ea1
Tree
95b3715

10 changed files

StatusFile+-
A waybar/config 396 0
A waybar/mediaplayer.py 127 0
A waybar/modules/almanta.sh 67 0
A waybar/modules/jetbrains_menu.sh 77 0
A waybar/modules/mail.py 42 0
A waybar/modules/spotify.sh 18 0
A waybar/modules/storage.sh 24 0
A waybar/modules/weather.sh 79 0
A waybar/style.css 360 0
A waybar/waybar.sh 10 0
waybar/configadded
@@ -0,0 +1,396 @@
1
+{
2
+    // -------------------------------------------------------------------------
3
+    // Global configuration
4
+    // -------------------------------------------------------------------------
5
+
6
+    "layer": "top",
7
+
8
+    "position": "top",
9
+
10
+    //"height": 20,
11
+
12
+    "margin-left": 10,
13
+    "margin-bottom": 0,
14
+    "margin-right": 10,
15
+
16
+    "spacing": 5, // Gaps between modules (4px)
17
+
18
+    "modules-left": [
19
+        //"custom/rofi",
20
+        "hyprland/workspaces",
21
+        //"hyprland/submap",
22
+        "temperature",
23
+        //"idle_inhibitor",
24
+        //"mpd"
25
+        "custom/spotify"
26
+    ],
27
+    "modules-center": [
28
+        //"hyprland/window"
29
+        "clock#date",
30
+        "custom/weather"
31
+        //"custom/gammastep"
32
+    ],
33
+    "modules-right": [
34
+        "backlight",
35
+        "custom/storage",
36
+        "memory",
37
+        "cpu",
38
+        "battery",
39
+        //"pulseaudio",
40
+        "wireplumber",
41
+        "custom/almanta",
42
+        "custom/screenshot_t",
43
+        "custom/jetbrains",
44
+        "tray",
45
+        "custom/power"
46
+    ],
47
+
48
+
49
+    // -------------------------------------------------------------------------
50
+    // Modules
51
+    // -------------------------------------------------------------------------
52
+
53
+    "custom/sp1": {
54
+        "format": " | ",
55
+        "tooltip": false
56
+    },
57
+    "custom/sp2": {
58
+        "format": " |",
59
+        "tooltip": false
60
+    },
61
+
62
+    "custom/jetbrains": {
63
+      "format": "",                         // Nerd Font IntelliJ glyph; fallback: ""
64
+      "tooltip": "JetBrains (click to choose IDE; right-click: Toolbox)",
65
+      "on-click": "~/.config/waybar/modules/jetbrains_menu.sh",
66
+      "on-click-right": "jetbrains-toolbox",
67
+      "interval": 0,
68
+      "class": "chip jetbrains",
69
+    },
70
+
71
+	"custom/almanta": {
72
+	  "format": "{}",
73
+	  "return-type": "json",
74
+	  "interval": 20,
75
+	  "exec": "/home/mfwolffe/.config/waybar/modules/almanta.sh",
76
+	  "on-click": "alacritty -e ssh espadon@almanta",
77
+	  "tooltip": true,
78
+	  "class": "chip almanta"
79
+	},
80
+    
81
+    "custom/rofi": {
82
+        "format": "",
83
+        "tooltip": false,
84
+        "on-click-right": "nwg-drawer",
85
+        "on-click": "wofi --show run",
86
+        "on-click-middle": "pkill -9 wofi"
87
+    },
88
+    "custom/screenshot_t":{
89
+        "format":" ",
90
+        "on-click": "~/.config/hypr/scripts/screenshot_full",
91
+        "on-click-right":"~/.config/hypr/scripts/screenshot_area"
92
+    },
93
+
94
+    "clock#1": {
95
+        "format": " {:%a}",
96
+        "tooltip": false,
97
+        "on-click": "gsimplecal"
98
+    },
99
+    "clock#2": {
100
+        "format": " {:%d-%h-%Y}",
101
+        "tooltip": false,
102
+        "on-click": "gsimplecal"
103
+    },
104
+    "clock#3": {
105
+        "format": " {:%H:%M:%S %p}",
106
+        "tooltip": false,
107
+        "on-click": "gsimplecal"
108
+    },
109
+
110
+    "temperature": {
111
+        // "thermal-zone": 1,
112
+        "interval": 4,
113
+        //"hwmon-path": "/sys/class/hwmon/hwmon3/temp1_input",
114
+        "critical-threshold": 80,
115
+        // "format-critical": " {temperatureC}°C",
116
+        "format-critical": "  {temperatureC}°C",
117
+        "format": "{icon}  {temperatureC}°C",
118
+        "format-icons": ["", "", ""],
119
+        "max-length": 7,
120
+        "min-length": 7,
121
+        "on-click": "xsensors"
122
+    },
123
+
124
+    "memory": {
125
+        "interval": 30,
126
+        "format": "  {used:0.2f} / {total:0.0f} GB",
127
+        "on-click": "alacritty -e btop"
128
+    },
129
+
130
+    "battery": {
131
+        "interval": 2,
132
+        "states": {
133
+            "good": 95,
134
+            "warning": 30,
135
+            "critical": 15
136
+        },
137
+        "format": "{icon} {capacity}%",
138
+        "format-charging": " {capacity}%",
139
+        "format-plugged": " {capacity}%",
140
+        "format-icons": [
141
+            "",
142
+            "",
143
+            "",
144
+            "",
145
+            ""
146
+        ]
147
+    },
148
+    "network": {
149
+        "format-wifi": " {essid} ({signalStrength}%)",
150
+        "format-ethernet": "{ifname}: {ipaddr}/{cidr} ",
151
+        "format-linked": "{ifname} (No IP) ",
152
+        "format": "",
153
+        "format-disconnected": "",
154
+        "format-alt": "{ifname}: {ipaddr}/{cidr}",
155
+        "on-click": "wl-copy $(ip address show up scope global | grep inet | head -n1 | cut -d/ -f 1 | tr -d [:space:] | cut -c5-)",
156
+        "on-click-right": "wl-copy $(ip address show up scope global | grep inet6 | head -n1 | cut -d/ -f 1 | tr -d [:space:] | cut -c6-)",
157
+        "tooltip-format": " {bandwidthUpBits}  {bandwidthDownBits}\n{ifname}\n{ipaddr}/{cidr}\n",
158
+        "tooltip-format-wifi": " {essid} {frequency}MHz\nStrength: {signaldBm}dBm ({signalStrength}%)\nIP: {ipaddr}/{cidr}\n {bandwidthUpBits}  {bandwidthDownBits}",
159
+        "interval": 10
160
+    },
161
+    "custom/storage": {
162
+        "format": " {}",
163
+        "format-alt": "{percentage}% ",
164
+        "format-alt-click": "click-right",
165
+        "return-type": "json",
166
+        "interval": 60,
167
+        "exec": "~/.config/waybar/modules/storage.sh"
168
+    },
169
+
170
+    "backlight": {
171
+        "device": "intel_backlight",
172
+        "format": "{icon} {percent}%",
173
+        "format-alt": "{percent}% {icon}",
174
+        "format-alt-click": "click-right",
175
+        //"format-icons": ["", ""],
176
+        "format-icons": ["", ""],
177
+        "on-scroll-down": "brightnessctl s 5%-",
178
+        "on-scroll-up": "brightnessctl s +5%"
179
+    },
180
+    "idle_inhibitor": {
181
+        "format": "{icon}",
182
+        "format-icons": {
183
+            "activated": "",
184
+            "deactivated": ""
185
+        },
186
+        "tooltip": "true"
187
+    },
188
+    "custom/weather": {
189
+        "format": "{}",
190
+        "format-alt": "{alt}: {}",
191
+        "format-alt-click": "click-right",
192
+        "interval": 3600,
193
+        "exec": "curl -s 'https://wttr.in/?format=1'",
194
+        //"return-type": "json",
195
+        //"exec": "~/.config/waybar/modules/weather.sh",
196
+        "exec-if": "ping wttr.in -c1"
197
+    },
198
+    "custom/pacman": {
199
+        "format": "<big>􏆲</big>  {}",
200
+        "interval": 3600,                     // every hour
201
+        "exec": "checkupdates | wc -l",       // # of updates
202
+        "exec-if": "exit 0",                  // always run; consider advanced run conditions
203
+        "on-click": "alacritty -e 'paru'; pkill -SIGRTMIN+8 waybar", // update system
204
+        "signal": 8,
205
+        "max-length": 5,
206
+        "min-length": 3
207
+    },
208
+
209
+"custom/spotify": {
210
+    "exec": "~/.config/waybar/mediaplayer.py --player spotify",
211
+    "format": "{}  ",
212
+    "return-type": "json",
213
+    "on-click": "playerctl play-pause",
214
+    "on-scroll-up": "playerctl next",
215
+    "on-scroll-down": "playerctl previous"
216
+},
217
+
218
+    "custom/media": {
219
+        "format": "{0} {1}",
220
+        "return-type": "json",
221
+        "max-length": 40,
222
+        "format-icons": {
223
+            "spotify": "",
224
+            "default": "🎜"
225
+        },
226
+        "escape": true,
227
+        //"exec": "~/.config/waybar/mediaplayer.py" // Script in resources folder
228
+        // "exec": "~/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
229
+    },
230
+
231
+    "custom/power": {
232
+        "format": " 󰐥 ",
233
+        "tooltip": false,
234
+        "on-click": "wlogout"
235
+    },
236
+
237
+    "clock": {
238
+        "format": "  {:%H:%M   %e %b}",
239
+        "tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
240
+        "today-format": "<b>{}</b>"
241
+    },
242
+
243
+    "clock#date": {
244
+        "format": "󰥔  {:%H:%M \n %e %b}",
245
+        "tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
246
+        "today-format": "<b>{}</b>"
247
+    },
248
+
249
+    "custom/gammastep": {
250
+      "interval": 5,
251
+      "return-type": "json",
252
+      "exec": {
253
+        "pre": "if unit_status=\"$(systemctl --user is-active gammastep)\"; then\nstatus=\"$unit_status ($(journalctl --user -u gammastep.service -g 'Period: ' | tail -1 | cut -d ':' -f6 | xargs))\"\nelse\nstatus=\"$unit_status\"\nfi",
254
+        "alt": "${status:-inactive}",
255
+        "tooltip": "Gammastep is $status",
256
+      },
257
+      "format": "{icon}",
258
+      "format-icons": {
259
+        "activating": "󰁪 ",
260
+        "deactivating": "󰁪 ",
261
+        "inactive": "? ",
262
+        "active (Night)": " ",
263
+        "active (Nighttime)": " ",
264
+        "active (Transition (Night)": " ",
265
+        "active (Transition (Nighttime)": " ",
266
+        "active (Day)": " ",
267
+        "active (Daytime)": " ",
268
+        "active (Transition (Day)": " ",
269
+        "active (Transition (Daytime)": " ",
270
+      },
271
+      "on-click": "systemctl --user is-active gammastep && systemctl --user stop gammastep || systemctl --user start gammastep",
272
+    },
273
+
274
+    "cpu": {
275
+        "interval": 1,
276
+        //"format": " {}%", // Icon: microchip
277
+        "format": "{max_frequency}GHz <span color=\"darkgray\">| {usage}%</span>",
278
+        "max-length": 13,
279
+        "min-length": 13
280
+    },
281
+
282
+    "mpd": {
283
+        "max-length": 25,
284
+        "format": "<span foreground='#bb9af7'></span> {title}",
285
+        "format-paused": " {title}",
286
+        "format-stopped": "<span foreground='#bb9af7'></span>",
287
+        "format-disconnected": "",
288
+        "on-click": "mpc --quiet toggle",
289
+        "on-click-right": "mpc update; mpc ls | mpc add",
290
+        "on-click-middle": "alacritty -e ncmpcpp",
291
+        "on-scroll-up": "mpc --quiet prev",
292
+        "on-scroll-down": "mpc --quiet next",
293
+        "smooth-scrolling-threshold": 5,
294
+        "tooltip-format": "{title} - {artist} ({elapsedTime:%M:%S}/{totalTime:%H:%M:%S})"
295
+    },
296
+
297
+    "custom/title": {
298
+        "format": "{}",
299
+        "interval": 0,
300
+        "return-type": "json",
301
+        //"max-length": 35,
302
+        "tooltip": false
303
+    },
304
+
305
+    "custom/title#name": {
306
+        "format": "{}",
307
+        "interval": 0,
308
+        "return-type": "json",
309
+
310
+        "max-length": 35,
311
+        "exec": "$HOME/.scripts/title"
312
+    },
313
+
314
+    /*"custom/keyboard": {
315
+        "format": " {}",
316
+        "interval": 1,
317
+        "exec": "$HOME/.config/waybar/get_kbdlayout.sh"
318
+    },*/
319
+
320
+    "hyprland/workspaces": {
321
+        "all-outputs": true,
322
+        "format": "{name}",
323
+        "format-icons": {
324
+            "1": "一",
325
+            "2": "二",
326
+            "3": "三",
327
+            "4": "四",
328
+            "5": "五",
329
+            "6": "六",
330
+            "7": "七",
331
+            "8": "八",
332
+            "9": "九",
333
+            "10": "十",
334
+        },
335
+        "on-scroll-up": "hyprctl dispatch workspace e+1 1>/dev/null",
336
+        "on-scroll-down": "hyprctl dispatch workspace e-1 1>/dev/null",
337
+        "sort-by-number": true,
338
+        "active-only": false,
339
+    },
340
+
341
+    "hyprland/window": {
342
+        "max-length": 100,
343
+        "separate-outputs": true
344
+    },
345
+
346
+    "pulseaudio": {
347
+        "scroll-step": 3, // %, can be a float
348
+        "format": "{icon} {volume}% {format_source}",
349
+        "format-bluetooth": "{volume}% {icon} {format_source}",
350
+        "format-bluetooth-muted": " {icon} {format_source}",
351
+        "format-muted": " {format_source}",
352
+        //"format-source": "{volume}% ",
353
+        //"format-source-muted": "",
354
+        "format-source": "",
355
+        "format-source-muted": "",
356
+        "format-icons": {
357
+            "headphone": "",
358
+            "hands-free": "",
359
+            "headset": "",
360
+            "phone": "",
361
+            "portable": "",
362
+            "car": "",
363
+            "default": ["", "", ""]
364
+        },
365
+        "on-click": "pavucontrol",
366
+        "on-click-right": "amixer sset Master toggle"
367
+    },
368
+
369
+    "wireplumber": {
370
+        "on-click": "pavucontrol",
371
+        "on-click-right": "amixer sset Master toggle 1>/dev/null",
372
+        //on-click: "${wpctl} set-mute @DEFAULT_AUDIO_SINK@ toggle";
373
+        //on-scroll-down: "${wpctl} set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 0.04+";
374
+        //on-scroll-up: "${wpctl} set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 0.04-";
375
+        "format": "<span foreground='#fab387'>{icon}</span>  {volume}%",
376
+        "format-muted": " ",
377
+        "format-source": "",
378
+        "format-source-muted": "",
379
+        //"format-muted": "<span foreground='#fab387'> </span>",
380
+        //"format-icons": [ "<span foreground='#fab387'></span>" ]
381
+        "format-icons": {
382
+            "headphone": " ",
383
+            "hands-free": " ",
384
+            "headset": " ",
385
+            "phone": " ",
386
+            "portable": " ",
387
+            "car": " ",
388
+            "default": [" ", " ", " "]
389
+        },
390
+    },
391
+
392
+    "tray": {
393
+        "icon-size": 15,
394
+        "spacing": 5
395
+    }
396
+}
waybar/mediaplayer.pyadded
@@ -0,0 +1,127 @@
1
+#!/usr/bin/env python3
2
+import argparse
3
+import logging
4
+import sys
5
+import signal
6
+import gi
7
+import json
8
+gi.require_version('Playerctl', '2.0')
9
+from gi.repository import Playerctl, GLib
10
+
11
+logger = logging.getLogger(__name__)
12
+
13
+
14
+def write_output(text, player):
15
+    logger.info('Writing output')
16
+
17
+    output = {'text': text,
18
+              'class': 'custom-' + player.props.player_name,
19
+              'alt': player.props.player_name}
20
+
21
+    sys.stdout.write(json.dumps(output) + '\n')
22
+    sys.stdout.flush()
23
+
24
+
25
+def on_play(player, status, manager):
26
+    logger.info('Received new playback status')
27
+    on_metadata(player, player.props.metadata, manager)
28
+
29
+
30
+def on_metadata(player, metadata, manager):
31
+    logger.info('Received new metadata')
32
+    track_info = ''
33
+
34
+    if player.props.player_name == 'spotify' and \
35
+            'mpris:trackid' in metadata.keys() and \
36
+            ':ad:' in player.props.metadata['mpris:trackid']:
37
+        track_info = 'AD PLAYING'
38
+    elif player.get_artist() != '' and player.get_title() != '':
39
+        track_info = '{artist} - {title}'.format(artist=player.get_artist(),
40
+                                                 title=player.get_title())
41
+    else:
42
+        track_info = player.get_title()
43
+
44
+    if player.props.status != 'Playing' and track_info:
45
+        track_info = ' ' + track_info
46
+    write_output(track_info, player)
47
+
48
+
49
+def on_player_appeared(manager, player, selected_player=None):
50
+    if player is not None and (selected_player is None or player.name == selected_player):
51
+        init_player(manager, player)
52
+    else:
53
+        logger.debug("New player appeared, but it's not the selected player, skipping")
54
+
55
+
56
+def on_player_vanished(manager, player):
57
+    logger.info('Player has vanished')
58
+    sys.stdout.write('\n')
59
+    sys.stdout.flush()
60
+
61
+
62
+def init_player(manager, name):
63
+    logger.debug('Initialize player: {player}'.format(player=name.name))
64
+    player = Playerctl.Player.new_from_name(name)
65
+    player.connect('playback-status', on_play, manager)
66
+    player.connect('metadata', on_metadata, manager)
67
+    manager.manage_player(player)
68
+    on_metadata(player, player.props.metadata, manager)
69
+
70
+
71
+def signal_handler(sig, frame):
72
+    logger.debug('Received signal to stop, exiting')
73
+    sys.stdout.write('\n')
74
+    sys.stdout.flush()
75
+    # loop.quit()
76
+    sys.exit(0)
77
+
78
+
79
+def parse_arguments():
80
+    parser = argparse.ArgumentParser()
81
+
82
+    # Increase verbosity with every occurance of -v
83
+    parser.add_argument('-v', '--verbose', action='count', default=0)
84
+
85
+    # Define for which player we're listening
86
+    parser.add_argument('--player')
87
+
88
+    return parser.parse_args()
89
+
90
+
91
+def main():
92
+    arguments = parse_arguments()
93
+
94
+    # Initialize logging
95
+    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG,
96
+                        format='%(name)s %(levelname)s %(message)s')
97
+
98
+    # Logging is set by default to WARN and higher.
99
+    # With every occurrence of -v it's lowered by one
100
+    logger.setLevel(max((3 - arguments.verbose) * 10, 0))
101
+
102
+    # Log the sent command line arguments
103
+    logger.debug('Arguments received {}'.format(vars(arguments)))
104
+
105
+    manager = Playerctl.PlayerManager()
106
+    loop = GLib.MainLoop()
107
+
108
+    manager.connect('name-appeared', lambda *args: on_player_appeared(*args, arguments.player))
109
+    manager.connect('player-vanished', on_player_vanished)
110
+
111
+    signal.signal(signal.SIGINT, signal_handler)
112
+    signal.signal(signal.SIGTERM, signal_handler)
113
+
114
+    for player in manager.props.player_names:
115
+        if arguments.player is not None and arguments.player != player.name:
116
+            logger.debug('{player} is not the filtered player, skipping it'
117
+                         .format(player=player.name)
118
+                         )
119
+            continue
120
+
121
+        init_player(manager, player)
122
+
123
+    loop.run()
124
+
125
+
126
+if __name__ == '__main__':
127
+    main()
waybar/modules/almanta.shadded
@@ -0,0 +1,67 @@
1
+#!/usr/bin/env bash
2
+# Simple working version that avoids complex quoting
3
+set -euo pipefail
4
+
5
+HOST="espadon@almanta"
6
+SSH_OPTS=(-T -o BatchMode=yes -o ConnectTimeout=3 -o ConnectionAttempts=1)
7
+
8
+# Test connectivity
9
+if ! ssh "${SSH_OPTS[@]}" "$HOST" "echo 'test'" >/dev/null 2>&1; then
10
+  echo '{"text":"⚬ almanta: down","tooltip":"SSH connection failed","class":"down"}'
11
+  exit 0
12
+fi
13
+
14
+# Get metrics with very simple commands
15
+get_load() {
16
+  ssh "${SSH_OPTS[@]}" "$HOST" "cut -d' ' -f1 /proc/loadavg" 2>/dev/null || echo "?"
17
+}
18
+
19
+get_disk() {
20
+  ssh "${SSH_OPTS[@]}" "$HOST" "df / | tail -1 | awk '{print \$5}' | tr -d '%'" 2>/dev/null || echo "?"
21
+}
22
+
23
+get_memory() {
24
+  ssh "${SSH_OPTS[@]}" "$HOST" "free | grep '^Mem:' | awk '{printf \"%.0f\", (\$3/\$2)*100}'" 2>/dev/null || echo "?"
25
+}
26
+
27
+get_service() {
28
+  # Check for web services
29
+  for service in nginx httpd apache2; do
30
+    if ssh "${SSH_OPTS[@]}" "$HOST" "systemctl is-active --quiet $service" 2>/dev/null; then
31
+      echo "$service|active"
32
+      return
33
+    elif ssh "${SSH_OPTS[@]}" "$HOST" "systemctl list-unit-files | grep -q '^$service\.service'" 2>/dev/null; then
34
+      echo "$service|inactive" 
35
+      return
36
+    fi
37
+  done
38
+  echo "web|unknown"
39
+}
40
+
41
+get_uptime() {
42
+  ssh "${SSH_OPTS[@]}" "$HOST" "uptime -p" 2>/dev/null || echo ""
43
+}
44
+
45
+# Gather all metrics
46
+load=$(get_load)
47
+disk=$(get_disk) 
48
+mem=$(get_memory)
49
+up=$(get_uptime)
50
+
51
+# Get service info
52
+service_info=$(get_service)
53
+IFS='|' read -r svc web <<<"$service_info"
54
+
55
+# Build output - escape newlines properly for JSON
56
+text="⚬ ${svc:-web}  L:$load  M:$mem%  D:$disk%"
57
+tip="almanta (${svc:-—}): ${web:-unknown}\\nload(1m): $load\\nmemory: $mem%\\ndisk /: $disk%\\n$up"
58
+
59
+# Determine class
60
+class="ok"
61
+[ "${web:-unknown}" != "active" ] && class="warn"
62
+[[ "$mem" =~ ^[0-9]+$ ]] && [ "$mem" -ge 90 ] && class="warn"  
63
+[[ "$disk" =~ ^[0-9]+$ ]] && [ "$disk" -ge 90 ] && class="warn"
64
+
65
+# Use jq for proper JSON encoding to handle all special characters
66
+jq -cn --arg text "$text" --arg tooltip "$tip" --arg class "$class" \
67
+  '{text:$text, tooltip:$tooltip, class:$class}'
waybar/modules/jetbrains_menu.shadded
@@ -0,0 +1,77 @@
1
+#!/usr/bin/env bash
2
+# JetBrains menu for Waybar
3
+# Left click shows IDEs from .desktop files (Toolbox, Flatpak, distro installs)
4
+# Right click (configured in Waybar) opens jetbrains-toolbox
5
+
6
+set -euo pipefail
7
+
8
+# Pick a menu program
9
+if command -v wofi >/dev/null 2>&1; then
10
+  MENU_CMD=(wofi --dmenu -i -p "JetBrains")
11
+elif command -v rofi >/dev/null 2>&1; then
12
+  MENU_CMD=(rofi -dmenu -i -p "JetBrains")
13
+else
14
+  command -v notify-send >/dev/null 2>&1 && notify-send "JetBrains" "Install wofi or rofi for the menu."
15
+  exit 1
16
+fi
17
+
18
+# Directories where .desktop files commonly live
19
+dirs=(
20
+  "$HOME/.local/share/applications"
21
+  "$HOME/.local/share/flatpak/exports/share/applications"
22
+  "/var/lib/flatpak/exports/share/applications"
23
+  "/usr/share/applications"
24
+)
25
+
26
+# Gather JetBrains desktop entries (Toolbox uses jetbrains-*.desktop, Flatpak uses com.jetbrains.*)
27
+mapfile -t files < <(
28
+  for d in "${dirs[@]}"; do
29
+    [[ -d "$d" ]] || continue
30
+    find "$d" -maxdepth 1 -type f \( -name 'jetbrains-*.desktop' -o -name 'com.jetbrains*.desktop' \) -print
31
+  done | awk '!seen[$0]++'
32
+)
33
+
34
+if (( ${#files[@]} == 0 )); then
35
+  if command -v jetbrains-toolbox >/dev/null 2>&1; then
36
+    command -v notify-send >/dev/null 2>&1 && notify-send "JetBrains" "No IDE entries found. Opening Toolbox…"
37
+    nohup jetbrains-toolbox >/dev/null 2>&1 &
38
+    exit 0
39
+  else
40
+    command -v notify-send >/dev/null 2>&1 && notify-send "JetBrains" "No JetBrains IDEs detected."
41
+    exit 0
42
+  fi
43
+fi
44
+
45
+# Build "Name|||desktop-id" lines so we can sort but keep mapping
46
+mapfile -t lines < <(
47
+  for f in "${files[@]}"; do
48
+    id="$(basename "$f" .desktop)"
49
+    name="$(grep -m1 '^Name=' "$f" | cut -d= -f2-)"
50
+    [[ -n "$name" ]] || name="$id"
51
+    printf '%s|||%s\n' "$name" "$id"
52
+  done | sort -f
53
+)
54
+
55
+# Show menu of just the names
56
+selection="$(printf '%s\n' "${lines[@]}" | sed 's/|||.*//' | "${MENU_CMD[@]}")"
57
+[[ -n "$selection" ]] || exit 0
58
+
59
+id="$(printf '%s\n' "${lines[@]}" | awk -F'\|\|\|' -v sel="$selection" '$1==sel{print $2; exit}')"
60
+
61
+# Launch via gtk-launch (preferred), else fall back to Exec= line
62
+if command -v gtk-launch >/dev/null 2>&1; then
63
+  setsid gtk-launch "$id" >/dev/null 2>&1 &
64
+  exit 0
65
+fi
66
+
67
+# Fallback: run the Exec= command from the .desktop file
68
+desktop_file=""
69
+for d in "${dirs[@]}"; do
70
+  [[ -f "$d/$id.desktop" ]] && desktop_file="$d/$id.desktop" && break
71
+done
72
+[[ -z "$desktop_file" ]] && exit 1
73
+
74
+exec_line="$(grep -m1 '^Exec=' "$desktop_file" | cut -d= -f2-)"
75
+# Strip desktop placeholders like %f %u etc.
76
+exec_line="${exec_line//%[fFuUdDnNickvm]/}"
77
+nohup bash -lc "$exec_line" >/dev/null 2>&1 &
waybar/modules/mail.pyadded
@@ -0,0 +1,42 @@
1
+#!/usr/bin/python
2
+
3
+import os
4
+import imaplib
5
+
6
+import mailsecrets
7
+
8
+def getmails(username, password, server):
9
+    imap = imaplib.IMAP4_SSL(server, 993)
10
+    imap.login(username, password)
11
+    imap.select('INBOX')
12
+    ustatus, uresponse = imap.uid('search', None, 'UNSEEN')
13
+    if ustatus == 'OK':
14
+        unread_msg_nums = uresponse[0].split()
15
+    else:
16
+        unread_msg_nums = []
17
+
18
+    fstatus, fresponse = imap.uid('search', None, 'FLAGGED')
19
+    if fstatus == 'OK':
20
+        flagged_msg_nums = fresponse[0].split()
21
+    else:
22
+        flagged_msg_nums = []
23
+
24
+    return [len(unread_msg_nums), len(flagged_msg_nums)]
25
+
26
+ping = os.system("ping " + mailsecrets.server + " -c1 > /dev/null 2>&1")
27
+if ping == 0:
28
+    mails = getmails(mailsecrets.username, mailsecrets.password, mailsecrets.server)
29
+    text = ''
30
+    alt = ''
31
+
32
+    if mails[0] > 0:
33
+        text = alt = str(mails[0])
34
+        if mails[1] > 0:
35
+            alt = str(mails[1]) + "  " + alt
36
+    else:
37
+        exit(1)
38
+
39
+    print('{"text":"' + text + '", "alt": "' + alt + '"}')
40
+
41
+else:
42
+    exit(1)
waybar/modules/spotify.shadded
@@ -0,0 +1,18 @@
1
+#!/bin/sh
2
+
3
+class=$(playerctl metadata --player=spotify --format '{{lc(status)}}')
4
+icon=""
5
+
6
+if [[ $class == "playing" ]]; then
7
+  info=$(playerctl metadata --player=spotify --format '{{artist}} - {{title}}')
8
+  if [[ ${#info} > 40 ]]; then
9
+    info=$(echo $info | cut -c1-40)"..."
10
+  fi
11
+  text=$info" "$icon
12
+elif [[ $class == "paused" ]]; then
13
+  text=$icon
14
+elif [[ $class == "stopped" ]]; then
15
+  text=""
16
+fi
17
+
18
+echo -e "{\"text\":\""$text"\", \"class\":\""$class"\"}"
waybar/modules/storage.shadded
@@ -0,0 +1,24 @@
1
+#!/bin/sh
2
+
3
+mount="/"
4
+warning=20
5
+critical=10
6
+
7
+df -h -P -l "$mount" | awk -v warning=$warning -v critical=$critical '
8
+/\/.*/ {
9
+  text=$4
10
+  tooltip="Filesystem: "$1"\rSize: "$2"\rUsed: "$3"\rAvail: "$4"\rUse%: "$5"\rMounted on: "$6
11
+  use=$5
12
+  exit 0
13
+}
14
+END {
15
+  class=""
16
+  gsub(/%$/,"",use)
17
+  if ((100 - use) < critical) {
18
+    class="critical"
19
+  } else if ((100 - use) < warning) {
20
+    class="warning"
21
+  }
22
+  print "{\"text\":\""text"\", \"percentage\":"use",\"tooltip\":\""tooltip"\", \"class\":\""class"\"}"
23
+}
24
+'
waybar/modules/weather.shadded
@@ -0,0 +1,79 @@
1
+#!/bin/bash
2
+
3
+cachedir=~/.cache/rbn
4
+cachefile=${0##*/}-$1
5
+
6
+if [ ! -d $cachedir ]; then
7
+    mkdir -p $cachedir
8
+fi
9
+
10
+if [ ! -f $cachedir/$cachefile ]; then
11
+    touch $cachedir/$cachefile
12
+fi
13
+
14
+# Save current IFS
15
+SAVEIFS=$IFS
16
+# Change IFS to new line.
17
+IFS=$'\n'
18
+
19
+cacheage=$(($(date +%s) - $(stat -c '%Y' "$cachedir/$cachefile")))
20
+if [ $cacheage -gt 1740 ] || [ ! -s $cachedir/$cachefile ]; then
21
+    data=($(curl -s https://en.wttr.in/$1\?0qnT 2>&1))
22
+    echo ${data[0]} | cut -f1 -d, > $cachedir/$cachefile
23
+    echo ${data[1]} | sed -E 's/^.{15}//' >> $cachedir/$cachefile
24
+    echo ${data[2]} | sed -E 's/^.{15}//' >> $cachedir/$cachefile
25
+fi
26
+
27
+weather=($(cat $cachedir/$cachefile))
28
+
29
+# Restore IFSClear
30
+IFS=$SAVEIFS
31
+
32
+temperature=$(echo ${weather[2]} | sed -E 's/([[:digit:]])+\.\./\1 to /g')
33
+
34
+#echo ${weather[1]##*,}
35
+
36
+# https://fontawesome.com/icons?s=solid&c=weather
37
+case $(echo ${weather[1]##*,} | tr '[:upper:]' '[:lower:]') in
38
+"clear" | "sunny")
39
+    condition=""
40
+    ;;
41
+"partly cloudy")
42
+    condition="杖"
43
+    ;;
44
+"cloudy")
45
+    condition=""
46
+    ;;
47
+"overcast")
48
+    condition=""
49
+    ;;
50
+"mist" | "fog" | "freezing fog")
51
+    condition=""
52
+    ;;
53
+"patchy rain possible" | "patchy light drizzle" | "light drizzle" | "patchy light rain" | "light rain" | "light rain shower" | "rain")
54
+    condition=""
55
+    ;;
56
+"moderate rain at times" | "moderate rain" | "heavy rain at times" | "heavy rain" | "moderate or heavy rain shower" | "torrential rain shower" | "rain shower")
57
+    condition=""
58
+    ;;
59
+"patchy snow possible" | "patchy sleet possible" | "patchy freezing drizzle possible" | "freezing drizzle" | "heavy freezing drizzle" | "light freezing rain" | "moderate or heavy freezing rain" | "light sleet" | "ice pellets" | "light sleet showers" | "moderate or heavy sleet showers")
60
+    condition="ﭽ"
61
+    ;;
62
+"blowing snow" | "moderate or heavy sleet" | "patchy light snow" | "light snow" | "light snow showers")
63
+    condition="流"
64
+    ;;
65
+"blizzard" | "patchy moderate snow" | "moderate snow" | "patchy heavy snow" | "heavy snow" | "moderate or heavy snow with thunder" | "moderate or heavy snow showers")
66
+    condition="ﰕ"
67
+    ;;
68
+"thundery outbreaks possible" | "patchy light rain with thunder" | "moderate or heavy rain with thunder" | "patchy light snow with thunder")
69
+    condition=""
70
+    ;;
71
+*)
72
+    condition=""
73
+    echo -e "{\"text\":\""$condition"\", \"alt\":\""${weather[0]}"\", \"tooltip\":\""${weather[0]}: $temperature ${weather[1]}"\"}"
74
+    ;;
75
+esac
76
+
77
+#echo $temp $condition
78
+
79
+echo -e "{\"text\":\""$temperature $condition"\", \"alt\":\""${weather[0]}"\", \"tooltip\":\""${weather[0]}: $temperature ${weather[1]}"\"}"
waybar/style.cssadded
@@ -0,0 +1,360 @@
1
+
2
+* {
3
+    font-family: "Fira Sans Semibold", "Font Awesome 6 Free", FontAwesome, Roboto, Helvetica, Arial, sans-serif;
4
+    font-size: 14px;  /* Slightly smaller font size */
5
+    font-weight: 900;
6
+    margin: 0;
7
+    padding: 0;
8
+    transition-property: background-color;
9
+    transition-duration: 0.5s;
10
+}
11
+
12
+/* Reset all styles */
13
+* {
14
+    border: none;
15
+    border-radius: 3px;
16
+    min-height: 0;
17
+    margin: 0.2em 0.3em 0.2em 0.3em;
18
+}
19
+
20
+/* The whole bar */
21
+#waybar {
22
+    background-color: transparent;
23
+    color: #ffffff;
24
+    transition-property: background-color;
25
+    transition-duration: 0.5s;
26
+    border-radius: 0px;
27
+    margin: 0px 0px;
28
+}
29
+
30
+window#waybar.hidden {
31
+  opacity: 0.2;
32
+}
33
+
34
+#workspaces button {
35
+    padding: 3px 5px;  /* Adjusted padding to reduce height */
36
+    margin: 3px 5px;    /* Reduced margin slightly */
37
+    border-radius: 6px;
38
+    color: @foreground;
39
+    background-color: #111827;
40
+    transition: all 0.3s ease-in-out;
41
+    font-size: 13px;  /* Slightly smaller font size */
42
+}
43
+
44
+#workspaces button.active {
45
+    color: @foreground;
46
+    background: #025939;
47
+}
48
+
49
+#workspaces button:hover {
50
+    background: #333333;
51
+}
52
+
53
+#workspaces button.urgent {
54
+    background-color: #eb4d4b;
55
+}
56
+
57
+#workspaces {
58
+    background-color: #111827;
59
+    border-radius: 14px;
60
+    padding: 3px 6px;  /* Adjusted padding to reduce height */
61
+}
62
+
63
+#window {
64
+    background-color: #111827;
65
+    font-size: 15px;   /* Slightly smaller font size */
66
+    font-weight: 800;
67
+    color: @foreground;
68
+    border-radius: 14px;
69
+    padding: 3px 6px;  /* Reduced padding */
70
+    margin: 2px;
71
+    opacity: 1;
72
+}
73
+
74
+#clock,
75
+#battery,
76
+#cpu,
77
+#memory,
78
+#disk,
79
+#temperature,
80
+#backlight,
81
+#network,
82
+#pulseaudio,
83
+#wireplumber,
84
+#custom-media,
85
+#mode,
86
+#idle_inhibitor,
87
+#mpd,
88
+#bluetooth,
89
+#custom-hyprPicker,
90
+#custom-power-menu,
91
+#custom-spotify,
92
+#custom-weather,
93
+#custom-weather.severe,
94
+#custom-weather.sunnyDay,
95
+#custom-weather.clearNight,
96
+#custom-weather.cloudyFoggyDay,
97
+#custom-weather.cloudyFoggyNight,
98
+#custom-weather.rainyDay,
99
+#custom-weather.rainyNight,
100
+#custom-weather.showyIcyDay,
101
+#custom-weather.snowyIcyNight,
102
+#custom-weather.default {
103
+    background-color: #111827;
104
+    border-radius: 14px;
105
+    padding: 6px;  /* Adjusted padding */
106
+}
107
+
108
+#custom-screenshot_t {
109
+    background-color: #111827;
110
+    border-radius: 14px;
111
+    padding: 6px;
112
+}
113
+
114
+#custom-spotify {
115
+    background-color: #111827;
116
+    border-radius: 14px;
117
+    padding: 3px 12px;
118
+}
119
+
120
+#tray {
121
+    background-color: #111827;
122
+    border-radius: 14px;
123
+    padding: 6px;  /* Adjusted padding */
124
+}
125
+
126
+#cpu {
127
+    color: #fb958b;
128
+    background-color: #111827;
129
+    padding: 6px;
130
+}
131
+
132
+#memory {
133
+    color: #ebcb8b;
134
+    background-color: #111827;
135
+    padding: 6px;
136
+}
137
+
138
+#custom-power {
139
+    background-color: #111827;
140
+    border-radius: 14px;
141
+    padding: 6px;
142
+}
143
+
144
+#custom-storage {
145
+    background-color: #111827;
146
+    border-radius: 14px;
147
+    padding: 6px;
148
+}
149
+
150
+#custom-launcher {
151
+    background-color: #1b242b;
152
+    color: #6a92d7;
153
+    border-radius: 7.5px;
154
+    padding: 3px 6px;  /* Adjusted padding */
155
+}
156
+
157
+#custom-weather.severe {
158
+    color: #eb937d;
159
+}
160
+
161
+#custom-weather.sunnyDay {
162
+    color: #c2ca76;
163
+}
164
+
165
+#custom-weather.clearNight {
166
+    color: #cad3f5;
167
+}
168
+
169
+#custom-weather.cloudyFoggyDay,
170
+#custom-weather.cloudyFoggyNight {
171
+    color: #c2ddda;
172
+}
173
+
174
+#custom-weather.rainyDay,
175
+#custom-weather.rainyNight {
176
+    color: #5aaca5;
177
+}
178
+
179
+#custom-weather.showyIcyDay,
180
+#custom-weather.snowyIcyNight {
181
+    color: #d6e7e5;
182
+}
183
+
184
+#custom-weather.default {
185
+    color: #dbd9d8;
186
+}
187
+
188
+/* If workspaces is the leftmost module, omit left margin */
189
+.modules-left > widget:first-child > #workspaces {
190
+    margin-left: 0;
191
+}
192
+
193
+/* If workspaces is the rightmost module, omit right margin */
194
+.modules-right > widget:last-child > #workspaces {
195
+    margin-right: 0;
196
+}
197
+
198
+#pulseaudio {
199
+    color: #7d9bba;
200
+}
201
+
202
+#backlight {
203
+    color: #8fbcbb;
204
+}
205
+
206
+#clock {
207
+    color: #c8d2e0;
208
+}
209
+
210
+#battery {
211
+    color: #c0caf5;
212
+}
213
+
214
+#battery.charging,
215
+#battery.full,
216
+#battery.plugged {
217
+    color: #26a65b;
218
+}
219
+
220
+@keyframes blink {
221
+    to {
222
+        background-color: rgba(30, 34, 42, 0.5);
223
+        color: #abb2bf;
224
+    }
225
+}
226
+
227
+#battery.critical:not(.charging) {
228
+    color: #f53c3c;
229
+    animation-name: blink;
230
+    animation-duration: 0.5s;
231
+    animation-timing-function: linear;
232
+    animation-iteration-count: infinite;
233
+    animation-direction: alternate;
234
+}
235
+
236
+label:focus {
237
+    background-color: #000000;
238
+}
239
+
240
+#disk {
241
+    background-color: #964b00;
242
+}
243
+
244
+#bluetooth {
245
+    color: #707d9d;
246
+}
247
+
248
+#bluetooth.disconnected {
249
+    color: #f53c3c;
250
+}
251
+
252
+#network {
253
+    color: #b48ead;
254
+}
255
+
256
+#network.disconnected {
257
+    color: #f53c3c;
258
+}
259
+
260
+#custom-media {
261
+    background-color: #66cc99;
262
+    color: #2a5c45;
263
+    min-width: 100px;
264
+}
265
+
266
+#custom-media.custom-spotify {
267
+    background-color: #66cc99;
268
+}
269
+
270
+#custom-media.custom-vlc {
271
+    background-color: #ffa000;
272
+}
273
+
274
+#temperature {
275
+    background-color: #f0932b;
276
+}
277
+
278
+#temperature.critical {
279
+    background-color: #eb4d4b;
280
+}
281
+
282
+#tray > .passive {
283
+    -gtk-icon-effect: dim;
284
+}
285
+
286
+#tray > .needs-attention {
287
+    -gtk-icon-effect: highlight;
288
+    background-color: #eb4d4b;
289
+}
290
+
291
+#idle_inhibitor {
292
+    background-color: #2d3436;
293
+}
294
+
295
+#idle_inhibitor.activated {
296
+    background-color: #ecf0f1;
297
+    color: #2d3436;
298
+}
299
+
300
+#language {
301
+    background: #00b093;
302
+    color: #740864;
303
+    padding: 0 0px;
304
+    margin: 0 5px;
305
+    min-width: 16px;
306
+}
307
+
308
+#keyboard-state {
309
+    background: #97e1ad;
310
+    color: #000000;
311
+    padding: 0 0px;
312
+    margin: 0 5px;
313
+    min-width: 16px;
314
+}
315
+
316
+#keyboard-state > label {
317
+    padding: 0 0px;
318
+}
319
+
320
+#keyboard-state > label.locked {
321
+    background: rgba(0, 0, 0, 0.2);
322
+}
323
+
324
+/* Reusable pill w/ shadow */
325
+.chip {
326
+  padding: 2px 10px;
327
+  margin: 0 3px;
328
+  border-radius: 10px;
329
+  background: rgba(0, 0, 0, 0.22);
330
+  box-shadow:
331
+    0 2px 8px rgba(0, 0, 0, 0.35),
332
+    inset 0 1px 0 rgba(255, 255, 255, 0.05);
333
+}
334
+
335
+/* JetBrains pill w/ shadow + larger glyph */
336
+#custom-jetbrains {
337
+  background-color: #111827;
338
+  border-radius: 14px;
339
+  padding: 6px;
340
+  box-shadow:
341
+    0 2px 8px rgba(0, 0, 0, 0.35),
342
+    inset 0 1px 0 rgba(255, 255, 255, 0.05);
343
+  font-size: 16px;            /* bump icon size (try 17–18px if you want bigger) */
344
+}
345
+
346
+#custom-jetbrains:hover {
347
+  background: rgba(17, 24, 39, 0.92); /* a hair brighter than #111827 */
348
+  box-shadow:
349
+    0 3px 12px rgba(0, 0, 0, 0.5),
350
+    inset 0 1px 0 rgba(255, 255, 255, 0.06);
351
+}
352
+
353
+/* chip look like jetbrains glyph */
354
+#custom-almanta {
355
+  padding: 0 8px;
356
+  border-radius: 10px;
357
+}
358
+#custom-almanta.ok   { color: #a6e3a1; }  /* green-ish */
359
+#custom-almanta.warn { color: #f38ba8; }  /* red-ish */
360
+#custom-almanta.down { color: #f38ba8; }
waybar/waybar.shadded
@@ -0,0 +1,10 @@
1
+#!/usr/bin/env sh
2
+
3
+# Terminate already running bar instances
4
+killall -q waybar
5
+
6
+# Wait until the processes have been shut down
7
+while pgrep -x waybar >/dev/null; do sleep 1; done
8
+
9
+# Launch main
10
+waybar &