diff options
| author | Kyle Javier [kj_sh604] | 2025-10-22 21:11:34 -0400 |
|---|---|---|
| committer | GitHub | 2025-10-22 21:11:34 -0400 |
| commit | 1e812a38e5af462e7aec57de9521f468671a3d27 (patch) | |
| tree | b1320099d0883de3d8ab063602a27daa2231bee0 | |
| parent | 235c5d1933ed24a6e2a3aef52859466b2d615153 (diff) | |
| parent | e79fd38edb3b7b746fbc23ca79f2991cf212ef57 (diff) | |
merge: pull request #1 from kj-sh604/feat/october-feature-dump
feat: october feature dump
| -rw-r--r-- | README.md | 146 | ||||
| -rw-r--r-- | assets/demo.gif | bin | 20821 -> 51713 bytes | |||
| -rwxr-xr-x | install.sh | 128 | ||||
| -rw-r--r-- | src/.gitignore | 7 | ||||
| -rwxr-xr-x | src/panahone | 461 | ||||
| -rwxr-xr-x | uninstall.sh | 51 |
6 files changed, 739 insertions, 54 deletions
@@ -1,27 +1,143 @@ -# README.txt +# panahone β
-```plaintext +simple yet *somewhat* π€· feature-rich GTK3 system tray weather applet using [wttr.in](https://wttr.in/)'s API, written in Python. -panahone β
-------------- +## features -simple gtk3 systray weather applet using wttr.in's API written in Python. +- π‘οΈ **semi-realtime weather information** with automatic updates +- πΎ **smart caching** to reduce API calls (10-minute cache by default) +- π **auto-refresh** support (configurable interval) +- π¨ **dynamic weather icons** that change based on conditions +- π **location-based** weather (or auto-detect) +- π‘οΈ **temperature units** (celsius/fahrenheit) with easy toggle +- π¨ **comprehensive weather data**: + - current temperature and "feels like" temperature + - weather conditions + - humidity levels + - wind speed and direction + - uv index + - visibility +- βοΈ **persistent configuration** with JSON config file ***(EXPERIMENTAL)*** +- π **detailed logging** for debugging +- π±οΈ **interactive system tray**: + - left click: fetch/refresh weather + - middle click: quit + - right click: context menu +- π **desktop notifications** with customizable timeout -(i really just wanted to practice using the `requests` and `json` modules in -python so i made this). +## install -usage -------------- +### system dependencies -usage: panahone [-h] [-l LOCATION] [-f] +- python 3.6+ +- gtk3 +- gobject +- libnotify -options: - -h, --help show this help message and exit - -l, --location LOCATION location for weather - -f, --fahrenheit use Fahrenheit instead of Celsius +on Arch Linux: +```bash +sudo pacman -S python python-gobject gtk3 libnotify +``` + +on Ubuntu/Debian: +```bash +sudo apt install python3 python3-gi gir1.2-gtk-3.0 gir1.2-notify-0.7 +``` + +on Fedora: +```bash +sudo dnf install python3 python3-gobject gtk3 libnotify +``` + +### in-app/venv Dependencies + +```bash +cd src +pip install -r requirements.txt +``` + +## usage + +```bash +# auto-detect location +./panahone + +# specify location +./panahone -l "New York" + +# use fahrenheit +./panahone -f + +# enable debug logging +./panahone --debug +``` + +### cli args ``` +-h, --help Show help message +-l, --location LOCATION Specify location (city, coordinates, airport code) +-f, --fahrenheit Use Fahrenheit instead of Celsius +-v, --version Show version +--debug Enable debug logging +``` + +### mouse + +- **left click**: fetch/refresh weather +- **middle click**: quit application +- **right click**: context menu (refresh, toggle units, about, quit) + +## config (honestly, still experimental I would still use overrides at this point) + +config file: `~/.config/panahone/config.json` (auto-created on first run) + +```json +{ + "location": "", // default location (empty = auto-detect) + "use_fahrenheit": false, // temperature unit + "auto_refresh": true, // enable auto-updates + "refresh_interval_minutes": 30, // update frequency + "show_wind": true, // show wind data + "show_humidity": true, // show humidity + "show_feels_like": true, // show "feels like" temp + "notification_timeout": 10000 // notification duration (ms) +} +``` + +**file:** +- config: `~/.config/panahone/config.json` +- cache: `~/.cache/panahone/weather_cache.json` +- logs: `~/.cache/panahone/panahone.log` + +## weather icons + +`panahone` uses system theme icons and automatically maps weather conditions to appropriate icons (yaru would have all of these, but some icon themes may have some missing): + +- βοΈ clear/sunny β `weather-clear` +- π€οΈ partly cloudy β `weather-few-clouds` +- βοΈ cloudy/overcast β `weather-overcast` +- π«οΈ fog/mist β `weather-fog` +- π§οΈ rain β `weather-showers` +- βοΈ thunderstorm β `weather-storm` +- βοΈ snow/sleet β `weather-snow` + +## troubleshooting + +**Debug mode:** +```bash +./panahone --debug +cat ~/.cache/panahone/panahone.log +``` +## changelog (2025.10.22) + +**improvements:** +- added persistent configuration system with json config file +- implemented smart caching to reduce api calls +- added auto-refresh functionality with configurable intervals +- enhanced weather display with humidity, wind, uv index, and visibility +- added dynamic weather icons that change based on conditions # πΈ - +
\ No newline at end of file diff --git a/assets/demo.gif b/assets/demo.gif Binary files differindex 82fe807..a680f17 100644 --- a/assets/demo.gif +++ b/assets/demo.gif diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..5aca202 --- /dev/null +++ b/install.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash + +set -e + +echo "π€οΈ panahone weather applet installer" +echo "======================================" +echo "" + +# checks +if [[ "$OSTYPE" != "linux-gnu"* ]]; then + echo "β error: this app is designed for linux systems." + exit 1 +fi + +if ! command -v python3 &>/dev/null; then + echo "β error: python 3 is not installed." + echo "please install python 3 first." + exit 1 +fi + +PYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2) +echo "β
python 3 found: $(python3 --version)" + +if ! python3 -c "import gi; gi.require_version('Gtk', '3.0')" 2>/dev/null; then + echo "β οΈ gtk3 python bindings not found." + echo "" + echo "please install the required system packages:" + echo "" + echo "Arch:" + echo " sudo pacman -S python-gobject gtk3 libnotify" + echo "" + echo "Ubuntu/Debian:" + echo " sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-notify-0.7" + echo "" + echo "Fedora:" + echo " sudo dnf install python3-gobject gtk3 libnotify" + echo "" + read -p "press enter to continue after installing, or ctrl+c to exit..." +fi + +# deps check +echo "" +echo "π¦ installing python dependencies..." +cd src +pip3 install --user -r requirements.txt + +if [ $? -eq 0 ]; then + echo "β
dependencies installed successfully" +else + echo "β failed to install dependencies" + exit 1 +fi + +# +x check +chmod +x panahone +echo "β
made panahone executable" + +# symbolic link (my personal preferred) +echo "" +read -p "do you want to create a symbolic link in ~/.local/bin? (y/n) " -n 1 -r +echo "" +if [[ $REPLY =~ ^[Yy]$ ]]; then + mkdir -p ~/.local/bin + ln -sf "$(pwd)/panahone" ~/.local/bin/panahone + echo "β
symbolic link created at ~/.local/bin/panahone" + echo "" + echo "β οΈ make sure ~/.local/bin is in your path:" + echo " add this to your ~/.bashrc or ~/.zshrc:" + echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" +fi + +# .desktop file (i do not prefer this, personally but y'all go ahead!) +echo "" +read -p "do you want to create a desktop entry? (y/n) " -n 1 -r +echo "" +if [[ $REPLY =~ ^[Yy]$ ]]; then + DESKTOP_FILE="$HOME/.local/share/applications/panahone.desktop" + mkdir -p ~/.local/share/applications + + cat >"$DESKTOP_FILE" <<EOF +[Desktop Entry] +Name=Panahone +Comment=GTK3 Weather Applet +Exec=$(pwd)/panahone +Icon=weather-overcast +Terminal=false +Type=Application +Categories=Utility; +StartupNotify=false +EOF + + echo "β
desktop entry created at $desktop_file" +fi + +# xdg autostart +echo "" +read -p "do you want panahone to start automatically on login? (y/n) " -n 1 -r +echo "" +if [[ $REPLY =~ ^[Yy]$ ]]; then + AUTOSTART_FILE="$HOME/.config/autostart/panahone.desktop" + mkdir -p ~/.config/autostart + + cat >"$AUTOSTART_FILE" <<EOF +[Desktop Entry] +Name=Panahone +Comment=GTK3 Weather Applet +Exec=$(pwd)/panahone +Icon=weather-overcast +Terminal=false +Type=Application +X-GNOME-Autostart-enabled=true +EOF + + echo "β
autostart entry created at $autostart_file" +fi + +echo "" +echo "π installation complete!" +echo "" +echo "you can now run panahone with:" +echo " cd $(pwd) && ./panahone" +if [[ -L ~/.local/bin/panahone ]]; then + echo " or simply: panahone (if ~/.local/bin is in PATH)" +fi +echo "" +echo "for usage, run:" +echo " ./panahone --help" +echo ""
\ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore index 9c64fb6..ce03b6a 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -13,3 +13,10 @@ venv/ *.atom/ .DS_Store # macOS Thumbs.db # Windows + +# Panahone specific +*.cache +cache/ +config/ +weather_cache.json + diff --git a/src/panahone b/src/panahone index a4ef7ac..6a4d0f6 100755 --- a/src/panahone +++ b/src/panahone @@ -1,14 +1,20 @@ #!/usr/bin/env python3 import argparse -import gi -import requests +import json +import logging +import os import signal +import sys import warnings -# import sys -# import json +from datetime import datetime, timedelta +from pathlib import Path +from typing import Optional, Dict, Any + +import gi +import requests -# will fix this when it breaks :D +# suppress gtk statusicon FOR NOW warnings.filterwarnings( "ignore", category=DeprecationWarning, @@ -17,81 +23,458 @@ warnings.filterwarnings( gi.require_version("Gtk", "3.0") gi.require_version("Notify", "0.7") -from gi.repository import Gtk, Notify +from gi.repository import Gtk, GLib, Notify + +APP_NAME = "Panahone" +APP_VERSION = "2025.10.22" +CACHE_DIR = Path.home() / ".cache" / "panahone" +CONFIG_DIR = Path.home() / ".config" / "panahone" +CACHE_FILE = CACHE_DIR / "weather_cache.json" +CONFIG_FILE = CONFIG_DIR / "config.json" +LOG_FILE = CACHE_DIR / "panahone.log" +CACHE_DURATION = timedelta(minutes=10) # cache weather data for 10 minutes +AUTO_REFRESH_INTERVAL = 30 * 60 * 1000 # 30 minutes + +# weather icon mapping +WEATHER_ICONS = { + "clear": "weather-clear", + "sunny": "weather-clear", + "partly cloudy": "weather-few-clouds", + "cloudy": "weather-overcast", + "overcast": "weather-overcast", + "mist": "weather-fog", + "fog": "weather-fog", + "patchy rain": "weather-showers-scattered", + "light rain": "weather-showers", + "rain": "weather-showers", + "heavy rain": "weather-storm", + "thunderstorm": "weather-storm", + "thunder": "weather-storm", + "snow": "weather-snow", + "sleet": "weather-snow", + "blizzard": "weather-snow", +} + +# logging if desired +CACHE_DIR.mkdir(parents=True, exist_ok=True) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler(LOG_FILE), logging.StreamHandler(sys.stdout)], +) +logger = logging.getLogger(APP_NAME) + + +class WeatherCache: + """Manages caching of weather data to reduce API calls""" + + def __init__(self, cache_file: Path): + self.cache_file = cache_file + self.cache_file.parent.mkdir(parents=True, exist_ok=True) + + def get(self, location: str) -> Optional[Dict[str, Any]]: + """Retrieve cached weather data if still valid""" + if not self.cache_file.exists(): + return None + + try: + with open(self.cache_file, "r") as f: + cache = json.load(f) + + if cache.get("location") != location: + return None + + cached_time = datetime.fromisoformat(cache.get("timestamp", "")) + if datetime.now() - cached_time > CACHE_DURATION: + return None + + logger.info(f"Using cached weather data for {location}") + return cache.get("data") + except (json.JSONDecodeError, ValueError, KeyError) as e: + logger.warning(f"Cache read error: {e}") + return None + + def set(self, location: str, data: Dict[str, Any]): + """Save weather data to cache""" + try: + cache = { + "location": location, + "timestamp": datetime.now().isoformat(), + "data": data, + } + with open(self.cache_file, "w") as f: + json.dump(cache, f, indent=2) + logger.info(f"Cached weather data for {location}") + except Exception as e: + logger.error(f"Cache write error: {e}") + + +class ConfigManager: + """Manages application configuration""" + + DEFAULT_CONFIG = { + "location": "", + "use_fahrenheit": False, + "auto_refresh": True, + "refresh_interval_minutes": 30, + "show_wind": True, + "show_humidity": True, + "show_feels_like": True, + "notification_timeout": 10000, # milliseconds + } + + def __init__(self, config_file: Path): + self.config_file = config_file + self.config_file.parent.mkdir(parents=True, exist_ok=True) + self.config = self.load() + + def load(self) -> Dict[str, Any]: + """Load configuration from file""" + if not self.config_file.exists(): + self.save(self.DEFAULT_CONFIG) + return self.DEFAULT_CONFIG.copy() + + try: + with open(self.config_file, "r") as f: + config = json.load(f) + # ensure all keys exist + merged = self.DEFAULT_CONFIG.copy() + merged.update(config) + return merged + except Exception as e: + logger.error(f"Config load error: {e}") + return self.DEFAULT_CONFIG.copy() + + def save(self, config: Dict[str, Any]): + """Save configuration to file""" + try: + with open(self.config_file, "w") as f: + json.dump(config, f, indent=2) + logger.info("Configuration saved") + except Exception as e: + logger.error(f"Config save error: {e}") + + def get(self, key: str, default=None): + """Get configuration value""" + return self.config.get(key, default) + + def set(self, key: str, value: Any): + """Set configuration value""" + self.config[key] = value + self.save(self.config) class PanahoneApplet: - def __init__(self, location, use_fahrenheit): - self.location = location or "" - self.use_fahrenheit = use_fahrenheit - Notify.init("Panahone") + """Main application class for the weather applet""" + + def __init__( + self, location: Optional[str] = None, use_fahrenheit: Optional[bool] = None + ): + self.config = ConfigManager(CONFIG_FILE) + self.cache = WeatherCache(CACHE_FILE) + + # args override config + self.location = ( + location if location is not None else self.config.get("location", "") + ) + self.use_fahrenheit = ( + use_fahrenheit + if use_fahrenheit is not None + else self.config.get("use_fahrenheit", False) + ) + + Notify.init(APP_NAME) + + # create systray icon self.icon = Gtk.StatusIcon() - self.icon.set_from_icon_name('weather-overcast') - self.icon.set_tooltip_text("Panahone: click to get weather") + self.icon.set_from_icon_name("weather-overcast") + self.icon.set_tooltip_text(f"{APP_NAME}: Click to get weather") self.icon.connect("button-press-event", self.on_click) + + # refresh timer + self.auto_refresh_timer = None + if self.config.get("auto_refresh", True): + self.start_auto_refresh() + + # sigint handling signal.signal(signal.SIGINT, self.quit) + logger.info(f"{APP_NAME} v{APP_VERSION} started") + + def start_auto_refresh(self): + """Start automatic weather refresh timer""" + interval = self.config.get("refresh_interval_minutes", 30) * 60 * 1000 + self.auto_refresh_timer = GLib.timeout_add(interval, self.auto_refresh_callback) + logger.info(f"Auto-refresh enabled (every {interval // 60000} minutes)") + + def auto_refresh_callback(self): + """Callback for auto-refresh timer""" + logger.info("Auto-refreshing weather data") + self.fetch_and_notify(silent=True) + return True # Continue timer + def on_click(self, icon, event): - if event.button == 1: - Notify.Notification.new( - "Panahone", - "Retrieving Weather Dataβ¦", - "weather-overcast" - ).show() + """Handle mouse clicks on the system tray icon""" + if event.button == 1: # left + self.show_notification( + "Retrieving Weather Dataβ¦", "weather-overcast", timeout=2000 + ) self.fetch_and_notify() - elif event.button == 2: + elif event.button == 2: # middle self.quit() + elif event.button == 3: # right + self.show_menu(event) + + def show_menu(self, event): + """Show context menu on right-click""" + menu = Gtk.Menu() + + # refresh + refresh_item = Gtk.MenuItem(label="Refresh Weather") + refresh_item.connect("activate", lambda w: self.fetch_and_notify()) + menu.append(refresh_item) + menu.append(Gtk.SeparatorMenuItem()) + + # f/c toggle + temp_unit = "Celsius" if self.use_fahrenheit else "Fahrenheit" + toggle_item = Gtk.MenuItem(label=f"Switch to {temp_unit}") + toggle_item.connect("activate", self.toggle_temperature_unit) + menu.append(toggle_item) + menu.append(Gtk.SeparatorMenuItem()) + + # about + about_item = Gtk.MenuItem(label="About") + about_item.connect("activate", self.show_about) + menu.append(about_item) + quit_item = Gtk.MenuItem(label="Quit") + quit_item.connect("activate", self.quit) + menu.append(quit_item) + + menu.show_all() + menu.popup(None, None, None, None, event.button, event.time) + + def toggle_temperature_unit(self, widget): + """Toggle between Fahrenheit and Celsius""" + self.use_fahrenheit = not self.use_fahrenheit + self.config.set("use_fahrenheit", self.use_fahrenheit) + unit = "Fahrenheit" if self.use_fahrenheit else "Celsius" + self.show_notification( + f"Temperature unit changed to {unit}", "weather-overcast" + ) + logger.info(f"Temperature unit changed to {unit}") + + def show_about(self, widget): + """Show about dialog""" + message = ( + f"{APP_NAME} v{APP_VERSION}\n" + f"A GTK3 weather applet using wttr.in API\n\n" + f"Features:\n" + f"β’ Real-time weather updates\n" + f"β’ Automatic caching\n" + f"β’ Auto-refresh support\n" + f"β’ Customizable display\n\n" + f"Left click: Fetch weather\n" + f"Middle click: Quit\n" + f"Right click: Menu" + ) + self.show_notification(message, "weather-overcast", timeout=15000) + + def get_weather_icon(self, weather_desc: str) -> str: + """Map weather description to system icon""" + weather_lower = weather_desc.lower() + for keyword, icon in WEATHER_ICONS.items(): + if keyword in weather_lower: + return icon + return "weather-overcast" # Default icon + + def fetch_and_notify(self, silent: bool = False): + """Fetch weather data and show notification""" + # always use cache first + cached_data = self.cache.get(self.location) + if cached_data: + self.display_weather(cached_data, from_cache=True, silent=silent) + return - def fetch_and_notify(self): if not self.location: url = "https://wttr.in/?format=j1" else: url = f"https://wttr.in/{self.location}?format=j1" + + try: + logger.info(f"Fetching weather from {url}") + response = requests.get(url, timeout=15) + response.raise_for_status() + data = response.json() + + self.cache.set(self.location, data) + self.display_weather(data, from_cache=False, silent=silent) + + except requests.exceptions.Timeout: + error_msg = "Request timed out. Please check your internet connection." + logger.error(error_msg) + self.show_notification(f"Error: {error_msg}", "dialog-error") + except requests.exceptions.RequestException as e: + error_msg = f"Network error: {str(e)}" + logger.error(error_msg) + self.show_notification(f"Error: {error_msg}", "dialog-error") + except json.JSONDecodeError: + error_msg = "Invalid response from weather service" + logger.error(error_msg) + self.show_notification(f"Error: {error_msg}", "dialog-error") + except KeyError as e: + error_msg = f"Unexpected data format: {str(e)}" + logger.error(error_msg) + self.show_notification(f"Error: {error_msg}", "dialog-error") + except Exception as e: + error_msg = f"Unexpected error: {str(e)}" + logger.error(error_msg) + self.show_notification(f"Error: {error_msg}", "dialog-error") + + def display_weather( + self, data: Dict[str, Any], from_cache: bool = False, silent: bool = False + ): + """Parse and display weather data""" try: - r = requests.get(url, timeout=10) - data = r.json() current = data["current_condition"][0] - location = data["nearest_area"][0]["areaName"][0]["value"] + location_info = data["nearest_area"][0] + location = location_info["areaName"][0]["value"] + country = location_info["country"][0]["value"] temp_c = current["temp_C"] temp_f = current["temp_F"] + feels_c = current["FeelsLikeC"] + feels_f = current["FeelsLikeF"] unit = "Β°F" if self.use_fahrenheit else "Β°C" temp = temp_f if self.use_fahrenheit else temp_c + feels = feels_f if self.use_fahrenheit else feels_c weather = current["weatherDesc"][0]["value"] - message = f"Weather in {location}: {weather}, Temp: {temp}{unit}" - except Exception as e: - if not self.location: - message = f"Error fetching weather: {e}" - else: - message = f"Error fetching weather for {self.location}: {e}" + humidity = current["humidity"] + wind_speed_kmph = current["windspeedKmph"] + wind_speed_mph = current["windspeedMiles"] + wind_speed = wind_speed_mph if self.use_fahrenheit else wind_speed_kmph + wind_unit = "mph" if self.use_fahrenheit else "km/h" + wind_dir = current["winddir16Point"] + uv_index = current.get("uvIndex", "N/A") + visibility_km = current.get("visibility", "N/A") + visibility_mi = current.get("visibilityMiles", "N/A") + visibility = visibility_mi if self.use_fahrenheit else visibility_km + visibility_unit = "mi" if self.use_fahrenheit else "km" + + message_lines = [ + f"π {location}, {country}", + ( + f"π‘οΈ {temp}{unit} (feels like {feels}{unit})" + if self.config.get("show_feels_like") + else f"π‘οΈ {temp}{unit}" + ), + f"βοΈ {weather}", + ] + + if self.config.get("show_humidity", True): + message_lines.append(f"π§ Humidity: {humidity}%") + + if self.config.get("show_wind", True): + message_lines.append(f"π¨ Wind: {wind_speed} {wind_unit} {wind_dir}") + + message_lines.extend( + [ + f"βοΈ UV Index: {uv_index}", + f"ποΈ Visibility: {visibility} {visibility_unit}", + ] + ) + + if from_cache: + message_lines.append("\n(from cache)") + + message = "\n".join(message_lines) + + # systray icon update + icon_name = self.get_weather_icon(weather) + self.icon.set_from_icon_name(icon_name) - Notify.Notification.new( - "Panahone", - message, - "weather-overcast" - ).show() + # tooltip update + tooltip = f"{location}: {weather}, {temp}{unit}" + self.icon.set_tooltip_text(tooltip) + + # show notification (unless silent) + if not silent: + timeout = self.config.get("notification_timeout", 10000) + self.show_notification(message, icon_name, timeout=timeout) + + logger.info(f"Weather displayed for {location}: {weather}, {temp}{unit}") + + except (KeyError, IndexError) as e: + error_msg = f"Error parsing weather data: {str(e)}" + logger.error(error_msg) + self.show_notification(f"Error: {error_msg}", "dialog-error") + + def show_notification( + self, message: str, icon: str = "weather-overcast", timeout: int = 10000 + ): + """Display a desktop notification""" + try: + notification = Notify.Notification.new(APP_NAME, message, icon) + notification.set_timeout(timeout) + notification.show() + except Exception as e: + logger.error(f"Notification error: {e}") def quit(self, *args): + """Clean up and quit the application""" + logger.info(f"{APP_NAME} shutting down") + if self.auto_refresh_timer: + GLib.source_remove(self.auto_refresh_timer) + Notify.uninit() Gtk.main_quit() def run(self): + """Start the GTK main loop""" Gtk.main() def parse_args(): - p = argparse.ArgumentParser(prog="panahone") - p.add_argument("-l", "--location", help="location for weather") - p.add_argument("-f", "--fahrenheit", action="store_true", - help="use Fahrenheit instead of Celsius") - return p.parse_args() + """Parse command line arguments""" + parser = argparse.ArgumentParser( + prog="panahone", + description=f"{APP_NAME} - A GTK3 weather applet using wttr.in API", + epilog="Left click: fetch weather | Middle click: quit | Right click: menu", + ) + parser.add_argument( + "-l", + "--location", + help="Location for weather (city name, coordinates, etc.)", + metavar="LOCATION", + ) + parser.add_argument( + "-f", + "--fahrenheit", + action="store_true", + help="Use Fahrenheit instead of Celsius", + ) + parser.add_argument( + "-v", "--version", action="version", version=f"{APP_NAME} {APP_VERSION}" + ) + parser.add_argument("--debug", action="store_true", help="Enable debug logging") + return parser.parse_args() def main(): + """Main entry point""" args = parse_args() + + # loglevel + if args.debug: + logger.setLevel(logging.DEBUG) + logger.debug("Debug logging enabled") + app = PanahoneApplet(args.location, args.fahrenheit) + try: app.run() except KeyboardInterrupt: app.quit() + except Exception as e: + logger.critical(f"Fatal error: {e}", exc_info=True) + sys.exit(1) if __name__ == "__main__": diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..9a12a4e --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# set -e + +echo "π€οΈ panahone weather applet uninstaller" +echo "========================================" +echo "" + +read -p "are you sure you want to uninstall panahone? (y/n) " -n 1 -r +echo "" +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "uninstallation cancelled." + exit 0 +fi + +# rm symbolic link +if [ -L ~/.local/bin/panahone ]; then + rm ~/.local/bin/panahone + echo "β
removed symbolic link from ~/.local/bin" +fi + +# rm .desktop file +if [ -f ~/.local/share/applications/panahone.desktop ]; then + rm ~/.local/share/applications/panahone.desktop + echo "β
removed desktop entry" +fi + +# rm xdg autostart shenanigans +if [ -f ~/.config/autostart/panahone.desktop ]; then + rm ~/.config/autostart/panahone.desktop + echo "β
removed autostart entry" +fi + +echo "" +read -p "do you want to remove configuration and cache files? (y/n) " -n 1 -r +echo "" +if [[ $REPLY =~ ^[Yy]$ ]]; then + if [ -d ~/.config/panahone ]; then + rm -rf ~/.config/panahone + echo "β
removed configuration directory" + fi + + if [ -d ~/.cache/panahone ]; then + rm -rf ~/.cache/panahone + echo "β
removed cache directory" + fi +fi + +echo "" +echo "π panahone has been uninstalled." +echo "you can manually remove the source directory if desired."
\ No newline at end of file |
