diff --git a/README.md b/README.md index a9b09a1..625a11d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,119 @@ -# webpaper +# Webpaper -Pagine web come sfondi animati \ No newline at end of file +Pagina web come sfondo del desktop. Ispirato a +[Hidamari](https://github.com/jeffshee/hidamari), dal quale è stato estratto e +ridotto all'essenziale il meccanismo della pagina web come wallpaper: niente +server DBus, niente video, niente GUI di controllo — solo lo sfondo. + +## Come funziona + +1. Una `Gtk.ApplicationWindow` piena di una `WebKit2.WebView` (WebKitGTK 4.1) + carica l'URI richiesto; +2. la finestra viene marcata con `Gdk.WindowTypeHint.DESKTOP`: il window + manager la tiene in fondo allo stack, sotto tutte le altre finestre, + esattamente come lo sfondo nativo; +3. viene creata una finestra per ogni monitor, dimensionata e posizionata + sulla geometria del monitor (`monitor.get_geometry()`), con aggiornamento + automatico su eventi `monitor-added` / `monitor-removed` / `size-changed`; +4. il backend è forzato a **X11** (`GDK_BACKEND=x11`): le finestre DESKTOP non + funzionano su Wayland nativo, quindi su una sessione Wayland GDK ricade su + XWayland; +5. workaround NVIDIA: se è presente una GPU NVIDIA, WebKit viene puntato su un + render node Mesa o su buffer SHM (il renderer DMA-BUF di WebKitGTK fallisce + sui driver NVIDIA e la pagina resterebbe bianca). + +## Requisiti + +- Python 3 con PyGObject +- GTK 3 +- WebKitGTK 4.1 + +Debian/Ubuntu: + +```sh +sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 +``` + +Arch: + +```sh +sudo pacman -S python-gobject gtk3 webkit2gtk-4.1 +``` + +## Uso + +```sh +# da questa cartella, un file HTML locale: +python3 -m webpaper /percorso/pagina.html + +# oppure un URL: +python3 -m webpaper https://esempio.com + +# DUAL MONITOR: una pagina per monitor (prima sorgente = primo monitor, +# seconda = secondo monitor): +python3 -m webpaper /percorso/pagina1.html /percorso/pagina2.html + +# log di debug: +python3 -m webpaper -d /percorso/pagina.html + +# non sistemare le icone desktop (le icone restano sotto il wallpaper): +python3 -m webpaper --no-fix-icons /percorso/pagina.html +``` + +Con **una sola** sorgente, viene mostrata su tutti i monitor (comportamento +originale). Con **più sorgenti** la mappatura è 1:1 nell'ordine dei monitor; +se i monitor sono più delle sorgenti, gli extra ripetono l'ultima sorgente, se +le sorgenti superano i monitor quelle in eccesso vengono ignorate (con un +warning nel log). + +In alternativa, installabile come comando: + +```sh +pip install -e . +webpaper /percorso/pagina.html +``` + +Per uscire: click destro sulla pagina → **Esci** (oppure `Ctrl+C`). + +## Autostart al login + +Crea il file `~/.config/autostart/webpaper.desktop` con: + +```ini +[Desktop Entry] +Type=Application +Name=Webpaper +Comment=Pagina web come sfondo del desktop +Exec=sh -c "cd /percorso/del/progetto && sleep 3 && exec python3 -m webpaper /percorso/pagina.html" +Terminal=false +X-GNOME-Autostart-enabled=true +X-GNOME-Autostart-Delay=0 +``` + +Nota: il `sleep 3` è importante — dà tempo a `nemo-desktop` (le icone della +scrivania) di partire per primo. Se webpaper parte prima, nemo-desktop si +rifiuta di avviarsi ("Desktop already managed") e le icone spariscono del +tutto. + +Per disattivare l'autostart: elimina il file oppure toglilo da *Impostazioni +di sistema → Applicazioni all'avvio*. + +## Note + +- Il meccanismo funziona su **X11 e XWayland** (Wayland puro non è supportato, + come in Hidamari). +- **Icone della scrivania**: le estensioni che disegnano le icone desktop + (nemo-desktop su Cinnamon, DING su GNOME) usano a loro volta finestre di + tipo DESKTOP; il nostro wallpaper, essendo mappato dopo, le copre. All'avvio + Webpaper le porta automaticamente sopra il wallpaper (attivandole una a una, + via `xdotool`). Requisito: `xdotool` installato (`sudo apt install xdotool` + oppure `sudo pacman -S xdotool`). Se non è presente, o con + `--no-fix-icons`, le icone restano sotto il wallpaper. +- La pagina è un vero browser (WebKitGTK): funzionano animazioni CSS, + canvas/WebGL, iframe, ecc. L'audio della pagina viene riprodotto; per + mutarlo non c'è un'API GTK semplice (in Hidamari è un TODO). + +## Licenza + +GPL-3.0-or-later, coerente con il progetto originale +[Hidamari](https://github.com/jeffshee/hidamari) dal quale deriva. diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..35f5747 --- /dev/null +++ b/install.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +# install.sh - Script di installazione per Webpaper +# Requisiti: bash, pip3, xrandr (solitamente preinstallato) + +# --- Colori --- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# --- Variabili --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOLE_HTML="$SCRIPT_DIR/sistema_stellare/sole.html" +ALPHA_HTML="$SCRIPT_DIR/sistema_stellare/alpha-centauri.html" +AUTOSTART_DIR="$HOME/.config/autostart" + +log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# --- 1. Rilevamento Distribuzione --- +detect_distro() { + if [ -f /etc/os-release ]; then + . /etc/os-release + echo "$ID" + else + echo "unknown" + fi +} + +DISTRO=$(detect_distro) + +# --- 2. Installazione Dipendenze (corretta per tutte le distro) --- +install_deps() { + log_info "Rilevata distribuzione: $DISTRO" + + local sudo_cmd="" + if [ "$(id -u)" -ne 0 ]; then + sudo_cmd="sudo" + fi + + # Definizione dei pacchetti per ogni famiglia + declare -A pkg_map + pkg_map[ubuntu]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl" + pkg_map[debian]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl" + pkg_map[linuxmint]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl" + pkg_map[pop]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl" + pkg_map[arch]="python-gobject gtk3 webkit2gtk-4.1 xdotool wmctrl" + pkg_map[manjaro]="python-gobject gtk3 webkit2gtk-4.1 xdotool wmctrl" + pkg_map[endeavouros]="python-gobject gtk3 webkit2gtk-4.1 xdotool wmctrl" + pkg_map[fedora]="python3-gobject gtk3 webkit2gtk4.1 xdotool wmctrl" + pkg_map[rhel]="python3-gobject gtk3 webkit2gtk4.1 xdotool wmctrl" + + local packages=(${pkg_map[$DISTRO]}) + if [ ${#packages[@]} -eq 0 ]; then + log_warn "Distribuzione sconosciuta ($DISTRO). Installa manualmente:" + log_warn "- python3-gi (PyGObject)" + log_warn "- GTK3" + log_warn "- WebKit2GTK 4.1" + log_warn "- xdotool" + return 0 + fi + + # Verifica pacchetti installati + local missing=() + for pkg in "${packages[@]}"; do + local installed=false + case "$DISTRO" in + arch|manjaro|endeavouros) + if pacman -Q "$pkg" &>/dev/null; then installed=true; fi + ;; + fedora|rhel) + if rpm -q "$pkg" &>/dev/null; then installed=true; fi + ;; + *) + if dpkg -s "$pkg" &>/dev/null; then installed=true; fi + ;; + esac + if [ "$installed" = false ]; then + missing+=("$pkg") + fi + done + + if [ ${#missing[@]} -eq 0 ]; then + log_info "Tutte le dipendenze di sistema sono installate." + return 0 + fi + + log_warn "Pacchetti mancanti: ${missing[*]}" + log_info "Installazione in corso..." + + case "$DISTRO" in + arch|manjaro|endeavouros) + $sudo_cmd pacman -S --noconfirm "${missing[@]}" + ;; + fedora|rhel) + $sudo_cmd dnf install -y "${missing[@]}" + ;; + *) + $sudo_cmd apt-get update && $sudo_cmd apt-get install -y "${missing[@]}" + ;; + esac + + if [ $? -ne 0 ]; then + log_error "Errore durante l'installazione delle dipendenze." + exit 1 + fi +} + +# --- 3. Installazione Python --- +install_python_pkg() { + log_info "Installazione pacchetto Python..." + + if ! command -v pip3 &>/dev/null && ! command -v pip &>/dev/null; then + log_error "pip non trovato. Installalo prima." + exit 1 + fi + + # Usa pip3 se disponibile, altrimenti pip + local pip_cmd="pip3" + if ! command -v pip3 &>/dev/null; then + pip_cmd="pip" + fi + + $pip_cmd install -e "$SCRIPT_DIR" --user + if [ $? -ne 0 ]; then + log_error "Errore installazione pacchetto Python." + exit 1 + fi + log_info "Pacchetto Python installato." +} + +# --- 4. Rilevamento Monitor --- +detect_monitors() { + local count + count=$(xrandr --query 2>/dev/null | grep " connected" | wc -l) + if [ "$count" -eq 0 ]; then + log_warn "Impossibile rilevare monitor con xrandr. Imposto 1 monitor." + echo 1 + else + echo "$count" + fi +} + +# --- 5. Configurazione Autostart (CORRETTA) --- +setup_autostart() { + read -p "Vuoi aggiungere Webpaper all'avvio automatico? (y/N): " answer + if [[ ! "$answer" =~ ^[Yy]$ ]]; then + log_info "Autostart saltato." + return + fi + + mkdir -p "$AUTOSTART_DIR" + + local monitor_count=$(detect_monitors) + local exec_cmd="" + if [ "$monitor_count" -ge 2 ]; then + exec_cmd="python3 -m webpaper \"$SOLE_HTML\" \"$ALPHA_HTML\"" + log_info "Rilevati $monitor_count monitor. Configurerò Webpaper per mostrare:" + log_info " - Monitor 1: Sole.html" + log_info " - Monitor 2: Alpha-Centauri.html" + else + exec_cmd="python3 -m webpaper \"$SOLE_HTML\"" + log_info "Rilevato $monitor_count monitor. Configurerò Webpaper per mostrare:" + log_info " - Monitor 1: Sole.html" + fi + + # IMPORTANTE: qui correggiamo l'here-document e le virgolette + cat > "$AUTOSTART_DIR/webpaper.desktop" <<-EOF +[Desktop Entry] +Type=Application +Name=Webpaper +Comment=Pagina web come sfondo del desktop +Exec=sh -c "cd \"$SCRIPT_DIR\" && sleep 3 && exec $exec_cmd" +Terminal=false +X-GNOME-Autostart-enabled=true +X-GNOME-Autostart-Delay=0 +Hidden=false +NoDisplay=false +EOF + + log_info "File creato in: $AUTOSTART_DIR/webpaper.desktop" +} + +# --- Esecuzione --- +main() { + echo "========================================" + echo " Webpaper Installer" + echo "========================================" + + install_deps + install_python_pkg + setup_autostart + + echo "========================================" + log_info "Installazione completata!" + echo "========================================" + echo "Per avviare manualmente: webpaper" + echo "Oppure: python3 -m webpaper" +} + +main \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a2f28b6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "webpaper" +version = "0.1.0" +description = "Pagina web come sfondo del desktop (ispirato a Hidamari)" +readme = "README.md" +requires-python = ">=3.8" +license = { text = "GPL-3.0-or-later" } + +[project.scripts] +webpaper = "webpaper.__main__:main" + +[tool.setuptools] +packages = ["webpaper"] diff --git a/sistema_stellare/alpha-centauri.html b/sistema_stellare/alpha-centauri.html new file mode 100644 index 0000000..c04bbfe --- /dev/null +++ b/sistema_stellare/alpha-centauri.html @@ -0,0 +1,380 @@ + + + + + + Alpha Centauri + + + + +
Alpha Centauri — Simulazione
+ + + + diff --git a/sistema_stellare/alpha-centauri.ts b/sistema_stellare/alpha-centauri.ts new file mode 100644 index 0000000..0195354 --- /dev/null +++ b/sistema_stellare/alpha-centauri.ts @@ -0,0 +1,445 @@ +// === SISTEMA ALPHA CENTAURI — Simulazione TypeScript === +// Sistema triplo: Alpha Cen A + Alpha Cen B + Proxima Centauri +// Distanza totale: 4.37 anni luce dalla Terra + +interface CorpoCeleste { + nome: string; + raggio: number; + asseMaggiore: number; + eccentricita: number; + velocita: number; + colore: string; + angoli?: number[]; + raggiMondi?: number[]; + coloriMondi?: string[]; + nomiMondi?: string[]; + velocitaMondi?: number[]; + anelli?: boolean; + dettagli?: Dettaglio[]; +} + +interface Dettaglio { + tipo: string; + colore: string; + x?: number; + y?: number; + r?: number; +} + +const canvas = document.getElementById("alpha") as HTMLCanvasElement; +const ctx = canvas.getContext("2d")!; + +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; + +// === DATI ASTROFISICI === +// Alpha Centauri A (Rigil Kentaurus): G2V, 1.1 M☉, 1.221 R☉ +// Alpha Centauri B (Toliman): K1V, 0.907 M☉, 0.863 R☉ +// Orbita A-B: a=23.4 UA, e=0.517, P=79.91 anni +// Proxima Centauri: M5.5Ve, 0.122 M☉ +// Orbita Proxima: a~125 UA, e~0.509, P~547 anni + +// === PIANETI CONFERMATI === +// Alpha Cen Bb: 0.011 M⊕, 3.236 giorni, 0.064 UA (controverso) +// Alpha Cen Ab: 1.1 M⊕, 224 giorni, ~1.25 UA (confermato HARPS 2012) +// Proxima d: 0.26 M⊕, 5.122 giorni, 0.029 UA (2022) +// Proxima b: 1.07 M⊕, 11.186 giorni, 0.0485 UA (2016) +// Proxima c: ~7 M⊕, ~5.28 anni, ~1.489 UA (2019) + +let tempo = 0; + +// Stelle del sistema triplo +interface Stella { + nome: string; + tipo: string; + colore: string; + raggio: number; + asseMaggiore: number; + eccentricita: number; + velocita: number; + angoloIniziale: number; + massa: string; + coloreTesto: string; +} + +const stelle: Stella[] = [ + { + nome: "Alpha Cen A", + tipo: "G2V", + colore: "#fff4e0", + raggio: 18, + asseMaggiore: 175, + eccentricita: 0.517, + velocita: 0.0125, // P=79.91 anni → scala + angoloIniziale: 0, + massa: "1.1 M☉", + coloreTesto: "rgba(255,244,224,0.8)", + }, + { + nome: "Alpha Cen B", + tipo: "K1V", + colore: "#ffcc66", + raggio: 14, + asseMaggiore: 215, + eccentricita: 0.517, + velocita: 0.0125, + angoloIniziale: Math.PI, // opposto ad A + massa: "0.907 M☉", + coloreTesto: "rgba(255,204,102,0.8)", + }, + { + nome: "Proxima Cen", + tipo: "M5.5Ve", + colore: "#cc4422", + raggio: 10, + asseMaggiore: 650, + eccentricita: 0.509, + velocita: 0.00091, // P=547 anni → scala + angoloIniziale: Math.PI * 0.7, + massa: "0.122 M☉", + coloreTesto: "rgba(255,150,100,0.8)", + }, +]; + +// Pianeti: [stella_ospite, dati_pianeta] +type Pianeta = [number, CorpoCeleste]; + +const pianeti: Pianeta[] = [ + // === SU ALPHA CENTAURI B === + [ + 1, // Alpha Cen B + { + nome: "Alpha Cen Ab", + raggio: 7, + asseMaggiore: 55, + eccentricita: 0.02, + velocita: 1.63, // 224 giorni → scala: 10s orbita + colore: "#5a8ab5", + angoli: [0], + dettagli: [ + { tipo: "oceano", colore: "#3a6a9a", x: -2, y: -1, r: 3 }, + { tipo: "continente", colore: "#4a8a4a", x: 2, y: 1, r: 2.5 }, + { tipo: "nuvola", colore: "rgba(255,255,255,0.25)", x: 0, y: -2, r: 1.5 }, + ], + }, + ], + + // === SU PROXIMA CENTAURI === + [ + 2, // Proxima Centauri + { + nome: "Proxima d", + raggio: 3, + asseMaggiore: 30, + eccentricita: 0.04, + velocita: 2.094, // 10 secondi orbita + colore: "#6b7b8d", + angoli: [0], + dettagli: [ + { tipo: "superficie", colore: "#5a6a7a", x: -1, y: -1, r: 1.5 }, + { tipo: "superficie", colore: "#7a8a9a", x: 1, y: 1, r: 1 }, + ], + }, + ], + [ + 2, + { + nome: "Proxima b", + raggio: 8, + asseMaggiore: 50, + eccentricita: 0.11, + velocita: 0.959, // ~22 secondi orbita + colore: "#4a90d9", + angoli: [0], + dettagli: [ + { tipo: "oceano", colore: "#2a5a8a", x: -3, y: -2, r: 4 }, + { tipo: "continente", colore: "#3a7a3a", x: 2, y: 1, r: 3 }, + { tipo: "continente", colore: "#4a8a4a", x: -1, y: 3, r: 2.5 }, + { tipo: "nuvola", colore: "rgba(255,255,255,0.3)", x: 0, y: -3, r: 2 }, + ], + }, + ], + [ + 2, + { + nome: "Proxima c", + raggio: 14, + asseMaggiore: 200, + eccentricita: 0.04, + velocita: 0.00559, // ~62 minuti orbita + colore: "#c8944a", + angoli: [0], + raggiMondi: [35, 55, 75], + coloriMondi: ["#d4c4a4", "#b4a484", "#948464"], + nomiMondi: ["Candidate-m1", "Candidate-m2", "Candidate-m3"], + velocitaMondi: [8, 4, 2.5], + anelli: true, + dettagli: [ + { tipo: "banda", colore: "#a8743a", y: -4 }, + { tipo: "banda", colore: "#b8844a", y: 0 }, + { tipo: "banda", colore: "#98642a", y: 4 }, + ], + }, + ], +]; + +// === FUNZIONI DI DISEGNO === + +function disegnaStelle() { + stelleSfondo.forEach((s) => { + ctx.fillStyle = `rgba(255,255,255,${s.a})`; + ctx.beginPath(); + ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); + ctx.fill(); + }); +} + +function disegnaStella(stella: Stella, x: number, y: number) { + // Glow + const grad = ctx.createRadialGradient(x, y, stella.raggio * 0.3, x, y, stella.raggio * 4); + grad.addColorStop(0, stella.colore); + grad.addColorStop(0.3, stella.colore.replace(")", ",0.5)").replace("#", "rgba(").replace(/(..)(..)(..)/, (m, r, g, b) => { + const red = parseInt(r, 16); + const green = parseInt(g, 16); + const blue = parseInt(b, 16); + return `rgba(${red},${green},${blue}`; + }) + ",0.3)"); + grad.addColorStop(1, "rgba(0,0,0,0)"); + + // Glow semplificato per tipo di stella + if (stella.tipo === "G2V") { + // Gialla come il Sole + const g = ctx.createRadialGradient(x, y, stella.raggio * 0.5, x, y, stella.raggio * 5); + g.addColorStop(0, "#fff8e0"); + g.addColorStop(0.2, "#ffdd44"); + g.addColorStop(0.5, "rgba(255,221,68,0.2)"); + g.addColorStop(1, "rgba(255,200,0,0)"); + ctx.fillStyle = g; + } else if (stella.tipo === "K1V") { + // Arancione + const g = ctx.createRadialGradient(x, y, stella.raggio * 0.5, x, y, stella.raggio * 4); + g.addColorStop(0, "#ffe0a0"); + g.addColorStop(0.2, "#ffaa44"); + g.addColorStop(0.5, "rgba(255,170,68,0.2)"); + g.addColorStop(1, "rgba(255,150,0,0)"); + ctx.fillStyle = g; + } else { + // Rossa (Proxima) + const g = ctx.createRadialGradient(x, y, stella.raggio * 0.3, x, y, stella.raggio * 4); + g.addColorStop(0, "rgba(255,100,50,0.8)"); + g.addColorStop(0.2, "rgba(200,50,20,0.4)"); + g.addColorStop(0.5, "rgba(150,30,10,0.1)"); + g.addColorStop(1, "rgba(100,20,5,0)"); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(x, y, stella.raggio * 5, 0, Math.PI * 2); + ctx.fill(); + + // Stella + ctx.fillStyle = stella.colore; + ctx.shadowColor = stella.colore; + ctx.shadowBlur = stella.raggio * 2; + ctx.beginPath(); + ctx.arc(x, y, stella.raggio, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; +} + +function disegnaAnelli(cx: number, cy: number, raggio: number) { + ctx.strokeStyle = "rgba(210, 180, 140, 0.4)"; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.ellipse(cx, cy, raggio * 2.0, raggio * 0.5, 0, 0, Math.PI * 2); + ctx.stroke(); + + ctx.strokeStyle = "rgba(190, 160, 120, 0.25)"; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.ellipse(cx, cy, raggio * 2.4, raggio * 0.6, 0, 0, Math.PI * 2); + ctx.stroke(); +} + +function disegnaOrbita(cx: number, cy: number, a: number, b: number) { + ctx.strokeStyle = "rgba(255,255,255,0.06)"; + ctx.lineWidth = 1; + ctx.setLineDash([3, 3]); + ctx.beginPath(); + ctx.ellipse(cx, cy, a, b, 0, 0, Math.PI * 2); + ctx.stroke(); + ctx.setLineDash([]); +} + +function disegnaDettaglioPianeta( + dettagli: Dettaglio[], + x: number, + y: number, + raggio: number +) { + dettagli.forEach((d) => { + ctx.fillStyle = d.colore; + ctx.beginPath(); + ctx.arc(x + (d.x || 0), y + (d.y || 0), d.r || 1, 0, Math.PI * 2); + ctx.fill(); + }); +} + +function disegnaCorpo( + c: CorpoCeleste, + stellaX: number, + stellaY: number, + angolo: number +) { + const a = c.asseMaggiore; + const b = a * Math.sqrt(1 - c.eccentricita * c.eccentricita); + const x = stellaX + a * Math.cos(angolo); + const y = stellaY + b * Math.sin(angolo); + + // Orbita + disegnaOrbita(stellaX, stellaY, a, b); + + // Anelli + if (c.anelli) { + disegnaAnelli(x, y, c.raggio); + } + + // Pianeta + ctx.fillStyle = c.colore; + ctx.beginPath(); + ctx.arc(x, y, c.raggio, 0, Math.PI * 2); + ctx.fill(); + + // Dettagli + if (c.dettagli) { + disegnaDettaglioPianeta(c.dettagli, x, y, c.raggio); + } + + // Lune + if (c.raggiMondi && c.coloriMondi) { + const coloriM = c.coloriMondi; + c.raggiMondi.forEach((raggioM, i) => { + const velocitaM = c.velocitaMondi?.[i] ?? 3; + const angoloM = angolo + (c.angoli?.[i] || 0) + tempo * velocitaM; + const mx = stellaX + Math.cos(angoloM) * raggioM; + const my = stellaY + Math.sin(angoloM) * raggioM; + + ctx.strokeStyle = "rgba(255,255,255,0.04)"; + ctx.lineWidth = 0.5; + ctx.setLineDash([2, 2]); + ctx.beginPath(); + ctx.arc(stellaX, stellaY, raggioM, 0, Math.PI * 2); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.fillStyle = coloriM[i]; + ctx.beginPath(); + ctx.arc(mx, my, 2, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = "rgba(255,255,255,0.3)"; + ctx.font = "7px monospace"; + ctx.fillText(c.nomiMondi?.[i] || "", mx + 3, my - 3); + }); + } + + // Etichetta + ctx.fillStyle = "rgba(255,255,255,0.6)"; + ctx.font = "11px monospace"; + ctx.fillText(c.nome, x + c.raggio + 5, y - 5); +} + +function disegnaLineaTraStelle(x1: number, y1: number, x2: number, y2: number) { + ctx.strokeStyle = "rgba(255,255,255,0.03)"; + ctx.lineWidth = 1; + ctx.setLineDash([5, 10]); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + ctx.setLineDash([]); +} + +function animazione() { + // Sfondo + ctx.fillStyle = "#000010"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Stelle + disegnaStelle(); + + // Centro del sistema (baricentro A-B) + const cx = canvas.width / 2; + const cy = canvas.height / 2; + + // Calcola posizioni stelle relative al baricentro + const posizioniStelle: { x: number; y: number }[] = []; + + stelle.forEach((s) => { + const a = s.asseMaggiore; + const b = a * Math.sqrt(1 - s.eccentricita * s.eccentricita); + const angolo = tempo * s.velocita + s.angoloIniziale; + const x = cx + a * Math.cos(angolo); + const y = cy + b * Math.sin(angolo); + posizioniStelle.push({ x, y }); + }); + + // Calcola baricentro del triangolo formato dalle tre stelle + const baricentroX = (posizioniStelle[0].x + posizioniStelle[1].x + posizioniStelle[2].x) / 3; + const baricentroY = (posizioniStelle[0].y + posizioniStelle[1].y + posizioniStelle[2].y) / 3; + + // Offset per centrare tutto il sistema sul canvas + const offsetX = baricentroX - cx; + const offsetY = baricentroY - cy; + + // Linea tra Alpha Cen A e B + disegnaLineaTraStelle( + posizioniStelle[0].x - offsetX, + posizioniStelle[0].y - offsetY, + posizioniStelle[1].x - offsetX, + posizioniStelle[1].y - offsetY + ); + + // Disegna stelle + stelle.forEach((s, i) => { + disegnaStella(s, posizioniStelle[i].x - offsetX, posizioniStelle[i].y - offsetY); + }); + + // Pianeti + pianeti.forEach(([stellaIdx, p]) => { + const angolo = tempo * p.velocita; + disegnaCorpo(p, posizioniStella[stellaIdx].x - offsetX, posizioniStelle[stellaIdx].y - offsetY, angolo); + }); + + // Info + ctx.fillStyle = "rgba(255,255,255,0.4)"; + ctx.font = "10px monospace"; + ctx.fillText( + "Alpha Centauri — Sistema triplo (4.37 anni luce) | A+B orbita: 79.9 anni | Proxima orbita: 547 anni", + 10, + canvas.height - 10 + ); + + tempo += 0.005; + requestAnimationFrame(animazione); +} + +// Stelle di sfondo +const stelleSfondo: { x: number; y: number; r: number; a: number }[] = []; +for (let i = 0; i < 300; i++) { + stelleSfondo.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + r: Math.random() * 1.2, + a: Math.random() * 0.5 + 0.2, + }); +} + +// Resize +window.addEventListener("resize", () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; +}); + +// Avvia +animazione(); diff --git a/sistema_stellare/script.sh b/sistema_stellare/script.sh new file mode 100755 index 0000000..3f62536 --- /dev/null +++ b/sistema_stellare/script.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# 1. Chromium in modalità app, senza vero fullscreen, dimensionato allo schermo +chromium index.html \ + --window-position=0,0 --window-size=1920,1080 \ + --disable-infobars --noerrdialogs --disable-session-crashed-bubble & + +sleep 3 + +# 2. Trova l'ID finestra +WID=$(xdotool search --sync --onlyvisible --class "chromium" | head -1) + +# 3. Riposiziona/ridimensiona per sicurezza +xdotool windowmove "$WID" 0 0 +xdotool windowsize "$WID" 1920 1080 + +# 4. Marcala come finestra "desktop" e mandala in fondo allo stack +wmctrl -i -r "$WID" -b add,below +wmctrl -i -r "$WID" -b add,sticky,skip_taskbar,skip_pager +xprop -id "$WID" -f _NET_WM_WINDOW_TYPE 32a \ + -set _NET_WM_WINDOW_TYPE _NET_WM_WINDOW_TYPE_DESKTOP + +# 5. Forza nemo-desktop a ridisegnarsi sopra (necessario spesso su Mint) +killall nemo-desktop 2>/dev/null +nemo-desktop & diff --git a/sistema_stellare/sistema-solare.ts b/sistema_stellare/sistema-solare.ts new file mode 100644 index 0000000..ea8f2d1 --- /dev/null +++ b/sistema_stellare/sistema-solare.ts @@ -0,0 +1,455 @@ +// === SISTEMA SOLARE - Simulazione TypeScript === + +interface CorpoCeleste { + nome: string; + raggio: number; + asseMaggiore: number; + eccentricita: number; + velocita: number; + colore: string; + angoli?: number[]; + raggiMondi?: number[]; + coloriMondi?: string[]; + nomiMondi?: string[]; + velocitaMondi?: number[]; + anelli?: boolean; +} + +const canvas = document.getElementById("sistema") as HTMLCanvasElement; +const ctx = canvas.getContext("2d")!; + +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; + +// === COMETA DI HALLEY === +// Periodo: ~75.3 anni | Eccentricità: 0.967 | Inclinazione: 162° (retrograda) +// Perielio: 0.586 UA | Afelio: 35.1 UA | Semiasse maggiore: 17.8 UA +interface ParticellaCoda { + distanza: number; + larghezza: number; + angoloOffset: number; + opacita: number; +} + +interface Cometa { + nome: string; + asseMaggiore: number; + eccentricita: number; + velocita: number; + angoloIniziale: number; + angoloOrbita: number; + coda: ParticellaCoda[]; +} + +const cometaHalley: Cometa = { + nome: "Halley", + asseMaggiore: 267, // ~17.8 UA in scala + eccentricita: 0.967, + velocita: 0.00084, // P=75.3 anni → scala + angoloIniziale: Math.PI * 0.45, // posizione iniziale + angoloOrbita: Math.PI * 0.85, // rotazione ellisse (simula inclinazione) + coda: [], // particelle coda +}; + +// Inizializza particelle della coda +for (let i = 0; i < 60; i++) { + cometaHalley.coda.push({ + distanza: i * 3, + larghezza: (60 - i) * 0.08, + angoloOffset: (Math.random() - 0.5) * 0.3, + opacita: 1 - i / 60, + }); +} + +const corpo: CorpoCeleste[] = [ + { + nome: "Mercurio", + raggio: 4, + asseMaggiore: 60, + eccentricita: 0.206, + velocita: 4.15, + colore: "#b5b5b5", + }, + { + nome: "Venere", + raggio: 7, + asseMaggiore: 90, + eccentricita: 0.007, + velocita: 1.62, + colore: "#e8cda0", + }, + { + nome: "Terra", + raggio: 8, + asseMaggiore: 130, + eccentricita: 0.017, + velocita: 1.0, + colore: "#4a90d9", + angoli: [0], + raggiMondi: [18], + coloriMondi: ["#cccccc"], + nomiMondi: ["Luna"], + }, + { + nome: "Marte", + raggio: 6, + asseMaggiore: 170, + eccentricita: 0.093, + velocita: 0.53, + colore: "#c1440e", + angoli: [0, 0.5], + raggiMondi: [12, 16], + coloriMondi: ["#aaaaaa", "#999999"], + nomiMondi: ["Fobos", "Deimos"], + }, + { + nome: "Giove", + raggio: 20, + asseMaggiore: 240, + eccentricita: 0.049, + velocita: 0.084, + colore: "#c88b3a", + angoli: [0, 0.8, 1.6, 2.4], + raggiMondi: [30, 36, 42, 48], + coloriMondi: ["#dddddd", "#bbbbbb", "#aaaaaa", "#999999"], + nomiMondi: ["Io", "Europa", "Ganimede", "Callisto"], + }, + { + nome: "Saturno", + raggio: 17, + asseMaggiore: 320, + eccentricita: 0.057, + velocita: 0.034, + colore: "#e8d191", + angoli: [0, 1.2, 2.4], + raggiMondi: [30, 38, 46], + coloriMondi: ["#cccccc", "#bbbbbb", "#aaaaaa"], + nomiMondi: ["Encelado", "Mimante", "Titano"], + anelli: true, + }, + { + nome: "Urano", + raggio: 12, + asseMaggiore: 390, + eccentricita: 0.046, + velocita: 0.011, + colore: "#72b5c4", + angoli: [0, 1.5, 3.0], + raggiMondi: [20, 26, 32], + coloriMondi: ["#cccccc", "#bbbbbb", "#aaaaaa"], + nomiMondi: ["Miranda", "Ariel", "Titania"], + }, + { + nome: "Nettuno", + raggio: 11, + asseMaggiore: 450, + eccentricita: 0.011, + velocita: 0.006, + colore: "#3f54ba", + angoli: [0, 1.0], + raggiMondi: [20, 26], + coloriMondi: ["#cccccc", "#bbbbbb"], + nomiMondi: ["Tritone", "Nereide"], + }, + { + nome: "Plutone", + raggio: 5, + asseMaggiore: 590, + eccentricita: 0.244, + velocita: 0.004, + colore: "#a89f91", + angoli: [0, 1.2, 2.4, 3.6, 4.8], + raggiMondi: [25, 45, 65, 42, 52], + coloriMondi: ["#cccccc", "#bbbbbb", "#aaaaaa", "#999999", "#888888"], + nomiMondi: ["Caronte", "Nix", "Idra", "Cerbero", "Stige"], + velocitaMondi: [10, 2.5, 1.7, 3.2, 5.3], + }, +]; + +let tempo = 0; + +function disegnaSole(cx: number, cy: number) { + // Glow esterno + const grad = ctx.createRadialGradient(cx, cy, 10, cx, cy, 60); + grad.addColorStop(0, "#fff700"); + grad.addColorStop(0.3, "#ff8c00"); + grad.addColorStop(1, "rgba(255,140,0,0)"); + ctx.fillStyle = grad; + ctx.beginPath(); + ctx.arc(cx, cy, 60, 0, Math.PI * 2); + ctx.fill(); + + // Sole + ctx.fillStyle = "#ffcc00"; + ctx.shadowColor = "#ff8800"; + ctx.shadowBlur = 30; + ctx.beginPath(); + ctx.arc(cx, cy, 25, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; +} + +function disegnaAnelliSaturno(cx: number, cy: number, raggio: number) { + ctx.strokeStyle = "rgba(210, 180, 140, 0.5)"; + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.ellipse(cx, cy, raggio * 2.2, raggio * 0.6, 0, 0, Math.PI * 2); + ctx.stroke(); + + ctx.strokeStyle = "rgba(190, 160, 120, 0.3)"; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.ellipse(cx, cy, raggio * 2.6, raggio * 0.7, 0, 0, Math.PI * 2); + ctx.stroke(); +} + +function disegnaOrbita(cx: number, cy: number, a: number, b: number) { + ctx.strokeStyle = "rgba(255,255,255,0.08)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.ellipse(cx, cy, a, b, 0, 0, Math.PI * 2); + ctx.stroke(); +} + +function disegnaCorpo(c: CorpoCeleste, cx: number, cy: number, angolo: number) { + const a = c.asseMaggiore; + const b = a * Math.sqrt(1 - c.eccentricita * c.eccentricita); + const x = cx + a * Math.cos(angolo); + const y = cy + b * Math.sin(angolo); + + // Disegna orbita ellittica + disegnaOrbita(cx, cy, a, b); + + // Anelli di Saturno (dietro il pianeta) + if (c.anelli) { + disegnaAnelliSaturno(x, y, c.raggio); + } + + // Pianeta + ctx.fillStyle = c.colore; + ctx.beginPath(); + ctx.arc(x, y, c.raggio, 0, Math.PI * 2); + ctx.fill(); + + // Dettaglio Giove (fasce) + if (c.nome === "Giove") { + ctx.strokeStyle = "rgba(160, 100, 30, 0.4)"; + ctx.lineWidth = 2; + for (let i = -2; i <= 2; i++) { + ctx.beginPath(); + ctx.arc(x, y, c.raggio * 0.6, 0, Math.PI); + ctx.stroke(); + } + // Grande macchia rossa + ctx.fillStyle = "rgba(200, 80, 50, 0.6)"; + ctx.beginPath(); + ctx.ellipse(x + 5, y + 3, 5, 3, 0, 0, Math.PI * 2); + ctx.fill(); + } + + // Dettaglio Terra (continenti) + if (c.nome === "Terra") { + ctx.fillStyle = "rgba(50, 150, 50, 0.5)"; + ctx.beginPath(); + ctx.arc(x - 2, y - 2, 3, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = "rgba(50, 150, 50, 0.4)"; + ctx.beginPath(); + ctx.arc(x + 3, y + 2, 2, 0, Math.PI * 2); + ctx.fill(); + } + + // Disegna lune + if (c.raggiMondi && c.coloriMondi) { + const coloriM = c.coloriMondi; + c.raggiMondi.forEach((raggioM, i) => { + const velocitaM = c.velocitaMondi?.[i] ?? 3; + const angoloM = angolo + (c.angoli?.[i] || 0) + tempo * velocitaM; + const mx = x + Math.cos(angoloM) * raggioM; + const my = y + Math.sin(angoloM) * raggioM; + + // Orbita luna (circolare per semplicità) + ctx.strokeStyle = "rgba(255,255,255,0.05)"; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.arc(x, y, raggioM, 0, Math.PI * 2); + ctx.stroke(); + + // Luna + ctx.fillStyle = coloriM[i]; + ctx.beginPath(); + ctx.arc(mx, my, 2, 0, Math.PI * 2); + ctx.fill(); + + // Etichetta + ctx.fillStyle = "rgba(255,255,255,0.4)"; + ctx.font = "8px monospace"; + ctx.fillText(c.nomiMondi?.[i] || "", mx + 4, my - 4); + }); + } + + // Etichetta pianeta + ctx.fillStyle = "rgba(255,255,255,0.6)"; + ctx.font = "11px monospace"; + ctx.fillText(c.nome, x + c.raggio + 5, y - 5); +} + +function animazione() { + // Sfondo + ctx.fillStyle = "#000010"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Stelle + disegnaStelle(); + + const cx = canvas.width / 2; + const cy = canvas.height / 2; + + // Sole + disegnaSole(cx, cy); + + // Pianeti + corpo.forEach((c) => { + const angolo = tempo * c.velocita; + disegnaCorpo(c, cx, cy, angolo); + }); + + // === COMETA DI HALLEY === + disegnaCometaHalley(cx, cy); + + tempo += 0.005; + requestAnimationFrame(animazione); +} + +// Stelle di sfondo (statiche) +const stelle: { x: number; y: number; r: number; a: number }[] = []; +for (let i = 0; i < 300; i++) { + stelle.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + r: Math.random() * 1.2, + a: Math.random() * 0.5 + 0.2, + }); +} + +function disegnaCometaHalley(cx: number, cy: number) { + const c = cometaHalley; + const a = c.asseMaggiore; + const b = a * Math.sqrt(1 - c.eccentricita * c.eccentricita); + const angolo = tempo * c.velocita + c.angoloIniziale; + + // Posizione cometa sull'orbita ellittica + const xOrbita = a * Math.cos(angolo); + const yOrbita = b * Math.sin(angolo); + + // Ruota l'orbita per simulare inclinazione/retrograda + const cosA = Math.cos(c.angoloOrbita); + const sinA = Math.sin(c.angoloOrbita); + const x = cx + xOrbita * cosA - yOrbita * sinA; + const y = cy + xOrbita * sinA + yOrbita * cosA; + + // Disegna orbita (ellisse ruotata) + ctx.save(); + ctx.translate(cx, cy); + ctx.rotate(c.angoloOrbita); + ctx.strokeStyle = "rgba(150,200,255,0.12)"; + ctx.lineWidth = 1; + ctx.setLineDash([4, 6]); + ctx.beginPath(); + ctx.ellipse(0, 0, a, b, 0, Math.PI * 2); + ctx.stroke(); + ctx.setLineDash([]); + ctx.restore(); + + // Distanza dal Sole per direzione coda + const dxSole = cx - x; + const dySole = cy - y; + const distSole = Math.sqrt(dxSole * dxSole + dySole * dySole); + const direzioneX = dxSole / distSole; + const direzioneY = dySole / distSole; + + // Perielio: la cometa è più luminosa quando è vicina al Sole + const perielioDist = a * (1 - c.eccentricita); // ~9px in scala + const luminosita = Math.min(1, perielioDist / distSole); + + // Coda di polvere (più larga, curva leggermente) + for (let i = 0; i < cometaHalley.coda.length; i++) { + const p = cometaHalley.coda[i]; + const tailLen = p.distanza * (0.5 + luminosita * 1.5); + const tailW = p.larghezza * (0.5 + luminosita); + const angoloCoda = Math.atan2(direzioneY, direzioneX) + p.angoloOffset; + + const px = x - Math.cos(angoloCoda) * tailLen; + const py = y - Math.sin(angoloCoda) * tailLen; + + const alpha = p.opacita * luminosita * 0.6; + if (alpha > 0.01) { + ctx.fillStyle = `rgba(180,220,255,${alpha})`; + ctx.beginPath(); + ctx.arc(px, py, tailW, 0, Math.PI * 2); + ctx.fill(); + } + } + + // Coda ionica (più sottile, dritta, blu) + const codaIonicaLunghezza = 40 + luminosita * 80; + const angoloIonico = Math.atan2(direzioneY, direzioneX); + for (let i = 0; i < 30; i++) { + const t = i / 30; + const ix = x - Math.cos(angoloIonico) * codaIonicaLunghezza * t; + const iy = y - Math.sin(angoloIonico) * codaIonicaLunghezza * t; + const alpha = (1 - t) * luminosita * 0.5; + if (alpha > 0.01) { + ctx.fillStyle = `rgba(100,150,255,${alpha})`; + ctx.beginPath(); + ctx.arc(ix, iy, 1.5 * (1 - t), 0, Math.PI * 2); + ctx.fill(); + } + } + + // Coma (chioma) intorno al nucleo + if (luminosita > 0.1) { + const comaRaggio = 6 + luminosita * 12; + const comaGrad = ctx.createRadialGradient(x, y, 2, x, y, comaRaggio); + comaGrad.addColorStop(0, `rgba(200,230,255,${luminosita * 0.8})`); + comaGrad.addColorStop(0.3, `rgba(150,200,255,${luminosita * 0.3})`); + comaGrad.addColorStop(1, "rgba(100,150,255,0)"); + ctx.fillStyle = comaGrad; + ctx.beginPath(); + ctx.arc(x, y, comaRaggio, 0, Math.PI * 2); + ctx.fill(); + } + + // Nucleo della cometa + const nucleoRaggio = 2.5 + luminosita * 1.5; + ctx.fillStyle = `rgba(220,240,255,${0.5 + luminosita * 0.5})`; + ctx.shadowColor = "rgba(150,200,255,0.8)"; + ctx.shadowBlur = 8 * luminosita; + ctx.beginPath(); + ctx.arc(x, y, nucleoRaggio, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; + + // Etichetta + ctx.fillStyle = `rgba(180,220,255,${0.4 + luminosita * 0.4})`; + ctx.font = "10px monospace"; + ctx.fillText(c.nome, x + 10, y - 8); +} + +function disegnaStelle() { + stelle.forEach((s) => { + ctx.fillStyle = `rgba(255,255,255,${s.a})`; + ctx.beginPath(); + ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); + ctx.fill(); + }); +} + +// Resize +window.addEventListener("resize", () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; +}); + +// Avvia +animazione(); diff --git a/sistema_stellare/sole.html b/sistema_stellare/sole.html new file mode 100644 index 0000000..196865b --- /dev/null +++ b/sistema_stellare/sole.html @@ -0,0 +1,353 @@ + + + + + + Sistema Solare + + + + +
Sistema Solare — Simulazione
+ + + + diff --git a/webpaper/__init__.py b/webpaper/__init__.py new file mode 100644 index 0000000..3c14277 --- /dev/null +++ b/webpaper/__init__.py @@ -0,0 +1,3 @@ +"""webpaper — pagina web come sfondo del desktop.""" + +__version__ = "0.1.0" diff --git a/webpaper/__main__.py b/webpaper/__main__.py new file mode 100644 index 0000000..c619a0f --- /dev/null +++ b/webpaper/__main__.py @@ -0,0 +1,311 @@ +"""webpaper — pagina web come sfondo del desktop. + +Ispirato a Hidamari (https://github.com/jeffshee/hidamari), video wallpaper +per Linux, dal quale è stato estratto il meccanismo della pagina web: + + una normale Gtk.ApplicationWindow piena di una WebKit2.WebView viene marcata + come finestra DESKTOP (Gdk.WindowTypeHint.DESKTOP): il window manager la + tiene in fondo allo stack, sotto ogni altra finestra, esattamente come lo + sfondo nativo. Viene creata una finestra per ogni monitor, dimensionata e + posizionata sulla geometria del monitor stesso. + +Uso: + python3 -m webpaper /percorso/pagina.html + python3 -m webpaper https://esempio.com + python3 -m webpaper pagina1.html pagina2.html # dual monitor: una pagina per monitor +""" + +import argparse +import glob +import logging +import os +import pathlib +import subprocess +import sys + +# Le finestre con WindowTypeHint.DESKTOP funzionano solo su X11. +# Su una sessione Wayland, GDK ricade automaticamente su XWayland. +os.environ["GDK_BACKEND"] = "x11" + +import gi # noqa: E402 + +gi.require_version("Gtk", "3.0") +gi.require_version("Gdk", "3.0") +gi.require_version("WebKit2", "4.1") +from gi.repository import Gdk, GLib, Gtk, WebKit2 # noqa: E402 + +logger = logging.getLogger("webpaper") + +# Disattivabile con --no-fix-icons +_fix_icons_enabled = True + +# Al login le icone desktop (nemo-desktop/DING) potrebbero non esserci ancora: +# riprova alcune volte prima di rinunciare. +_fix_attempts = 0 +MAX_FIX_ATTEMPTS = 4 + +# Classi delle finestre che disegnano le icone desktop, per DE: +# Cinnamon -> nemo-desktop +# GNOME (DING/desktop icons) -> org.gnome.NautilusDesktopWindow +DESKTOP_ICONS_CLASSES = ("nemo-desktop", "org.gnome.NautilusDesktopWindow") + + +def fix_desktop_icons(): + """Porta sopra le icone della scrivania, sotto il wallpaper. + + Le icone desktop (es. nemo-desktop su Cinnamon, DING su GNOME) sono a loro + volta finestre di tipo DESKTOP: quando il nostro wallpaper viene mappato + dopo di loro, il window manager lo impila sopra e le icone spariscono. + Su Muffin (Cinnamon) funziona attivarle (windowactivate), non alzarle + (windowraise). Richiede xdotool: se non è installato o non ci sono + finestre corrispondenti, non fa nulla. + """ + if not _fix_icons_enabled: + return False + global _fix_attempts + try: + import shutil + + if shutil.which("xdotool") is None: + logger.debug("xdotool non disponibile: icone desktop non sistemate") + return False + except Exception: + return False + found = False + for cls in DESKTOP_ICONS_CLASSES: + try: + result = subprocess.run( + ["xdotool", "search", "--class", cls], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode != 0: + continue + window_ids = result.stdout.split() + if not window_ids: + continue + found = True + # Nota: attivare le finestre una alla volta (non la forma + # concatenata `search ... windowactivate`): su Muffin solo cosi' + # le finestre DESKTOP vengono rimesse sopra il wallpaper. + for window_id in window_ids: + subprocess.run( + ["xdotool", "windowactivate", window_id], + capture_output=True, + timeout=5, + ) + logger.info("Icone desktop (%s) portate sopra il wallpaper", cls) + except (subprocess.TimeoutExpired, OSError): + logger.debug("xdotool non riuscito per %s", cls) + _fix_attempts += 1 + if not found and _fix_attempts < MAX_FIX_ATTEMPTS: + return True # per GLib.timeout_add: riprova tra 2 s + _fix_attempts = 0 + return False # per GLib.timeout_add: esegui una sola volta + + +def setup_render_device(): + """Workaround NVIDIA. + + Il renderer DMA-BUF di WebKitGTK non riesce ad allocare buffer GBM sui + driver NVIDIA ("Failed to create GBM buffer: Invalid argument"), e la + pagina resterebbe bianca. Se è presente una GPU NVIDIA, si punta WebKit su + un render node Mesa oppure, su sistemi solo-NVIDIA, si ripiega su buffer + condivisi (SHM). Va chiamato prima della creazione della prima WebView. + """ + overrides = ( + "WEBKIT_WEB_RENDER_DEVICE_FILE", + "WEBKIT_DMABUF_RENDERER_FORCE_SHM", + "WEBKIT_DISABLE_DMABUF_RENDERER", + ) + if any(var in os.environ for var in overrides): + return + drivers = {} + for node in sorted(glob.glob("/dev/dri/renderD*")): + driver = os.path.realpath( + f"/sys/class/drm/{os.path.basename(node)}/device/driver" + ) + drivers[node] = os.path.basename(driver) + if "nvidia" not in drivers.values(): + return + for node, driver in drivers.items(): + if driver != "nvidia": + logger.info("Usando render device %s (%s)", node, driver) + os.environ["WEBKIT_WEB_RENDER_DEVICE_FILE"] = node + return + logger.info("Sistema solo-NVIDIA: buffer SHM per WebKit") + os.environ["WEBKIT_DMABUF_RENDERER_FORCE_SHM"] = "1" + + +def to_uri(source): + """Converte un percorso locale in un URI file://. + + Gli URL (http/https) e gli URI file:// già completi vengono lasciati + invariati. Solleva FileNotFoundError se un percorso locale non esiste. + """ + if source.startswith(("http://", "https://", "file://")): + return source + path = pathlib.Path(source).expanduser() + if not path.is_file(): + raise FileNotFoundError(f"File non trovato: {source}") + return path.resolve().as_uri() + + +class WebWindow(Gtk.ApplicationWindow): + """Finestra DESKTOP che contiene la pagina web (una WebView a tutto schermo).""" + + def __init__(self, uri, *args, **kwargs): + super().__init__(*args, **kwargs) + self.uri = uri + self.webview = WebKit2.WebView() + self.add(self.webview) + self.webview.show() + # Click destro: menu con Ricarica / Esci + self.webview.connect("button-press-event", self._on_button_press_event) + self._menu = None + + def load(self): + self.webview.load_uri(self.uri) + + def _on_button_press_event(self, widget, event): + if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: + if self._menu is None: + self._menu = Gtk.Menu() + reload_item = Gtk.MenuItem(label="Ricarica") + reload_item.connect("activate", lambda *_: self.webview.reload()) + self._menu.append(reload_item) + quit_item = Gtk.MenuItem(label="Esci") + quit_item.connect("activate", lambda *_: self.get_application().quit()) + self._menu.append(quit_item) + self._menu.show_all() + self._menu.popup_at_pointer() + return True + return False + + +class WebWallpaper(Gtk.Application): + """Applicazione: una finestra DESKTOP per ogni monitor.""" + + def __init__(self, uris): + super().__init__(application_id="org.webpaper.wallpaper") + self.uris = uris + self.windows = {} # Gdk.Monitor -> WebWindow + self._monitor_detect() + if len(self.uris) > len(self.windows): + logger.warning( + "%d sorgenti ma solo %d monitor: le sorgenti in eccesso verranno ignorate", + len(self.uris), + len(self.windows), + ) + + def _uri_for_index(self, i): + """Sorgente per l'i-esimo monitor. + + Una sola sorgente => mostrata su tutti i monitor (compatibilità col + comportamento originale). Con N sorgenti => mappatura 1:1 nell'ordine + dei monitor; eventuali monitor in più ripetono l'ultima sorgente. + """ + return self.uris[min(i, len(self.uris) - 1)] + + # -- gestione monitor ------------------------------------------------- + + def _monitor_detect(self): + display = Gdk.Display.get_default() + screen = display.get_default_screen() + for i in range(display.get_n_monitors()): + self.windows[display.get_monitor(i)] = None + screen.connect("size-changed", self._on_size_changed) + display.connect("monitor-added", self._on_monitor_added) + display.connect("monitor-removed", self._on_monitor_removed) + + def _on_size_changed(self, *args): + logger.info("size-changed") + for monitor, window in self.windows.items(): + if window is not None: + self._place(window, monitor) + + def _on_monitor_added(self, _, gdk_monitor, *args): + logger.info("monitor-added") + self.windows[gdk_monitor] = None + self._create_windows() + + def _on_monitor_removed(self, _, gdk_monitor, *args): + logger.info("monitor-removed") + window = self.windows.pop(gdk_monitor, None) + if window is not None: + window.close() + + # -- finestre --------------------------------------------------------- + + @staticmethod + def _place(window, monitor): + rect = monitor.get_geometry() + window.set_size_request(rect.width, rect.height) + window.move(rect.x, rect.y) + + def _create_windows(self): + for i, (monitor, window) in enumerate(self.windows.items()): + uri = self._uri_for_index(i) + if window is None: + logger.info("Monitor %d -> %s", i, uri) + window = WebWindow(uri, application=self) + # Il trucco: hint DESKTOP => il WM la tratta come sfondo + window.set_type_hint(Gdk.WindowTypeHint.DESKTOP) + self._place(window, monitor) + window.load() + self.windows[monitor] = window + window.present() + # Lascia assestare il WM, poi porta le icone desktop sopra il wallpaper + # (riprovando un paio di volte: al login nemo-desktop potrebbe tardare) + GLib.timeout_add(1500, fix_desktop_icons) + + def do_activate(self): + self._create_windows() + + +def main(argv=None): + parser = argparse.ArgumentParser( + prog="webpaper", + description="Usa una pagina web come sfondo del desktop (X11/XWayland).", + ) + parser.add_argument( + "source", + nargs="+", + help=( + "file HTML oppure URL (http://, https://, file://). " + "Con due sorgenti e due monitor, ogni monitor mostra la rispettiva pagina " + "(prima sorgente = primo monitor, seconda = secondo)." + ), + ) + parser.add_argument("-d", "--debug", action="store_true", help="log di debug") + parser.add_argument( + "--no-fix-icons", + action="store_true", + help="non sistemare le icone desktop (le icone resterebbero sotto il wallpaper)", + ) + args = parser.parse_args(argv) + + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.INFO, + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + ) + + try: + uris = [to_uri(source) for source in args.source] + except FileNotFoundError as e: + parser.error(str(e)) + + setup_render_device() + + global _fix_icons_enabled + _fix_icons_enabled = not args.no_fix_icons + + app = WebWallpaper(uris) + # Gtk.Application parsa sys.argv: togliamo i nostri argomenti + sys.argv = [sys.argv[0]] + return app.run(sys.argv) + + +if __name__ == "__main__": + sys.exit(main())