aboutsummaryrefslogtreecommitdiff
path: root/.local/src
diff options
context:
space:
mode:
authorkj-sh6042025-06-06 20:18:26 -0400
committerkj-sh6042025-06-06 20:18:26 -0400
commit15cd209a581c2ac9ec63db880cac640cdea3ea04 (patch)
treef0f38b54908e5c650ee63d8c2f0abfa724e941e4 /.local/src
parentfa7425e3ca53eab2dfc255b3652a7bed8e21283d (diff)
refactor: move directory locations
Diffstat (limited to '.local/src')
-rw-r--r--.local/src/python-dateTimeSetter/Makefile4
-rw-r--r--.local/src/python-dateTimeSetter/dateTime-GTK3.old.py87
-rw-r--r--.local/src/python-dateTimeSetter/dateTime.py360
-rw-r--r--.local/src/python-playerctl_systray/Makefile4
-rw-r--r--.local/src/python-playerctl_systray/playerctl_systray.py123
-rw-r--r--.local/src/python-rainfall/Makefile4
-rw-r--r--.local/src/python-rainfall/rainfall.py161
7 files changed, 743 insertions, 0 deletions
diff --git a/.local/src/python-dateTimeSetter/Makefile b/.local/src/python-dateTimeSetter/Makefile
new file mode 100644
index 0000000..91b1c6e
--- /dev/null
+++ b/.local/src/python-dateTimeSetter/Makefile
@@ -0,0 +1,4 @@
+compile:
+ cython3 --embed -o dateTime.c -X language_level=3 dateTime.py
+ PYTHON_VERSION=`ls --sort version /usr/include | grep -o 'python[3-9]\+\.[0-9]\+' | tail -1` ; \
+ gcc -march=native -O2 -pipe -fno-plt -I /usr/include/$$PYTHON_VERSION -o dateTime dateTime.c -l$$PYTHON_VERSION -lpthread -lm -lutil -ldl
diff --git a/.local/src/python-dateTimeSetter/dateTime-GTK3.old.py b/.local/src/python-dateTimeSetter/dateTime-GTK3.old.py
new file mode 100644
index 0000000..186c671
--- /dev/null
+++ b/.local/src/python-dateTimeSetter/dateTime-GTK3.old.py
@@ -0,0 +1,87 @@
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, GLib
+import subprocess
+
+class dateTimeSetter(Gtk.Window):
+ def __init__(self):
+ Gtk.Window.__init__(self, title="dateTimeSetter")
+ self.set_border_width(10)
+ self.set_default_size(400, 300)
+
+ vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
+ self.add(vbox)
+
+ # Automatic time setting checkbox
+ automatic_time_label = Gtk.Label(label="Set the date, time, and timezone automatically?")
+ vbox.pack_start(automatic_time_label, False, False, 0)
+
+ self.automatic_time_check = Gtk.CheckButton(label="Yes")
+ self.automatic_time_check.connect("toggled", self.on_automatic_time_toggled)
+ vbox.pack_start(self.automatic_time_check, False, False, 0)
+
+ # Date and time entry fields
+ date_label = Gtk.Label(label="Enter the desired date (format: YYYY-MM-DD):")
+ vbox.pack_start(date_label, False, False, 0)
+
+ self.date_entry = Gtk.Entry()
+ vbox.pack_start(self.date_entry, False, False, 0)
+
+ time_label = Gtk.Label(label="Enter the desired time (format: HH:MM:SS):")
+ vbox.pack_start(time_label, False, False, 0)
+
+ self.time_entry = Gtk.Entry()
+ vbox.pack_start(self.time_entry, False, False, 0)
+
+ # Timezone entry field
+ timezone_label = Gtk.Label(label="Enter the desired timezone (e.g., America/New_York):")
+ vbox.pack_start(timezone_label, False, False, 0)
+
+ self.timezone_entry = Gtk.Entry()
+ vbox.pack_start(self.timezone_entry, False, False, 0)
+
+ # Apply button
+ apply_button = Gtk.Button(label="Apply")
+ apply_button.connect("clicked", self.on_apply_clicked)
+ vbox.pack_start(apply_button, False, False, 0)
+
+ def on_automatic_time_toggled(self, button):
+ if button.get_active():
+ self.date_entry.set_sensitive(False)
+ self.time_entry.set_sensitive(False)
+ self.timezone_entry.set_sensitive(False)
+ else:
+ self.date_entry.set_sensitive(True)
+ self.time_entry.set_sensitive(True)
+ self.timezone_entry.set_sensitive(True)
+
+ def on_apply_clicked(self, button):
+ automatic_time = self.automatic_time_check.get_active()
+
+ if automatic_time:
+ subprocess.run(["timedatectl", "set-ntp", "true"])
+ print("Automatic time synchronization using NTP initiated.")
+
+ automatic_timezone_output = subprocess.run(["curl", "--fail", "https://ipapi.co/timezone"], capture_output=True, text=True)
+ automatic_timezone = automatic_timezone_output.stdout.strip()
+
+ if automatic_timezone:
+ subprocess.run(["timedatectl", "set-timezone", automatic_timezone])
+ print("Automatic timezone setting complete.")
+ else:
+ print("Automatic timezone setting failed. Please set the timezone manually.")
+ else:
+ date_input = self.date_entry.get_text()
+ time_input = self.time_entry.get_text()
+ timezone_input = self.timezone_entry.get_text()
+
+ subprocess.run(["timedatectl", "set-ntp", "false"])
+ subprocess.run(["timedatectl", "set-time", f"{date_input} {time_input}"])
+ subprocess.run(["timedatectl", "set-timezone", timezone_input])
+ print("Manual date, time, and timezone setting complete.")
+
+
+win = dateTimeSetter()
+win.connect("destroy", Gtk.main_quit)
+win.show_all()
+Gtk.main()
diff --git a/.local/src/python-dateTimeSetter/dateTime.py b/.local/src/python-dateTimeSetter/dateTime.py
new file mode 100644
index 0000000..4d7ab5e
--- /dev/null
+++ b/.local/src/python-dateTimeSetter/dateTime.py
@@ -0,0 +1,360 @@
+import tkinter as tk
+from tkinter import ttk, messagebox
+import subprocess
+import requests
+import datetime
+import pytz
+import time
+
+
+class DateTimeSetter(tk.Tk):
+ def __init__(self):
+ super().__init__()
+
+ self.title("dateTimeSetter")
+ self.geometry("800x600")
+ self.resizable(True, True)
+ self.attributes("-type", "dialog") # make the window floating
+
+ self.create_widgets()
+ self.populate_fields()
+
+ def create_widgets(self):
+ # automatic time setting checkbox
+ automatic_time_label = ttk.Label(
+ self, text="Set the date, time, and timezone automatically?")
+ automatic_time_label.pack(pady=5)
+
+ self.automatic_time_check = ttk.Checkbutton(
+ self, text="Yes", command=self.on_automatic_time_toggled)
+ self.automatic_time_check.pack(pady=5)
+
+ # date entry fields
+ date_frame = ttk.LabelFrame(self, text="Date")
+ date_frame.pack(pady=10, padx=10, fill="x")
+
+ ttk.Label(date_frame, text="YYYY:").grid(
+ row=0, column=0, padx=5, pady=5)
+ self.year_var = tk.StringVar()
+ self.year_combo = ttk.Combobox(date_frame, textvariable=self.year_var, values=[str(
+ year) for year in range(1970, 2039)], state="normal")
+ self.year_combo.grid(row=0, column=1, padx=5, pady=5)
+
+ ttk.Label(date_frame, text="MM:").grid(row=0, column=2, padx=5, pady=5)
+ self.month_var = tk.StringVar()
+ self.month_combo = ttk.Combobox(date_frame, textvariable=self.month_var, values=[str(
+ month) for month in range(1, 13)], state="normal")
+ self.month_combo.grid(row=0, column=3, padx=5, pady=5)
+
+ ttk.Label(date_frame, text="DD:").grid(row=0, column=4, padx=5, pady=5)
+ self.day_var = tk.StringVar()
+ self.day_combo = ttk.Combobox(
+ date_frame, textvariable=self.day_var, values=[str(day) for day in range(1, 32)], state="normal")
+ self.day_combo.grid(row=0, column=5, padx=5, pady=5)
+
+ # time entry fields
+ time_frame = ttk.LabelFrame(self, text="Time (24hr)")
+ time_frame.pack(pady=10, padx=10, fill="x")
+
+ ttk.Label(time_frame, text=" HH:").grid(
+ row=0, column=0, padx=5, pady=5)
+ self.hour_var = tk.StringVar()
+ self.hour_combo = ttk.Combobox(time_frame, textvariable=self.hour_var, values=[str(
+ hour) for hour in range(0, 24)], state="normal")
+ self.hour_combo.grid(row=0, column=1, padx=5, pady=5)
+
+ ttk.Label(time_frame, text="MM:").grid(row=0, column=2, padx=5, pady=5)
+ self.minute_var = tk.StringVar()
+ self.minute_combo = ttk.Combobox(time_frame, textvariable=self.minute_var, values=[str(
+ minute) for minute in range(0, 60)], state="normal")
+ self.minute_combo.grid(row=0, column=3, padx=5, pady=5)
+
+ ttk.Label(time_frame, text="SS:").grid(row=0, column=4, padx=5, pady=5)
+ self.second_var = tk.StringVar()
+ self.second_combo = ttk.Combobox(time_frame, textvariable=self.second_var, values=[str(
+ second) for second in range(0, 60)], state="normal")
+ self.second_combo.grid(row=0, column=5, padx=5, pady=5)
+
+ # timezone entry field
+ timezone_frame = ttk.LabelFrame(self, text="Timezone")
+ timezone_frame.pack(pady=10, padx=10, fill="x")
+
+ ttk.Label(timezone_frame, text="Timezone:").grid(
+ row=0, column=0, padx=5, pady=5)
+ self.timezone_var = tk.StringVar()
+ self.timezone_combo = ttk.Combobox(
+ timezone_frame, textvariable=self.timezone_var, values=pytz.all_timezones, state="normal")
+ self.timezone_combo.grid(row=0, column=1, padx=5, pady=5)
+
+ # set-local-rtc checkbox
+ self.local_rtc_check = ttk.Checkbutton(
+ timezone_frame, text="Set local RTC")
+ self.local_rtc_check.grid(
+ row=1, column=0, columnspan=2, padx=5, pady=5)
+
+ # apply button
+ button_frame = ttk.Frame(self)
+ button_frame.pack(pady=20)
+
+ self.apply_button = ttk.Button(
+ button_frame, text="Apply All", command=self.on_apply_clicked)
+ self.apply_button.grid(row=0, column=0, padx=5)
+
+ self.timezone_apply_button = ttk.Button(
+ button_frame, text="Apply Timezone", command=self.on_timezone_apply_clicked)
+ self.timezone_apply_button.grid(row=0, column=1, padx=5)
+
+ self.date_time_apply_button = ttk.Button(
+ button_frame, text="Apply Date & Time", command=self.on_date_time_apply_clicked)
+ self.date_time_apply_button.grid(row=0, column=2, padx=5)
+
+ self.local_rtc_apply_button = ttk.Button(
+ button_frame, text="Apply Local RTC", command=self.on_local_rtc_apply_clicked)
+ self.local_rtc_apply_button.grid(row=0, column=3, padx=5)
+
+ self.update_apply_button_state()
+
+ # Set trace on all variables to call update_apply_button_state when they change
+ self.year_var.trace_add("write", self.update_apply_button_state)
+ self.month_var.trace_add("write", self.update_apply_button_state)
+ self.day_var.trace_add("write", self.update_apply_button_state)
+ self.hour_var.trace_add("write", self.update_apply_button_state)
+ self.minute_var.trace_add("write", self.update_apply_button_state)
+ self.second_var.trace_add("write", self.update_apply_button_state)
+ self.timezone_var.trace_add("write", self.update_apply_button_state)
+
+ def on_automatic_time_toggled(self):
+ if self.automatic_time_check.instate(['selected']):
+ self.year_combo.state(['disabled'])
+ self.month_combo.state(['disabled'])
+ self.day_combo.state(['disabled'])
+ self.hour_combo.state(['disabled'])
+ self.minute_combo.state(['disabled'])
+ self.second_combo.state(['disabled'])
+ self.timezone_combo.state(['disabled'])
+ else:
+ self.year_combo.state(['!disabled'])
+ self.month_combo.state(['!disabled'])
+ self.day_combo.state(['!disabled'])
+ self.hour_combo.state(['!disabled'])
+ self.minute_combo.state(['!disabled'])
+ self.second_combo.state(['!disabled'])
+ self.timezone_combo.state(['!disabled'])
+ self.update_apply_button_state()
+
+ def update_apply_button_state(self, *_):
+ if self.automatic_time_check.instate(['selected']):
+ self.apply_button.state(['!disabled'])
+ self.timezone_apply_button.state(['disabled'])
+ self.date_time_apply_button.state(['disabled'])
+ self.local_rtc_apply_button.state(['disabled'])
+ else:
+ all_date_filled = all([
+ self.year_combo.get(),
+ self.month_combo.get(),
+ self.day_combo.get(),
+ ])
+ all_time_filled = all([
+ self.hour_combo.get(),
+ self.minute_combo.get(),
+ self.second_combo.get(),
+ ])
+ timezone_filled = self.timezone_combo.get()
+ all_filled = all_date_filled and all_time_filled and timezone_filled
+
+ if all_filled:
+ self.apply_button.state(['!disabled'])
+ else:
+ self.apply_button.state(['disabled'])
+
+ if timezone_filled and self.validate_timezone(timezone_filled):
+ self.timezone_apply_button.state(['!disabled'])
+ else:
+ self.timezone_apply_button.state(['disabled'])
+
+ if all_date_filled and all_time_filled:
+ self.date_time_apply_button.state(['!disabled'])
+ else:
+ self.date_time_apply_button.state(['disabled'])
+
+ self.local_rtc_apply_button.state(['!disabled'])
+
+ def on_apply_clicked(self):
+ automatic_time = self.automatic_time_check.instate(['selected'])
+ local_rtc = self.local_rtc_check.instate(['selected'])
+
+ if automatic_time:
+ subprocess.run(["timedatectl", "set-ntp", "true"])
+
+ try:
+ automatic_timezone_output = subprocess.run(
+ ["curl", "--fail", "https://ipinfo.io/timezone"], capture_output=True, text=True)
+ automatic_timezone = automatic_timezone_output.stdout.strip()
+
+ if automatic_timezone:
+ subprocess.run(
+ ["timedatectl", "set-timezone", automatic_timezone])
+ messagebox.showinfo(
+ "Info", "Automatic date, time, and timezone setting complete.")
+ else:
+ messagebox.showwarning(
+ "Warning", "Automatic date, time, and timezone setting failed. Please try again or use timedatectl.")
+ except requests.RequestException:
+ messagebox.showwarning(
+ "Warning", "Failed to fetch date, time, and timezone information. Please try again or use timedatectl.")
+ else:
+ try:
+ date_input = f"{self.year_combo.get()}-{int(self.month_combo.get()):02d}-{int(self.day_combo.get()):02d}"
+ time_input = f"{int(self.hour_combo.get()):02d}:{int(self.minute_combo.get()):02d}:{int(self.second_combo.get()):02d}"
+ timezone_input = self.timezone_combo.get()
+
+ # check for invalid numbers in fields
+ if not all([
+ self.validate_number(self.year_combo.get(), 1970, 2038),
+ self.validate_number(self.month_combo.get(), 1, 12),
+ self.validate_number(self.day_combo.get(), 1, 31),
+ self.validate_number(self.hour_combo.get(), 0, 23),
+ self.validate_number(self.minute_combo.get(), 0, 59),
+ self.validate_number(self.second_combo.get(), 0, 59),
+ self.validate_timezone(timezone_input)
+ ]):
+ messagebox.showerror(
+ "Error", "Invalid number entered in one or more fields.")
+ return
+
+ subprocess.run(["timedatectl", "set-ntp", "false"])
+ time.sleep(1)
+ subprocess.run(["timedatectl", "set-timezone", timezone_input])
+ time.sleep(1)
+ subprocess.run(["timedatectl", "set-time",
+ f"{date_input} {time_input}"])
+ messagebox.showinfo(
+ "Info", "Manual date, time, and timezone setting complete.")
+ except ValueError as e:
+ messagebox.showerror("Error", f"Error:\n{e}\n\nOne or more blank fields!")
+
+ # handle local rtc setting
+ if local_rtc:
+ subprocess.run(["timedatectl", "set-local-rtc", "1"])
+ else:
+ subprocess.run(["timedatectl", "set-local-rtc", "0"])
+
+ def on_timezone_apply_clicked(self):
+ timezone_input = self.timezone_combo.get()
+
+ if not timezone_input or not self.validate_timezone(timezone_input):
+ messagebox.showerror("Error", "Invalid or empty timezone field.")
+ return
+
+ try:
+ subprocess.run(["timedatectl", "set-timezone", timezone_input])
+ messagebox.showinfo(
+ "Info", "Timezone setting complete.")
+ except subprocess.CalledProcessError as e:
+ messagebox.showerror("Error", f"Error setting timezone:\n{e}")
+
+ def on_date_time_apply_clicked(self):
+ try:
+ date_input = f"{self.year_combo.get()}-{int(self.month_combo.get()):02d}-{int(self.day_combo.get()):02d}"
+ time_input = f"{int(self.hour_combo.get()):02d}:{int(self.minute_combo.get()):02d}:{int(self.second_combo.get()):02d}"
+
+ # check for invalid numbers in fields
+ if not all([
+ self.validate_number(self.year_combo.get(), 1970, 2038),
+ self.validate_number(self.month_combo.get(), 1, 12),
+ self.validate_number(self.day_combo.get(), 1, 31),
+ self.validate_number(self.hour_combo.get(), 0, 23),
+ self.validate_number(self.minute_combo.get(), 0, 59),
+ self.validate_number(self.second_combo.get(), 0, 59),
+ ]):
+ messagebox.showerror(
+ "Error", "Invalid number entered in one or more fields.")
+ return
+
+ subprocess.run(["timedatectl", "set-ntp", "false"])
+ time.sleep(1)
+ subprocess.run(["timedatectl", "set-time", f"{date_input} {time_input}"])
+ messagebox.showinfo(
+ "Info", "Manual date and time setting complete.")
+ except ValueError as e:
+ messagebox.showerror("Error", f"Error:\n{e}\n\nOne or more blank fields!")
+
+ def on_local_rtc_apply_clicked(self):
+ local_rtc = self.local_rtc_check.instate(['selected'])
+
+ if local_rtc:
+ subprocess.run(["timedatectl", "set-local-rtc", "1"])
+ messagebox.showinfo("Info", "Local RTC setting enabled.")
+ else:
+ subprocess.run(["timedatectl", "set-local-rtc", "0"])
+ messagebox.showinfo("Info", "Local RTC setting disabled.")
+
+ def validate_number(self, value, min_val, max_val):
+ try:
+ num = int(value)
+ return min_val <= num <= max_val
+ except ValueError:
+ return False
+
+ def validate_timezone(self, timezone):
+ return timezone in pytz.all_timezones
+
+ def populate_fields(self):
+ now = datetime.datetime.now()
+ self.year_combo.set(now.year)
+ self.month_combo.set(now.month)
+ self.day_combo.set(now.day)
+ self.hour_combo.set(now.hour)
+ self.minute_combo.set(now.minute)
+ self.second_combo.set(now.second)
+
+ try:
+ current_timezone = subprocess.check_output(
+ ["timedatectl", "show", "--property=Timezone"]).decode().strip().split("=")[1]
+ self.timezone_combo.set(current_timezone)
+ except subprocess.CalledProcessError:
+ pass
+
+ try:
+ ntp_status = subprocess.check_output(
+ ["timedatectl", "show", "--property=NTP"]).decode().strip().split("=")[1]
+ if ntp_status == "yes":
+ self.automatic_time_check.state(['selected'])
+ self.on_automatic_time_toggled()
+ else:
+ self.automatic_time_check.state(['!selected'])
+ self.on_automatic_time_toggled()
+ except subprocess.CalledProcessError:
+ pass
+
+ try:
+ local_rtc_status = subprocess.check_output(
+ ["timedatectl", "show", "--property=LocalRTC"]).decode().strip().split("=")[1]
+ if local_rtc_status == "yes":
+ self.local_rtc_check.state(['selected'])
+ else:
+ self.local_rtc_check.state(['!selected'])
+ except subprocess.CalledProcessError:
+ pass
+
+ self.year_combo.bind("<<ComboboxSelected>>",
+ self.update_apply_button_state)
+ self.month_combo.bind("<<ComboboxSelected>>",
+ self.update_apply_button_state)
+ self.day_combo.bind("<<ComboboxSelected>>",
+ self.update_apply_button_state)
+ self.hour_combo.bind("<<ComboboxSelected>>",
+ self.update_apply_button_state)
+ self.minute_combo.bind("<<ComboboxSelected>>",
+ self.update_apply_button_state)
+ self.second_combo.bind("<<ComboboxSelected>>",
+ self.update_apply_button_state)
+ self.timezone_combo.bind(
+ "<<ComboboxSelected>>", self.update_apply_button_state)
+ self.timezone_combo.bind(
+ "<KeyRelease>", self.update_apply_button_state)
+
+
+if __name__ == "__main__":
+ app = DateTimeSetter()
+ app.mainloop()
diff --git a/.local/src/python-playerctl_systray/Makefile b/.local/src/python-playerctl_systray/Makefile
new file mode 100644
index 0000000..a0ab881
--- /dev/null
+++ b/.local/src/python-playerctl_systray/Makefile
@@ -0,0 +1,4 @@
+compile:
+ cython3 --embed -o playerctl_systray.c -X language_level=3 playerctl_systray.py
+ PYTHON_VERSION=`ls --sort version /usr/include | grep -o 'python[3-9]\+\.[0-9]\+' | tail -1` ; \
+ gcc -march=native -O2 -pipe -fno-plt -I /usr/include/$$PYTHON_VERSION -o playerctl_systray playerctl_systray.c -l$$PYTHON_VERSION -lpthread -lm -lutil -ldl `pkg-config --cflags --libs gtk+-3.0 appindicator3-0.1 dbus-1 dbus-glib-1`
diff --git a/.local/src/python-playerctl_systray/playerctl_systray.py b/.local/src/python-playerctl_systray/playerctl_systray.py
new file mode 100644
index 0000000..79a4c0c
--- /dev/null
+++ b/.local/src/python-playerctl_systray/playerctl_systray.py
@@ -0,0 +1,123 @@
+import gi
+import subprocess
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('AppIndicator3', '0.1')
+gi.require_version('GLib', '2.0')
+from gi.repository import Gtk, AppIndicator3, GLib
+
+class playerctl_systray:
+ def __init__(self):
+ self.last_instances = self.get_player_instances()
+ self.indicator = AppIndicator3.Indicator.new(
+ "media-control-app",
+ "audio-x-generic",
+ AppIndicator3.IndicatorCategory.APPLICATION_STATUS
+ )
+ self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
+ self.indicator.set_menu(self.create_menu())
+
+ def create_menu(self):
+ menu = Gtk.Menu()
+
+ instances = self.get_player_instances()
+ if instances:
+ for instance in instances:
+ instance_submenu = Gtk.Menu()
+
+ play_item = Gtk.MenuItem(label="play-pause")
+ play_item.connect("activate", self.play_pause, instance)
+ instance_submenu.append(play_item)
+
+ next_item = Gtk.MenuItem(label="next")
+ next_item.connect("activate", self.next_track, instance)
+ instance_submenu.append(next_item)
+
+ prev_item = Gtk.MenuItem(label="previous")
+ prev_item.connect("activate", self.prev_track, instance)
+ instance_submenu.append(prev_item)
+
+ instance_menu_item = Gtk.MenuItem(label=instance)
+ instance_menu_item.set_submenu(instance_submenu)
+ menu.append(instance_menu_item)
+
+ a_submenu = Gtk.Menu()
+
+ a_pause_item = Gtk.MenuItem(label="pause")
+ a_pause_item.connect("activate", self.a_pause)
+ a_submenu.append(a_pause_item)
+
+ a_play_item = Gtk.MenuItem(label="play")
+ a_play_item.connect("activate", self.a_play)
+ a_submenu.append(a_play_item)
+
+ a_next_item = Gtk.MenuItem(label="next")
+ a_next_item.connect("activate", self.a_next_track)
+ a_submenu.append(a_next_item)
+
+ a_prev_item = Gtk.MenuItem(label="previous")
+ a_prev_item.connect("activate", self.a_prev_track)
+ a_submenu.append(a_prev_item)
+
+ a_menu_item = Gtk.MenuItem(label="-a")
+ a_menu_item.set_submenu(a_submenu)
+ menu.append(a_menu_item)
+ else:
+ no_players_item = Gtk.MenuItem(label="no players found")
+ no_players_item.set_sensitive(False) # greyed out
+ menu.append(no_players_item)
+
+ menu.append(Gtk.SeparatorMenuItem()) # separator
+
+ quit_item = Gtk.MenuItem(label="quit")
+ quit_item.connect("activate", self.quit)
+ menu.append(quit_item)
+
+ menu.show_all()
+ return menu
+
+ def get_player_instances(self):
+ result = subprocess.run("playerctl -l", shell=True, capture_output=True, text=True)
+ return result.stdout.strip().split('\n') if result.stdout.strip() else []
+
+ def play_pause(self, _, instance):
+ self.run_command(f"playerctl -p {instance} play-pause")
+
+ def next_track(self, _, instance):
+ self.run_command(f"playerctl -p {instance} next")
+
+ def prev_track(self, _, instance):
+ self.run_command(f"playerctl -p {instance} previous")
+
+ def a_play(self, _):
+ self.run_command("playerctl -a play")
+
+ def a_pause(self, _):
+ self.run_command("playerctl -a pause")
+
+ def a_next_track(self, _):
+ self.run_command("playerctl -a next")
+
+ def a_prev_track(self, _):
+ self.run_command("playerctl -a previous")
+
+ def run_command(self, command):
+ subprocess.run(command, shell=True)
+
+ def quit(self, _):
+ Gtk.main_quit()
+
+ def refresh_menu(self):
+ current_instances = self.get_player_instances()
+ if current_instances != self.last_instances:
+ self.indicator.set_menu(self.create_menu())
+ self.last_instances = current_instances
+ return True # returns True so that timeout continues
+
+def main():
+ app = playerctl_systray()
+ GLib.timeout_add(1024, app.refresh_menu) # probably should be event-based in the future
+ Gtk.main()
+
+if __name__ == "__main__":
+ main()
diff --git a/.local/src/python-rainfall/Makefile b/.local/src/python-rainfall/Makefile
new file mode 100644
index 0000000..c7cdd98
--- /dev/null
+++ b/.local/src/python-rainfall/Makefile
@@ -0,0 +1,4 @@
+compile:
+ cython3 --embed -o rainfall.c -X language_level=3 rainfall.py
+ PYTHON_VERSION=`ls --sort version /usr/include | grep -o 'python[3-9]\+\.[0-9]\+' | tail -1` ; \
+ gcc -march=native -O2 -pipe -fno-plt -I /usr/include/$$PYTHON_VERSION -o rainfall rainfall.c -l$$PYTHON_VERSION -lpthread -lm -lutil -ldl
diff --git a/.local/src/python-rainfall/rainfall.py b/.local/src/python-rainfall/rainfall.py
new file mode 100644
index 0000000..9175022
--- /dev/null
+++ b/.local/src/python-rainfall/rainfall.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import time
+import random
+import os
+import sys
+
+
+colors = {
+ "black": "\u001b[30m",
+ "red": "\u001b[31m",
+ "green": "\u001b[32m",
+ "yellow": "\u001b[33m",
+ "blue": "\u001b[34m",
+ "magenta": "\u001b[35m",
+ "cyan": "\u001b[36m",
+ "white": "\u001b[37m",
+ "reset": "\u001b[0m",
+
+ "b_black": "\u001b[30;1m",
+ "b_red": "\u001b[31;1m",
+ "b_green": "\u001b[32;1m",
+ "b_yellow": "\u001b[33;1m",
+ "b_blue": "\u001b[34;1m",
+ "b_magenta": "\u001b[35;1m",
+ "b_cyan": "\u001b[36;1m",
+ "b_white": "\u001b[37;1m",
+
+ "Reset": "\u001b[0m",
+}
+
+def Colored(string, color):
+ return string if "-m" in sys.argv else ( colors[color] + string + colors["Reset"])
+
+
+def Clear_Screen():
+ print("\033[2J") # erase saved lines
+ print("\033[3J") # erase entire screen
+ print("\033[H") # moves cursor to home position
+
+
+def Get_Arguments():
+ args_dict = {"colors": []}
+ if sys.argv:
+ for arg in sys.argv:
+ if "-i=" in arg:
+ args_dict["intensity"] = int(arg.split("-i=")[1])
+ if "-t=" in arg:
+ args_dict["timing"] = float(arg.split("-t=")[1])
+ if arg in colors:
+ args_dict["colors"].append(arg)
+ if not args_dict["colors"]:
+ del args_dict["colors"]
+ return args_dict
+
+
+def New_Drop():
+ for i in range(intensity):
+ shape = random.choice(DROPSHAPES)
+ color = random.choice(drop_colors)
+
+ raindrop = {
+ "shape": Colored(shape, color),
+ "x": random.randint(0, xmax),
+ "y": 0,
+ }
+ rainfall.append(raindrop)
+
+def Rain():
+ ## iterate over every line
+ for i in range(ymax):
+ line = " "*xmax
+
+ ### to avoid splicing of ansi codes, splice in the drops from the end of the line
+ this_line_raindrops = [raindrop for raindrop in rainfall if raindrop["y"] == i]
+ this_line_raindrops.sort(key=lambda y: y["x"])
+ this_line_raindrops.reverse()
+
+ ## insert new drops and shift existing drops
+ for raindrop in this_line_raindrops:
+ x = raindrop["x"]
+ line = line[:x] + raindrop["shape"] +line[x:]
+
+ print(line)
+
+ ### update raindrop positions
+ for raindrop in rainfall:
+ raindrop["y"] += 1
+
+ ## once a raindrop reaches the ground, they splash
+ if raindrop["y"] > ymax-2:
+ raindrop["shape"] = Colored("o", random.choice(drop_colors))
+
+ # raindrops outside the window evaporate
+ if raindrop["y"] > ymax:
+ rainfall.remove(raindrop)
+
+ New_Drop()
+
+
+
+def Weather_Forecast():
+ global weather
+ global intensity
+
+ weather += 1
+ if weather == 100:
+ weather = 0
+ intensity += random.choice([-1,1])
+ if intensity < 1:
+ intensity = 1
+ if intensity > 10:
+ intensity = 10
+
+
+size = os.get_terminal_size()
+xmax = size.columns
+ymax = int(size.lines)
+
+weather = 0
+rainfall = []
+DROPSHAPES =["|", "│", "┃", "╽", "╿", "║", "┆", "┇", "┊", "┋", "╵", "╹", "╻"]
+
+args = Get_Arguments()
+intensity = args.get("intensity", 1)
+timing = args.get("timing", 0.08)
+drop_colors = args.get("colors", ["blue", "b_blue"])
+
+
+
+print('\033[?25l', end="") ## hides the cursor
+New_Drop()
+
+try:
+ while True:
+ Rain()
+ time.sleep(timing)
+ Clear_Screen()
+ Weather_Forecast()
+
+except KeyboardInterrupt:
+ Clear_Screen()
+ print('\033[?25h', end="") # makes cursor visible again
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+