- Plugin API (plugin.py, context.py, result.py) - Core engine (search.py, manager.py, registry.py, loader.py) - Built-in plugins: app, calculator, clipboard, emoji, power_menu, query, wallpapers - Spotlight docs and example plugins - Spotlight CSS styling
129 lines
3.5 KiB
Python
129 lines
3.5 KiB
Python
"""Example: package plugin with third-party dependencies.
|
|
|
|
Demonstrates:
|
|
- Package plugin structure
|
|
- Third-party deps via requirements.txt (uv venv auto-install)
|
|
- Keyword activation + global search
|
|
- Deep reload during development
|
|
- Cancellation checks for I/O
|
|
- External command support
|
|
|
|
Usage:
|
|
cp -r examples/plugins/weather config/plugins/weather
|
|
|
|
Set your API key:
|
|
env = OPENWEATHER_API_KEY, your_key_here
|
|
|
|
Open spotlight and type "wth london" or just "london".
|
|
After editing, deep-reload:
|
|
fabric-cli exec modus1 'deep_reload weather'
|
|
"""
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
from window.spotlight.api import SearchResult, SpotlightPlugin
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
requests = None # type: ignore[assignment]
|
|
|
|
|
|
class WeatherPlugin(SpotlightPlugin):
|
|
id = "weather"
|
|
name = "Weather"
|
|
icon = "weather-clear-symbolic"
|
|
keywords = ["wth", "weather"]
|
|
searchable = True
|
|
priority = 75
|
|
|
|
def __init__(self, context):
|
|
super().__init__(context)
|
|
self._cache: dict | None = None
|
|
|
|
def initialize(self) -> None:
|
|
pass
|
|
|
|
def cleanup(self) -> None:
|
|
self._cache = None
|
|
|
|
def release_memory(self) -> None:
|
|
self._cache = None
|
|
|
|
def search(self, query: str, token: Any) -> list[SearchResult]:
|
|
q = query.strip().lower()
|
|
|
|
for prefix in ("wth ", "weather "):
|
|
if q.startswith(prefix):
|
|
q = q[len(prefix) :].strip()
|
|
break
|
|
if q in ("wth", "weather"):
|
|
q = ""
|
|
|
|
if not q:
|
|
return [
|
|
SearchResult(
|
|
id="weather_hint",
|
|
title="Weather",
|
|
subtitle="Type 'wth <city>' to get current weather",
|
|
icon_name="weather-clear-symbolic",
|
|
score=100.0,
|
|
render_type="default",
|
|
)
|
|
]
|
|
|
|
data = self._fetch_weather(q, token)
|
|
if token.is_cancelled or not data:
|
|
return []
|
|
|
|
temp = data.get("main", {}).get("temp", "?")
|
|
desc = data.get("weather", [{}])[0].get("description", "?")
|
|
city = data.get("name", q)
|
|
|
|
return [
|
|
SearchResult(
|
|
id=f"weather_{q}",
|
|
title=f"{city}: {temp}°C",
|
|
subtitle=desc.capitalize(),
|
|
icon_name="weather-clear-symbolic",
|
|
score=100.0,
|
|
render_type="default",
|
|
)
|
|
]
|
|
|
|
def _fetch_weather(self, city: str, token: Any) -> dict | None:
|
|
if self._cache:
|
|
return self._cache
|
|
|
|
api_key = os.environ.get("OPENWEATHER_API_KEY")
|
|
if not api_key or requests is None:
|
|
return None
|
|
|
|
try:
|
|
resp = requests.get(
|
|
"https://api.openweathermap.org/data/2.5/weather",
|
|
params={"q": city, "appid": api_key, "units": "metric"},
|
|
timeout=10,
|
|
)
|
|
if token.is_cancelled:
|
|
return None
|
|
if resp.status_code == 200:
|
|
self._cache = resp.json()
|
|
return self._cache
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
def handle_external(self, command: str, args: str) -> None:
|
|
print(f"[Weather] External: {command} {args}")
|
|
|
|
def on_activate(self, update_fn=None) -> None:
|
|
print("[Weather] Keyword mode activated")
|
|
|
|
def on_deactivate(self) -> None:
|
|
print("[Weather] Keyword mode deactivated")
|
|
|
|
|
|
PLUGIN = WeatherPlugin
|