From e8d93e6b1d6350402ba26d2ec8387abad896c929 Mon Sep 17 00:00:00 2001 From: Karim Date: Thu, 25 Jun 2026 00:40:21 +0200 Subject: [PATCH] gui: native libadwaita-free UI backend + dark shell; packaging/iso updates - gui/ui: native GTK4 widget vocabulary (.tn-*) behind the ui.* seam, default backend now native; style_apple.css design language - shell: sidebar/topbar/content all #0f0f0f in dark (matches eww panels) - new pages: wine, online_accounts - packaging: tanin-icons (accent folder theme), tanin-calendar, gtklock, taninux-gtk3-theme, flatpak install handler - iso/distro: profile + manifest tweaks (ISO binary not included) --- HANDOVER.md | 248 +- distro/MANIFEST.md | 15 +- .../modules/shellprocess-striplive.conf | 11 +- .../getty@tty1.service.d/autologin.conf | 2 +- iso/build-profile/airootfs/root/.zprofile | 5 - .../airootfs/root/customize_airootfs.sh | 27 +- packaging/gtklock/config.ini | 13 + packaging/gtklock/gtklock.css | 61 + packaging/tanin-calendar/PKGBUILD | 26 + .../ch.gabrielevarano.TaninCalendar.desktop | 13 + packaging/tanin-calendar/install-local.sh | 25 + packaging/tanin-calendar/tanin-calendar | 2090 +++++++++++++++++ packaging/tanin-calendar/tanin-calendar.css | 511 ++++ packaging/tanin-desktop/PKGBUILD | 44 +- .../tanin-icons/pkg/tanin-icons/.BUILDINFO | 1237 ++++++++++ .../tanin-icons/pkg/tanin-icons/.INSTALL | 9 + packaging/tanin-icons/pkg/tanin-icons/.MTREE | Bin 0 -> 7101 bytes .../tanin-icons/pkg/tanin-icons/.PKGINFO | 16 + .../16x16/places/folder-documents.png | Bin 0 -> 369 bytes .../16x16/places/folder-download.png | Bin 0 -> 361 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 303 bytes .../Tanin-beni/16x16/places/folder-music.png | Bin 0 -> 369 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 365 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 383 bytes .../Tanin-beni/16x16/places/folder-remote.png | Bin 0 -> 402 bytes .../16x16/places/folder-templates.png | Bin 0 -> 367 bytes .../Tanin-beni/16x16/places/folder-videos.png | Bin 0 -> 366 bytes .../icons/Tanin-beni/16x16/places/folder.png | Bin 0 -> 303 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 518 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 355 bytes .../Tanin-beni/16x16/places/user-desktop.png | Bin 0 -> 361 bytes .../Tanin-beni/16x16/places/user-home.png | Bin 0 -> 378 bytes .../usr/share/icons/Tanin-beni/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-beni/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../Tanin-beni/scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 369 bytes .../16x16/places/folder-download.png | Bin 0 -> 364 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 302 bytes .../16x16/places/folder-music.png | Bin 0 -> 370 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 365 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 378 bytes .../16x16/places/folder-remote.png | Bin 0 -> 403 bytes .../16x16/places/folder-templates.png | Bin 0 -> 367 bytes .../16x16/places/folder-videos.png | Bin 0 -> 365 bytes .../Tanin-chikyu/16x16/places/folder.png | Bin 0 -> 302 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 510 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 356 bytes .../16x16/places/user-desktop.png | Bin 0 -> 359 bytes .../Tanin-chikyu/16x16/places/user-home.png | Bin 0 -> 378 bytes .../usr/share/icons/Tanin-chikyu/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-chikyu/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 377 bytes .../16x16/places/folder-download.png | Bin 0 -> 369 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 306 bytes .../16x16/places/folder-music.png | Bin 0 -> 371 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 368 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 384 bytes .../16x16/places/folder-remote.png | Bin 0 -> 405 bytes .../16x16/places/folder-templates.png | Bin 0 -> 373 bytes .../16x16/places/folder-videos.png | Bin 0 -> 373 bytes .../Tanin-shinkai/16x16/places/folder.png | Bin 0 -> 306 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 511 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 365 bytes .../16x16/places/user-desktop.png | Bin 0 -> 364 bytes .../Tanin-shinkai/16x16/places/user-home.png | Bin 0 -> 380 bytes .../usr/share/icons/Tanin-shinkai/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-shinkai/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 367 bytes .../16x16/places/folder-download.png | Bin 0 -> 359 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 299 bytes .../Tanin-take/16x16/places/folder-music.png | Bin 0 -> 365 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 361 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 379 bytes .../Tanin-take/16x16/places/folder-remote.png | Bin 0 -> 400 bytes .../16x16/places/folder-templates.png | Bin 0 -> 364 bytes .../Tanin-take/16x16/places/folder-videos.png | Bin 0 -> 364 bytes .../icons/Tanin-take/16x16/places/folder.png | Bin 0 -> 299 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 510 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 355 bytes .../Tanin-take/16x16/places/user-desktop.png | Bin 0 -> 358 bytes .../Tanin-take/16x16/places/user-home.png | Bin 0 -> 376 bytes .../usr/share/icons/Tanin-take/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-take/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../Tanin-take/scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 372 bytes .../16x16/places/folder-download.png | Bin 0 -> 368 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 305 bytes .../Tanin-ume/16x16/places/folder-music.png | Bin 0 -> 371 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 367 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 385 bytes .../Tanin-ume/16x16/places/folder-remote.png | Bin 0 -> 401 bytes .../16x16/places/folder-templates.png | Bin 0 -> 370 bytes .../Tanin-ume/16x16/places/folder-videos.png | Bin 0 -> 371 bytes .../icons/Tanin-ume/16x16/places/folder.png | Bin 0 -> 305 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 506 bytes .../Tanin-ume/16x16/places/user-bookmarks.png | Bin 0 -> 360 bytes .../Tanin-ume/16x16/places/user-desktop.png | Bin 0 -> 362 bytes .../Tanin-ume/16x16/places/user-home.png | Bin 0 -> 380 bytes .../usr/share/icons/Tanin-ume/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-ume/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../Tanin-ume/scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 369 bytes .../16x16/places/folder-download.png | Bin 0 -> 361 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 303 bytes .../Tanin-beni/16x16/places/folder-music.png | Bin 0 -> 369 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 365 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 383 bytes .../Tanin-beni/16x16/places/folder-remote.png | Bin 0 -> 402 bytes .../16x16/places/folder-templates.png | Bin 0 -> 367 bytes .../Tanin-beni/16x16/places/folder-videos.png | Bin 0 -> 366 bytes .../out/Tanin-beni/16x16/places/folder.png | Bin 0 -> 303 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 518 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 355 bytes .../Tanin-beni/16x16/places/user-desktop.png | Bin 0 -> 361 bytes .../out/Tanin-beni/16x16/places/user-home.png | Bin 0 -> 378 bytes .../src/out/Tanin-beni/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../out/Tanin-beni/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../Tanin-beni/scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 369 bytes .../16x16/places/folder-download.png | Bin 0 -> 364 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 302 bytes .../16x16/places/folder-music.png | Bin 0 -> 370 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 365 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 378 bytes .../16x16/places/folder-remote.png | Bin 0 -> 403 bytes .../16x16/places/folder-templates.png | Bin 0 -> 367 bytes .../16x16/places/folder-videos.png | Bin 0 -> 365 bytes .../out/Tanin-chikyu/16x16/places/folder.png | Bin 0 -> 302 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 510 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 356 bytes .../16x16/places/user-desktop.png | Bin 0 -> 359 bytes .../Tanin-chikyu/16x16/places/user-home.png | Bin 0 -> 378 bytes .../src/out/Tanin-chikyu/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-chikyu/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 377 bytes .../16x16/places/folder-download.png | Bin 0 -> 369 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 306 bytes .../16x16/places/folder-music.png | Bin 0 -> 371 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 368 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 384 bytes .../16x16/places/folder-remote.png | Bin 0 -> 405 bytes .../16x16/places/folder-templates.png | Bin 0 -> 373 bytes .../16x16/places/folder-videos.png | Bin 0 -> 373 bytes .../out/Tanin-shinkai/16x16/places/folder.png | Bin 0 -> 306 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 511 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 365 bytes .../16x16/places/user-desktop.png | Bin 0 -> 364 bytes .../Tanin-shinkai/16x16/places/user-home.png | Bin 0 -> 380 bytes .../src/out/Tanin-shinkai/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../Tanin-shinkai/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 367 bytes .../16x16/places/folder-download.png | Bin 0 -> 359 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 299 bytes .../Tanin-take/16x16/places/folder-music.png | Bin 0 -> 365 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 361 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 379 bytes .../Tanin-take/16x16/places/folder-remote.png | Bin 0 -> 400 bytes .../16x16/places/folder-templates.png | Bin 0 -> 364 bytes .../Tanin-take/16x16/places/folder-videos.png | Bin 0 -> 364 bytes .../out/Tanin-take/16x16/places/folder.png | Bin 0 -> 299 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 510 bytes .../16x16/places/user-bookmarks.png | Bin 0 -> 355 bytes .../Tanin-take/16x16/places/user-desktop.png | Bin 0 -> 358 bytes .../out/Tanin-take/16x16/places/user-home.png | Bin 0 -> 376 bytes .../src/out/Tanin-take/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../out/Tanin-take/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../Tanin-take/scalable/places/user-home.svg | 15 + .../16x16/places/folder-documents.png | Bin 0 -> 372 bytes .../16x16/places/folder-download.png | Bin 0 -> 368 bytes .../16x16/places/folder-drag-accept.png | Bin 0 -> 305 bytes .../Tanin-ume/16x16/places/folder-music.png | Bin 0 -> 371 bytes .../16x16/places/folder-pictures.png | Bin 0 -> 367 bytes .../16x16/places/folder-publicshare.png | Bin 0 -> 385 bytes .../Tanin-ume/16x16/places/folder-remote.png | Bin 0 -> 401 bytes .../16x16/places/folder-templates.png | Bin 0 -> 370 bytes .../Tanin-ume/16x16/places/folder-videos.png | Bin 0 -> 371 bytes .../src/out/Tanin-ume/16x16/places/folder.png | Bin 0 -> 305 bytes .../16x16/places/network-workgroup.png | Bin 0 -> 506 bytes .../Tanin-ume/16x16/places/user-bookmarks.png | Bin 0 -> 360 bytes .../Tanin-ume/16x16/places/user-desktop.png | Bin 0 -> 362 bytes .../out/Tanin-ume/16x16/places/user-home.png | Bin 0 -> 380 bytes .../tanin-icons/src/out/Tanin-ume/index.theme | 24 + .../scalable/mimetypes/inode-directory.svg | 14 + .../scalable/places/folder-documents.svg | 15 + .../scalable/places/folder-download.svg | 15 + .../scalable/places/folder-drag-accept.svg | 14 + .../scalable/places/folder-music.svg | 15 + .../scalable/places/folder-pictures.svg | 18 + .../scalable/places/folder-publicshare.svg | 15 + .../scalable/places/folder-remote.svg | 37 + .../scalable/places/folder-templates.svg | 15 + .../scalable/places/folder-videos.svg | 18 + .../out/Tanin-ume/scalable/places/folder.svg | 14 + .../scalable/places/network-workgroup.svg | 103 + .../scalable/places/user-bookmarks.svg | 15 + .../scalable/places/user-desktop.svg | 18 + .../Tanin-ume/scalable/places/user-home.svg | 15 + .../tanin-icons-0.1.0-1-any.pkg.tar.zst | Bin 0 -> 49860 bytes packaging/tanin-libadwaita/PKGBUILD | 2 +- packaging/taninux-gtk3-theme/PKGBUILD | 32 + .../TANINUX-dark/gtk-3.0/gtk.css | 368 +++ .../TANINUX/gtk-3.0/gtk.css | 368 +++ packaging/taninux-gtk3-theme/generate.py | 18 + packaging/taninux/PKGBUILD | 18 +- ...rielevarano.Taninux.FlatpakInstall.desktop | 12 + packaging/taninux/flatpak-bundle-handler.sh | 52 + page_demo.py | 61 + shell_demo.py | 47 + src/taninux/cli.py | 101 + src/taninux/core/lock.py | 32 +- src/taninux/core/online_accounts.py | 165 ++ src/taninux/core/panel.py | 35 + src/taninux/core/wine.py | 125 + src/taninux/gui/app.py | 14 +- src/taninux/gui/confirm.py | 12 +- src/taninux/gui/console.py | 90 +- src/taninux/gui/pages/__init__.py | 12 + src/taninux/gui/pages/about.py | 17 +- src/taninux/gui/pages/accessibility.py | 15 +- src/taninux/gui/pages/appdetail.py | 30 +- src/taninux/gui/pages/audio.py | 32 +- src/taninux/gui/pages/backup.py | 34 +- src/taninux/gui/pages/bluetooth.py | 31 +- src/taninux/gui/pages/bundles.py | 23 +- src/taninux/gui/pages/dashboard.py | 18 +- src/taninux/gui/pages/defaultapps.py | 10 +- src/taninux/gui/pages/display.py | 42 +- src/taninux/gui/pages/firewall.py | 12 +- src/taninux/gui/pages/hub_home.py | 18 +- src/taninux/gui/pages/kernel.py | 36 +- src/taninux/gui/pages/locale.py | 34 +- src/taninux/gui/pages/lock.py | 12 +- src/taninux/gui/pages/maintain.py | 44 +- src/taninux/gui/pages/network.py | 35 +- src/taninux/gui/pages/online_accounts.py | 171 ++ src/taninux/gui/pages/panel.py | 114 +- src/taninux/gui/pages/power.py | 44 +- src/taninux/gui/pages/printers.py | 37 +- src/taninux/gui/pages/settings.py | 62 +- src/taninux/gui/pages/shares.py | 45 +- src/taninux/gui/pages/storage.py | 18 +- src/taninux/gui/pages/store.py | 24 +- src/taninux/gui/pages/update.py | 22 +- src/taninux/gui/pages/users.py | 52 +- src/taninux/gui/pages/vpn.py | 30 +- src/taninux/gui/pages/wallpaper.py | 22 +- src/taninux/gui/pages/wine.py | 365 +++ src/taninux/gui/style.py | 461 +++- src/taninux/gui/ui/__init__.py | 96 + src/taninux/gui/ui/native.py | 683 ++++++ src/taninux/gui/ui/shell.py | 134 ++ src/taninux/gui/ui/style_apple.css | 442 ++++ ui_demo.py | 161 ++ 374 files changed, 12307 insertions(+), 686 deletions(-) delete mode 100644 iso/build-profile/airootfs/root/.zprofile create mode 100644 packaging/gtklock/config.ini create mode 100644 packaging/gtklock/gtklock.css create mode 100644 packaging/tanin-calendar/PKGBUILD create mode 100644 packaging/tanin-calendar/ch.gabrielevarano.TaninCalendar.desktop create mode 100644 packaging/tanin-calendar/install-local.sh create mode 100644 packaging/tanin-calendar/tanin-calendar create mode 100644 packaging/tanin-calendar/tanin-calendar.css create mode 100644 packaging/tanin-icons/pkg/tanin-icons/.BUILDINFO create mode 100644 packaging/tanin-icons/pkg/tanin-icons/.INSTALL create mode 100644 packaging/tanin-icons/pkg/tanin-icons/.MTREE create mode 100644 packaging/tanin-icons/pkg/tanin-icons/.PKGINFO create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/index.theme create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/index.theme create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/index.theme create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/index.theme create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/index.theme create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/index.theme create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/index.theme create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/index.theme create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/src/out/Tanin-take/index.theme create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-documents.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-download.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-drag-accept.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-music.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-pictures.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-publicshare.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-remote.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-templates.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-videos.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/network-workgroup.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/user-bookmarks.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/user-desktop.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/16x16/places/user-home.png create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/index.theme create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/mimetypes/inode-directory.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-documents.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-download.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-drag-accept.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-music.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-pictures.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-publicshare.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-remote.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-templates.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-videos.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/network-workgroup.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-bookmarks.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-desktop.svg create mode 100644 packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-home.svg create mode 100644 packaging/tanin-icons/tanin-icons-0.1.0-1-any.pkg.tar.zst create mode 100644 packaging/taninux-gtk3-theme/PKGBUILD create mode 100644 packaging/taninux-gtk3-theme/TANINUX-dark/gtk-3.0/gtk.css create mode 100644 packaging/taninux-gtk3-theme/TANINUX/gtk-3.0/gtk.css create mode 100644 packaging/taninux-gtk3-theme/generate.py create mode 100644 packaging/taninux/ch.gabrielevarano.Taninux.FlatpakInstall.desktop create mode 100644 packaging/taninux/flatpak-bundle-handler.sh create mode 100644 page_demo.py create mode 100644 shell_demo.py create mode 100644 src/taninux/core/online_accounts.py create mode 100644 src/taninux/core/wine.py create mode 100644 src/taninux/gui/pages/online_accounts.py create mode 100644 src/taninux/gui/pages/wine.py create mode 100644 src/taninux/gui/ui/__init__.py create mode 100644 src/taninux/gui/ui/native.py create mode 100644 src/taninux/gui/ui/shell.py create mode 100644 src/taninux/gui/ui/style_apple.css create mode 100644 ui_demo.py diff --git a/HANDOVER.md b/HANDOVER.md index 76528f4..b8550da 100644 --- a/HANDOVER.md +++ b/HANDOVER.md @@ -1,119 +1,179 @@ -# TANINUX — Handover (Session 2026-06-22) +# TANINUX — Handover (Session 2026-06-23) Context for a fresh instance. Repo: `~/projects/taninux` (distro codename **camel**, niri-only GTK4/libadwaita + eww desktop). User: Karim (German; iterates visually; hates UI jank; blanket perms in `~/projects`). UI strings are **English**. -This session did two things: (A) added packages to the `[tanin]` binary repo, -(B) built a custom **accent-reactive icon theme** to replace Papirus. - --- -## A. New packages in the `[tanin]` repo +## THIS SESSION (2026-06-23) — Native GTK4 shell, CSS overhaul -The `[tanin]` repo is a thin overlay on Arch (own packages + AUR rebuilds), -hosted on the Gitea Arch registry. Build/publish scripts in `packaging/`: -`build-tanin-repo.sh` (no-sudo), `finish-tanin-repo.sh` (sudo: taninux + AUR -rebuilds), `publish-tanin-repo.sh` (upload to Gitea). +`TANINUX_UI=native` means the app uses `src/taninux/gui/ui/native.py` widgets and +loads `src/taninux/gui/ui/style_apple.css` instead of libadwaita. This session +fixed the native theme comprehensively. -Added to the AUR-rebuild list (`build-tanin-repo.sh` + `finish-tanin-repo.sh`): -- `librewolf-bin` — default browser. Also a **hard dep** of `tanin-desktop`. - ⚠ Security: rebuild + republish on every LibreWolf release, else `pacman -Syu` - ships a stale (vulnerable) browser. -- `arch-update`, `timeshift` (+ `timeshift-autosnap`), `xdg-terminal-exec` — - all hard deps of `tanin-desktop` now. -- `paru` — built into the repo but stays **optdepend** (not forced). +### What was done -Deliberately **rejected**: `papirus-folders` (removed). The other 20 AUR pkgs on -Karim's machine are personal apps/games/emulators/hardware → not base-OS. -`swayfx`+`scenefx0.4` are pulled by `greetd-regreet` (a leftover alt-greeter; -TANINUX uses `tanin-greet`) — candidate for removal, not done. +**`src/taninux/gui/ui/style_apple.css`** +- All hardcoded blue accents (`#007aff`, `#0a84ff`, `rgba(0,122,255…)`) replaced + with `@accent_color` + `alpha(@accent_color, 0.10)`. +- Dark headerbar: `rgba(20,20,22,0.97)` with `box-shadow: none` (matches sidebar, + no GNOME "lighter band" effect). +- Light headerbar: `#ebebeb`. Light sidebar: `#ebebeb`. Light page bg: `#f6f6f6` + (matches libadwaita white). +- Explicit light-mode text colors (`color: #1d1d1f`) for all nav rows, nav header, + group titles — because GTK4 Adwaita can leave text light-colored in light mode. +- `button.suggested-action` → `background-color: @accent_color !important` (was blue). +- `progress, trough > progress` → `background-color: @accent_color !important`. +- Nav selection dark: glass wash `alpha(@accent_color, 0.18)` + border. ---- +**`src/taninux/gui/style.py`** +- `apply()` (native backend): now calls + `Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", dark)` + so GTK4 Adwaita widgets (buttons, sliders) follow the chosen scheme. +- `_build_css()` swatch buttons: selector is now `button.fuji-swatch.swatch-{key}` + with `background-color: {hex} !important; background: {hex} !important;` — fixes + swatches appearing black (Adwaita overrode them without !important). +- Swatch checked state: uses `outline: 2px solid {hex}; outline-offset: 3px` + (removed `box-shadow: inset 0 0 0 2px @window_bg_color` which was undefined in + native mode). +- `_set_gsettings()`: writes `TANINUX-dark`/`TANINUX` as the GTK3 theme + (for greetd/GTK3 apps). Also sets `color-scheme=prefer-dark/prefer-light`. + ⚠ This is SYSTEM-WIDE — affects eww and all other GTK4 apps. -## B. Icon theme: Papirus → `tanin-icons` (the main work) +**`src/taninux/gui/pages/settings.py`** +- `persist()`: after saving the color scheme, now also calls `native.set_dark(root, …)` + live so the theme switches immediately without restart. -Karim's decision: Papirus is "too flat"; **Adwaita fits the design**, folders -should follow the Fuji accent. The 5 Fuji accents are deliberately *muted* -(`ume #8f8aac`, `shinkai #8a98ac`, `take #8aac8b`, `chikyu #aca98a`, `beni #ac8a8c`) -— so `papirus-folders` (saturated presets) and native GNOME accent-folders (preset -accents only) both can't match. Hence a custom package. +**`src/taninux/gui/console.py`** — complete rewrite +- `LogConsole` is now a plain Python class (NO GTK widget). Writes to + `~/.local/share/taninux/taninux.log` with timestamps. +- `RunnerCard`: button only (no embedded log widget). Process output goes to + `_write_log()`. +- `log_expander()` and `collapsible_log()`: kept as no-ops returning `None` — + all callers already cleaned up. -**New package `packaging/tanin-icons/`:** -- `recolor.py` — generator. Builds 5 thin themes `Tanin-` that - `Inherits=Adwaita`, recolouring **only** the folder/place icons. Algorithm: - HSL shift of the Adwaita blue ramp → keep each stop's **lightness**, swap - hue+saturation to the accent (preserves folder depth). White emblems untouched - (saturation gate). **16px PNGs are rasterised via `rsvg-convert`** because - Adwaita's only fixed-size raster is 16x16 — without an override the inherited - blue PNG wins at 16px. -- `PKGBUILD` (depends `adwaita-icon-theme`; makedepends `librsvg python`) + - `tanin-icons.install` (gtk-update-icon-cache hook). Builds clean, ~52 KB. -- ⚠ `recolor.py` was fixed to only `rmtree` its own `Tanin-` subdirs, NOT - the whole `OUT` dir (it earlier wiped a shared dir when `OUT_DIR` pointed at - `~/.local/share/icons` — no data lost that time, but the bug is gone now). -- ⚠ Rebuild this package after any `adwaita-icon-theme` bump (reads - `/usr/share/icons/Adwaita` at build time). +**Log expanders removed from all settings pages:** +`backup.py`, `update.py`, `kernel.py`, `maintain.py`, `network.py`, +`bluetooth.py`, `shares.py`, `bundles.py`, `appdetail.py`, `store.py`, +`printers.py` — all formerly called `log_expander(…)` which is now a no-op; +the UI no longer has a "Log" section on any page. -**Integration (source only, NOT yet in the installed taninux):** -`src/taninux/gui/style.py` → `_set_gsettings()` now also sets -`icon-theme=Tanin-`, so folders recolour **live** when the accent -changes. Also fixed `src/taninux/gui/pages/panel.py` hint "Papirus" → "Adwaita". +**PKGBUILDs** (`packaging/taninux/PKGBUILD`, `packaging/tanin-desktop/PKGBUILD`) +- networkmanager, bluez-utils, wireplumber, brightnessctl, wlsunset, cups, + pacman-contrib moved from `optdepends` → `depends` in `taninux/PKGBUILD`. +- wlsunset, cups moved from `optdepends` → `depends` in `tanin-desktop/PKGBUILD`. +- paru, flatpak, timeshift remain optdepends. -**Wiring:** -- `build-tanin-repo.sh`: `tanin-icons` added to `OWN_SIMPLE`. -- `tanin-desktop/PKGBUILD`: depends `+tanin-icons`, **removed** `papirus-icon-theme` - (Adwaita is the base now), `pkgrel` 3→5. -- Distro manifests synced: `distro/camel.toml` (icon_theme, `icons=[adwaita...]`, - new `[[packages.own]] tanin-icons`), `distro/MANIFEST.md` (icon row + pkg table). - ---- - -## CURRENT STATE — read carefully +### Files touched this session +``` +src/taninux/gui/ui/style_apple.css +src/taninux/gui/style.py +src/taninux/gui/pages/settings.py +src/taninux/gui/console.py +src/taninux/gui/pages/{backup,update,kernel,maintain,network,bluetooth,shares, + bundles,appdetail,store,printers}.py +packaging/taninux/PKGBUILD +packaging/tanin-desktop/PKGBUILD +``` +### Current state | Thing | State | |---|---| -| `tanin-icons` package built (`*.pkg.tar.zst`) | ✅ in `packaging/tanin-icons/` | -| Verified GTK resolves folder @48px (svg) + @16px (png) to `Tanin-` | ✅ | -| **Active in Karim's running session** | ✅ but via a **user-dir copy** at `~/.local/share/icons/Tanin-*` + `gsettings icon-theme=Tanin-beni` (current accent). NOT the pacman package. | -| `tanin-icons` installed via pacman | ❌ (sudo needs a password; no TTY here) | -| `[tanin]` repo rebuilt + published with the new pkgs | ❌ | -| `taninux`/`tanin-desktop` rebuilt with the style.py change | ❌ → **accent→icon auto-switch is NOT live yet**; changing the accent in Settings won't move the icon-theme until taninux is reinstalled. Workaround now: `gsettings set org.gnome.desktop.interface icon-theme Tanin-`. | -| ISO rebuilt | ❌ | - -So: **wired in source + manifests, but no artifacts rebuilt** → a fresh ISO/install -does NOT have it yet. - -### To fully ship (needs Karim for sudo) -1. `./packaging/finish-tanin-repo.sh` — builds `tanin-icons` + the new `taninux`/ - `tanin-desktop` + AUR rebuilds. -2. `./packaging/publish-tanin-repo.sh` — upload to Gitea (`TANIN_PKG_TOKEN`). -3. Rebuild the ISO (pulls the updated `[tanin]`). -4. Optional cleanup: once installed system-wide, delete the user-dir copies - `~/.local/share/icons/Tanin-*` (identical, harmlessly shadowed). +| Native CSS theme (dark + light) | ✅ fixed, live | +| Accent colors correct (`@accent_color`) | ✅ | +| Live dark/light switch in Settings | ✅ | +| Log expanders removed everywhere | ✅ | +| PKGBUILDs updated | ✅ source only | +| taninux package rebuilt | ❌ needs `./packaging/finish-tanin-repo.sh` | +| tanin-icons built+committed (c5b0848) | ✅ from previous session | +| `[tanin]` repo published | ❌ blocked on `TANIN_PKG_TOKEN` | --- -## OPEN / OFFERED, not done -- **camel.toml `rebuilt_aur` list is stale** — still only eww/tiramisu/waypaper/ - calamares/paru; missing `librewolf-bin`, `arch-update`, `timeshift-autosnap`, - `xdg-terminal-exec`. Offered to sync; awaiting Karim. -- **Manual icon-theme picker** (`src/taninux/gui/desktop.py`) still lists the 5 - `Tanin-*` themes and a manual pick gets overridden on the next accent change. - Could filter them out — not done. -- **Accent-icon scope**: only folders/places are recoloured. Karim was asked - whether to extend to `emblem-*`/coloured mimetypes — undecided. -- `papirus-folders` still *installed* on Karim's machine (`paru -Rns papirus-folders`). +## THE CRITICAL BUG THIS SESSION — `awww-daemon` black screen + +**Symptom:** Entire desktop goes black (no wallpaper, no windows visible). +Opening any eww panel made it worse (visible as black backdrop). + +**Root cause:** `awww` (the blurred-wallpaper daemon used for niri's Overview +backdrop) creates a surface at the Wayland **Background layer**. After suspend/ +resume, awww's cached blurred image goes stale or the surface loses its content +→ the surface renders as **solid black**. Because awww-daemon sits ABOVE swaybg +in the Background layer stack, it covers the wallpaper entirely. + +The transparent panel-backdrop was fine — what appeared "black" was the awww +surface showing through the transparent backdrop, covering the wallpaper behind it. + +**Fix (immediate):** `pkill awww` +- This kills the awww daemon, the black surface disappears from niri's Background + layer, swaybg's wallpaper shows through again. + +**Permanent fix needed (TODO for next session):** +Option A — Regenerate blur cache on resume: + Add a systemd user sleep hook that calls `~/projects/eww/scripts/overview-backdrop.sh sync` + on `post-resume`. File: `~/.config/systemd/user/awww-refresh.service` + `.path`. +Option B — Make awww not start at login (only at Overview open): + Remove awww from `launch.sh`; call `overview-backdrop.sh start` only when niri's + Overview is about to open. niri has no "overview-opening" IPC event yet → tricky. +Option C — Restart awww after resume with a fresh cache: + In the sleep hook: `pkill awww; sleep 0.5; overview-backdrop.sh start`. + +**Diagnosis dead-ends (don't re-investigate):** +- ❌ NOT caused by our `~/.config/gtk-4.0/libadwaita.css` (tested: removed → still black) +- ❌ NOT caused by `color-scheme=prefer-dark` gsettings (tested: default → still black) +- ❌ NOT caused by GSK renderer (`GSK_RENDERER=cairo` → still black) +- ✅ CONFIRMED: `pkill awww` → wallpaper returns instantly + +--- + +## CURRENT SYSTEM STATE (after this session's debugging) + +⚠ **gsettings were modified during debugging.** Restore if needed: +```bash +gsettings set org.gnome.desktop.interface color-scheme prefer-dark +gsettings set org.gnome.desktop.interface gtk-theme TANINUX-dark +``` +The awww daemon was killed (`pkill awww`). The desktop now shows swaybg's +wallpaper correctly. Restarting eww (`~/projects/eww/launch.sh`) will restart +awww via launch.sh — which may make the black screen return. If it does: `pkill awww`. + +--- + +## PREVIOUS SESSION (2026-06-22) — Icon theme + +### A. New packages in the `[tanin]` repo +- `librewolf-bin`, `arch-update`, `timeshift` (+ `timeshift-autosnap`), + `xdg-terminal-exec` added to AUR rebuild list. +- `paru` built into repo but stays optdepend. +- `papirus-folders` removed. + +### B. Icon theme: `tanin-icons` +Custom package `packaging/tanin-icons/` — 5 thin Adwaita-based themes (`Tanin-`) +that recolour only folder/place icons in Fuji accent hues. Built + committed as c5b0848. +Installed to `~/.local/share/icons/Tanin-*` (user dir, not pacman). +`style.py` → `_set_gsettings()` sets `icon-theme=Tanin-` on accent change. +`tanin-desktop/PKGBUILD`: depends `+tanin-icons`, removed `papirus-icon-theme`. +⚠ NOT yet in the published `[tanin]` repo (needs `TANIN_PKG_TOKEN`). + +--- ## Gotchas to respect -- NEVER run lock/session/suspend commands to "test" (locked Karim out once). -- GTK3 can't be exact-Fuji recoloured (parse-time `@define-color`); only GTK4/ - libadwaita is exact. See memory `taninux-gtk-theming`. -- Lots of unrelated uncommitted changes exist in the tree (tanin-calendar, - online_accounts, gtklock, iso/*) — NOT from this session; don't bundle them. +- **NEVER** run lock/swaylock/suspend commands to test (locked Karim out once). + Exception: Karim explicitly authorised `sudo systemctl suspend` in the 2026-06-23 + session — that's not a standing permission. +- `write_global_theme()` writes `~/.config/gtk-4.0/libadwaita.css` which is read + by ALL GTK4 apps including eww (USER priority 800 — above GTK theme). It only + defines `@define-color` vars and button border-radius; it does NOT define + window backgrounds. The globals are safe. +- TANINUX tree has unrelated uncommitted files (tanin-calendar, online_accounts, + gtklock, iso/) — NOT from this session; do NOT `git add -A`. +- GTK3 can't exact-Fuji recolour at runtime (parse-time `@define-color` limit). -## Files touched this session -`packaging/tanin-icons/{PKGBUILD,recolor.py,tanin-icons.install}` (new), -`packaging/{build,finish}-tanin-repo.sh`, `packaging/tanin-desktop/PKGBUILD`, -`src/taninux/gui/style.py`, `src/taninux/gui/pages/panel.py`, -`distro/{camel.toml,MANIFEST.md}`. +## Open / not done +- Permanent fix for awww black screen after suspend (see above). +- Rebuild + publish `[tanin]` repo (needs `TANIN_PKG_TOKEN` env). +- Rebuild ISO. +- `camel.toml` `rebuilt_aur` list is stale (missing librewolf-bin, arch-update, + timeshift-autosnap, xdg-terminal-exec). +- Manual icon picker in `desktop.py` still lists all 5 `Tanin-*` themes (minor). diff --git a/distro/MANIFEST.md b/distro/MANIFEST.md index 444b258..16bb374 100644 --- a/distro/MANIFEST.md +++ b/distro/MANIFEST.md @@ -19,7 +19,7 @@ Dark, monochrome, libadwaita, rounded, soft short shadows. | bg / fg | `#0f0f0f` / `#f0f0f0`| theme | | UI font | Inter (Medium) | apps, bar | | icon font | Iosevka Nerd Font | `nf-md-*` glyphs | -| icon theme | Papirus | dock app icons | +| icon theme | Adwaita + `tanin-icons` | base + Fuji-accent folders (`Tanin-`) | ## The stack (what it boots into) @@ -30,7 +30,7 @@ Dark, monochrome, libadwaita, rounded, soft short shadows. | Bar/dock/panels | **eww** + Spotlight | `eww`, `tanin-eww` | | Notifications | **tiramisu** (headless) + eww UI | `tiramisu` | | Terminal | foot | `foot` | -| Wallpaper | waypaper | `waypaper` | +| Wallpaper | waypaper (swaybg) + blurred Overview backdrop (awww) | `waypaper`, `awww`, `imagemagick` | | Apps | tsettings · thub · taninux (TUI) | `taninux` | | GTK4 theme | **Fuji** recolour, light + dark | `tanin-libadwaita` | | Portals | gtk (dark mode) + gnome (screencast)| `xdg-desktop-portal-{gtk,gnome}` | @@ -54,6 +54,7 @@ AUR → OPT-IN only (paru = optdepend) | `taninux` | tsettings + thub + TUI, helper, polkit, .desktop | ready | | `tanin-greet` | custom GTK4 greeter for greetd → `/etc/greetd/` | ready (preview) | | `tanin-libadwaita` | Fuji libadwaita recolour, light + dark (pills) | ready (preview) | +| `tanin-icons` | Adwaita overlay, folders recoloured per accent (`Tanin-`) | ready | | `tanin-eww` | eww bar/dock/panels + niri config → `/usr/share/tanin/skel/.config` | ready | | `tanin-setup` | seed `~/.config` + enable session target | ready | | `tanin-desktop` | metapackage pulling the whole environment (niri) | ready | @@ -86,6 +87,16 @@ waypaper/cliphist) are exactly why `[tanin]` must carry rebuilt AUR packages. 4. **ISO** — ✅ scaffolded in `iso/` (archiso releng-base + overrides). Calamares `packages` module installs `tanin-desktop`; greetd + tanin-greet enabled; session target wired. +### Overview backdrop (blurred wallpaper) +niri's Overview shows a flat grey backdrop behind the zoomed-out workspaces. We +fill it with a blurred copy of the wallpaper via a **second** background surface: +`awww` (namespace `awww-daemon`) shows the blurred image, and a niri +`layer-rule … place-within-backdrop true` moves it into the backdrop (so the +sharp swaybg wallpaper stays on the desktop). niri has no blur of its own — the +copy is pre-blurred by `~/.config/eww/scripts/overview-backdrop.sh` with +ImageMagick. Started via niri `spawn-at-startup "… overview-backdrop.sh start"` +and regenerated on each wallpaper change via waypaper's `post_command`. + ### Session / dark-mode fix libadwaita apps read light/dark via `org.freedesktop.portal.Settings`, which needs `graphical-session.target` active. `tanin-setup` installs diff --git a/iso/build-profile/airootfs/etc/calamares/modules/shellprocess-striplive.conf b/iso/build-profile/airootfs/etc/calamares/modules/shellprocess-striplive.conf index 28802d8..4e24f68 100644 --- a/iso/build-profile/airootfs/etc/calamares/modules/shellprocess-striplive.conf +++ b/iso/build-profile/airootfs/etc/calamares/modules/shellprocess-striplive.conf @@ -1,12 +1,13 @@ --- dontChroot: false timeout: 30 -# Remove the live-only installer bits from the installed target so it boots the -# desktop (greetd) instead of auto-launching Calamares. +# Turn the cloned live image into a normal installed system: +# - drop the live installer launcher +# - restore the greetd config to the tanin-greet greeter (the live used an +# initial_session that auto-ran Calamares) +# - boot to the desktop; seatd is live-only (greetd uses logind on the target) script: -- "rm -f /etc/systemd/system/getty@tty1.service.d/autologin.conf" -- "rmdir --ignore-fail-on-non-empty /etc/systemd/system/getty@tty1.service.d 2>/dev/null || true" -- "rm -f /root/.zprofile" - "rm -f /usr/local/bin/tanin-installer" +- "install -Dm644 /usr/share/tanin/greetd/config.toml /etc/greetd/config.toml" - "systemctl set-default graphical.target" - "systemctl disable seatd.service" diff --git a/iso/build-profile/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf b/iso/build-profile/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf index 280039b..263125b 100644 --- a/iso/build-profile/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf +++ b/iso/build-profile/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf @@ -1,3 +1,3 @@ [Service] ExecStart= -ExecStart=-/usr/bin/agetty --autologin root --noclear %I 38400 linux +ExecStart=-/usr/bin/agetty --noreset --noclear --autologin root - ${TERM} diff --git a/iso/build-profile/airootfs/root/.zprofile b/iso/build-profile/airootfs/root/.zprofile deleted file mode 100644 index d81920a..0000000 --- a/iso/build-profile/airootfs/root/.zprofile +++ /dev/null @@ -1,5 +0,0 @@ -# Live ISO: auto-launch the TANINUX installer on the first console. -# (Removed from the installed target by Calamares' shellprocess-striplive.) -if [[ "$(tty)" == /dev/tty1 ]] && [[ -z "$WAYLAND_DISPLAY" ]] && ! pgrep -x cage >/dev/null 2>&1; then - exec tanin-installer -fi diff --git a/iso/build-profile/airootfs/root/customize_airootfs.sh b/iso/build-profile/airootfs/root/customize_airootfs.sh index 38d80c8..9cdd448 100755 --- a/iso/build-profile/airootfs/root/customize_airootfs.sh +++ b/iso/build-profile/airootfs/root/customize_airootfs.sh @@ -14,14 +14,27 @@ pacman-key --lsign-key 63F0415879323F6BFE0FBFB2B6FDF356206AF4F6 # --- services in the LIVE image --- systemctl enable NetworkManager.service -# seat manager for cage (the getty-autologin root session has no logind seat). systemctl enable seatd.service -gpasswd -a root seat 2>/dev/null || true -# Live boots to a console on tty1 that auto-launches the installer (cage + -# calamares) via getty autologin + /root/.bash_profile. No display-manager. -systemctl set-default multi-user.target -# greetd is for the TARGET only (Calamares enables it post-install). -systemctl disable greetd.service 2>/dev/null || true + +# Live: greetd auto-launches the installer (cage + calamares) via initial_session. +# greetd creates a real logind session WITH A SEAT (the getty-autologin couldn't), +# so cage/wlroots starts. No login prompt — the installer comes up directly. +# (The tanin-greet package set config.toml to its greeter; we override it here for +# the live, and Calamares restores the tanin-greet config on the target.) +cat > /etc/greetd/config.toml <<'GEOF' +[terminal] +vt = 1 + +[initial_session] +command = "cage -s -- calamares" +user = "root" + +[default_session] +command = "agreety --cmd /bin/zsh" +user = "greeter" +GEOF +systemctl enable greetd.service +systemctl set-default graphical.target # --- plymouth: add the hook so the target initramfs shows the splash --- # (Calamares regenerates the initramfs on the target; this drop-in is carried diff --git a/packaging/gtklock/config.ini b/packaging/gtklock/config.ini new file mode 100644 index 0000000..61ee5e6 --- /dev/null +++ b/packaging/gtklock/config.ini @@ -0,0 +1,13 @@ +# gtklock — TANINUX lockscreen config (Fuji greeter look). +# glib keyfile format. gtklock is launched as: gtklock -d -c +[main] +# stylesheet + wallpaper-as-css-background live next to this file +style=/home/karim/.config/gtklock/gtklock.css +# modules (system .so, suffix optional): user avatar+name, and power buttons +modules=userinfo;powerbar +# built-in clock — time over date, like the greeter's centre clock +time-format=%H:%M +date-format=%A, %d. %B %Y +# hide the input when idle so the wallpaper+clock stay clean; reveal on keypress +idle-hide=true +idle-timeout=15 diff --git a/packaging/gtklock/gtklock.css b/packaging/gtklock/gtklock.css new file mode 100644 index 0000000..54ab0ed --- /dev/null +++ b/packaging/gtklock/gtklock.css @@ -0,0 +1,61 @@ +/* gtklock — Fuji greeter look (GTK3). Mirrors /etc/greetd/tanin-greet.css. + * Palette: bg #0f0f0f bg_alt #191919 fg #f0f0f0 muted #8a8a8a accent #a39ec4 error #e0a3a3 + * + * Wallpaper: url('wallpaper') resolves NEXT TO this css (~/.config/gtklock/wallpaper), + * kept in sync with the desktop wallpaper by scripts/greeter-wallpaper.sh. The dark + * linear-gradient on top is the scrim (keeps text readable; matches the greeter's .scrim). + * + * NOTE: selectors target gtklock 4.x core + the userinfo/powerbar modules. If one + * doesn't apply on the first real run, verify the actual widget id with + * GTK_DEBUG=interactive — then we tweak. swaylock stays the fallback meanwhile. + */ +window { + background-image: linear-gradient(rgba(0,0,0,0.55), rgba(0,0,0,0.55)), url('wallpaper'); + background-size: cover, cover; + background-position: center, center; + color: #f0f0f0; +} + +/* built-in clock (driven by time-format / date-format in config.ini) */ +#clock-label { color: #f0f0f0; font-size: 64px; font-weight: 300; margin-bottom: 30px; } + +/* userinfo module — avatar + username */ +#user-image { + color: #f0f0f0; + background-color: rgba(163, 158, 196, 0.14); /* accent @ 0.14, like the greeter avatar */ + border-radius: 999px; + padding: 22px; + margin-bottom: 10px; +} +#user-name, #username-label { color: #f0f0f0; font-size: 19px; font-weight: 600; margin-bottom: 16px; } + +/* password pill — grey translucent fill, accent focus ring, 999px like the bar */ +entry { + min-width: 300px; + background-color: rgba(25, 25, 25, 0.55); + color: #f0f0f0; + border: 1.5px solid rgba(240, 240, 240, 0.22); + border-radius: 999px; + padding: 11px 18px; + box-shadow: none; + caret-color: #f0f0f0; +} +entry:focus { border-color: #a39ec4; } +entry image { color: rgba(240, 240, 240, 0.6); } + +#unlock-button { + margin-top: 14px; + background: transparent; + border: none; + box-shadow: none; + color: #8a8a8a; +} +#unlock-button:hover { color: #a39ec4; } + +#error-label { color: #e0a3a3; margin-top: 8px; } +#caps-lock-label { color: #8a8a8a; margin-top: 4px; } +#info-label { color: #8a8a8a; } + +/* powerbar module (reboot/poweroff) — quiet glyphs, accent on hover, like the greeter */ +.powerbar-button, #powerbar button { background: transparent; border: none; box-shadow: none; color: #8a8a8a; } +.powerbar-button:hover, #powerbar button:hover { color: #a39ec4; } diff --git a/packaging/tanin-calendar/PKGBUILD b/packaging/tanin-calendar/PKGBUILD new file mode 100644 index 0000000..ef8513a --- /dev/null +++ b/packaging/tanin-calendar/PKGBUILD @@ -0,0 +1,26 @@ +# Maintainer: Karim +# +# tanin-calendar — TANINUX calendar app (GTK4 / libadwaita). A detailed agenda +# over your calendars, read straight from Evolution Data Server (the same +# backend GNOME Online Accounts feeds). Inherits the Fuji monochrome look from +# tanin-libadwaita. German UI. +pkgname=tanin-calendar +pkgver=0.1.0 +pkgrel=1 +pkgdesc="TANINUX calendar — GTK4/libadwaita agenda over Evolution Data Server (GNOME Online Accounts)" +arch=('any') +url="https://taninux.kgva.ch" +license=('GPL-3.0-or-later') +depends=('python' 'python-gobject' 'gtk4' 'libadwaita' + 'evolution-data-server' 'adwaita-icon-theme') +optdepends=('gnome-online-accounts: add Google/Nextcloud/CalDAV calendars' + 'tanin-libadwaita: Fuji monochrome theming for the app') + +package() { + install -Dm755 "$startdir/tanin-calendar" \ + "$pkgdir/usr/bin/tanin-calendar" + install -Dm644 "$startdir/tanin-calendar.css" \ + "$pkgdir/usr/share/tanin/calendar/tanin-calendar.css" + install -Dm644 "$startdir/ch.gabrielevarano.TaninCalendar.desktop" \ + "$pkgdir/usr/share/applications/ch.gabrielevarano.TaninCalendar.desktop" +} diff --git a/packaging/tanin-calendar/ch.gabrielevarano.TaninCalendar.desktop b/packaging/tanin-calendar/ch.gabrielevarano.TaninCalendar.desktop new file mode 100644 index 0000000..cfc41c2 --- /dev/null +++ b/packaging/tanin-calendar/ch.gabrielevarano.TaninCalendar.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name=Calendar +Name[de]=Kalender +Comment=Termine aus deinen Kalendern (Evolution Data Server) +Comment[de]=Termine aus deinen Kalendern (Evolution Data Server) +Exec=tanin-calendar +Icon=x-office-calendar +Terminal=false +Categories=Office;Calendar;GTK; +Keywords=Kalender;Termine;Agenda;Calendar;Events; +StartupNotify=true +StartupWMClass=ch.gabrielevarano.TaninCalendar diff --git a/packaging/tanin-calendar/install-local.sh b/packaging/tanin-calendar/install-local.sh new file mode 100644 index 0000000..a310181 --- /dev/null +++ b/packaging/tanin-calendar/install-local.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Install tanin-calendar on THIS machine (no packaging round-trip). Run with +# sudo: +# sudo bash packaging/tanin-calendar/install-local.sh +set -euo pipefail +SRC="$(cd "$(dirname "$0")" && pwd)" + +install -Dm755 "$SRC/tanin-calendar" /usr/bin/tanin-calendar +install -Dm644 "$SRC/tanin-calendar.css" /usr/share/tanin/calendar/tanin-calendar.css +install -Dm644 "$SRC/ch.gabrielevarano.TaninCalendar.desktop" \ + /usr/share/applications/ch.gabrielevarano.TaninCalendar.desktop + +# refresh the desktop database so the launcher entry shows up immediately +command -v update-desktop-database >/dev/null 2>&1 && \ + update-desktop-database /usr/share/applications || true + +cat < 12: + self.cur_month = 1 + self.cur_year += 1 + # clamp the day so the 1st-driven grid math stays valid + self.cur_day = min( + self.cur_day, + _days_in_month(self.cur_year, self.cur_month)) + self._reload() + + # ---- content ---------------------------------------------------------- # + def _set_content(self, widget): + """Swap the single child of the content box.""" + child = self.content.get_first_child() + while child is not None: + nxt = child.get_next_sibling() + self.content.remove(child) + child = nxt + self.content.append(widget) + + def _grid_bounds(self): + """Six-week window the grid actually displays. + + Returns (grid_start, grid_end) where: + • grid_start is the Monday on/just before the 1st of the month, + • grid_end is exclusive (grid_start + 42 days). + Fetching over this window means every visible cell is populated. + """ + first = GLib.DateTime.new_local(self.cur_year, self.cur_month, 1, 0, 0, 0) + # get_day_of_week: 1=Mon … 7=Sun → days to step back to reach Monday. + offset = first.get_day_of_week() - 1 + grid_start = first.add_days(-offset) + grid_end = grid_start.add_days(42) # 6 weeks × 7 days, exclusive + return grid_start, grid_end + + def _week_bounds(self): + """Monday→Sunday week containing the focused date. + + Returns (week_start, week_end) where week_start is that Monday (local + midnight) and week_end is exclusive (week_start + 7 days). + """ + focus = self._focus_date() + offset = focus.get_day_of_week() - 1 # 1=Mon … 7=Sun + week_start = focus.add_days(-offset) + return week_start, week_start.add_days(7) + + def _day_bounds(self): + """[focused day, next day) — the single day's range.""" + start = self._focus_date() + return start, start.add_days(1) + + def _reload(self): + # Keep the switcher in sync with view_mode (e.g. after Today / restore). + self._switching = True + if self.view_stack.get_visible_child_name() != self.view_mode: + self.view_stack.set_visible_child_name(self.view_mode) + self._switching = False + + self._update_strip() + + if not EDS_OK: + self._set_content(self._eds_missing_page()) + self._clear_bottom_agenda() + return + + if self.view_mode == "day": + start, end = self._day_bounds() + elif self.view_mode == "week": + start, end = self._week_bounds() + else: + start, end = self._grid_bounds() + + # Load EDS events in a WORKER THREAD. The ECal/SourceRegistry *_sync calls + # spin the GLib main context internally, which DEADLOCKS when invoked from + # the running GTK main loop (the window then never paints). Do the blocking + # read off-thread and apply the result back on the main thread. A token + # drops stale loads if the user navigates/switches before this finishes. + mode = self.view_mode + self._load_token = getattr(self, "_load_token", 0) + 1 + token = self._load_token + + def worker(): + try: + result, err = load_events(start, end), None + except Exception as e: # noqa: BLE001 + result, err = None, str(e) + GLib.idle_add(self._on_loaded, token, mode, start, result, err) + + threading.Thread(target=worker, daemon=True).start() + + def _on_loaded(self, token, mode, start, result, err): + if token != getattr(self, "_load_token", 0) or mode != self.view_mode: + return False # a newer load superseded this one + if err is not None: + self._set_content(self._error_page(err)) + return False + events, errors = result + buckets = {} + for ev in events: + buckets.setdefault(ev.day_key, []).append(ev) + self._last_buckets = buckets + if mode == "day": + self._set_content(self._build_day(start, buckets, errors)) + elif mode == "week": + self._set_content(self._build_week(start, buckets, errors)) + else: + self._set_content(self._build_grid(start, buckets, errors)) + self._update_bottom_agenda() + return False + + # ---- month grid ------------------------------------------------------- # + def _build_grid(self, grid_start, buckets, errors): + outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + outer.set_vexpand(True) + outer.set_hexpand(True) + + if errors: + outer.append(self._error_banner(errors)) + + grid = Gtk.Grid() + grid.add_css_class("month-grid") + grid.set_vexpand(True) + grid.set_hexpand(True) + grid.set_row_homogeneous(True) + grid.set_column_homogeneous(True) + + # Weekday header row (Mon…Sun). + short = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + for col, name in enumerate(short): + lbl = Gtk.Label(label=name) + lbl.add_css_class("weekday-header") + lbl.set_hexpand(True) + grid.attach(lbl, col, 0, 1, 1) + + today_key = GLib.DateTime.new_now_local().format("%Y-%m-%d") + + # 6 week-rows × 7 day-columns. + for week in range(6): + for dow in range(7): + day = grid_start.add_days(week * 7 + dow) + cell = self._build_cell(day, buckets, today_key) + grid.attach(cell, dow, week + 1, 1, 1) + + # Clamp the grid so it stays centred and readable on wide/maximised + # windows instead of stretching the cells edge-to-edge ("pulled out of + # shape"). The clamp caps the width; the grid itself fills that. + clamp = Adw.Clamp() + clamp.set_maximum_size(GRID_MAX_WIDTH) + clamp.set_tightening_threshold(GRID_MAX_WIDTH) + clamp.add_css_class("grid-clamp") + clamp.set_child(grid) + clamp.set_vexpand(True) + clamp.set_hexpand(True) + + scroller = Gtk.ScrolledWindow() + scroller.set_vexpand(True) + scroller.set_hexpand(True) + scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroller.set_child(clamp) + outer.append(scroller) + return outer + + def _build_cell(self, day, buckets, today_key): + """One day cell: day number + up to 3 event chips + '+N more'.""" + key = day.format("%Y-%m-%d") + day_events = buckets.get(key, []) + in_month = (day.get_month() == self.cur_month + and day.get_year() == self.cur_year) + + cell = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + cell.add_css_class("day-cell") + if not in_month: + cell.add_css_class("other-month") + if key == today_key: + cell.add_css_class("today") + if key == self._selected_key(): + cell.add_css_class("selected") + + # Day number, top-left. + num = Gtk.Label(label=str(day.get_day_of_month())) + num.add_css_class("day-number") + num.set_halign(Gtk.Align.START) + num.set_xalign(0) + cell.append(num) + + # Event chips (up to 3), then a muted "+N more". + MAX_CHIPS = 3 + for ev in day_events[:MAX_CHIPS]: + cell.append(self._event_chip(ev)) + extra = len(day_events) - MAX_CHIPS + if extra > 0: + more = Gtk.Label(label="+%d more" % extra) + more.add_css_class("more-label") + more.set_halign(Gtk.Align.START) + more.set_xalign(0) + cell.append(more) + + # Click anywhere in the cell → SELECT that day. The bottom agenda then + # shows its events (this replaces the old click-a-day popover). + click = Gtk.GestureClick() + click.set_button(1) + click.connect("released", lambda *_a, d=day: self._select_day(d)) + cell.add_controller(click) + + # Right-click on a month day cell → "New event" defaulting to this day. + self._attach_day_menu(cell, day) + return cell + + def _event_chip(self, ev): + """A small coloured chip: colour bar + time + truncated title. + + Activatable; clicking opens the event-detail dialog. + """ + btn = Gtk.Button() + btn.add_css_class("event-chip") + btn.add_css_class("flat") + btn.set_hexpand(True) + btn.connect("clicked", lambda _b, e=ev: self._show_detail(e)) + + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) + + dot = self._color_dot(ev.cal_color, size=8) + dot.add_css_class("chip-dot") + row.append(dot) + + text = ev.summary + if not ev.all_day and ev.start is not None: + text = "%s %s" % (ev.start.format("%H:%M"), ev.summary) + lbl = Gtk.Label(label=text) + lbl.add_css_class("chip-label") + lbl.set_halign(Gtk.Align.START) + lbl.set_xalign(0) + lbl.set_ellipsize(Pango.EllipsizeMode.END) + lbl.set_hexpand(True) + row.append(lbl) + + btn.set_child(row) + btn.set_tooltip_text(ev.summary) + return btn + + # ---- day view (time grid, one column) --------------------------------- # + def _build_day(self, day, buckets, errors): + """The shared time grid restricted to a single full-width day column.""" + return self._build_time_grid([day], buckets, errors, single=True) + + # ---- week view (time grid, seven columns) ----------------------------- # + def _build_week(self, week_start, buckets, errors): + """The shared time grid across Mon→Sun (seven equal day columns).""" + days = [week_start.add_days(i) for i in range(7)] + return self._build_time_grid(days, buckets, errors, single=False) + + # ---- shared time grid ------------------------------------------------- # + def _build_time_grid(self, days, buckets, errors, single): + """A GNOME/Apple-style time grid. + + Layout (top → bottom): + • a pinned header row (outside the scroller): an empty axis-width + spacer, then one header per day (weekday name + date, today lit); + • a thin all-day row (axis spacer + one all-day cell per day); + • a vertically scrolling area: a fixed hour axis on the LEFT (00–23) + and one ``.day-column`` per day, all sharing PX_PER_HOUR. Timed + events are absolutely placed in a per-column Gtk.Fixed at + y = start-hour-fraction × PX_PER_HOUR (height = duration × scale, + min EVENT_MIN_HEIGHT). A now-line is drawn on today's column. + + Each day column carries a Gtk.GestureDrag for drag-to-create. + """ + outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + outer.set_vexpand(True) + outer.set_hexpand(True) + if errors: + outer.append(self._error_banner(errors)) + + today_key = GLib.DateTime.new_now_local().format("%Y-%m-%d") + now = GLib.DateTime.new_now_local() + + short = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + + # --- pinned header row (weekday + date), outside the scroller ------- # + header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + header.add_css_class("timegrid-header") + corner = Gtk.Box() + corner.add_css_class("timegrid-corner") + corner.set_size_request(HOUR_AXIS_WIDTH, -1) + header.append(corner) + for day in days: + key = day.format("%Y-%m-%d") + wd = WEEKDAYS_EN[day.get_day_of_week() - 1] + col_head = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + col_head.add_css_class("timegrid-col-header") + col_head.set_hexpand(True) + if key == today_key: + col_head.add_css_class("today") + if key == self._selected_key(): + col_head.add_css_class("selected") + if single: + name = Gtk.Label( + label="%s, %d %s %d" % ( + wd, day.get_day_of_month(), + MONTHS_EN[day.get_month() - 1], day.get_year())) + name.add_css_class("timegrid-col-name") + col_head.append(name) + else: + nm = Gtk.Label(label=short[day.get_day_of_week() - 1]) + nm.add_css_class("timegrid-col-name") + col_head.append(nm) + num = Gtk.Label(label=str(day.get_day_of_month())) + num.add_css_class("timegrid-col-date") + col_head.append(num) + hclick = Gtk.GestureClick() + hclick.set_button(1) + hclick.connect("released", lambda *_a, d=day: self._select_day(d)) + col_head.add_controller(hclick) + header.append(col_head) + + # --- all-day row (axis spacer + one cell per day) ------------------ # + allday = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + allday.add_css_class("allday-row") + ad_axis = Gtk.Label(label="all-day") + ad_axis.add_css_class("allday-axis") + ad_axis.set_size_request(HOUR_AXIS_WIDTH, ALLDAY_ROW_HEIGHT) + allday.append(ad_axis) + for day in days: + key = day.format("%Y-%m-%d") + cell = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + cell.add_css_class("allday-cell") + cell.set_hexpand(True) + if key == today_key: + cell.add_css_class("today") + ad_events = [e for e in buckets.get(key, []) if e.all_day] + for ev in ad_events: + cell.append(self._allday_chip(ev)) + allday.append(cell) + + # --- the scrolling time grid --------------------------------------- # + grid_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + grid_row.add_css_class("timegrid-body") + + # Hour axis: a fixed-width column of "HH:00" labels, one per hour. + axis = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + axis.add_css_class("time-axis") + axis.set_size_request(HOUR_AXIS_WIDTH, GRID_HEIGHT) + for hour in range(24): + lbl = Gtk.Label(label="%02d:00" % hour) + lbl.add_css_class("hour-label") + lbl.set_halign(Gtk.Align.END) + lbl.set_valign(Gtk.Align.START) + lbl.set_xalign(1) + lbl.set_size_request(-1, PX_PER_HOUR) + axis.append(lbl) + grid_row.append(axis) + + for day in days: + grid_row.append(self._build_day_column( + day, buckets, today_key, now)) + + scroller = Gtk.ScrolledWindow() + scroller.set_vexpand(True) + scroller.set_hexpand(True) + scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroller.set_child(grid_row) + + outer.append(header) + outer.append(allday) + outer.append(scroller) + + # On open, scroll so ~07:00–08:00 is visible. Deferred to idle so the + # adjustment has its real upper bound by then. + def _scroll_to_morning(): + adj = scroller.get_vadjustment() + if adj is not None: + target = min(SCROLL_TO_HOUR * PX_PER_HOUR, + max(0, adj.get_upper() - adj.get_page_size())) + adj.set_value(target) + return False + GLib.idle_add(_scroll_to_morning) + return outer + + def _build_day_column(self, day, buckets, today_key, now): + """One day's column: hairline hour lines, absolutely-placed event + blocks, an optional now-line, and a drag-to-create gesture.""" + key = day.format("%Y-%m-%d") + + column = Gtk.Fixed() + column.add_css_class("day-column") + column.set_hexpand(True) + column.set_size_request(-1, GRID_HEIGHT) + if key == today_key: + column.add_css_class("today") + + # Hour + half-hour hairlines as thin Fixed children spanning the width. + # We don't know the column's pixel width yet, so the lines are added as + # full-width boxes via a size allocation hook. + lines = [] + for hour in range(24): + full = Gtk.Box() + full.add_css_class("hour-line") + full.set_size_request(10, 1) + column.put(full, 0, hour * PX_PER_HOUR) + lines.append(full) + if hour < 24: + half = Gtk.Box() + half.add_css_class("half-line") + half.set_size_request(10, 1) + column.put(half, 0, int((hour + 0.5) * PX_PER_HOUR)) + lines.append(half) + + # A selection rectangle reused for drag-to-create (hidden until a drag). + # It is a child of THIS column's Gtk.Fixed, so it can only ever live + # inside this one day column. Its width tracks the column's real pixel + # width (see _on_width below); x is pinned to 0 so it never spills into + # a neighbouring column — it only ever grows vertically (time). + sel_box = Gtk.Box() + sel_box.add_css_class("drag-selection") + sel_box.set_visible(False) + sel_box.set_halign(Gtk.Align.START) + column.put(sel_box, 0, 0) + # The column's current pixel width, kept up to date by the + # notify::width handler and reused to size the selection box. Starts at + # 1 so a drag before the first allocation still draws something sane. + col_w = {"w": 1} + + # Timed event blocks, with best-effort side-by-side overlap splitting. + timed = [e for e in buckets.get(key, []) + if not e.all_day and e.start is not None] + timed.sort(key=lambda e: e.start.to_unix()) + placements = _layout_overlaps(timed, day) + blocks = [] + for ev, col_i, col_n in placements: + y, h = self._event_geometry(ev, day) + block = self._event_block(ev) + column.put(block, 0, y) + blocks.append((block, y, h, col_i, col_n)) + + # Now-line on today's column. + now_line = None + if key == today_key: + now_line = Gtk.Box() + now_line.add_css_class("now-line") + now_line.set_size_request(10, 2) + frac = (now.get_hour() * 60 + now.get_minute()) / 60.0 + column.put(now_line, 0, int(frac * PX_PER_HOUR) - 1) + + # Resize hook: stretch hairlines / now-line / blocks to the real width. + def _on_width(_col, w, _h, _b=None): + # Remember THIS column's real pixel width so the drag-selection box + # can be sized to exactly one column (never the whole grid). + col_w["w"] = max(1, w) + for ln in lines: + ln.set_size_request(w, 1) + if now_line is not None: + now_line.set_size_request(w, 2) + for block, _y, _bh, col_i, col_n in blocks: + bw = max(1, int(w / col_n) - 2) + column.move(block, col_i * int(w / col_n) + 1, _y) + block.set_size_request(bw, _bh) + # Keep an in-progress selection box pinned to one column's width. + if sel_box.get_visible(): + _bh2 = sel_box.get_height() + sel_box.set_size_request(col_w["w"], max(1, _bh2)) + # Gtk.Fixed has no allocate signal; track size via the widget's + # "notify" on width through a size-allocate via a tick callback. + column.connect("notify::width", lambda c, _p: + _on_width(c, c.get_width(), c.get_height())) + + # --- drag-to-create ------------------------------------------------ # + drag = Gtk.GestureDrag() + drag.set_button(1) + state = {"start_y": 0.0} + + def _drag_begin(_g, _sx, sy): + state["start_y"] = sy + sel_box.set_visible(True) + # Width = this single column's tracked pixel width; x pinned to 0 so + # the box stays wholly inside the column the drag started on. + w = col_w["w"] or max(1, column.get_width()) + sel_box.set_size_request(max(1, w), 1) + column.move(sel_box, 0, int(sy)) + + def _drag_update(_g, ox, oy): + sy = state["start_y"] + cy = sy + oy + top = max(0, min(sy, cy)) + bot = min(GRID_HEIGHT, max(sy, cy)) + w = col_w["w"] or max(1, column.get_width()) + sel_box.set_size_request(max(1, w), max(1, int(bot - top))) + column.move(sel_box, 0, int(top)) + + def _drag_end(_g, ox, oy): + sel_box.set_visible(False) + sy = state["start_y"] + cy = sy + oy + self._drag_to_new_event(day, sy, cy, abs(oy)) + + drag.connect("drag-begin", _drag_begin) + drag.connect("drag-update", _drag_update) + drag.connect("drag-end", _drag_end) + column.add_controller(drag) + + # Right-click on the empty time grid → "New event here" at the clicked + # time. Right-clicks that land on an event block are consumed by the + # block's own button-3 menu, so they don't reach the column. + self._attach_newhere_menu(column, day, get_minutes=_y_to_minutes) + return column + + def _event_geometry(self, ev, day): + """(y_px, height_px) for a timed event clamped to this day's 0–24h.""" + day_start = GLib.DateTime.new_local( + day.get_year(), day.get_month(), day.get_day_of_month(), 0, 0, 0) + day_end = day_start.add_days(1) + s = ev.start + e = ev.end if ev.end is not None else ev.start.add_hours(1) + # Clamp to the visible day. + s_unix = max(s.to_unix(), day_start.to_unix()) + e_unix = min(e.to_unix(), day_end.to_unix()) + start_min = (s_unix - day_start.to_unix()) / 60.0 + end_min = (e_unix - day_start.to_unix()) / 60.0 + y = int(start_min / 60.0 * PX_PER_HOUR) + h = max(EVENT_MIN_HEIGHT, int((end_min - start_min) / 60.0 * PX_PER_HOUR)) + return y, h + + def _event_block(self, ev): + """A positioned, clickable event block: time + title, calendar tint.""" + btn = Gtk.Button() + btn.add_css_class("event-block") + btn.add_css_class("flat") + btn.connect("clicked", lambda _b, e=ev: self._show_detail(e)) + btn.set_tooltip_text(ev.summary) + + # Right-click → context menu (Delete / Details). A separate button-3 + # GestureClick: it does not conflict with the left-button click above. + self._attach_event_menu(btn, ev) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + time_lbl = Gtk.Label(label=self._time_label(ev)) + time_lbl.add_css_class("event-block-time") + time_lbl.set_halign(Gtk.Align.START) + time_lbl.set_xalign(0) + time_lbl.set_ellipsize(Pango.EllipsizeMode.END) + box.append(time_lbl) + title = Gtk.Label(label=ev.summary) + title.add_css_class("event-block-title") + title.set_halign(Gtk.Align.START) + title.set_xalign(0) + title.set_ellipsize(Pango.EllipsizeMode.END) + box.append(title) + btn.set_child(box) + + # Tint the block with the calendar colour (left accent + faint fill). + if ev.cal_color: + cls = _color_class(ev.cal_color) + css = (".event-block.cb-%s { border-left: 3px solid %s; " + "background-color: %s; }" + % (cls, ev.cal_color, _tint(ev.cal_color))) + provider = Gtk.CssProvider() + try: + provider.load_from_data(css.encode("utf-8")) + btn.get_style_context().add_provider( + provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + btn.add_css_class("cb-%s" % cls) + except Exception: # noqa: BLE001 + pass + return btn + + def _allday_chip(self, ev): + """A compact all-day pill for the all-day row.""" + btn = Gtk.Button() + btn.add_css_class("allday-chip") + btn.add_css_class("flat") + btn.connect("clicked", lambda _b, e=ev: self._show_detail(e)) + btn.set_tooltip_text(ev.summary) + lbl = Gtk.Label(label=ev.summary) + lbl.add_css_class("allday-chip-label") + lbl.set_halign(Gtk.Align.START) + lbl.set_xalign(0) + lbl.set_ellipsize(Pango.EllipsizeMode.END) + btn.set_child(lbl) + if ev.cal_color: + cls = _color_class(ev.cal_color) + css = (".allday-chip.cb-%s { border-left: 3px solid %s; }" + % (cls, ev.cal_color)) + provider = Gtk.CssProvider() + try: + provider.load_from_data(css.encode("utf-8")) + btn.get_style_context().add_provider( + provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + btn.add_css_class("cb-%s" % cls) + except Exception: # noqa: BLE001 + pass + return btn + + def _drag_to_new_event(self, day, y0, y1, drag_dist): + """Map two Y positions on a day column → start/end times and open the + New-event dialog pre-filled. A near-zero drag (a click) defaults to a + one-hour event at the clicked hour.""" + if drag_dist < DRAG_THRESHOLD: + # Treat as a click: 1-hour event at the clicked hour (snapped). + start_min = _snap(_y_to_minutes(y0), SNAP_MINUTES) + end_min = start_min + 60 + else: + a = _snap(_y_to_minutes(min(y0, y1)), SNAP_MINUTES) + b = _snap(_y_to_minutes(max(y0, y1)), SNAP_MINUTES) + if b <= a: + b = a + SNAP_MINUTES + start_min, end_min = a, b + # Clamp within the day [0, 24h]. Keep at least one snap step. + start_min = max(0, min(start_min, 24 * 60 - SNAP_MINUTES)) + end_min = max(start_min + SNAP_MINUTES, min(end_min, 24 * 60)) + + base = GLib.DateTime.new_local( + day.get_year(), day.get_month(), day.get_day_of_month(), 0, 0, 0) + start_dt = base.add_seconds(start_min * 60) + end_dt = base.add_seconds(end_min * 60) + self._on_new_event(None, defaults={ + "date": base, "start_dt": start_dt, "end_dt": end_dt, + "all_day": False, + }) + + # ---- right-click context menus ---------------------------------------- # + def _attach_event_menu(self, widget, ev): + """Attach a button-3 (right-click) context menu to an event widget. + + The menu offers Delete (removes from EDS off-thread) and Details (the + existing detail dialog). The left-button gestures on the same widget are + untouched, so normal click-to-open still works. + """ + gesture = Gtk.GestureClick() + gesture.set_button(3) + + popover = Gtk.Popover() + popover.set_has_arrow(True) + popover.set_parent(widget) + popover.set_position(Gtk.PositionType.BOTTOM) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + box.add_css_class("context-menu") + + del_btn = Gtk.Button(label="Delete") + del_btn.add_css_class("flat") + del_btn.add_css_class("context-menu-item") + del_btn.add_css_class("destructive") + del_btn.set_halign(Gtk.Align.FILL) + del_btn.get_child().set_halign(Gtk.Align.START) + + def _on_delete(_b, e=ev): + popover.popdown() + self._delete_event(e) + del_btn.connect("clicked", _on_delete) + box.append(del_btn) + + det_btn = Gtk.Button(label="Details") + det_btn.add_css_class("flat") + det_btn.add_css_class("context-menu-item") + det_btn.set_halign(Gtk.Align.FILL) + det_btn.get_child().set_halign(Gtk.Align.START) + + def _on_details(_b, e=ev): + popover.popdown() + self._show_detail(e) + det_btn.connect("clicked", _on_details) + box.append(det_btn) + + popover.set_child(box) + + def _on_pressed(_g, _n, x, y): + # Anchor the popover at the pointer within the clicked widget. + popover.set_pointing_to(_point_rect(x, y)) + popover.popup() + gesture.connect("pressed", _on_pressed) + widget.add_controller(gesture) + + def _attach_newhere_menu(self, widget, day, get_minutes=None): + """Attach a button-3 menu offering "New event here" to a time-grid day + column. ``get_minutes`` maps the click Y (within the column) → minutes + since midnight so the dialog opens pre-filled at the clicked time.""" + gesture = Gtk.GestureClick() + gesture.set_button(3) + + popover = Gtk.Popover() + popover.set_has_arrow(True) + popover.set_parent(widget) + popover.set_position(Gtk.PositionType.BOTTOM) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + box.add_css_class("context-menu") + + new_btn = Gtk.Button(label="New event here") + new_btn.add_css_class("flat") + new_btn.add_css_class("context-menu-item") + new_btn.set_halign(Gtk.Align.FILL) + new_btn.get_child().set_halign(Gtk.Align.START) + box.append(new_btn) + popover.set_child(box) + + click_state = {"y": 0.0} + + def _on_new(_b, d=day): + popover.popdown() + self._new_event_at(d, click_state["y"], get_minutes) + new_btn.connect("clicked", _on_new) + + def _on_pressed(_g, _n, x, y): + click_state["y"] = y + popover.set_pointing_to(_point_rect(x, y)) + popover.popup() + gesture.connect("pressed", _on_pressed) + widget.add_controller(gesture) + + def _attach_day_menu(self, widget, day): + """Attach a button-3 menu offering "New event" to a month-view day + cell (defaults the date to that day).""" + gesture = Gtk.GestureClick() + gesture.set_button(3) + + popover = Gtk.Popover() + popover.set_has_arrow(True) + popover.set_parent(widget) + popover.set_position(Gtk.PositionType.BOTTOM) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + box.add_css_class("context-menu") + new_btn = Gtk.Button(label="New event") + new_btn.add_css_class("flat") + new_btn.add_css_class("context-menu-item") + new_btn.set_halign(Gtk.Align.FILL) + new_btn.get_child().set_halign(Gtk.Align.START) + box.append(new_btn) + popover.set_child(box) + + def _on_new(_b, d=day): + popover.popdown() + base = GLib.DateTime.new_local( + d.get_year(), d.get_month(), d.get_day_of_month(), 0, 0, 0) + self._on_new_event(None, defaults={"date": base, "all_day": False}) + new_btn.connect("clicked", _on_new) + + def _on_pressed(_g, _n, x, y): + popover.set_pointing_to(_point_rect(x, y)) + popover.popup() + gesture.connect("pressed", _on_pressed) + widget.add_controller(gesture) + + def _new_event_at(self, day, y, get_minutes): + """Open the create dialog pre-filled with ``day`` and the time the user + right-clicked (a one-hour default). Reuses the drag/click→create path so + snapping/clamping match drag-to-create exactly.""" + if get_minutes is not None: + start_min = _snap(get_minutes(y), SNAP_MINUTES) + else: + start_min = _snap(_y_to_minutes(y), SNAP_MINUTES) + start_min = max(0, min(start_min, 24 * 60 - SNAP_MINUTES)) + end_min = min(start_min + 60, 24 * 60) + base = GLib.DateTime.new_local( + day.get_year(), day.get_month(), day.get_day_of_month(), 0, 0, 0) + start_dt = base.add_seconds(start_min * 60) + end_dt = base.add_seconds(end_min * 60) + self._on_new_event(None, defaults={ + "date": base, "start_dt": start_dt, "end_dt": end_dt, + "all_day": False, + }) + + # ---- delete (write) --------------------------------------------------- # + def _delete_event(self, ev): + """Remove an event from EDS in a WORKER THREAD, then reload + toast. + + Like every other EDS access in this app, connect_sync/remove_object_sync + spin the GLib main context and DEADLOCK if called on the GTK main loop, + so the blocking work happens off-thread and the result is applied back + via GLib.idle_add. + """ + if not EDS_OK: + self._toast("Evolution Data Server is not available.") + return + if ev.source is None or not ev.uid: + self._toast("This event cannot be deleted (missing source).") + return + + source = ev.source + uid = ev.uid + rid = ev.rid + + def worker(): + err = None + try: + client = ECal.Client.connect_sync( + source, ECal.ClientSourceType.EVENTS, 30, None) + if client is None: + raise RuntimeError("calendar not reachable") + ok = client.remove_object_sync( + uid, rid, ECal.ObjModType.THIS, + ECal.OperationFlags.NONE, None) + if not ok: + raise RuntimeError("the calendar rejected the deletion") + except Exception as e: # noqa: BLE001 + err = str(e) + GLib.idle_add(self._on_event_deleted, err) + + threading.Thread(target=worker, daemon=True).start() + + def _on_event_deleted(self, err): + if err is not None: + self._toast("Could not delete event: %s" % err) + return False + self._toast("Event deleted.") + self._reload() + return False + + def _agenda_row(self, ev): + """A roomy agenda line: colour dot + time range + title + location.""" + btn = Gtk.Button() + btn.add_css_class("agenda-row") + btn.add_css_class("flat") + btn.set_hexpand(True) + btn.connect("clicked", lambda _b, e=ev: self._show_detail(e)) + + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + + dot = self._color_dot(ev.cal_color, size=10) + dot.set_valign(Gtk.Align.CENTER) + row.append(dot) + + time_lbl = Gtk.Label(label=self._time_label(ev)) + time_lbl.add_css_class("agenda-time") + time_lbl.set_halign(Gtk.Align.START) + time_lbl.set_xalign(0) + time_lbl.set_width_chars(11) + time_lbl.set_valign(Gtk.Align.CENTER) + row.append(time_lbl) + + text = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + text.set_valign(Gtk.Align.CENTER) + title = Gtk.Label(label=ev.summary) + title.add_css_class("agenda-title") + title.set_halign(Gtk.Align.START) + title.set_xalign(0) + title.set_ellipsize(Pango.EllipsizeMode.END) + text.append(title) + if ev.location: + loc = Gtk.Label(label=ev.location) + loc.add_css_class("agenda-location") + loc.set_halign(Gtk.Align.START) + loc.set_xalign(0) + loc.set_ellipsize(Pango.EllipsizeMode.END) + text.append(loc) + text.set_hexpand(True) + row.append(text) + + btn.set_child(row) + btn.set_tooltip_text(ev.summary) + return btn + + # ---- bottom selected-day agenda --------------------------------------- # + def _clear_bottom_agenda(self): + child = self.bottom_agenda.get_first_child() + while child is not None: + nxt = child.get_next_sibling() + self.bottom_agenda.remove(child) + child = nxt + + def _update_bottom_agenda(self): + """Render the always-visible bottom section listing the SELECTED day's + events. Day view is itself an agenda for the focused day, so we hide the + bottom section there to avoid duplicating it.""" + self._clear_bottom_agenda() + # Day and Week are now full time grids that show events in place, so the + # bottom selected-day agenda would only duplicate them — hide it there. + # It stays for Month (where cells only fit a few chips). + if self.view_mode in ("day", "week"): + self.bottom_agenda.set_visible(False) + return + self.bottom_agenda.set_visible(True) + + day = self._selected_date() + wd = WEEKDAYS_EN[day.get_day_of_week() - 1] + heading_text = "%s, %d %s" % ( + wd, day.get_day_of_month(), MONTHS_EN[day.get_month() - 1]) + + inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + inner.add_css_class("selected-agenda-inner") + + heading = Gtk.Label(label=heading_text) + heading.add_css_class("selected-heading") + heading.set_halign(Gtk.Align.START) + heading.set_xalign(0) + inner.append(heading) + + day_events = list(self._last_buckets.get(self._selected_key(), [])) + all_day = [e for e in day_events if e.all_day] + timed = [e for e in day_events if not e.all_day] + timed.sort(key=lambda e: (e.start.to_unix() if e.start else 0)) + + if not day_events: + empty = Gtk.Label(label="No events") + empty.add_css_class("selected-empty") + empty.set_halign(Gtk.Align.START) + empty.set_xalign(0) + inner.append(empty) + else: + listbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) + for ev in all_day + timed: + listbox.append(self._agenda_row(ev)) + inner.append(listbox) + + # Clamp the bottom agenda to align with the (clamped) grid above. + clamp = Adw.Clamp() + clamp.set_maximum_size(GRID_MAX_WIDTH) + clamp.set_tightening_threshold(GRID_MAX_WIDTH) + clamp.set_child(inner) + + scroller = Gtk.ScrolledWindow() + scroller.add_css_class("selected-agenda-scroller") + scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroller.set_min_content_height(120) + scroller.set_max_content_height(220) + scroller.set_propagate_natural_height(True) + scroller.set_child(clamp) + self.bottom_agenda.append(scroller) + + # ---- day popover ------------------------------------------------------ # + def _show_day(self, day, day_events): + """Popover/dialog listing all of a day's events (overflow reachable).""" + dialog = Adw.Dialog() + wd = WEEKDAYS_EN[day.get_day_of_week() - 1] + title = "%s, %d %s %d" % (wd, day.get_day_of_month(), + MONTHS_EN[day.get_month() - 1], + day.get_year()) + dialog.set_title(title) + dialog.set_content_width(420) + + tv = Adw.ToolbarView() + tv.add_top_bar(Adw.HeaderBar()) + + page = Adw.PreferencesPage() + group = Adw.PreferencesGroup() + group.set_title(title) + + if not day_events: + empty = Adw.ActionRow() + empty.set_title("No events") + empty.set_subtitle("There is nothing scheduled for this day.") + group.add(empty) + else: + for ev in day_events: + group.add(self._event_row(ev, dialog)) + page.add(group) + + tv.set_content(page) + dialog.set_child(tv) + dialog.present(self) + + def _event_row(self, ev, parent_dialog=None): + row = Adw.ActionRow() + row.set_title(GLib.markup_escape_text(ev.summary)) + row.set_title_lines(2) + + subtitle_bits = [self._time_label(ev)] + if ev.location: + subtitle_bits.append(ev.location) + row.set_subtitle(GLib.markup_escape_text(" · ".join(subtitle_bits))) + row.set_subtitle_lines(1) + + row.add_prefix(self._color_dot(ev.cal_color)) + + row.set_activatable(True) + + def _open(_r, e=ev): + if parent_dialog is not None: + parent_dialog.close() + self._show_detail(e) + row.connect("activated", _open) + chevron = Gtk.Image.new_from_icon_name("go-next-symbolic") + chevron.add_css_class("dim-label") + row.add_suffix(chevron) + return row + + def _color_dot(self, color, size=10): + dot = Gtk.Box() + dot.add_css_class("event-dot") + dot.set_size_request(size, size) + dot.set_valign(Gtk.Align.CENTER) + if color: + css = (".event-dot.c-%s { background-color: %s; }" + % (_color_class(color), color)) + provider = Gtk.CssProvider() + try: + provider.load_from_data(css.encode("utf-8")) + dot.get_style_context().add_provider( + provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + dot.add_css_class("c-%s" % _color_class(color)) + except Exception: # noqa: BLE001 + pass + return dot + + def _time_label(self, ev): + if ev.all_day or ev.start is None: + return "All day" + start = ev.start.format("%H:%M") + if ev.end is not None: + # only show the end time when it's on the same day + if ev.end.format("%Y-%m-%d") == ev.start.format("%Y-%m-%d"): + return "%s – %s" % (start, ev.end.format("%H:%M")) + return start + + # ---- detail ----------------------------------------------------------- # + def _show_detail(self, ev): + dialog = Adw.Dialog() + dialog.set_title(ev.summary or "Event") + dialog.set_content_width(420) + + tv = Adw.ToolbarView() + tv.add_top_bar(Adw.HeaderBar()) + + page = Adw.PreferencesPage() + + head = Adw.PreferencesGroup() + head.set_title(ev.summary or "(No title)") + page.add(head) + + info = Adw.PreferencesGroup() + info.add(self._detail_row("Time", self._detail_time(ev))) + if ev.location: + info.add(self._detail_row("Location", ev.location)) + info.add(self._detail_row("Calendar", ev.cal_name or "—")) + page.add(info) + + if ev.description: + desc_group = Adw.PreferencesGroup() + desc_group.set_title("Description") + desc_label = Gtk.Label(label=ev.description) + desc_label.set_wrap(True) + desc_label.set_xalign(0) + desc_label.set_selectable(True) + desc_label.set_margin_top(6) + desc_label.set_margin_bottom(6) + desc_label.set_margin_start(12) + desc_label.set_margin_end(12) + desc_group.add(desc_label) + page.add(desc_group) + + tv.set_content(page) + dialog.set_child(tv) + dialog.present(self) + + def _detail_row(self, title, value): + row = Adw.ActionRow() + row.set_title(title) + row.set_subtitle(value) + row.set_subtitle_selectable(True) + return row + + def _detail_time(self, ev): + if ev.all_day or ev.start is None: + day = ev.start.format("%d.%m.%Y") if ev.start else "" + return "All day · %s" % day if day else "All day" + out = ev.start.format("%a, %d.%m.%Y %H:%M") + if ev.end is not None: + if ev.end.format("%Y-%m-%d") == ev.start.format("%Y-%m-%d"): + out += " – %s" % ev.end.format("%H:%M") + else: + out += " – %s" % ev.end.format("%d.%m.%Y %H:%M") + return out + + # ---- event creation (write) ------------------------------------------- # + def _on_new_event(self, _btn, defaults=None): + """Open the New-event form. + + ``defaults`` is an optional dict of pre-fill values + ``{date, start_dt, end_dt, all_day}`` (any/all may be omitted). A plain + "+" click passes nothing → the form falls back to the selected day with + a 09:00–10:00 default. A drag-to-create passes the dragged column date + and the snapped start/end times. + """ + if not EDS_OK: + self._toast("Evolution Data Server is not available.") + return + # _writable_calendars() hits EDS (SourceRegistry.new_sync). A sync EDS call + # on the main loop DEADLOCKS the whole app — do it off-thread, then present + # the dialog back on the main thread. + def worker(): + try: + writable = _writable_calendars() + except Exception: # noqa: BLE001 + writable = [] + GLib.idle_add(self._after_writable, writable, defaults) + + threading.Thread(target=worker, daemon=True).start() + + def _after_writable(self, writable, defaults=None): + if not writable: + self._toast("No writable calendar found.") + return False + self._present_new_event_dialog(writable, defaults) + return False + + def _present_new_event_dialog(self, writable, defaults=None): + # Resolve the pre-fill defaults. ``date`` drives the calendar picker; + # ``start_dt``/``end_dt`` (GLib.DateTime, local) drive the time spinners. + defaults = defaults or {} + def_date = defaults.get("date") or self._selected_date() + def_start = defaults.get("start_dt") + def_end = defaults.get("end_dt") + def_all_day = bool(defaults.get("all_day", False)) + def_sh = def_start.get_hour() if def_start is not None else 9 + def_sm = def_start.get_minute() if def_start is not None else 0 + def_eh = def_end.get_hour() if def_end is not None else 10 + def_em = def_end.get_minute() if def_end is not None else 0 + dialog = Adw.Dialog() + dialog.set_title("New Event") + dialog.set_content_width(440) + + tv = Adw.ToolbarView() + header = Adw.HeaderBar() + header.set_show_end_title_buttons(False) + header.set_show_start_title_buttons(False) + + cancel_btn = Gtk.Button(label="Cancel") + cancel_btn.connect("clicked", lambda _b: dialog.close()) + header.pack_start(cancel_btn) + + save_btn = Gtk.Button(label="Save") + save_btn.add_css_class("suggested-action") + header.pack_end(save_btn) + tv.add_top_bar(header) + + page = Adw.PreferencesPage() + + # --- main group: title + calendar -------------------------------- # + grp = Adw.PreferencesGroup() + + title_row = Adw.EntryRow() + title_row.set_title("Title") + grp.add(title_row) + + cal_row = Adw.ComboRow() + cal_row.set_title("Calendar") + cal_model = Gtk.StringList() + for _src, name in writable: + cal_model.append(name) + cal_row.set_model(cal_model) + grp.add(cal_row) + page.add(grp) + + # --- when group: all-day switch + date + times ------------------- # + when = Adw.PreferencesGroup() + when.set_title("When") + + allday_row = Adw.SwitchRow() + allday_row.set_title("All day") + allday_row.set_active(def_all_day) + when.add(allday_row) + + sel = def_date + date_row = Adw.ActionRow() + date_row.set_title("Date") + cal_picker = Gtk.Calendar() + cal_picker.select_day( + GLib.DateTime.new_local( + sel.get_year(), sel.get_month(), sel.get_day_of_month(), + 12, 0, 0)) + date_btn = Gtk.MenuButton() + date_btn.add_css_class("flat") + date_btn.set_valign(Gtk.Align.CENTER) + date_pop = Gtk.Popover() + date_pop.set_child(cal_picker) + date_btn.set_popover(date_pop) + + def _fmt_date_btn(*_a): + d = cal_picker.get_date() + date_btn.set_label(d.format("%a, %d %b %Y")) + _fmt_date_btn() + cal_picker.connect("day-selected", _fmt_date_btn) + date_row.add_suffix(date_btn) + date_row.set_activatable_widget(date_btn) + when.add(date_row) + + start_row = Adw.SpinRow.new_with_range(0, 23, 1) + start_row.set_title("Start hour") + start_row.set_value(def_sh) + when.add(start_row) + + start_min_row = Adw.SpinRow.new_with_range(0, 55, 5) + start_min_row.set_title("Start minute") + start_min_row.set_value(def_sm) + when.add(start_min_row) + + end_row = Adw.SpinRow.new_with_range(0, 23, 1) + end_row.set_title("End hour") + end_row.set_value(def_eh) + when.add(end_row) + + end_min_row = Adw.SpinRow.new_with_range(0, 55, 5) + end_min_row.set_title("End minute") + end_min_row.set_value(def_em) + when.add(end_min_row) + page.add(when) + + # All-day hides the time rows. + def _toggle_allday(*_a): + show = not allday_row.get_active() + for r in (start_row, start_min_row, end_row, end_min_row): + r.set_visible(show) + allday_row.connect("notify::active", _toggle_allday) + _toggle_allday() + + # --- optional details -------------------------------------------- # + details = Adw.PreferencesGroup() + details.set_title("Details") + loc_row = Adw.EntryRow() + loc_row.set_title("Location") + details.add(loc_row) + desc_row = Adw.EntryRow() + desc_row.set_title("Description") + details.add(desc_row) + page.add(details) + + tv.set_content(page) + dialog.set_child(tv) + + def _on_save(_b): + title = title_row.get_text().strip() + if not title: + title_row.add_css_class("error") + self._toast("Please enter a title.") + return + src, _name = writable[cal_row.get_selected()] + all_day = allday_row.get_active() + d = cal_picker.get_date() + year, month, day = d.get_year(), d.get_month(), d.get_day_of_month() + sh = int(start_row.get_value()) + sm = int(start_min_row.get_value()) + eh = int(end_row.get_value()) + em = int(end_min_row.get_value()) + + spec = { + "summary": title, + "location": loc_row.get_text().strip(), + "description": desc_row.get_text().strip(), + "all_day": all_day, + "year": year, "month": month, "day": day, + "sh": sh, "sm": sm, "eh": eh, "em": em, + } + save_btn.set_sensitive(False) + cancel_btn.set_sensitive(False) + save_btn.set_label("Saving…") + self._save_event_async(src, spec, dialog, save_btn, cancel_btn) + + save_btn.connect("clicked", _on_save) + dialog.present(self) + + def _save_event_async(self, source, spec, dialog, save_btn, cancel_btn): + """Build the VEVENT and write it via ECal in a WORKER THREAD. + + EVERY EDS call here (connect_sync, create_object_sync) is synchronous and + spins the GLib main context, so it MUST run off the GTK main loop or the + window deadlocks. On completion we hop back to the main thread via + GLib.idle_add to close the dialog + reload (success) or report (error). + """ + def worker(): + err = None + try: + client = ECal.Client.connect_sync( + source, ECal.ClientSourceType.EVENTS, 30, None) + if client is None: + raise RuntimeError("calendar not reachable") + comp = _build_vevent(spec) + ok, _uid = client.create_object_sync( + comp, ECal.OperationFlags.NONE, None) + if not ok: + raise RuntimeError("the calendar rejected the event") + except Exception as e: # noqa: BLE001 + err = str(e) + GLib.idle_add(self._on_event_saved, err, dialog, + save_btn, cancel_btn) + + threading.Thread(target=worker, daemon=True).start() + + def _on_event_saved(self, err, dialog, save_btn, cancel_btn): + if err is not None: + save_btn.set_sensitive(True) + cancel_btn.set_sensitive(True) + save_btn.set_label("Save") + self._toast("Could not save event: %s" % err) + return False + dialog.close() + self._toast("Event created.") + self._reload() + return False + + def _toast(self, text): + if hasattr(self, "_toast_overlay") and self._toast_overlay is not None: + self._toast_overlay.add_toast(Adw.Toast.new(text)) + + # ---- placeholder pages ------------------------------------------------ # + def _eds_missing_page(self): + page = Adw.StatusPage() + page.set_icon_name("dialog-warning-symbolic") + page.set_title("Evolution Data Server not installed") + page.set_description( + "Displaying calendars requires the " + "evolution-data-server package.\n\n" + EDS_ERROR + ) + return page + + def _error_page(self, message): + page = Adw.StatusPage() + page.set_icon_name("dialog-error-symbolic") + page.set_title("Calendars could not be loaded") + page.set_description(message) + return page + + def _error_banner(self, errors): + banner = Adw.Banner() + banner.set_title("Some calendars are not reachable") + banner.set_revealed(True) + banner.set_tooltip_text("\n".join(errors)) + return banner + + +def _writable_calendars(): + """List (source, display_name) for every enabled, WRITABLE calendar source. + + The registry build is a synchronous EDS call, but it returns quickly and is + only invoked from the New-event click (which opens a dialog before any write + happens off-thread). Read-only calendars (Source.get_writable() is False) are + skipped so users can only create where the backend permits. + """ + out = [] + try: + registry = EDataServer.SourceRegistry.new_sync(None) + except Exception: # noqa: BLE001 + return out + for source in registry.list_sources( + EDataServer.SOURCE_EXTENSION_CALENDAR): + try: + if not source.get_enabled(): + continue + if not source.get_writable(): + continue + except Exception: # noqa: BLE001 + continue + out.append((source, source.get_display_name() or "Calendar")) + return out + + +def _ical_date(year, month, day): + """A floating DATE (is_date=True) with the given Y/M/D set explicitly. + + Building from Y/M/D directly avoids the timezone truncation you get when + converting a local-midnight unix timestamp to a UTC DATE (which can land on + the previous calendar day). + """ + t = ICalGLib.Time.new_null_date() + t.set_date(year, month, day) + t.set_is_date(True) + return t + + +def _ical_datetime_utc(year, month, day, hour, minute): + """A DATE-TIME in UTC for the given local wall clock.""" + local = GLib.DateTime.new_local(year, month, day, hour, minute, 0) + utc = ICalGLib.Timezone.get_utc_timezone() + return ICalGLib.Time.new_from_timet_with_zone(local.to_unix(), False, utc) + + +def _build_vevent(spec): + """Assemble a VEVENT ICalGLib.Component from the form spec dict.""" + comp = ICalGLib.Component.new_vevent() + comp.add_property(ICalGLib.Property.new_uid(GLib.uuid_string_random())) + + now_utc = ICalGLib.Time.new_from_timet_with_zone( + GLib.DateTime.new_now_utc().to_unix(), False, + ICalGLib.Timezone.get_utc_timezone()) + comp.add_property(ICalGLib.Property.new_dtstamp(now_utc)) + + comp.add_property(ICalGLib.Property.new_summary(spec["summary"])) + if spec.get("location"): + comp.add_property(ICalGLib.Property.new_location(spec["location"])) + if spec.get("description"): + comp.add_property( + ICalGLib.Property.new_description(spec["description"])) + + all_day = spec["all_day"] + y, m, d = spec["year"], spec["month"], spec["day"] + + if all_day: + comp.add_property(ICalGLib.Property.new_dtstart(_ical_date(y, m, d))) + # All-day DTEND is the exclusive next day. + nxt = GLib.DateTime.new_local(y, m, d, 0, 0, 0).add_days(1) + comp.add_property(ICalGLib.Property.new_dtend( + _ical_date(nxt.get_year(), nxt.get_month(), + nxt.get_day_of_month()))) + else: + comp.add_property(ICalGLib.Property.new_dtstart( + _ical_datetime_utc(y, m, d, spec["sh"], spec["sm"]))) + local_start = GLib.DateTime.new_local(y, m, d, spec["sh"], spec["sm"], 0) + local_end = GLib.DateTime.new_local(y, m, d, spec["eh"], spec["em"], 0) + # Guard against an end at/before the start (roll to +1h). + if local_end.to_unix() <= local_start.to_unix(): + local_end = local_start.add_hours(1) + comp.add_property(ICalGLib.Property.new_dtend( + _ical_datetime_utc( + local_end.get_year(), local_end.get_month(), + local_end.get_day_of_month(), + local_end.get_hour(), local_end.get_minute()))) + return comp + + +def _color_class(color): + """A CSS-safe class suffix derived from a colour string.""" + return "".join(c for c in color if c.isalnum()) or "x" + + +def _point_rect(x, y): + """A 1×1 Gdk.Rectangle at (x, y). + + NB: ``Gdk.Rectangle(x=…, y=…)`` SILENTLY IGNORES its kwargs (boxed-struct + init is deprecated/no-op), so the fields must be assigned after the fact or + every popover would anchor at (0, 0) instead of the pointer. + """ + rect = Gdk.Rectangle() + rect.x = int(x) + rect.y = int(y) + rect.width = 1 + rect.height = 1 + return rect + + +# --------------------------------------------------------------------------- # +# Time-grid math helpers +# --------------------------------------------------------------------------- # +def _y_to_minutes(y): + """A Y pixel offset within a day column → minutes since 00:00.""" + return (y / float(PX_PER_HOUR)) * 60.0 + + +def _snap(minutes, step): + """Round ``minutes`` to the nearest ``step`` (e.g. 15).""" + return int(round(minutes / float(step)) * step) + + +def _tint(color): + """A translucent fill derived from a calendar colour for event blocks. + + Returns a GTK ``alpha(#rrggbb, …)`` expression so the block reads as a + faint wash of the calendar colour over the dark Fuji background. + """ + return "alpha(%s, 0.22)" % color + + +def _layout_overlaps(timed, day): + """Best-effort overlap layout for a day's timed events. + + Greedy sweep: events are grouped into clusters that transitively overlap; + within a cluster each event gets the first column index that's free, and + every event in the cluster is told how many columns the cluster spans so + blocks split the width evenly. Returns a list of (event, col_index, + col_count) in input order. + """ + if not timed: + return [] + + def _bounds(ev): + s = ev.start.to_unix() + e = (ev.end if ev.end is not None else ev.start.add_hours(1)).to_unix() + if e <= s: + e = s + 3600 + return s, e + + out = [] + cluster = [] # list of (ev, s, e, col) + cluster_max_end = None + + def _flush(cluster): + n = 0 + for _ev, _s, _e, col in cluster: + n = max(n, col + 1) + for ev, _s, _e, col in cluster: + out.append((ev, col, n)) + + for ev in timed: + s, e = _bounds(ev) + if cluster and s >= cluster_max_end: + _flush(cluster) + cluster = [] + cluster_max_end = None + # find the lowest free column among events still overlapping ``s`` + used = set() + for _ev2, _s2, e2, col2 in cluster: + if e2 > s: + used.add(col2) + col = 0 + while col in used: + col += 1 + cluster.append((ev, s, e, col)) + cluster_max_end = e if cluster_max_end is None else max(cluster_max_end, e) + if cluster: + _flush(cluster) + + # restore input order + order = {id(ev): i for i, ev in enumerate(timed)} + out.sort(key=lambda t: order[id(t[0])]) + return out + + +def _days_in_month(year, month): + """Number of days in the given month (1..12), Gregorian.""" + if month == 12: + nxt = GLib.DateTime.new_local(year + 1, 1, 1, 0, 0, 0) + else: + nxt = GLib.DateTime.new_local(year, month + 1, 1, 0, 0, 0) + return nxt.add_days(-1).get_day_of_month() + + +# --------------------------------------------------------------------------- # +# Application +# --------------------------------------------------------------------------- # +class CalendarApp(Adw.Application): + def __init__(self): + super().__init__(application_id="ch.gabrielevarano.TaninCalendar", + flags=Gio.ApplicationFlags.FLAGS_NONE) + + def do_activate(self): + self._load_css() + win = self.props.active_window + if not win: + win = CalendarWindow(self) + win.present() + + def _load_css(self): + if not os.path.exists(CSS_PATH): + return + provider = Gtk.CssProvider() + try: + provider.load_from_path(CSS_PATH) + except Exception: # noqa: BLE001 + return + display = Gdk.Display.get_default() + if display is not None: + Gtk.StyleContext.add_provider_for_display( + display, provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) + + +if __name__ == "__main__": + sys.exit(CalendarApp().run(sys.argv)) diff --git a/packaging/tanin-calendar/tanin-calendar.css b/packaging/tanin-calendar/tanin-calendar.css new file mode 100644 index 0000000..bd36d86 --- /dev/null +++ b/packaging/tanin-calendar/tanin-calendar.css @@ -0,0 +1,511 @@ +/* tanin-calendar — TANINUX calendar + * + * The bulk of the look comes from tanin-libadwaita (Fuji monochrome recolour of + * GTK4/libadwaita). This sheet adds the month grid: weekday header, day cells + * (with today / other-month states), and the small per-event colour chips. + * + * Fuji palette: + * bg #0f0f0f bg_alt #191919 + * fg #f0f0f0 muted #8a8a8a accent #a39ec4 + * + * Per-event/calendar background colours are injected at runtime (see + * _color_dot in tanin-calendar); the .event-dot rule below is the shape / + * fallback so a dot is visible even without a calendar colour from EDS. + */ + +/* --------------------------------------------------------------- strip --- */ +/* The masthead strip below the header bar: prev/next nav on the left, the big + * month name (bold) on the left and the big year (lighter/muted) on the right. + */ +.cal-strip { + background-color: #0f0f0f; + border-bottom: 1px solid alpha(#f0f0f0, 0.07); +} + +/* Inner box of the clamped strip carries the padding so the clamp can keep the + * content aligned with the (equally clamped) grid below. */ +.cal-strip-inner { + padding: 8px 8px 12px 8px; +} + +.cal-strip .month-title { + font-size: 1.9em; + font-weight: 800; + color: #f0f0f0; + margin-left: 8px; +} + +.cal-strip .day-subtitle { + font-size: 1.05em; + font-weight: 500; + color: #8a8a8a; + margin-left: 8px; +} + +.cal-strip .year-title { + font-size: 1.9em; + font-weight: 300; + color: #8a8a8a; +} + +.cal-strip .cal-nav button { + min-width: 30px; + min-height: 30px; + padding: 2px; + color: #f0f0f0; +} + +.cal-strip .cal-nav button:hover { + background-color: #242424; +} + +/* ------------------------------------------------------------------ dots --- */ +.event-dot { + min-width: 10px; + min-height: 10px; + border-radius: 9999px; + /* fallback: the dim UI foreground, so colourless calendars still show a dot */ + background-color: alpha(@window_fg_color, 0.35); +} + +.chip-dot { + min-width: 8px; + min-height: 8px; +} + +/* ----------------------------------------------------------- month grid --- */ +.month-grid { + background-color: #0f0f0f; + padding: 0; +} + +/* Weekday header (Mon…Sun). */ +.weekday-header { + color: #8a8a8a; + font-weight: bold; + font-size: 0.85em; + text-transform: uppercase; + letter-spacing: 0.04em; + padding: 8px 6px; + background-color: #191919; +} + +/* One day cell. The 1px outline forms the grid's hairlines without doubling. */ +.day-cell { + background-color: #0f0f0f; + border: 1px solid alpha(#f0f0f0, 0.08); + padding: 4px 5px; + min-height: 88px; +} + +/* Days outside the current month are dimmed. */ +.day-cell.other-month { + background-color: #0d0d0d; +} + +.day-cell.other-month .day-number { + color: alpha(#8a8a8a, 0.55); +} + +/* Today's cell: accent ring + subtle tinted background. */ +.day-cell.today { + background-color: alpha(#a39ec4, 0.10); + border: 1px solid #a39ec4; +} + +.day-cell.today .day-number { + color: #a39ec4; + font-weight: bold; +} + +/* The SELECTED cell (distinct from today): a brighter fill + inset ring so the + * user can tell which day's events the bottom agenda is showing. */ +.day-cell.selected { + background-color: #242424; + border: 1px solid alpha(#f0f0f0, 0.30); +} + +.day-cell.selected.today { + background-color: alpha(#a39ec4, 0.18); + border: 1px solid #a39ec4; +} + +.day-cell.selected .day-number { + color: #f0f0f0; + font-weight: bold; +} + +/* Day number, top-left. */ +.day-number { + color: #f0f0f0; + font-size: 0.9em; + padding: 1px 2px; + margin-bottom: 2px; +} + +/* ---------------------------------------------------------- event chips --- */ +.event-chip { + background-color: #191919; + border-radius: 5px; + padding: 1px 5px; + min-height: 0; + margin: 0; +} + +.event-chip:hover { + background-color: #232323; +} + +.chip-label { + color: #f0f0f0; + font-size: 0.82em; +} + +/* The muted "+N more" line when a day overflows its chip budget. */ +.more-label { + color: #8a8a8a; + font-size: 0.78em; + padding: 0 4px; +} + +/* ------------------------------------------------------------- day view --- */ +.day-agenda { + padding: 18px 16px 24px 16px; +} + +.day-agenda .agenda-heading { + color: #f0f0f0; + font-size: 1.25em; + font-weight: 700; + margin-bottom: 6px; +} + +.day-agenda .agenda-empty { + color: #8a8a8a; + font-size: 0.95em; + padding: 6px 2px; +} + +.agenda-row { + background-color: #191919; + border-radius: 8px; + padding: 8px 12px; + min-height: 0; + margin: 0; +} + +.agenda-row:hover { + background-color: #242424; +} + +.agenda-row .agenda-time { + color: #8a8a8a; + font-size: 0.88em; + font-variant-numeric: tabular-nums; +} + +.agenda-row .agenda-title { + color: #f0f0f0; + font-size: 0.98em; + font-weight: 500; +} + +.agenda-row .agenda-location { + color: #8a8a8a; + font-size: 0.82em; +} + +/* ------------------------------------------------------------ week view --- */ +.week-grid { + background-color: #0f0f0f; +} + +.week-column { + background-color: #0f0f0f; + border-left: 1px solid alpha(#f0f0f0, 0.07); + padding: 4px 5px 10px 5px; +} + +.week-column:first-child { + border-left: none; +} + +.week-column.today { + background-color: alpha(#a39ec4, 0.08); +} + +.week-column.selected { + background-color: #1d1d1d; +} + +.week-column.selected .week-col-header { + border-bottom-color: alpha(#f0f0f0, 0.30); +} + +.week-column.selected .week-col-header .week-col-date { + color: #f0f0f0; +} + +.week-col-header:hover { + background-color: alpha(#f0f0f0, 0.04); +} + +.week-col-header { + padding: 6px 2px 8px 2px; + margin-bottom: 2px; + border-bottom: 1px solid alpha(#f0f0f0, 0.07); +} + +.week-col-header .week-col-name { + color: #8a8a8a; + font-size: 0.78em; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.week-col-header .week-col-date { + color: #f0f0f0; + font-size: 1.25em; + font-weight: 600; +} + +.week-column.today .week-col-header .week-col-date { + color: #a39ec4; + font-weight: bold; +} + +.week-column.today .week-col-header { + border-bottom-color: #a39ec4; +} + +/* ------------------------------------------------------ grid clamp -------- */ +/* The Adw.Clamp wrapping the month/week grid keeps the content centred on wide + * windows. A faint side gutter is fine; just give the grid a little breathing + * room so cells don't kiss the clamp edge. */ +.grid-clamp { + padding: 0 8px 8px 8px; +} + +/* --------------------------------------------- bottom selected agenda ----- */ +/* The always-visible bottom section listing the SELECTED day's events. Sits + * under the grid with a clear separator so it reads as its own panel. */ +.selected-agenda { + background-color: #0d0d0d; + border-top: 1px solid alpha(#f0f0f0, 0.10); +} + +.selected-agenda-scroller { + background-color: transparent; +} + +.selected-agenda-inner { + padding: 12px 12px 16px 12px; +} + +.selected-agenda .selected-heading { + color: #f0f0f0; + font-size: 1.05em; + font-weight: 700; + margin-bottom: 4px; +} + +.selected-agenda .selected-empty { + color: #8a8a8a; + font-size: 0.92em; + padding: 2px 2px; +} + +/* ============================================================ time grid === */ +/* Day / Week time-grid views: a pinned header + all-day strip above a + * vertically-scrolling hour grid. Fuji palette throughout, GNOME-clean. */ + +/* --- pinned header row (weekday + date), stays above the scroller -------- */ +.timegrid-header { + background-color: #0f0f0f; + border-bottom: 1px solid alpha(#f0f0f0, 0.10); +} + +.timegrid-corner { + background-color: #0f0f0f; +} + +.timegrid-col-header { + padding: 6px 4px 7px 4px; + border-left: 1px solid alpha(#f0f0f0, 0.06); +} + +.timegrid-col-header:hover { + background-color: alpha(#f0f0f0, 0.04); +} + +.timegrid-col-header .timegrid-col-name { + color: #8a8a8a; + font-size: 0.78em; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.timegrid-col-header .timegrid-col-date { + color: #f0f0f0; + font-size: 1.25em; + font-weight: 600; +} + +.timegrid-col-header.today .timegrid-col-name, +.timegrid-col-header.today .timegrid-col-date { + color: #a39ec4; + font-weight: bold; +} + +.timegrid-col-header.selected { + background-color: #1d1d1d; +} + +/* --- thin all-day strip --------------------------------------------------- */ +.allday-row { + background-color: #0d0d0d; + border-bottom: 1px solid alpha(#f0f0f0, 0.10); + min-height: 30px; +} + +.allday-axis { + color: #8a8a8a; + font-size: 0.7em; + text-transform: uppercase; + letter-spacing: 0.03em; + padding-right: 8px; +} + +.allday-cell { + border-left: 1px solid alpha(#f0f0f0, 0.06); + padding: 2px 4px; +} + +.allday-cell.today { + background-color: alpha(#a39ec4, 0.08); +} + +.allday-chip { + background-color: #191919; + border-left: 3px solid #8a8a8a; + border-radius: 4px; + padding: 0 6px; + min-height: 0; + margin: 1px 0; +} + +.allday-chip:hover { + background-color: #232323; +} + +.allday-chip-label { + color: #f0f0f0; + font-size: 0.8em; +} + +/* --- scrolling hour grid -------------------------------------------------- */ +.timegrid-body { + background-color: #0f0f0f; +} + +/* The left hour axis with "08:00" labels. */ +.time-axis { + background-color: #0f0f0f; + border-right: 1px solid alpha(#f0f0f0, 0.08); +} + +.time-axis .hour-label { + color: #8a8a8a; + font-size: 0.72em; + font-variant-numeric: tabular-nums; + padding: 1px 6px 0 0; +} + +/* One day's column holding the hour lines and positioned event blocks. */ +.day-column { + background-color: #0f0f0f; + border-left: 1px solid alpha(#f0f0f0, 0.06); +} + +.day-column.today { + background-color: alpha(#a39ec4, 0.05); +} + +/* Hairline separators: full opacity on the hour, fainter on the half-hour. */ +.day-column .hour-line { + background-color: alpha(#f0f0f0, 0.09); +} + +.day-column .half-line { + background-color: alpha(#f0f0f0, 0.04); +} + +/* A positioned timed-event block (rounded, calendar-colour tinted). */ +.event-block { + background-color: alpha(#a39ec4, 0.18); + border-left: 3px solid #a39ec4; + border-radius: 5px; + padding: 2px 6px; + min-height: 0; + margin: 0; +} + +.event-block:hover { + background-color: alpha(#a39ec4, 0.30); +} + +.event-block .event-block-time { + color: #f0f0f0; + font-size: 0.72em; + font-variant-numeric: tabular-nums; + opacity: 0.85; +} + +.event-block .event-block-title { + color: #f0f0f0; + font-size: 0.82em; + font-weight: 600; +} + +/* The accent "now" line on today's column. */ +.now-line { + background-color: #a39ec4; +} + +/* The translucent drag-to-create selection rectangle. */ +.drag-selection { + background-color: alpha(#a39ec4, 0.25); + border: 1px solid #a39ec4; + border-radius: 4px; +} + +/* ----------------------------------------------------- context menus --- */ +/* Right-click popovers (event block: Delete/Details; time grid / month cell: + * New event). A compact column of flat, left-aligned menu rows. */ +.context-menu { + padding: 4px; + min-width: 140px; +} + +.context-menu-item { + padding: 6px 12px; + border-radius: 6px; + color: #f0f0f0; + font-weight: 500; +} + +.context-menu-item:hover { + background-color: #242424; +} + +/* Destructive (Delete) row reads in a warm red, brighter on hover. */ +.context-menu-item.destructive { + color: #e06c75; +} + +.context-menu-item.destructive:hover { + background-color: alpha(#e06c75, 0.18); + color: #e06c75; +} diff --git a/packaging/tanin-desktop/PKGBUILD b/packaging/tanin-desktop/PKGBUILD index f796582..8dbeec3 100644 --- a/packaging/tanin-desktop/PKGBUILD +++ b/packaging/tanin-desktop/PKGBUILD @@ -3,11 +3,12 @@ # Metapackage: pulls the complete TANINUX (camel) desktop — niri + eww, managed # by the TANINUX apps. Own packages come from the [tanin] repo, everything else # from the official Arch repos. AUR-only runtime deps (eww, tiramisu, waypaper, -# cliphist) are rebuilt into [tanin] so this never hard-depends on the AUR. +# cliphist, librewolf-bin, arch-update, timeshift-autosnap, xdg-terminal-exec, +# paru) are rebuilt into [tanin] so this never hard-depends on the AUR. # Calamares just installs this package at the end. pkgname=tanin-desktop pkgver=0.2.0 -pkgrel=3 +pkgrel=6 pkgdesc="TANINUX desktop — metapackage pulling the full niri/eww environment" arch=('any') url="https://taninux.kgva.ch" @@ -20,12 +21,20 @@ depends=( 'tanin-libadwaita' # Fuji recolour for GTK4/libadwaita + GTK3 'tanin-eww' # eww bar/dock/panels + niri config -> /usr/share/tanin/skel 'tanin-setup' # seeds ~/.config + session portal on first run + 'tanin-calendar' # GTK4/libadwaita calendar app (reads EDS) # --- compositor + login --- 'niri' # Wayland compositor (niri-only) 'greetd' # display manager (greeter = tanin-greet) 'cage' # kiosk compositor hosting the greeter + # --- lock / idle (ext-session-lock + ext-idle-notify, niri-native) --- + 'gtklock' # screen lock (Fuji greeter look): Super+Alt+L, eww Control-Center, core/lock.py + 'gtklock-userinfo-module' # avatar + username on the lock (matches the greeter) + 'gtklock-powerbar-module' # power buttons on the lock + 'swaylock' # robust fallback locker + 'swayidle' # idle timeouts (screen-off/suspend) — core/power.py + # --- session portals --- 'xdg-desktop-portal' 'xdg-desktop-portal-gtk' # Settings/Appearance portal (dark mode!) @@ -35,31 +44,50 @@ depends=( 'eww' # bar/dock/panels/spotlight 'tiramisu-git' # headless notification daemon -> eww UI 'waypaper' # wallpaper (niri autostart: waypaper --restore) + 'awww' # 2nd wallpaper surface -> blurred niri Overview backdrop (place-within-backdrop) + 'imagemagick' # generates the blurred backdrop copy (overview-backdrop.sh) 'cliphist' # clipboard history for Spotlight + # --- browser (rebuilt into [tanin]; keep current with LibreWolf releases) --- + 'librewolf-bin' # default browser (privacy-focused Firefox fork) + # --- terminal + CLI used by the bar/scripts --- 'foot' 'jq' 'wl-clipboard' 'xdg-utils' 'libqalculate' # libqalculate -> qalc 'grim' 'slurp' # screenshots + # --- online accounts + calendar ------------------------------------------- + # GOA = central account hub (Google/Microsoft/Nextcloud/CalDAV, like Apple's + # Internet Accounts); it feeds Evolution Data Server, which the eww bar panel + # (scripts/calendar.sh -> eds-events.py) and tanin-calendar read. + 'gnome-online-accounts' # account hub (OAuth: Google/MS/Nextcloud) -> TANINUX "Online Accounts" + 'gnome-online-accounts-gtk' # the add/manage UI launched by that page + 'evolution-data-server' # calendar/contact backend GOA feeds (primary source) + 'evolution-ews' # Microsoft 365/Exchange backend (no CalDAV) + 'khal' # fallback calendar source when EDS is unavailable + 'vdirsyncer' # optional CalDAV/Nextcloud <-> local vdir sync (khal path) + # --- connectivity / audio / hardware --- 'networkmanager' 'bluez' 'bluez-utils' 'pipewire' 'pipewire-pulse' 'wireplumber' # wpctl / pactl 'brightnessctl' 'playerctl' + 'wlsunset' # display night-light settings + 'cups' # printers settings page - # --- packages / updates --- + # --- packages / updates ([tanin] AUR rebuilds) --- 'flatpak' 'pacman-contrib' # checkupdates / paccache + 'arch-update' # update notifier + pre/post tasks + 'timeshift' 'timeshift-autosnap' # snapshot before each pacman upgrade (rollback safety) + 'xdg-terminal-exec' # launch Terminal=true apps in the user's terminal # --- theming / fonts --- - 'adw-gtk-theme' # GTK3 base theme tanin-libadwaita recolours - 'adwaita-icon-theme' 'papirus-icon-theme' + 'taninux-gtk3-theme' # eigenständiges GTK3 Apple-Optik Theme (kein adw-gtk3) + 'adwaita-icon-theme' # base icon theme + 'tanin-icons' # Fuji-accent folder overlay (Tanin-), set by the accent picker 'inter-font' 'ttf-iosevka-nerd' 'ttc-iosevka-aile' 'ttf-nerd-fonts-symbols' # Inter UI · Iosevka Nerd/Aile · Symbols Nerd ) optdepends=( 'paru: AUR support in the Software Hub (opt-in)' - 'wlsunset: night light' - 'cups: printer management' - 'timeshift: system snapshots/backups' 'waybar: fallback bar if eww is missing (launch.sh falls back to it)' ) diff --git a/packaging/tanin-icons/pkg/tanin-icons/.BUILDINFO b/packaging/tanin-icons/pkg/tanin-icons/.BUILDINFO new file mode 100644 index 0000000..257e5f7 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/.BUILDINFO @@ -0,0 +1,1237 @@ +format = 2 +pkgname = tanin-icons +pkgbase = tanin-icons +pkgver = 0.1.0-1 +pkgarch = any +pkgbuild_sha256sum = 7bcf2ec837fb236cec86dc2650c59bdd1223a67222e4c510cb45b8cf2104c083 +packager = Unknown Packager +builddate = 1782150443 +builddir = /home/karim/projects/taninux/packaging/tanin-icons +startdir = /home/karim/projects/taninux/packaging/tanin-icons +buildtool = makepkg +buildtoolver = 7.1.0 +buildenv = !distcc +buildenv = color +buildenv = !ccache +buildenv = check +buildenv = !sign +options = strip +options = docs +options = !libtool +options = !staticlibs +options = emptydirs +options = zipman +options = purge +options = debug +options = lto +installed = 7zip-26.01-1-x86_64 +installed = aalib-1.4rc5-19-x86_64 +installed = abseil-cpp-20260526.0-2-x86_64 +installed = accountsservice-26.13.3-1-x86_64 +installed = acl-2.3.2-2-x86_64 +installed = ada-3.4.4-1-x86_64 +installed = adw-gtk-theme-6.5-1-any +installed = adwaita-cursors-50.0-1-any +installed = adwaita-fonts-50.0-1-any +installed = adwaita-icon-theme-50.0-1-any +installed = adwaita-icon-theme-legacy-46.2-3-any +installed = alembic-1.8.11-2-x86_64 +installed = alsa-card-profiles-1:1.6.7-1-x86_64 +installed = alsa-lib-1.2.16.1-1-x86_64 +installed = alsa-plugins-1:1.2.12-5-x86_64 +installed = alsa-topology-conf-1.2.5.1-4-any +installed = alsa-ucm-conf-1.2.16.1-1-any +installed = alsa-utils-1.2.16-1-x86_64 +installed = amd-ucode-20260519-1-any +installed = ananicy-cpp-1.2.0-1-x86_64 +installed = antimicrox-3.6.1-1-x86_64 +installed = aom-3.14.1-1-x86_64 +installed = appstream-1.1.3-1-x86_64 +installed = arch-install-scripts-31-1-any +installed = arch-update-3.20.2-1-any +installed = archiso-88-1-any +installed = archlinux-contrib-20260611-1-any +installed = archlinux-keyring-20260612-1-any +installed = argon2-20190702-6-x86_64 +installed = at-spi2-core-2.60.4-1-x86_64 +installed = atkmm-2.28.5-2-x86_64 +installed = attr-2.5.2-2-x86_64 +installed = audit-4.1.4-2-x86_64 +installed = autoconf-2.73-1-any +installed = automake-1.18.1-1-any +installed = avahi-1:0.9rc4-1-x86_64 +installed = awesome-terminal-fonts-1.1.0-5-any +installed = ayatana-ido-0.10.4-1-x86_64 +installed = base-3-3-any +installed = base-devel-1-2-any +installed = bash-5.3.15-1-x86_64 +installed = bats-1.13.0-1-any +installed = binutils-2.46+r70+g155188ea10a7-1-x86_64 +installed = bison-3.8.2-8-x86_64 +installed = blas-3.12.1-2-x86_64 +installed = blender-17:5.1.2-1-x86_64 +installed = blosc-1.21.6-2-x86_64 +installed = bluez-5.86-6-x86_64 +installed = bluez-libs-5.86-6-x86_64 +installed = bluez-utils-5.86-6-x86_64 +installed = boost-libs-1.91.0-1-x86_64 +installed = botan-3.12.0-1-x86_64 +installed = brightnessctl-0.5.1-3-x86_64 +installed = brotli-1.2.0-1-x86_64 +installed = btrfs-progs-7.0-1-x86_64 +installed = bubblewrap-0.11.2-1-x86_64 +installed = bzip2-1.0.8-6-x86_64 +installed = c-ares-1.34.6-1-x86_64 +installed = ca-certificates-20240618-1-any +installed = ca-certificates-mozilla-3.125-1-x86_64 +installed = ca-certificates-utils-20240618-1-any +installed = cabextract-1.11-2-x86_64 +installed = cage-0.3.0-1-x86_64 +installed = cairo-1.18.4-1-x86_64 +installed = cairomm-1.14.6-1-x86_64 +installed = cairomm-1.16-1.18.1-1-x86_64 +installed = calamares-3.4.2-2-x86_64 +installed = capstone-5.0.9-1-x86_64 +installed = cblas-3.12.1-2-x86_64 +installed = ccache-4.13.6-1-x86_64 +installed = cdparanoia-10.2-9-x86_64 +installed = ceres-solver-2.2.0-5-x86_64 +installed = chatbox-bin-1.21.0-1-x86_64 +installed = cifs-utils-7.5-1-x86_64 +installed = clang-22.1.6-1-x86_64 +installed = claude-code-2.1.185-1-x86_64 +installed = cliphist-1:0.7.0-2-x86_64 +installed = clucene-2.3.3.4-17-x86_64 +installed = cmake-4.3.4-1-x86_64 +installed = cmark-0.31.2-1-x86_64 +installed = code-1.124.2-1-x86_64 +installed = compiler-rt-22.1.6-1-x86_64 +installed = composefs-1.0.8-1-x86_64 +installed = coreutils-9.11-1-x86_64 +installed = cpio-2.15-3-x86_64 +installed = cppdap-1.58.0-3-x86_64 +installed = cracklib-2.10.3-1-x86_64 +installed = cronie-1.7.2-2-x86_64 +installed = cryptsetup-2.8.6-1-x86_64 +installed = cups-2:2.4.19-1-x86_64 +installed = cups-filters-2.0.1-2-x86_64 +installed = curl-8.20.0-7-x86_64 +installed = dav1d-1.5.3-1-x86_64 +installed = db5.3-5.3.28-7-x86_64 +installed = dbus-1.16.2-1-x86_64 +installed = dbus-broker-37-3-x86_64 +installed = dbus-broker-units-37-3-x86_64 +installed = dbus-glib-0.114-1-x86_64 +installed = dbus-units-37-3-x86_64 +installed = dconf-0.49.0-1-x86_64 +installed = debugedit-5.3-1-x86_64 +installed = default-cursors-3-1-any +installed = desktop-file-utils-0.28-1-x86_64 +installed = device-mapper-2.03.41-1-x86_64 +installed = dialog-1:1.3_20260107-1-x86_64 +installed = diffutils-3.12-2-x86_64 +installed = djvulibre-3.5.30.1-1-x86_64 +installed = dmidecode-3.7-1-x86_64 +installed = dnsmasq-2.93-1-x86_64 +installed = docbook-xml-4.5-11-any +installed = docbook-xsl-1.79.2-9-any +installed = dos2unix-7.5.6-1-x86_64 +installed = dosfstools-4.2-5-x86_64 +installed = double-conversion-3.4.0-1-x86_64 +installed = draco-1.5.7-2-x86_64 +installed = dtc-1:1.7.2-1-x86_64 +installed = duktape-2.7.0-7-x86_64 +installed = e2fsprogs-1.47.4-1-x86_64 +installed = editorconfig-core-c-0.12.11-1-x86_64 +installed = edk2-ovmf-202605-1-any +installed = efibootmgr-18-4-x86_64 +installed = efivar-39-2-x86_64 +installed = eigen-5.0.1-2-x86_64 +installed = electron39-39.8.10-1-x86_64 +installed = electron42-42.3.0-1-x86_64 +installed = elfutils-0.195-1-x86_64 +installed = embree-4.4.1-1-x86_64 +installed = enchant-2.8.15-2-x86_64 +installed = epson-inkjet-printer-escpr2-1.2.39-1-x86_64 +installed = erofs-utils-1.9.1-1-x86_64 +installed = evolution-3.60.2-1-x86_64 +installed = evolution-data-server-3.60.2-2-x86_64 +installed = evolution-ews-3.60.2-1-x86_64 +installed = eww-git-0.6.0.r89.g865cf63-1-x86_64 +installed = exempi-2.6.6-3-x86_64 +installed = exiv2-0.28.8-2-x86_64 +installed = expat-2.8.1-1-x86_64 +installed = extra-cmake-modules-6.27.0-1-any +installed = fakeroot-1:1.37.2-2-x86_64 +installed = fcft-3.3.3-1-x86_64 +installed = fd-10.4.2-2-x86_64 +installed = ffmpeg-2:8.1.2-6-x86_64 +installed = fftw-3.3.11-1-x86_64 +installed = file-5.48-1-x86_64 +installed = filesystem-2025.10.12-1-any +installed = findutils-4.10.0-3-x86_64 +installed = flac-1.5.0-1-x86_64 +installed = flatpak-1:1.18.0-1-x86_64 +installed = flex-2.6.4-6-x86_64 +installed = fmt-12.2.0-1-x86_64 +installed = font-manager-0.9.4-3-x86_64 +installed = fontconfig-2:2.18.1-1-x86_64 +installed = foot-1.27.0-1-x86_64 +installed = freeglut-3.8.0-1-x86_64 +installed = freetype2-2.14.3-1-x86_64 +installed = fribidi-1.0.16-2-x86_64 +installed = fuse-common-3.18.2-1-x86_64 +installed = fuse2-2.9.9-5-x86_64 +installed = fuse3-3.18.2-1-x86_64 +installed = gamemode-1.8.2-2-x86_64 +installed = gawk-5.4.0-1-x86_64 +installed = gc-8.2.12-1-x86_64 +installed = gcc-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = gcr-3.41.2-2-x86_64 +installed = gcr-4-4.4.0.1-1-x86_64 +installed = gd-2.3.3-9-x86_64 +installed = gdb-17.2-1-x86_64 +installed = gdb-common-17.2-1-x86_64 +installed = gdbm-1.26-2-x86_64 +installed = gdk-pixbuf2-2.44.6-2-x86_64 +installed = gedit-50.0-3-x86_64 +installed = geoclue-2.8.1-1-x86_64 +installed = geocode-glib-3.26.4-6-x86_64 +installed = gettext-1.0-2-x86_64 +installed = gexiv2-0.16.0-2-x86_64 +installed = gflags-2.2.2-6-x86_64 +installed = gfxstream-0.1.2-2-x86_64 +installed = ghostscript-10.07.1-1-x86_64 +installed = giflib-6.1.3-1-x86_64 +installed = git-2.54.0-1-x86_64 +installed = glew-2.3.1-1-x86_64 +installed = glfw-1:3.4-1-x86_64 +installed = glib-networking-1:2.80.1-1-x86_64 +installed = glib2-2.88.1-1-x86_64 +installed = glib2-docs-2.88.1-1-x86_64 +installed = glibc-2.43+r22+g8362e8ce10b2-2-x86_64 +installed = glibmm-2.66.8-2-x86_64 +installed = glibmm-2.68-2.88.0-2-x86_64 +installed = glm-1.0.3-1-x86_64 +installed = glslang-1:1.4.350.1-1-x86_64 +installed = glu-9.0.3-3-x86_64 +installed = glycin-2.1.1-1-x86_64 +installed = glycin-gtk4-2.1.1-1-x86_64 +installed = gmp-6.3.0-3-x86_64 +installed = gnome-autoar-0.4.5-1-x86_64 +installed = gnome-clocks-50.0-2-x86_64 +installed = gnome-desktop-1:44.5-1-x86_64 +installed = gnome-desktop-4-1:44.5-1-x86_64 +installed = gnome-desktop-common-1:44.5-1-x86_64 +installed = gnome-disk-utility-46.1-2-x86_64 +installed = gnome-keyring-1:50.0-1-x86_64 +installed = gnome-online-accounts-3.58.1-1-x86_64 +installed = gnome-online-accounts-gtk-3.50.10-1-x86_64 +installed = gnome-text-editor-50.1-1-x86_64 +installed = gnu-free-fonts-20120503-9-any +installed = gnulib-l10n-20241231-1-any +installed = gnupg-2.4.9-1-x86_64 +installed = gnutls-3.8.13-2-x86_64 +installed = go-2:1.26.4-1-x86_64 +installed = gobject-introspection-runtime-1.86.0-2-x86_64 +installed = google-glog-0.7.1-2-x86_64 +installed = gperftools-2.18.1-1-x86_64 +installed = gpgme-2.1.0-1-x86_64 +installed = gpgmepp-2.1.0-1-x86_64 +installed = gpm-1.20.7.r38.ge82d1a6-6-x86_64 +installed = gpsd-3.27.5-1-x86_64 +installed = graphene-1.10.8-2-x86_64 +installed = graphite-1:1.3.15-1-x86_64 +installed = graphviz-14.1.5-1-x86_64 +installed = greetd-0.10.3-2-x86_64 +installed = greetd-agreety-0.10.3-2-x86_64 +installed = greetd-regreet-0.4.0-1-x86_64 +installed = greetd-tuigreet-0.9.1-2-x86_64 +installed = grep-3.12-2-x86_64 +installed = grim-1.5.0-2-x86_64 +installed = groff-1.24.1-1-x86_64 +installed = grub-2:2.14-1-x86_64 +installed = gsettings-desktop-schemas-50.1-1-any +installed = gsettings-system-schemas-50.1-1-any +installed = gsfonts-20200910-6-any +installed = gsm-1.0.24-1-x86_64 +installed = gspell-1.14.3-1-x86_64 +installed = gssdp-1.6.5-1-x86_64 +installed = gst-plugins-bad-libs-1.28.4-2-x86_64 +installed = gst-plugins-base-1.28.4-2-x86_64 +installed = gst-plugins-base-libs-1.28.4-2-x86_64 +installed = gst-plugins-good-1.28.4-2-x86_64 +installed = gstreamer-1.28.4-2-x86_64 +installed = gtest-1.17.0-2-x86_64 +installed = gtk-doc-1.36.1-1-any +installed = gtk-layer-shell-0.10.1-1-x86_64 +installed = gtk-session-lock-0.2.0-2-x86_64 +installed = gtk-update-icon-cache-1:4.22.4-1-x86_64 +installed = gtk-vnc-1.5.0-1-x86_64 +installed = gtk3-1:3.24.52-1-x86_64 +installed = gtk4-1:4.22.4-1-x86_64 +installed = gtklock-4.0.0-1-x86_64 +installed = gtklock-powerbar-module-4.0.0-1-x86_64 +installed = gtklock-userinfo-module-4.0.1-2-x86_64 +installed = gtkmm-4.0-4.22.0-2-x86_64 +installed = gtkmm3-3.24.11-1-x86_64 +installed = gtksourceview4-4.8.4-2-x86_64 +installed = gtksourceview5-5.20.0-1-x86_64 +installed = gts-0.7.6.121130-5-x86_64 +installed = guile-3.0.11-1-x86_64 +installed = gumbo-parser-0.13.2-1-x86_64 +installed = gupnp-1:1.6.10-1-x86_64 +installed = gupnp-dlna-0.12.0-5-x86_64 +installed = gupnp-igd-1.6.0-2-x86_64 +installed = gvfs-1.60.0-2-x86_64 +installed = gvfs-smb-1.60.0-2-x86_64 +installed = gweather-locations-2026.2-1-x86_64 +installed = gzip-1.14-2-x86_64 +installed = harfbuzz-14.2.1-1-x86_64 +installed = harfbuzz-icu-14.2.1-1-x86_64 +installed = hdf5-2.1.1-1-x86_64 +installed = heroic-games-launcher-bin-2.22.0-1-x86_64 +installed = hicolor-icon-theme-0.18-1-any +installed = hidapi-0.15.0-1-x86_64 +installed = highway-1.4.0-1-x86_64 +installed = hiredis-1.3.0-1-x86_64 +installed = htmlq-0.4.0-3-x86_64 +installed = hugo-0.163.3-1-x86_64 +installed = hunspell-1.7.3-1-x86_64 +installed = hwdata-0.408-1-any +installed = hwloc-2.14.0-1-x86_64 +installed = hyphen-2.8.9-1-x86_64 +installed = hyprlang-0.6.8-4-x86_64 +installed = hyprshot-1.3.0-4-any +installed = hyprutils-0.13.1-1-x86_64 +installed = hyprwayland-scanner-0.4.6-1-x86_64 +installed = iana-etc-20260530-1-any +installed = icu-78.3-1-x86_64 +installed = ijs-0.35-7-x86_64 +installed = imagemagick-7.1.2.25-1-x86_64 +installed = imath-3.2.2-6-x86_64 +installed = imlib2-1.12.6-1-x86_64 +installed = iniparser-4.2.6-2-x86_64 +installed = intel-oneapi-common-2026.0.0_235-1-any +installed = intel-oneapi-compiler-dpcpp-cpp-runtime-libs-2026.0.0_947-2-x86_64 +installed = intel-oneapi-compiler-shared-runtime-libs-2026.0.0_947-1-x86_64 +installed = intel-oneapi-openmp-2026.0.0_947-1-x86_64 +installed = intel-oneapi-tbb-2023.0.0_724-1-x86_64 +installed = intel-oneapi-tcm-1.5.0_489-1-x86_64 +installed = intel-oneapi-umf-1.1.0_340-2-x86_64 +installed = inter-font-4.1-1-any +installed = iproute2-7.1.0-1-x86_64 +installed = iptables-1:1.8.13-1-x86_64 +installed = iputils-20250605-1-x86_64 +installed = iso-codes-4.20.1-1-any +installed = jansson-2.15.0-1-x86_64 +installed = jbig2dec-0.20-2-x86_64 +installed = jbigkit-2.1-8-x86_64 +installed = jemalloc-1:5.3.1-2-x86_64 +installed = jq-1.8.2-1-x86_64 +installed = json-c-0.18-2-x86_64 +installed = json-glib-1.10.8-1-x86_64 +installed = jsoncpp-1.9.6-3-x86_64 +installed = karchive-6.27.0-1-x86_64 +installed = kbd-2.10.0-1-x86_64 +installed = kcoreaddons-6.27.0-1-x86_64 +installed = kddockwidgets-2.4.0-3-x86_64 +installed = kdsingleapplication-1.2.1-1-x86_64 +installed = keyutils-1.6.3-4-x86_64 +installed = kguiaddons-6.27.0-1-x86_64 +installed = khal-0.14.0-1-any +installed = ki18n-6.27.0-1-x86_64 +installed = kitty-0.47.1-1-x86_64 +installed = kitty-shell-integration-0.47.1-1-x86_64 +installed = kitty-terminfo-0.47.1-1-x86_64 +installed = kmod-34.2-1-x86_64 +installed = kpmcore-26.04.2-1-x86_64 +installed = krb5-1.22.2-1-x86_64 +installed = kwidgetsaddons-6.27.0-1-x86_64 +installed = l-smash-2.14.5-4-x86_64 +installed = lact-0.9.0-1-x86_64 +installed = lame-3.101.r6531-1-x86_64 +installed = lapack-3.12.1-2-x86_64 +installed = lcms2-2.19.1-1-x86_64 +installed = ldb-2:4.24.3-1-x86_64 +installed = leancrypto-1.7.2-1-x86_64 +installed = level-zero-loader-1.28.6-1-x86_64 +installed = lib32-alsa-lib-1.2.16.1-1-x86_64 +installed = lib32-alsa-plugins-1.2.12-1-x86_64 +installed = lib32-brotli-1.1.0-1-x86_64 +installed = lib32-bzip2-1.0.8-4-x86_64 +installed = lib32-curl-8.20.0-7-x86_64 +installed = lib32-dbus-1.16.2-1-x86_64 +installed = lib32-e2fsprogs-1.47.4-1-x86_64 +installed = lib32-expat-2.8.1-1-x86_64 +installed = lib32-fontconfig-2:2.18.1-1-x86_64 +installed = lib32-freetype2-2.14.3-1-x86_64 +installed = lib32-gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = lib32-glib2-2.88.1-1-x86_64 +installed = lib32-glibc-2.43+r22+g8362e8ce10b2-2-x86_64 +installed = lib32-gmp-6.3.0-2-x86_64 +installed = lib32-gnutls-3.8.13-3-x86_64 +installed = lib32-icu-78.3-1-x86_64 +installed = lib32-json-c-0.18-2-x86_64 +installed = lib32-keyutils-1.6.3-4-x86_64 +installed = lib32-krb5-1.22.2-1-x86_64 +installed = lib32-libdisplay-info-0.3.0-1-x86_64 +installed = lib32-libdrm-2.4.134-1-x86_64 +installed = lib32-libelf-0.195-1-x86_64 +installed = lib32-libffi-3.5.2-1-x86_64 +installed = lib32-libgcrypt-1.12.2-1-x86_64 +installed = lib32-libglvnd-1.7.0-1-x86_64 +installed = lib32-libgpg-error-1.61-1-x86_64 +installed = lib32-libidn2-2.3.8-1-x86_64 +installed = lib32-libldap-2.6.13-1-x86_64 +installed = lib32-libnghttp2-1.69.0-1-x86_64 +installed = lib32-libnghttp3-1.16.0-1-x86_64 +installed = lib32-libngtcp2-1.23.0-1-x86_64 +installed = lib32-libnm-1.56.1-1-x86_64 +installed = lib32-libpciaccess-0.19-1-x86_64 +installed = lib32-libpipewire-1:1.6.7-1-x86_64 +installed = lib32-libpng-1.6.58-1-x86_64 +installed = lib32-libpsl-0.21.5-1-x86_64 +installed = lib32-libssh2-1.11.1-1-x86_64 +installed = lib32-libtasn1-4.21.0-1-x86_64 +installed = lib32-libunistring-1.4.2-1-x86_64 +installed = lib32-libva-2.22.0-1-x86_64 +installed = lib32-libx11-1.8.13-1-x86_64 +installed = lib32-libxau-1.0.12-1-x86_64 +installed = lib32-libxcb-1.17.0-1-x86_64 +installed = lib32-libxcrypt-4.5.2-1-x86_64 +installed = lib32-libxcrypt-compat-4.5.2-1-x86_64 +installed = lib32-libxdmcp-1.1.5-1-x86_64 +installed = lib32-libxext-1.3.7-1-x86_64 +installed = lib32-libxfixes-6.0.1-2-x86_64 +installed = lib32-libxinerama-1.1.6-1-x86_64 +installed = lib32-libxml2-2.15.3-1-x86_64 +installed = lib32-libxshmfence-1.3.3-1-x86_64 +installed = lib32-libxss-1.2.5-1-x86_64 +installed = lib32-libxxf86vm-1.1.5-2-x86_64 +installed = lib32-llvm-libs-1:22.1.6-1-x86_64 +installed = lib32-lm_sensors-1:3.6.2-2-x86_64 +installed = lib32-mesa-1:26.1.3-2-x86_64 +installed = lib32-ncurses-6.6-2-x86_64 +installed = lib32-nettle-4.0-2-x86_64 +installed = lib32-nspr-4.39-1-x86_64 +installed = lib32-nss-3.125-1-x86_64 +installed = lib32-openssl-1:3.6.3-1-x86_64 +installed = lib32-p11-kit-0.26.2-1-x86_64 +installed = lib32-pcre2-10.47-1-x86_64 +installed = lib32-pipewire-1:1.6.7-1-x86_64 +installed = lib32-spirv-tools-1:1.4.350.1-1-x86_64 +installed = lib32-sqlite-3.53.2-1-x86_64 +installed = lib32-systemd-260.2-1-x86_64 +installed = lib32-util-linux-2.42.2-1-x86_64 +installed = lib32-vulkan-icd-loader-1.4.350.1-1-x86_64 +installed = lib32-vulkan-mesa-implicit-layers-1:26.1.3-2-x86_64 +installed = lib32-vulkan-radeon-1:26.1.3-2-x86_64 +installed = lib32-wayland-1.25.0-1-x86_64 +installed = lib32-xcb-util-keysyms-0.4.1-2-x86_64 +installed = lib32-xz-5.8.3-1-x86_64 +installed = lib32-zlib-1.3.2-1-x86_64 +installed = lib32-zstd-1.5.7-2-x86_64 +installed = libabw-0.1.4-1-x86_64 +installed = libadwaita-1:1.9.1-1-x86_64 +installed = libaec-1.1.7-1-x86_64 +installed = libaemu-0.1.2-5-x86_64 +installed = libaio-0.3.113-4-x86_64 +installed = libappindicator-12.10.1-1-x86_64 +installed = libarchive-3.8.7-1-x86_64 +installed = libasan-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libass-0.17.4-2-x86_64 +installed = libassuan-3.0.0-1-x86_64 +installed = libasyncns-1:0.8+r3+g68cd5af-3-x86_64 +installed = libatasmart-0.19-8-x86_64 +installed = libatomic-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libatomic_ops-7.8.2-2-x86_64 +installed = libavc1394-0.5.4-7-x86_64 +installed = libavif-1.4.2-1-x86_64 +installed = libayatana-appindicator-0.6.0-1-x86_64 +installed = libayatana-indicator-0.9.5-1-x86_64 +installed = libb2-0.98.1-3-x86_64 +installed = libb64-1.2.1-5-x86_64 +installed = libbacktrace-1.0.git1-1-x86_64 +installed = libblake3-1.8.4-1-x86_64 +installed = libblockdev-3.5.0-2-x86_64 +installed = libblockdev-crypto-3.5.0-2-x86_64 +installed = libblockdev-fs-3.5.0-2-x86_64 +installed = libblockdev-loop-3.5.0-2-x86_64 +installed = libblockdev-mdraid-3.5.0-2-x86_64 +installed = libblockdev-nvme-3.5.0-2-x86_64 +installed = libblockdev-part-3.5.0-2-x86_64 +installed = libblockdev-smart-3.5.0-2-x86_64 +installed = libblockdev-swap-3.5.0-2-x86_64 +installed = libbluray-1.4.1-1-x86_64 +installed = libbpf-1.7.0-1-x86_64 +installed = libbs2b-3.1.0-10-x86_64 +installed = libbsd-0.12.2-2-x86_64 +installed = libburn-1.5.8-1-x86_64 +installed = libbytesize-2.12-3-x86_64 +installed = libcaca-0.99.beta20-7-x86_64 +installed = libcacard-2.8.1-1-x86_64 +installed = libcanberra-1:0.30+r2+gc0620e4-6-x86_64 +installed = libcap-2.78-1-x86_64 +installed = libcap-ng-0.9.3-1-x86_64 +installed = libcbor-0.14.0-1-x86_64 +installed = libcdio-2.3.0-1-x86_64 +installed = libcdio-paranoia-10.2+2.0.2-2-x86_64 +installed = libcdr-0.1.9-1-x86_64 +installed = libcloudproviders-0.4.0-1-x86_64 +installed = libcmis-0.6.3-1-x86_64 +installed = libcolord-1.4.8-1-x86_64 +installed = libcue-2.3.0-2-x86_64 +installed = libcups-2:2.4.19-1-x86_64 +installed = libcupsfilters-2.1.1-4-x86_64 +installed = libdaemon-0.14-6-x86_64 +installed = libdatrie-0.2.14-1-x86_64 +installed = libdbusmenu-glib-18.10.20180917-1-x86_64 +installed = libdbusmenu-gtk3-18.10.20180917-1-x86_64 +installed = libde265-1.1.1-1-x86_64 +installed = libdecor-0.2.5-1-x86_64 +installed = libdeflate-1.25-1-x86_64 +installed = libdisplay-info-0.3.0-1-x86_64 +installed = libdovi-3.3.2-1-x86_64 +installed = libdrm-2.4.134-1-x86_64 +installed = libdv-1.0.0-12-x86_64 +installed = libdvdnav-7.0.0-1-x86_64 +installed = libdvdread-7.0.1-1-x86_64 +installed = libe-book-0.1.3-20-x86_64 +installed = libebur128-1.2.6-2-x86_64 +installed = libedit-20260512_3.1-1-x86_64 +installed = libelf-0.195-1-x86_64 +installed = libepoxy-1.5.10-3-x86_64 +installed = libepubgen-0.1.1-6-x86_64 +installed = libetonyek-0.1.13-2-x86_64 +installed = libevdev-1.13.6-1-x86_64 +installed = libevent-2.1.12-5-x86_64 +installed = libexif-0.6.26-1-x86_64 +installed = libexttextcat-3.4.8-1-x86_64 +installed = libfdk-aac-2.0.3-2-x86_64 +installed = libffi-3.5.2-1-x86_64 +installed = libfreeaptx-0.2.2-1-x86_64 +installed = libfreehand-0.1.3-1-x86_64 +installed = libfyaml-0.9.6-2-x86_64 +installed = libgcc-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libgcrypt-1.12.2-1-x86_64 +installed = libgedit-amtk-5.10.0-1-x86_64 +installed = libgedit-gfls-1:0.4.1-1-x86_64 +installed = libgedit-gtksourceview-299.7.0-1-x86_64 +installed = libgedit-tepl-6.14.0-2-x86_64 +installed = libgee-0.20.8-1-x86_64 +installed = libgfortran-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libgirepository-1.86.0-2-x86_64 +installed = libgit2-1:1.9.4-1-x86_64 +installed = libglvnd-1.7.0-3-x86_64 +installed = libgnomekbd-1:3.28.1-2-x86_64 +installed = libgoa-3.58.1-1-x86_64 +installed = libgomp-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libgpg-error-1.61-1-x86_64 +installed = libgsf-1.14.58-1-x86_64 +installed = libgudev-238-3-x86_64 +installed = libgweather-4-4.6.0-1-x86_64 +installed = libgxps-0.3.2-5-x86_64 +installed = libhandy-1.8.3-2-x86_64 +installed = libharu-2.4.6-1-x86_64 +installed = libheif-1.23.0-2-x86_64 +installed = libical-4.0.3-2-x86_64 +installed = libice-1.1.2-1-x86_64 +installed = libidn-1.44-1-x86_64 +installed = libidn2-2.3.8-1-x86_64 +installed = libiec61883-1.2.0-9-x86_64 +installed = libigl-2.6.0-1-any +installed = libimagequant-4.4.1-2-x86_64 +installed = libimobiledevice-1.4.0-2-x86_64 +installed = libimobiledevice-glue-1.3.2-1-x86_64 +installed = libinih-62-2-x86_64 +installed = libinput-1.31.3-1-x86_64 +installed = libiptcdata-1.0.5-5-x86_64 +installed = libisl-0.27-1-x86_64 +installed = libisoburn-1.5.8.2-1-x86_64 +installed = libisofs-1.5.8.2-1-x86_64 +installed = libixion-0.20.0-7-x86_64 +installed = libjpeg-turbo-3.1.4.1-1-x86_64 +installed = libjxl-0.11.2-2-x86_64 +installed = libksba-1.8.0-1-x86_64 +installed = liblangtag-0.6.8-1-x86_64 +installed = liblc3-1.1.3-2-x86_64 +installed = libldac-2.0.2.3-3-x86_64 +installed = libldap-2.6.13-1-x86_64 +installed = libliftoff-0.5.0-1-x86_64 +installed = liblqr-0.4.3-1-x86_64 +installed = liblsan-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libmakepkg-dropins-20-1-any +installed = libmalcontent-0.14.0-4-x86_64 +installed = libmanette-0.2.13-2-x86_64 +installed = libmd-1.2.0-1-x86_64 +installed = libmm-glib-1.24.2-1-x86_64 +installed = libmnl-1.0.5-2-x86_64 +installed = libmodplug-0.8.9.0-7-x86_64 +installed = libmpc-1.4.1-1-x86_64 +installed = libmpdclient-2.23-1-x86_64 +installed = libmspack-1:1.11-2-x86_64 +installed = libmspub-0.1.5-1-x86_64 +installed = libmwaw-0.3.22-4-x86_64 +installed = libmysofa-1.3.4-1-x86_64 +installed = libnatpmp-20230423-3-x86_64 +installed = libnautilus-extension-50.2.2-1-x86_64 +installed = libnbd-1.24.2-2-x86_64 +installed = libndp-1.9-1-x86_64 +installed = libnetfilter_conntrack-1.1.1-1-x86_64 +installed = libnewt-0.52.25-2-x86_64 +installed = libnfnetlink-1.0.2-2-x86_64 +installed = libnfs-6.0.2-5-x86_64 +installed = libnftnl-1.3.1-1-x86_64 +installed = libnghttp2-1.69.0-1-x86_64 +installed = libnghttp3-1.16.0-1-x86_64 +installed = libngtcp2-1.23.0-1-x86_64 +installed = libnice-0.1.23-1-x86_64 +installed = libnl-3.12.0-1-x86_64 +installed = libnm-1.56.1-1-x86_64 +installed = libnma-1.10.6-3-x86_64 +installed = libnma-common-1.10.6-3-x86_64 +installed = libnotify-0.8.8-1-x86_64 +installed = libnsl-2.0.1-2-x86_64 +installed = libnumbertext-1.0.11-3-x86_64 +installed = libnvme-1.16.2-1-x86_64 +installed = libobjc-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libodfgen-0.1.8-5-x86_64 +installed = libogg-1.3.6-1-x86_64 +installed = libopenmpt-0.8.7-1-x86_64 +installed = liborcus-0.21.0-6-x86_64 +installed = libosinfo-1.12.0-3-x86_64 +installed = libp11-0.4.18-1-x86_64 +installed = libp11-kit-0.26.2-1-x86_64 +installed = libpagemaker-0.0.4-5-x86_64 +installed = libpaper-2.2.8-1-x86_64 +installed = libpcap-1.10.6-1-x86_64 +installed = libpciaccess-0.19-1-x86_64 +installed = libpeas-1.38.1-1-x86_64 +installed = libpgm-5.3.128-4-x86_64 +installed = libphonenumber-1:9.0.32-2-x86_64 +installed = libpipewire-1:1.6.7-1-x86_64 +installed = libplacebo-7.360.1-3-x86_64 +installed = libplist-2.7.0-3-x86_64 +installed = libpng-1.6.58-1-x86_64 +installed = libportal-0.10.0-1-x86_64 +installed = libportal-gtk4-0.10.0-1-x86_64 +installed = libppd-2.1.1-2-x86_64 +installed = libproxy-0.5.12-1-x86_64 +installed = libpsl-0.21.5-2-x86_64 +installed = libpst-0.6.76-10-x86_64 +installed = libpulse-17.0+r98+gb096704c0-1-x86_64 +installed = libpwquality-1.4.5-7-x86_64 +installed = libqalculate-5.11.0-1-x86_64 +installed = libquadmath-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libqxp-0.0.3-1-x86_64 +installed = libraqm-0.10.5-1-x86_64 +installed = libraw-0.22.1-1-x86_64 +installed = libraw1394-2.1.2-4-x86_64 +installed = libre-trainsim-bin-0.8-1-x86_64 +installed = libreoffice-fresh-26.2.4-2-x86_64 +installed = librest-0.10.2-1-x86_64 +installed = librevenge-0.0.5-4-x86_64 +installed = librewolf-bin-1:152.0.1_2-1-x86_64 +installed = librsvg-2:2.62.3-1-x86_64 +installed = librsync-1:2.3.4-2-x86_64 +installed = libsamplerate-0.2.2-3-x86_64 +installed = libsasl-2.1.28-5-x86_64 +installed = libseccomp-2.6.0-1-x86_64 +installed = libsecret-0.21.7-1-x86_64 +installed = libshout-1:2.4.6-5-x86_64 +installed = libsigc++-2.12.2-1-x86_64 +installed = libsigc++-3.0-3.8.1-1-x86_64 +installed = libslirp-4.9.3-1-x86_64 +installed = libsm-1.2.6-1-x86_64 +installed = libsndfile-1.2.2-4-x86_64 +installed = libsodium-1.0.22-1-x86_64 +installed = libsoup3-3.6.6-2-x86_64 +installed = libsoxr-0.1.3-4-x86_64 +installed = libspelling-0.4.10-1-x86_64 +installed = libspnav-1.2-1-x86_64 +installed = libssh-0.12.0-1-x86_64 +installed = libssh2-1.11.1-1-x86_64 +installed = libstaroffice-0.0.7-5-x86_64 +installed = libstdc++-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libstemmer-3.1.1-1-x86_64 +installed = libsysprof-capture-50.0-2-x86_64 +installed = libtasn1-4.21.0-1-x86_64 +installed = libtatsu-1.0.5-1-x86_64 +installed = libteam-1.32-3-x86_64 +installed = libthai-0.1.30-1-x86_64 +installed = libtheora-1.2.0-1-x86_64 +installed = libtiff-4.7.1-2-x86_64 +installed = libtirpc-1.3.7-1-x86_64 +installed = libtommath-1.3.0-2-x86_64 +installed = libtool-2.6.1-1-x86_64 +installed = libtpms-0.10.2-1-x86_64 +installed = libtraceevent-1:1.9.0-1-x86_64 +installed = libtracefs-1.8.3-1-x86_64 +installed = libtsan-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libubsan-16.1.1+r12+g301eb08fa2c5-1-x86_64 +installed = libunibreak-7.0-1-x86_64 +installed = libunistring-1.4.2-1-x86_64 +installed = libunwind-1.8.2-1-x86_64 +installed = liburing-2.14-1-x86_64 +installed = libusb-1.0.30-1-x86_64 +installed = libusbmuxd-2.1.1-2-x86_64 +installed = libutf8proc-2.11.3-1-x86_64 +installed = libuv-1.52.1-1-x86_64 +installed = libva-2.23.0-1-x86_64 +installed = libvdpau-1.5-4-x86_64 +installed = libverto-0.3.2-6-x86_64 +installed = libvirt-1:12.4.0-1-x86_64 +installed = libvirt-glib-5.0.0-3-x86_64 +installed = libvirt-python-1:12.4.0-1-x86_64 +installed = libvisio-0.1.11-1-x86_64 +installed = libvorbis-1.3.7-4-x86_64 +installed = libvpl-2.16.0-2-x86_64 +installed = libvpx-1.16.0-3-x86_64 +installed = libwacom-2.19.0-1-x86_64 +installed = libwbclient-2:4.24.3-1-x86_64 +installed = libwebp-1.6.0-2-x86_64 +installed = libwireplumber-0.5.15-1-x86_64 +installed = libwpd-0.10.3-6-x86_64 +installed = libwps-0.4.14-4-x86_64 +installed = libx11-1.8.13-1-x86_64 +installed = libxau-1.0.12-1-x86_64 +installed = libxcb-1.17.0-1-x86_64 +installed = libxcomposite-0.4.7-1-x86_64 +installed = libxcrypt-4.5.2-1-x86_64 +installed = libxcrypt-compat-4.5.2-1-x86_64 +installed = libxcursor-1.2.3-1-x86_64 +installed = libxdamage-1.1.7-1-x86_64 +installed = libxdmcp-1.1.5-2-x86_64 +installed = libxdp-1.6.3-1-x86_64 +installed = libxext-1.3.7-1-x86_64 +installed = libxfixes-6.0.2-1-x86_64 +installed = libxft-2.3.9-1-x86_64 +installed = libxi-1.8.3-1-x86_64 +installed = libxinerama-1.1.6-1-x86_64 +installed = libxkbcommon-1.13.2-1-x86_64 +installed = libxkbcommon-x11-1.13.2-1-x86_64 +installed = libxkbfile-1.2.0-1-x86_64 +installed = libxklavier-5.4-7-x86_64 +installed = libxml2-2.15.3-1-x86_64 +installed = libxmlb-0.3.27-1-x86_64 +installed = libxpm-3.5.19-1-x86_64 +installed = libxrandr-1.5.5-1-x86_64 +installed = libxrender-0.9.12-1-x86_64 +installed = libxshmfence-1.3.3-1-x86_64 +installed = libxslt-1.1.45-2-x86_64 +installed = libxss-1.2.5-1-x86_64 +installed = libxt-1.3.1-1-x86_64 +installed = libxtst-1.2.5-1-x86_64 +installed = libxv-1.0.13-1-x86_64 +installed = libxxf86vm-1.1.7-1-x86_64 +installed = libyaml-0.2.5-3-x86_64 +installed = libytnef-1:2.1.2-2-x86_64 +installed = libyuv-r2426+464c51a03-1-x86_64 +installed = libzip-1.11.4-1-x86_64 +installed = libzmf-0.0.2-20-x86_64 +installed = licenses-20240728-1-any +installed = lilv-0.28.0-1-x86_64 +installed = linux-api-headers-7.1-1-x86_64 +installed = linux-firmware-20260519-1-any +installed = linux-firmware-amdgpu-20260519-1-any +installed = linux-firmware-atheros-20260519-1-any +installed = linux-firmware-broadcom-20260519-1-any +installed = linux-firmware-cirrus-20260519-1-any +installed = linux-firmware-intel-20260519-1-any +installed = linux-firmware-mediatek-20260519-1-any +installed = linux-firmware-nvidia-20260519-1-any +installed = linux-firmware-other-20260519-1-any +installed = linux-firmware-radeon-20260519-1-any +installed = linux-firmware-realtek-20260519-1-any +installed = linux-firmware-whence-20260519-1-any +installed = linux-zen-7.0.12.zen1-1-x86_64 +installed = linux-zen-headers-7.0.12.zen1-1-x86_64 +installed = litehtml0.9-0.9-2-x86_64 +installed = littlenavmap-bin-3.0.18-1-x86_64 +installed = lld-22.1.6-1-x86_64 +installed = llhttp-9.3.1-1-x86_64 +installed = llvm-22.1.6-1-x86_64 +installed = llvm-libs-22.1.6-1-x86_64 +installed = lm_sensors-1:3.6.2-1-x86_64 +installed = lmdb-0.9.35-1-x86_64 +installed = localsearch-3.11.1-1-x86_64 +installed = log4cplus-2.1.2-1-x86_64 +installed = loupe-50.0-1-x86_64 +installed = lpsolve-5.5.2.14-1-x86_64 +installed = lsb-release-2.0.r55.a25a4fc-1-any +installed = lsof-4.99.7-1-x86_64 +installed = lua-5.5.0-2-x86_64 +installed = lua54-5.4.8-6-x86_64 +installed = lv2-1.18.10-2-x86_64 +installed = lz4-1:1.10.0-2-x86_64 +installed = lzo-2.10-5-x86_64 +installed = m4-1.4.21-2-x86_64 +installed = mailcap-2.1.54-2-any +installed = make-4.4.1-3-x86_64 +installed = manifold-3.4.1-1-x86_64 +installed = materialx-1.39.4-5-x86_64 +installed = md4c-0.5.3-1-x86_64 +installed = mdadm-4.6-2-x86_64 +installed = mesa-1:26.1.3-2-x86_64 +installed = mesa-utils-9.0.0-7-x86_64 +installed = meson-1.11.1-3-any +installed = mingw-w64-binutils-2.46.0-1-x86_64 +installed = mingw-w64-crt-14.0.0-1-any +installed = mingw-w64-gcc-16.1.0-1-x86_64 +installed = mingw-w64-headers-14.0.0-1-any +installed = mingw-w64-winpthreads-14.0.0-1-any +installed = miniupnpc-2.3.3-3-x86_64 +installed = minizip-1:1.3.2-3-x86_64 +installed = minizip-ng-4.2.1-1-x86_64 +installed = mkinitcpio-41-4-any +installed = mkinitcpio-busybox-1.36.1-1-x86_64 +installed = mobile-broadband-provider-info-20251101-1-any +installed = mpdecimal-4.0.1-3-x86_64 +installed = mpfr-4.2.2-1-x86_64 +installed = mpg123-1.33.5-1-x86_64 +installed = mtdev-1.1.7-1-x86_64 +installed = mtools-1:4.0.49-1-x86_64 +installed = nano-9.0-1-x86_64 +installed = nanobind-2.13.0-1-any +installed = nautilus-50.2.2-1-x86_64 +installed = ncurses-6.6-2-x86_64 +installed = ndctl-84-1-x86_64 +installed = neon-0.37.1-1-x86_64 +installed = netpbm-10.86.49-3-x86_64 +installed = nettle-4.0-1-x86_64 +installed = network-manager-applet-1.36.0-2-x86_64 +installed = networkmanager-1.56.1-1-x86_64 +installed = nextcloud-client-2:33.0.5-1-x86_64 +installed = nftables-1:1.1.6-3-x86_64 +installed = ninja-1.13.2-3-x86_64 +installed = niri-26.04-1-x86_64 +installed = nm-connection-editor-1.36.0-2-x86_64 +installed = node-gyp-13.0.0-1-any +installed = nodejs-26.3.1-1-x86_64 +installed = nodejs-nopt-10.0.1-1-any +installed = noto-fonts-emoji-1:2.051-1-any +installed = npm-11.16.0-1-any +installed = npth-1.8-1-x86_64 +installed = nspr-4.39-1-x86_64 +installed = nss-3.125-1-x86_64 +installed = numactl-2.0.19-1-x86_64 +installed = nwg-look-1.1.1-3-x86_64 +installed = obsidian-1.12.7-2-x86_64 +installed = ocl-icd-2.3.4-1-x86_64 +installed = onetbb-2023.0.0-1-x86_64 +installed = oniguruma-6.9.10-1-x86_64 +installed = openal-1.25.2-1-x86_64 +installed = opencolorio-2.5.1-1-x86_64 +installed = opencore-amr-0.1.6-2-x86_64 +installed = openexr-3.4.13-1-x86_64 +installed = openh264-2.6.0-2-x86_64 +installed = openimagedenoise-2.4.1-2-x86_64 +installed = openimageio-3.1.12.1-3-x86_64 +installed = openjpeg2-2.5.4-1-x86_64 +installed = openjph-0.30.0-1-x86_64 +installed = openpgl-0.7.1-1-x86_64 +installed = openpomodoro-0.3.0-2-x86_64 +installed = openshadinglanguage-1.15.3.0-1-x86_64 +installed = openssh-10.3p1-1-x86_64 +installed = openssl-3.6.3-1-x86_64 +installed = opensubdiv-3.7.0-1-x86_64 +installed = openvdb-13.0.0-1-x86_64 +installed = openxr-1.1.60-1-x86_64 +installed = opus-1.6.1-1-x86_64 +installed = orc-0.4.42-1-x86_64 +installed = orca-slicer-beta-bin-2.4.0-1-x86_64 +installed = osinfo-db-20251212-1-any +installed = ostree-2025.7-3-x86_64 +installed = otf-font-awesome-7.2.0-1-any +installed = p11-kit-0.26.2-1-x86_64 +installed = pacman-7.1.0.r9.g54d9411-2-x86_64 +installed = pacman-contrib-1.13.1-1-x86_64 +installed = pacman-mirrorlist-20260610-1-any +installed = pahole-1:1.31-2-x86_64 +installed = pam-1.7.2-2-x86_64 +installed = pambase-20260616-1-any +installed = pango-1:1.57.1-1-x86_64 +installed = pangomm-2.46.4-3-x86_64 +installed = pangomm-2.48-2.56.1-2-x86_64 +installed = papers-50.2-1-x86_64 +installed = papirus-folders-1.14.0-1-any +installed = papirus-icon-theme-20250501-1-any +installed = parallel-20260522-1-any +installed = parted-3.7-1-x86_64 +installed = paru-2.1.0-2-x86_64 +installed = patch-2.8-1-x86_64 +installed = pavucontrol-1:6.2-1-x86_64 +installed = pciutils-3.15.0-1-x86_64 +installed = pcre-8.45-4-x86_64 +installed = pcre2-10.47-1-x86_64 +installed = pcsclite-2.5.1-1-x86_64 +installed = pcsx2-git-2.7.423.r0.g8e73dea7e2-1-x86_64 +installed = perl-5.42.2-1-x86_64 +installed = perl-error-0.17030-3-any +installed = perl-mailtools-2.22-3-any +installed = perl-timedate-2.35-1-any +installed = pfetch-1.11.0-1-any +installed = phodav-3.0-4-x86_64 +installed = pinentry-1.3.2-2-x86_64 +installed = pipewire-1:1.6.7-1-x86_64 +installed = pipewire-audio-1:1.6.7-1-x86_64 +installed = pipewire-jack-1:1.6.7-1-x86_64 +installed = pipewire-pulse-1:1.6.7-1-x86_64 +installed = pipewire-session-manager-1:1.6.7-1-x86_64 +installed = pixman-0.46.4-1-x86_64 +installed = pkgconf-2.5.1-1-x86_64 +installed = playerctl-2.4.1-5-x86_64 +installed = plutosvg-0.0.8-1-x86_64 +installed = plutovg-git-1.3.3+12.r311.20260606.dd73459-1-x86_64 +installed = polkit-127-3-x86_64 +installed = polkit-gnome-0.105-12-x86_64 +installed = polkit-qt6-0.201.1-1-x86_64 +installed = poppler-26.06.0-1-x86_64 +installed = poppler-data-0.4.12-2-any +installed = poppler-glib-26.06.0-1-x86_64 +installed = popt-1.19-2-x86_64 +installed = portaudio-1:19.7.0-4-x86_64 +installed = potrace-1.16-5-x86_64 +installed = pps-tools-1.0.3-2-x86_64 +installed = procps-ng-4.0.6-2-x86_64 +installed = protobuf-35.0-2-x86_64 +installed = psmisc-23.7-2-x86_64 +installed = ptex-2.5.2-1-x86_64 +installed = pugixml-1.16-1-x86_64 +installed = pyalpm-0.11.1-1-x86_64 +installed = pybind11-3.0.4-1-any +installed = pyside6-6.11.1-1-x86_64 +installed = pystring-1.2.0-1-x86_64 +installed = python-3.14.6-1-x86_64 +installed = python-aiohappyeyeballs-2.6.1-4-any +installed = python-aiohttp-3.13.5-1-x86_64 +installed = python-aiosignal-1.4.0-3-any +installed = python-aiostream-0.7.1-3-any +installed = python-attrs-26.1.0-1-any +installed = python-autocommand-2.2.2-9-any +installed = python-build-1.4.3-1-any +installed = python-cairo-1.29.0-2-x86_64 +installed = python-certifi-2026.06.17-1-any +installed = python-charset-normalizer-3.4.7-1-x86_64 +installed = python-click-8.3.3-1-any +installed = python-click-log-0.4.0-6-any +installed = python-configobj-5.0.9-6-any +installed = python-dateutil-2.9.0-8-any +installed = python-editables-0.6-1-any +installed = python-frozenlist-1.8.0-2-x86_64 +installed = python-gobject-3.56.3-1-x86_64 +installed = python-hatchling-1.30.1-1-any +installed = python-icalendar-7.1.2-1-any +installed = python-idna-3.18-1-any +installed = python-imageio-2.37.3-1-any +installed = python-imageio-ffmpeg-0.6.0-2-any +installed = python-installer-1.0.0-1-any +installed = python-jaraco.collections-5.1.0-3-any +installed = python-jaraco.context-6.1.2-1-any +installed = python-jaraco.functools-4.1.0-3-any +installed = python-jaraco.text-4.0.0-4-any +installed = python-jinja-1:3.1.6-3-any +installed = python-lxml-6.1.1-1-x86_64 +installed = python-markupsafe-3.0.3-1-x86_64 +installed = python-more-itertools-11.1.0-1-any +installed = python-multidict-6.7.1-1-x86_64 +installed = python-numpy-2.4.6-1-x86_64 +installed = python-opengl-3.1.10-3-any +installed = python-packaging-26.2-1-any +installed = python-pathspec-1.1.1-1-any +installed = python-pillow-12.2.0-1-x86_64 +installed = python-pkg_resources-81.0.0-1-any +installed = python-platformdirs-4.10.0-1-any +installed = python-pluggy-1.6.0-3-any +installed = python-propcache-0.4.1-2-x86_64 +installed = python-pygments-2.20.0-1-any +installed = python-pyproject-hooks-1.2.0-6-any +installed = python-pyqt6-6.11.0-2-x86_64 +installed = python-pyqt6-sip-13.11.1-1-x86_64 +installed = python-pytz-2026.1-1-any +installed = python-pyxdg-0.28-7-any +installed = python-requests-2.34.2-1-any +installed = python-screeninfo-0.8.1-2-any +installed = python-setproctitle-1.3.7-2-x86_64 +installed = python-setuptools-1:82.0.1-1-any +installed = python-six-1.17.0-3-any +installed = python-tqdm-4.68.3-1-any +installed = python-trove-classifiers-2026.6.1.19-1-any +installed = python-typing_extensions-4.15.0-3-any +installed = python-tzlocal-1:5.3.1-2-any +installed = python-urllib3-2.7.0-1-any +installed = python-urwid-4.0.2-1-any +installed = python-wcwidth-0.8.1-1-any +installed = python-wheel-0.47.0-1-any +installed = python-yarl-1.23.0-1-x86_64 +installed = qemu-audio-alsa-11.0.1-1-x86_64 +installed = qemu-audio-dbus-11.0.1-1-x86_64 +installed = qemu-audio-jack-11.0.1-1-x86_64 +installed = qemu-audio-oss-11.0.1-1-x86_64 +installed = qemu-audio-pa-11.0.1-1-x86_64 +installed = qemu-audio-pipewire-11.0.1-1-x86_64 +installed = qemu-audio-sdl-11.0.1-1-x86_64 +installed = qemu-audio-spice-11.0.1-1-x86_64 +installed = qemu-base-11.0.1-1-x86_64 +installed = qemu-block-curl-11.0.1-1-x86_64 +installed = qemu-block-dmg-11.0.1-1-x86_64 +installed = qemu-block-nfs-11.0.1-1-x86_64 +installed = qemu-block-ssh-11.0.1-1-x86_64 +installed = qemu-chardev-spice-11.0.1-1-x86_64 +installed = qemu-common-11.0.1-1-x86_64 +installed = qemu-desktop-11.0.1-1-x86_64 +installed = qemu-hw-display-qxl-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-gpu-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-gpu-gl-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-gpu-pci-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-gpu-pci-gl-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-gpu-pci-rutabaga-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-gpu-rutabaga-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-vga-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-vga-gl-11.0.1-1-x86_64 +installed = qemu-hw-display-virtio-vga-rutabaga-11.0.1-1-x86_64 +installed = qemu-hw-uefi-vars-11.0.1-1-x86_64 +installed = qemu-hw-usb-host-11.0.1-1-x86_64 +installed = qemu-hw-usb-redirect-11.0.1-1-x86_64 +installed = qemu-hw-usb-smartcard-11.0.1-1-x86_64 +installed = qemu-img-11.0.1-1-x86_64 +installed = qemu-system-x86-11.0.1-1-x86_64 +installed = qemu-system-x86-firmware-11.0.1-1-x86_64 +installed = qemu-ui-curses-11.0.1-1-x86_64 +installed = qemu-ui-dbus-11.0.1-1-x86_64 +installed = qemu-ui-egl-headless-11.0.1-1-x86_64 +installed = qemu-ui-gtk-11.0.1-1-x86_64 +installed = qemu-ui-opengl-11.0.1-1-x86_64 +installed = qemu-ui-sdl-11.0.1-1-x86_64 +installed = qemu-ui-spice-app-11.0.1-1-x86_64 +installed = qemu-ui-spice-core-11.0.1-1-x86_64 +installed = qemu-vhost-user-gpu-11.0.1-1-x86_64 +installed = qpdf-12.3.2-2-x86_64 +installed = qt5-base-5.15.19+kde+r96-1-x86_64 +installed = qt5-declarative-5.15.19+kde+r23-1-x86_64 +installed = qt5-svg-5.15.19+kde+r5-1-x86_64 +installed = qt5-translations-5.15.19-1-any +installed = qt6-5compat-6.11.1-1-x86_64 +installed = qt6-base-6.11.1-1-x86_64 +installed = qt6-declarative-6.11.1-3-x86_64 +installed = qt6-positioning-6.11.1-1-x86_64 +installed = qt6-shadertools-6.11.1-1-x86_64 +installed = qt6-svg-6.11.1-1-x86_64 +installed = qt6-tools-6.11.1-3-x86_64 +installed = qt6-translations-6.11.1-1-any +installed = qt6-wayland-6.11.1-1-x86_64 +installed = qt6-webchannel-6.11.1-1-x86_64 +installed = qt6-webengine-6.11.1-4-x86_64 +installed = qt6-websockets-6.11.1-1-x86_64 +installed = qtkeychain-qt6-0.16.0-1-x86_64 +installed = rapidyaml-0.11.1-2-x86_64 +installed = raptor-2.0.16-9-x86_64 +installed = rasqal-1:0.9.33-9-x86_64 +installed = rav1e-0.8.1-2-x86_64 +installed = rdma-core-63.0-2-x86_64 +installed = re2-2:2025.11.05-5-x86_64 +installed = readline-8.3.003-1-x86_64 +installed = redland-1:1.0.17-10-x86_64 +installed = reflector-2023-5-any +installed = retroarch-1.22.2-1-x86_64 +installed = rhash-1.4.6-1-x86_64 +installed = ripgrep-15.1.0-4-x86_64 +installed = robin-map-1.4.1-1-x86_64 +installed = rofi-2.0.0-1-x86_64 +installed = rofi-calc-2.5.1-1-x86_64 +installed = rofi-themes-collection-git-1:r28.43ec2f5-1-any +installed = rpcs3-bin-0.0.41.19433-1-x86_64 +installed = rsync-3.4.4-1-x86_64 +installed = rtkit-0.14-1-x86_64 +installed = rubberband-4.0.0-2-x86_64 +installed = run-parts-5.23.2-1-x86_64 +installed = rust-1:1.96.0-1-x86_64 +installed = rutabaga-ffi-0.1.75-1-x86_64 +installed = sbc-2.2-1-x86_64 +installed = scdoc-1.11.4-1-x86_64 +installed = sdbus-cpp-2.3.1-1-x86_64 +installed = sdl2-compat-2.32.70-1-x86_64 +installed = sdl2_image-2.8.12-1-x86_64 +installed = sdl3-3.4.10-1-x86_64 +installed = seabios-1.17.0-2-any +installed = seatd-0.9.3-1-x86_64 +installed = sed-4.10-1-x86_64 +installed = semver-7.8.5-1-any +installed = serd-0.32.10-1-x86_64 +installed = shaderc-2026.2-2-x86_64 +installed = shadow-4.19.4.arch1-1-x86_64 +installed = shared-mime-info-2.4-3-x86_64 +installed = shiboken6-6.11.1-1-x86_64 +installed = signal-desktop-8.15.0-1-x86_64 +installed = simdjson-1:4.6.4-1-x86_64 +installed = slang-2.3.3-4-x86_64 +installed = slurp-1.5.0-2-x86_64 +installed = smartmontools-7.5-1-x86_64 +installed = smbclient-2:4.24.3-1-x86_64 +installed = snappy-1.2.2-3-x86_64 +installed = sndio-1.10.0-1-x86_64 +installed = sord-0.16.22-1-x86_64 +installed = sound-theme-freedesktop-0.8-6-any +installed = source-highlight-3.1.9-18-x86_64 +installed = spandsp-0.0.6-7-x86_64 +installed = spdlog-1.17.0-2-x86_64 +installed = speex-1.2.1-2-x86_64 +installed = speexdsp-1.2.1-2-x86_64 +installed = spice-0.16.0-2-x86_64 +installed = spice-gtk-0.42-5-x86_64 +installed = spice-protocol-0.14.5-1-any +installed = spirv-tools-1:1.4.350.1-1-x86_64 +installed = sqlite-3.53.2-1-x86_64 +installed = squashfs-tools-4.7.5-1-x86_64 +installed = sratom-0.6.22-1-x86_64 +installed = srt-1.5.5-1-x86_64 +installed = startup-notification-0.12-9-x86_64 +installed = steam-1.0.0.85-7-x86_64 +installed = steam-devices-1.0.0.85-7-x86_64 +installed = sudo-1.9.17.p2-6-x86_64 +installed = suitesparse-7.12.2-2-x86_64 +installed = superlu-7.0.1-2-x86_64 +installed = svt-av1-4.1.0-1-x86_64 +installed = swaybg-1.2.2-1-x86_64 +installed = swaylock-1.8.5-1-x86_64 +installed = swig-4.4.1-1-x86_64 +installed = swtpm-0.10.1-2-x86_64 +installed = systemd-260.2-2-x86_64 +installed = systemd-libs-260.2-2-x86_64 +installed = systemd-sysvcompat-260.2-2-x86_64 +installed = taglib-2.3-1-x86_64 +installed = talloc-2.4.4-1-x86_64 +installed = tanin-desktop-0.2.0-3-any +installed = tanin-eww-0.1.0-4-any +installed = tanin-greet-0.1.0-2-any +installed = tanin-libadwaita-0.1.0-2-any +installed = tanin-setup-0.1.0-1-any +installed = taninux-0.2.0-1-any +installed = tar-1.35-2-x86_64 +installed = tdb-1.4.15-1-x86_64 +installed = tevent-1:0.17.1-2-x86_64 +installed = texinfo-7.3-1-x86_64 +installed = thunderbird-152.0-1-x86_64 +installed = thunderbird-gnome-theme-git-r95.c61b897-1-any +installed = thunderbird-i18n-de-152.0-1-x86_64 +installed = thunderbird-ublock-origin-1.70.0-1-any +installed = timeshift-25.12.4-1-x86_64 +installed = timeshift-autosnap-0.10.0-1-any +installed = tinysparql-3.11.1-1-x86_64 +installed = tiramisu-git-r168.5dddd83-1-x86_64 +installed = totem-pl-parser-3.26.7-1-x86_64 +installed = tpm2-tss-4.1.3-1-x86_64 +installed = transmission-gtk-4.1.1-1-x86_64 +installed = tslib-1.24-1-x86_64 +installed = ttc-iosevka-34.6.1-1-any +installed = ttc-iosevka-aile-34.6.1-1-any +installed = ttc-iosevka-curly-34.6.1-1-any +installed = ttc-iosevka-curly-slab-34.6.1-1-any +installed = ttc-iosevka-etoile-34.6.1-1-any +installed = ttf-iosevka-nerd-3.4.0-2-any +installed = ttf-iosevkaterm-nerd-3.4.0-2-any +installed = ttf-liberation-2.1.5-2-any +installed = ttf-nerd-fonts-symbols-3.4.0-1-any +installed = ttf-nerd-fonts-symbols-common-3.4.0-1-any +installed = twolame-0.4.0-4-x86_64 +installed = tzdata-2026b-1-x86_64 +installed = uchardet-0.0.8-4-x86_64 +installed = udisks2-2.11.1-2-x86_64 +installed = unzip-6.0-23-x86_64 +installed = upower-1.91.2-1-x86_64 +installed = usbredir-0.15.0-1-x86_64 +installed = usbutils-019-1-x86_64 +installed = usd-26.05-3-x86_64 +installed = util-linux-2.42.2-1-x86_64 +installed = util-linux-libs-2.42.2-1-x86_64 +installed = uv-0.11.23-1-x86_64 +installed = v4l-utils-1.32.0-2-x86_64 +installed = vala-0.56.19-1-x86_64 +installed = vapoursynth-77-2-x86_64 +installed = vde2-2.3.3-8-x86_64 +installed = vdirsyncer-0.20.0-4-any +installed = vid.stab-1.1.1-2-x86_64 +installed = vim-9.2.0670-1-x86_64 +installed = vim-runtime-9.2.0670-1-x86_64 +installed = virglrenderer-1.3.0-2-x86_64 +installed = virt-install-5.1.0-3-any +installed = virt-manager-5.1.0-3-any +installed = virtiofsd-1.13.3-1-x86_64 +installed = vmaf-3.2.0-1-x86_64 +installed = volume_key-0.3.12-12-x86_64 +installed = vte-common-0.84.0-1-x86_64 +installed = vte3-0.84.0-1-x86_64 +installed = vulkan-headers-1:1.4.350.1-1-any +installed = vulkan-icd-loader-1.4.350.1-1-x86_64 +installed = vulkan-mesa-implicit-layers-1:26.1.3-2-x86_64 +installed = vulkan-radeon-1:26.1.3-2-x86_64 +installed = vulkan-tools-1.4.350.1-1-x86_64 +installed = wavpack-5.9.0-1-x86_64 +installed = waybar-0.15.0-2-x86_64 +installed = wayland-1.25.0-1-x86_64 +installed = wayland-protocols-1.49-1-any +installed = waypaper-2.8-1-any +installed = webkit2gtk-4.1-2.52.4-1-x86_64 +installed = webkitgtk-6.0-2.52.4-1-x86_64 +installed = webrtc-audio-processing-1-1.3-5-x86_64 +installed = wget-1.25.0-5-x86_64 +installed = which-2.25-1-x86_64 +installed = wine-11.11-1-x86_64 +installed = winetricks-20260125-2-any +installed = wireplumber-0.5.15-1-x86_64 +installed = wl-clipboard-1:2.3.0-1-x86_64 +installed = wlroots0.20-0.20.1-1-x86_64 +installed = wlsunset-0.4.0-1-x86_64 +installed = woff2-1.0.2-6-x86_64 +installed = wolfssl-5.9.1-1-x86_64 +installed = wpa_supplicant-2:2.11-5-x86_64 +installed = x264-3:0.165.r3222.b35605a-2-x86_64 +installed = x265-4.2-2-x86_64 +installed = xapp-3.2.2-1-x86_64 +installed = xapp-symbolic-icons-1.1.0-1-any +installed = xcb-imdkit-1.0.9-1-x86_64 +installed = xcb-proto-1.17.0-4-any +installed = xcb-util-0.4.1-2-x86_64 +installed = xcb-util-cursor-0.1.6-1-x86_64 +installed = xcb-util-errors-1.0.1-2-x86_64 +installed = xcb-util-image-0.4.1-3-x86_64 +installed = xcb-util-keysyms-0.4.1-5-x86_64 +installed = xcb-util-renderutil-0.3.10-2-x86_64 +installed = xcb-util-wm-0.4.2-2-x86_64 +installed = xcur2png-0.7.1-9-x86_64 +installed = xdg-dbus-proxy-0.1.7-1-x86_64 +installed = xdg-desktop-portal-1.22.1-2-x86_64 +installed = xdg-desktop-portal-gnome-50.0-1-x86_64 +installed = xdg-desktop-portal-gtk-1.15.3-1-x86_64 +installed = xdg-desktop-portal-hyprland-1.3.12-2-x86_64 +installed = xdg-terminal-exec-0.14.0-1-any +installed = xdg-user-dirs-0.20-1-x86_64 +installed = xdg-user-dirs-gtk-0.16-1-x86_64 +installed = xdg-utils-1.2.1-2-any +installed = xdotool-4.20260303.1-1-x86_64 +installed = xenia-canary-bin-02d2cb5-2-x86_64 +installed = xenia-edge-bin-20260620114512.5a04224-1-x86_64 +installed = xkeyboard-config-2.47-1-any +installed = xmlsec-1.3.11-1-x86_64 +installed = xorg-xkbcomp-1.5.0-1-x86_64 +installed = xorg-xprop-1.2.8-1-x86_64 +installed = xorg-xrandr-1.5.4-1-x86_64 +installed = xorgproto-2025.1-1-any +installed = xvidcore-1.3.7-3-x86_64 +installed = xxhash-0.8.3-1-x86_64 +installed = xz-5.8.3-1-x86_64 +installed = yaml-cpp-0.9.0-1-x86_64 +installed = yazi-26.5.6-4-x86_64 +installed = zenity-4.2.2-1-x86_64 +installed = zeromq-4.3.5-3-x86_64 +installed = zimg-3.0.6-1-x86_64 +installed = zint-2.16.0-2-x86_64 +installed = zix-0.8.2-1-x86_64 +installed = zlib-1:1.3.2-3-x86_64 +installed = zlib-ng-2.3.3-1-x86_64 +installed = zram-generator-1.2.1-1-x86_64 +installed = zsh-5.9.1-1-x86_64 +installed = zstd-1.5.7-3-x86_64 +installed = zxing-cpp-3.0.2-1-x86_64 diff --git a/packaging/tanin-icons/pkg/tanin-icons/.INSTALL b/packaging/tanin-icons/pkg/tanin-icons/.INSTALL new file mode 100644 index 0000000..636e60e --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/.INSTALL @@ -0,0 +1,9 @@ +# Refresh the icon caches so GTK picks up the Tanin- themes. +_themes() { + for t in ume shinkai take chikyu beni; do + gtk-update-icon-cache -qtf "/usr/share/icons/Tanin-$t" 2>/dev/null || true + done +} + +post_install() { _themes; } +post_upgrade() { _themes; } diff --git a/packaging/tanin-icons/pkg/tanin-icons/.MTREE b/packaging/tanin-icons/pkg/tanin-icons/.MTREE new file mode 100644 index 0000000000000000000000000000000000000000..2e6250a38d63e0d67a3fff3f667e8f4593e9d452 GIT binary patch literal 7101 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 BgU3^h&_vo&YN>)nB=iz)y734(fQp_##FcU3 z0X%@X6uR~}g@P``R-rAenkJLDGF{lA4gNMW@V)oGHv|7f06c`D;~3Tn-~nWaaZ4@k zyJ48D3orp3X`YdBw@l4=6PDlW^)7=TP-`1J-rR-=t_w6D7^yr8!mGZiQ&_gmPRGZq z)&9&rD5RJD_sByuvM=fZfefSgq;Uu2&4($^v+YRC_-ewKp-B%H_rMeRy1S=}LY8;6qoWxFN-> P00000NkvXXu0mjfzLBIE literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-pictures.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-pictures.png new file mode 100644 index 0000000000000000000000000000000000000000..823bff565b48ece2482b192bbd722683e3223d6c GIT binary patch literal 365 zcmV-z0h0cSP)bRk6KB7eD|wp1V;toJ%T3ULaBQYUzmz-(Yxk zO_qIpfKUYsoz7I>FWi!r?=!!=1o96AnUp-lF|VT$x@8p>6cWsNEdX@W%;!o3HOHam zIEBG41X-HC%yfR!XGQ~f8s6{NZsYPfNsKaKvtA!3ceh(rk51J)M{Y>K1yusT00000 LNkvXXu0mjf31pWC literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-publicshare.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e4a13b36f255feedba5630fa248844329cc6b5 GIT binary patch literal 383 zcmV-_0f7FAP)fR4Tc{Blv4CN9R^{@{Nh43z;p+>dI1Z6W zEsZrsLlBL%^$(gHlyDFWlZ)5F>rLPHG?+KJ5ov85c;UR~`QSWofIk)kfU{yT78Axk z0Bbm8o!OLix>+a$6B(Qzl?nhmjsRDha3Z*-s7NbW-7Ox}0DuDkh|NVw1Z*>g8%_K+ z`oMMNqEuQ30Quc&hG}AcIS(tH{(@a`N5Oxc0+?< w$v#Mj|8lt^m7*E|NDi^!Wcwq`U@Yw40nZ|F!S4^SGynhq07*qoM6N<$g6Vj!HUIzs literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-templates.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..d9602eef06325506690c7a088d04fdbfe3140bf4 GIT binary patch literal 367 zcmV-#0g(QQP)&;U%KT_Sn>p-K`(58^n!3B$0cZ18gb5TS5aqxnExiqkN=lZ0FJj`A7C_1 zjHX!{TuA^di(*>2xuI1?1IWjd3)^*GKeEi&CET#AdG?yH N002ovPDHLkV1keboq_-W literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-videos.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..d2277433a4543b450a9178e506a2d7d24e2115c0 GIT binary patch literal 366 zcmV-!0g?WRP)#4D09FdQgRJFo65)&i3Bq2iJ&E=Q z0H5s+-%*4uC0?mGb4Tm_`t;18*Q2W+Bwg3PC M07*qoM6N<$f=_gxj{pDw literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..4a59c5eff57b74a67a2a0b652493c4f7105499d1 GIT binary patch literal 303 zcmV+~0nq-5P)06PJ($sMHlmapdd3 z1#|)8u#j<=Q|M6q0|}^5e0h#+r7BI@Y47y=&bb$Mi~zVZCJ2;w27~|(@fzF3ZQq#B zO#%%NNIk$YURfOv!b`F&8>VS$H!hfrZVZC!3d`rJkSD3Rn(6;OJm2TAmk>6azix%K zNB6V&C2*kuqISCzCtakHrRt?pgsny&Kmr`JqT{mgGEVqQfkSxJ=pSpg1^0qAL1FDj zRq&h90B=t(=V8>CzUR4L2CmiXi~RZVv});}`2um+HnKSHf#3iD002ovPDHLkV1j}t BgU=0eyBsmLb zeuD%eSlQZYlN4za5G?G1SZJk^CWVj(Mo75x?sjJ_-t+l9F9Gr23_HvGX8y}8{HGki zoonshshVqu6P!5I3C=l09D)E4Nqor;p1pnl=5ZC^0j4KtoNU%m5mW>bL0u8WAufmP z?jN>CC-4Afh9v{5+z7>4Koa8ISPMBIC88M!q>$Jf3~@iO9s=goo*!-}& z_t2~0er=@+qFS64C(3?zFa(N4ujuCXjYnB%EMzv-adT%s4_Ymg`ExIZt(l#J$tKSJryn{sXgwCBrP)>eFRCuI8Wh6aBidWJ7)%UH||907*qo IM6N<$f&{4P!2kdN literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-bookmarks.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..9f24b3512f9bc0ddf75feb49647156e77bb3505a GIT binary patch literal 355 zcmV-p0i6DcP)qLtbH{qlcmyiXh=n6y}83!)F z1&Bi-W0zAX;y}_W)IeMNlRQVJg4X8Yncn-I`*Go)XaM(d>^Qo440r$%ZP?6bcbzz1 ztP>ajjxbJ<+%Bz>R5RO2l3YYll&@{@aD5Y_ai!3_rx)TdiZ8pystwb!*xqmB1;LM5 zk>{w17I|GJMsM5<2cK#Dq-7oXW482NcHHid;+!COUGjyL>&MC002ovPDHLkV1fhm BntA{L literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-desktop.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..8934336b720f998c2bd92cebef335b4db886f35d GIT binary patch literal 361 zcmV-v0ha!WP)BJ>u0V=uz5l6;> z3vdDAP{`P==%|qDL~0czphEHGIS^Y=LlQir3-_Myo_pb+5&)M`CN1FR+&KFe8L|B73j*r4HRD}tyPR}9)Cl!(>Mkcqz=y+u2O<1gE8i z-Tu|flv;lnC5OO)3DD|x`@O*i(vYRrOT)nH_(K2*(CoCf%9`iW!kz;(!m8$8DE9~V zH@65Ov1&EyOHKS>xf-DEc?A7604C$RTtM#Onbvu&atR#QWs;1)>PDkFz+VaQ2?`TP zfJ~|P!pR@{_Lu;#)8{>}7V)pnTA74-obOt00000NkvXX Hu0mjfzyG3+ literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-home.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/16x16/places/user-home.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c2a3a99e68bad40fc5d9ac18da1fb1ba886f8c GIT binary patch literal 378 zcmV-=0fqjFP)sii4&!=SQV!zmPg z&0;C5oo4G8IMM(--`^4 z_wYz8CB;e^)3z7tQhRsq?(zI}{LL!;b{jdFWDC$ulc?KcG#nyy1EDXzGBseWR3wgL z-UcsNDfQuxALLw@oa?3 + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-documents.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-documents.svg new file mode 100644 index 0000000..08583f1 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-download.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-download.svg new file mode 100644 index 0000000..4fbc92d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..badd5ad --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-music.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-music.svg new file mode 100644 index 0000000..9a02a94 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-pictures.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..d1c1134 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..8bd77e5 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-remote.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-remote.svg new file mode 100644 index 0000000..899eb22 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-templates.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-templates.svg new file mode 100644 index 0000000..53dca7d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-videos.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-videos.svg new file mode 100644 index 0000000..9240a30 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder.svg new file mode 100644 index 0000000..badd5ad --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/network-workgroup.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..efe0217 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..5a9bccf --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-desktop.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-desktop.svg new file mode 100644 index 0000000..c637d6d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-home.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-home.svg new file mode 100644 index 0000000..a483137 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-beni/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-documents.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..77970fc1c81bef039a8c6ed7593a2d8b9718ebd5 GIT binary patch literal 369 zcmV-%0gnEOP)S{wct#J-J>NZ-2mh1+Xf#6GHjEPl4nPXAzn$FH z>*0JEzyz=jd4@=DzEa!Iq83HbWe^0((gyc;*CB|j49y=6)M0QGHcj1xW#*{t?Bf*N zAD&XlUaQr-0M1Q-qFdVaw`xdfth8QA183c<14w{E!8=$rypTw93qUHczWcM;gxBs1 z{kZ#g8=VoqwQ;=)qw#<^?qml*ijgxKX0lc>=)o{OkbB z%ClLn>I;5I0;qYSKT@O5j0rFpcTb&Z@nJaZ=T-^l?X_t??j2`c`m5dnm0P@W zB&!4(z*6!Yk>Ac+U!Fy)+wETYzMrnV;Nkuz0C8R6@+U=hUyt(Q{a*o`)T1aRHP;kxBnL~hOmxxiS&B-5B=8ZY&f z{s7=Bjta;ys|(U(%18K$5Q19GM(f3BsX%E1$My=Sq`Xux8iquZ&q6jduwAd3z;?aT z(tIR9mZZ^2@>hO38elYeJ9A?1aXcQBzk4nJ}Sg4WNj4z}gj z&d*%&d*A)iSo0k_9@O6X?(juOXTH{ly8WO=@$?m{(qimm`xSgBJu|NUNpp!2qn_h1!KdA4xgWt^aAoF6z yu-og!v#0-!SU=rO#v(VZ?DWxc>&%z)e==75HY-wlpZ6H(T?S8AKbLh*2~7Zu?S32p literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-music.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-music.png new file mode 100644 index 0000000000000000000000000000000000000000..92d7afe1167843acf21ba0cd5fcb8cfd559a3b6d GIT binary patch literal 370 zcmV-&0ge8NP)=Sh_xf z4`3)POsIVv5)&;5VzCnbcV^cDMH898#;>`P+;h&oGXwub0JK`Z<5<=i0v8~Lpj_4W z&8Dx`1=s+NB`*;1-S5=itEjiz?Q75T^x6#`A8vgRHwiX>wup|s6Tf4d6e&B+{y_sb zSNPM5B7NBHcCLU+8z5gO)ykDRQd%30m(s$`7MlPPAeSp1ZE9X3;`A~g7NnB7e=xoQ zz(?@LbTUNgkV>_&QX@^~DAJ6E0lTFdV)0qY0{Xodv{K7z!gz(9{2rm2k+L&{YL+~} z*~;ROU7Ww_DkMOp^u(wz@a?eyM$`VeJI_Cj$Ak1H=8UsF9|Z5GNr$fF6Wu#kRJuOr QUH||907*qoM6N<$f^Y<&YybcN literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-pictures.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-pictures.png new file mode 100644 index 0000000000000000000000000000000000000000..f74f2bac7aa4be3b4edf80b1d75633a990a7fdd5 GIT binary patch literal 365 zcmV-z0h0cSP) zKRc!3&0ep23>=vNmFn__zuH1dBhz^)4cvu#8$behUVS@jcqXCkS%DT<`O;rX{6YWW z7Nr#Mf!tVHpX-x})f#FwpT$NKw9ZeUv}Q2q@iuwIvYo_&M1pd;4uEABW^*orV#%Xe z@)CnT2-GxwPIdm$XT}70dg&jylgjmYJhHQd3r=Y=8b0pjJvvwK&=*N)WgX8W00000 LNkvXXu0mjf1R|R+ literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-publicshare.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..08c5a1363542cab6af3308de7677071e64c19922 GIT binary patch literal 378 zcmV-=0fqjFP);)7ep)3_O*(Y8?>Wyoyzoy6fM(M*O+`6EU;!jBD6d7= zw(W+q04ji~$P+|z^_AM|h(@c`I(Hl=nz`Wi=F$amkzx6hLcH%BxNSAfp{Y8R^)0Mi z;m0kGqguDyJ_AlwfPA4;Ew46^Qkkn>N(F1NXah)qT&}n`Z+I$^=u02S4yw%CBStJht!A4TEZJ`_JE`0_qz(gmK7o=YhcYsBi9O18BO5 z29zpwywO|s1yhh_pe-AzcL@-Okv9wUU7r~h;Ck literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-remote.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..9a274f3f4551655a7357e17047749027a5857694 GIT binary patch literal 403 zcmV;E0c`$>P)uknk`T&^<~;QI0;2Zpl|<4%Fjh2k_CQYt9#9^@+kAp6@iJ78%MkY!-m zFY*BAy_>7k^_{(=2l-t!Lq;&au#A|M{NyPWNav}%51kKfE|BAYfUk zH2~lMU|Ffval=}SA{syp#$@TCHop!2H$cz#tCI$Me#Zd@Ke66=-iVmdTd&=;hnGB0 zr5Q+>QSmZ7pE9Q%&#i51K(W|p+IIDrlZb{(RwBMdq*kD){pN{MtKPT|y#VhW*R8cR xpfA}4DSa=M9%T?v1pp+6$k^#_=oyUZ^9Md=XXY&!By9iy002ovPDHLkV1g{6sBZuO literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-templates.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..203ef0e6fc2fb728cd01f85afdbc44e1588fdd1b GIT binary patch literal 367 zcmV-#0g(QQP)+wM9l=kKf7xMG3p2#Pc_Mz^WAfE;hz!!^?K;KN}VF`01`0e?dYyn z3uns!4uGrVIU>3JN^L)jYNOG(3W6Y7+Th{-CIoSvqWO~|J_?S*reph&aWs{k1H8Qd z!;9l+ztw790v8TI!7uHWx2i~~RvIs*!doxa03<*@Up!njypTxr0+0wY-~H)y%zNjB zuIc>U#%7q0Hh!_faMZ_`*Yp5LkWy0G++#W(rU&G_!s4xz^;p=?n9=lm27nE}lpdgU z7Ok_^;9L?Qo<;U0Et+Q>fc~g+=1mHZgF#oX5?*t2ldgF?NjvmceF9|UPg%8yb2|V4 N002ovPDHLkV1nR~p_2dr literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-videos.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..726d3e18d3fd6b84e618a4f21d5a8d4c097ece11 GIT binary patch literal 365 zcmV-z0h0cSP)ZZ2x{3XG%m z61)Jz;9%0(OK~J=i~|xJ08>Dut&?a1g%Z#Dp7WfO7yc;$P_Mg6Y1$bA10aS@c{{wX z)!gwSfDWKEd4Y)U{!&}7qUw3xwc|M9!Uc~Hw=RgAC6@ad(Xn&lHuNM%M$fWYIlwRr zQ@1Dz_nXbe6>zBo*oBSV@>UfowN&*|Y8b1<8h`{a&Eiqo@I)fcP5`kWvt0UDi9Z17 zcH4Y?4$0&cmbE_9Ba^}}{xgH%gSAqH;UGvXNdyQnZ(9n$&fXy_d4txQk5c(Wz}z2L zb_pxZf@mBL7Wk(0lhFZ!Vf)+|*-!m`FPkQuQ~6P^^M1N)=}dhCIR8kkYa$I}00000 LNkvXXu0mjfwQHSj literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..beba50776401bd8a995e46888839113986d7490e GIT binary patch literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_SJzX3_ zEPCJe?an%sAkjAee*%vg<55Q&McF0h4<{YJ}Sg4WNj4z}gj z&d*%&d*A)iSo0k_9@O6X?(juOXTH{ly8WO=@$?m{(qimm`xSgBJu|NUNpp!2qn_h1!KdA4xgWt^aAoF6z yu-og!v#0-!SU=rO#v(VZ?DWxc>&%z)e==75HY-wlpZ6H(T?S8AKbLh*2~7Zu?S32p literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/network-workgroup.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/16x16/places/network-workgroup.png new file mode 100644 index 0000000000000000000000000000000000000000..d8dda14c83ada1771a40cdfede601ce967a77997 GIT binary patch literal 510 zcmV$e}G^qf{+VXL_smXAqbY@ zKS&^gm0)S5Hc62-0U<>SFNj5o6bUvd1lL5H_b>zh zDF<+Cay%Oza&>VICxVK@i9?(qk~Ri|tr`Lut|NWRY65i5kwOtje>{+ zV0(LS;^YlHfSF+-0#;g*Qe8m$03yK*5TXUX?gpH7<7hIA6D+TtAHRO*F)B3AG~O(& zy#9C$u#isM-BE5&U87d7qUupqR6LUAmsI^cz=!vnacKqd05L{_r3l_v5s6sH`*vQ{ z(`pU4;|K)=q8XuUj%8Q;ZBq0g%b;3I5kd)YntJwNp#Yp4xjNl$f0^*AROAgl!b_kytR+ysjf_L1Pm<)BAnr|G4l^1i)Yr+P0=0A#eZ`M4hety5A48 zRRSHr*5nC7T`gNDkD@mm4$p%i$X717y}1lQT+~>8uTks=2jNIBOBlMz#`Z3b>;HHw zio81>kIsNo9l-NjJDtrQQfigXOR3>BntcEX;JVGds^tYjnG1mu81>rUl>R~VGT|eM z0kG@_p0{34kfw85?JfY*r-%9r0J1CvrBJFoQ!3C+i`QrZfMHto1-{?@Yuo|}P-OXg zrSuYC9Ub6pK0S64?=FsKW|gvKuO+kS`LOQMU-<+gCrpOs95*-s0000bksk~9K2A1V89`!%o)$#&ASjR8WKmufo z^tHh54?Z(Gz{}gyu{#g$X0xeTBwVqa`E>GpSoY|jyaRv&LhJ_saB%*apU zv{oCfR^u2rG6DQtalN!s!7$`P>kUKVE)}W(27u=kwigXgC8E?65DBbg=}*P}K&c6x zb`z}?`FxqQ?acHUae&tS4ZdHbxV%ZXbB9uI$pDs>rvKDtG#VgH3n^z`84Ji}3+PDm zJb1*8sSkhrAmeys94|Kbt%07X(Ol;bePm34m)G`zJNB=K!+vU!aN2gp{a$A;>C%~c Y2drvTi+&jVa{vGU07*qoM6N<$f_ZD7SO5S3 literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/index.theme b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/index.theme new file mode 100644 index 0000000..247cf09 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/index.theme @@ -0,0 +1,24 @@ +[Icon Theme] +Name=Tanin-chikyu +Comment=TANINUX Fuji accent (chikyu) over Adwaita +Inherits=Adwaita,hicolor +Directories=scalable/places,scalable/mimetypes,16x16/places + +[scalable/places] +Context=Places +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[scalable/mimetypes] +Context=MimeTypes +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[16x16/places] +Context=Places +Size=16 +Type=Fixed diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/mimetypes/inode-directory.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/mimetypes/inode-directory.svg new file mode 100644 index 0000000..95caf7d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/mimetypes/inode-directory.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-documents.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-documents.svg new file mode 100644 index 0000000..f7a30ef --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-download.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-download.svg new file mode 100644 index 0000000..789194d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..95caf7d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-music.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-music.svg new file mode 100644 index 0000000..c99bfab --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-pictures.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..146b542 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..5d41d6a --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-remote.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-remote.svg new file mode 100644 index 0000000..594dab0 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-templates.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-templates.svg new file mode 100644 index 0000000..934089e --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-videos.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-videos.svg new file mode 100644 index 0000000..ff188ec --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder.svg new file mode 100644 index 0000000..95caf7d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/network-workgroup.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..b114ce6 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..f74f6d9 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-desktop.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-desktop.svg new file mode 100644 index 0000000..9a28d5a --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-home.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-home.svg new file mode 100644 index 0000000..8993416 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-chikyu/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-documents.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..733622f1c476abf40cf695d87a7e06c6a47ed570 GIT binary patch literal 377 zcmV-<0fzpGP)Lcjflhq+BSm&?rjesG$$^iO>P XG%8dmbzz#F|7J1+|C9g-y4|7`7LO6Qc{v;DVdEwU z+Q~A20+8e4X$Bp#Z?4#UZ1sA*OW*hP(haUVw}F7GH8#IbwRISr`+a3{IF8MJbBB$Z z`=_VVwAp_cTmWYZV0+8kZS6I%q+DsdB_(ywZ39SvdaZJ>YWPzbI9~unX3sOt;)Gee z_z}h%07TI{0FG_889Q;Jc^bYV5R^*=l$vif0&HdBRq_v*!V3YTaKhU(%3-#JS1qpx z@Tz5F=%*xr)@iiV`Aa& z^8n#8n01dM=uV;#88IQeb6ZF$l+0Y*_Ak#l-}fKbGXg*j2ELQ|mk7f3*v#~_^`um9 zRU~i$Wu`c}JF?m)39xOf|<=VMWxAD;3b>Vpx&f6y(*27;r zo#*Z0cyb5axB$mT(P`(bg(GFDdPho{QP=~J0L}VAw=Db}1Ky7SVJcSiR_tp31-k`W z=Sy2quhxtU@HSmsMYZrHn}2v^;2@}ezE85gZRwx+01m)0Y2 z0K?LRwZ|dRxR5A@@DLT+Xbbu(O#fSNDn-QmzzlNJ$~*)&V3yA-i_4s`#52thoUqP9;+RLi+~* zFTDY8<4=Mhq_$sLIHT>YqKN)rOu4j$5JDTGHF$jL5{7EN>d-!6JD+7T4Mzg{SX6sQz6#du*sd1m)1w% z0SrqM);@G7OfWy4=mU`-3qR4!Bcn^NE4 z{$YR=5=fA@Hdg2Q(zzA*YgIPuwb}1xAy7)ve~K87Kd|le8>SL?ZW(}lJ~v%wA}Bcp zN=_j)_=7+Q`L@vcOJb5_On^c3a^#ksn|SnUEfX#l^Y6oce2{hMTzvw1y-TI>TYBsO O0000(|a=l(ZbzN6X-Qc`>=_zm)WApn^XuICA+t6hbrfINR zTw*@u{P+`LSZqABPJkmFAh(b%lvna-syb7=rmAGqP6a>($fo9YW;IU~f%yeMi457` zU^t@P@ueVX2mQf_Km_k(oOx$?Uo<}Bt!!Wure e{JprLzv>f$1X~{(7tLn?0000J*cB;t%DJF^lkclaac{c{02}~_Fg}1L+GLE~ z5UJb##W~7{M;EIAAb)zAVZ^XFKaDXnHK6%Q$x5xh1K>^qz|=%$c400L!TE2I0RVz? zWX<%7j*vCe%fp&=Lcq`fFp5QThbr2&{}|Brx{pzfUb|ZddLQZ8X{;rZ#-;1D^5G%X zY37JAC1m0?+)t)P>h?oxO9PzRz5CLh+~*_`!B9G5ZV*>XP}F4V!+LKx*MatC!?W$D zrUrbK?0}R$TUJ^6K2-rga)_CHt`lf6#`OLZ3TkKP)#7#J1;u5+5H99vj4y{XI z0fwOoW0ymsGlCG(lwiZ-whj zUT2*K&;Z2Dyo#Yq{LNJkM(uvTf9JZcOx@t2`{XHbpJDTtNUCSvjXThi9EPED(rQpC zTR&zpiduu$;Vp2j0XT>Dal2VZ6=JV?RS2rK)d3Iy)pGH4-|$v381VUsD6zh4SS(kJ zrvW)#r&cW`f)Z;-6Q71tVIGFOP5kVDI0m5znnw=dVwD|W7Yko!O;r;Ce}1DM%mHv} zx1n T^HS4000000NkvXXu0mjf2PT== literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-videos.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..0e27ea61cab1b3cabc680262391326fa86c4988a GIT binary patch literal 373 zcmV-*0gC>KP)zh44_htpnmKP+H;{-us_(zW;}RN&xuPs-bp`BLvpuIed|w z{I&0w$8iA~fYLKgLZ}e_ z^8n#8n01dM=uV;#88IQeb6ZF$l+0Y*_Ak#l-}fKbGXg*j2ELQ|mk7f3*v#~_^`um9 zRU~i$Wu`c}JF?m)39xOf|<=VMWxAD;3b>Vpx&f6y(*27;r zo#*Z0cyb5axB$mT(P`(bg(GFDdPho{QP=~J0L}VAw=Db}1Ky7SVJcSiR_tp31-k`W z=Sy2quhxtU@HSmsMYZrHn}2v^;2@}ezE85gZRwx+0HYBEryR#m?j~HW95MNl9+1>Y1J>0)J#=GQ4vH95kXZ!6o8zagD;;Sz213we-sb^Dz$30TAxML%ArU(s~VyO~y(s`6|=XQ-olc8NrNTiUn%~vkidvZ{KCM(E>mwd%^*;03yUxHjs}g z+l>0@c^S4)w2{FRWGBYNB=`7A07(4-u~GrR9AZl2zP}KuJQD!UE-u~P-`{HnGX^TF zX#!w0dn)fN06g7%yaj9lT)Tc}HAbK8EajtsQ4RWWH>R|FQud;S34o)+gBM>veYk68 z(ZA)M@$9%~-*~uYCwcx^zW@f&s9}xLZc+dM002ovPDHLkV1lRkW; literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-bookmarks.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..023b7bf429f2f7bfe1e144ec4a3972204a6a5880 GIT binary patch literal 365 zcmV-z0h0cSP)zfMbwSfxlKVq%m9@e*#>c!VB6jqXj1OY0Fh zfMIFUwZ|b*HxdF8ga~4pjtdk?w9>|JHJN$u{V#t8{)qA)&+280F+#3x4KtG7b4MkT?mSfT>}sR#r)bqQu7xI%y|Hs%A|5H5W9nR zx6f!i0bq5-V$*ff1-wl{Dmx_r9-lht8vumiboMoriq*shSe8Ni`Q`H*zoOWHEw}Jj zxj6zr%4nQOy?~s~JO)6g_j=;2+joQE$V_6+<*fH^YjBiy=&yVL@%v7+Yasoh00000 LNkvXXu0mjfG$)-H literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-desktop.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/16x16/places/user-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..9899f35d92ab37084043aa8a4d618f09cd50ed4f GIT binary patch literal 364 zcmV-y0h9iTP)rP4}8gD#K-@e*#>c!VB6jqXj1OY0Fh zfMIFE+N-!RBoH+r1Vao&O$^g4!+-6ntp2)woZwBau_Slknj~LbrSMQ|x7#O;Mnf!YaMrvC6*$e%{8|caCp>C&^fZTQ8f;Z7tgd)} z{)Cj(PPcaq9O?i;$zQK+l+jdmsqvbsQuMt#fC^At$!#wi{!jwuCjcceGNr#Mbq7-+ zxVal3grpJ_a2;!|FSYkow?|O&*<1^_y?;Ex7E$niqwFNx_aqXYD#(Y~MeF5}^=xlGt~t+zoh-px96 auHFIP8&|oY + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-documents.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-documents.svg new file mode 100644 index 0000000..b6fc63e --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-download.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-download.svg new file mode 100644 index 0000000..c02b909 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..e86ffba --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-music.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-music.svg new file mode 100644 index 0000000..2732989 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-pictures.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..4fd1fd3 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..88ebe43 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-remote.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-remote.svg new file mode 100644 index 0000000..e65bbcf --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-templates.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-templates.svg new file mode 100644 index 0000000..da22b20 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-videos.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-videos.svg new file mode 100644 index 0000000..68f7342 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder.svg new file mode 100644 index 0000000..e86ffba --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/network-workgroup.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..2ec6338 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..a06b602 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-desktop.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-desktop.svg new file mode 100644 index 0000000..1968d1e --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-home.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-home.svg new file mode 100644 index 0000000..b342940 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-shinkai/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-documents.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..69614a35bba0604ebbdda8e2632f3960026113d4 GIT binary patch literal 367 zcmV-#0g(QQP)ckW{^D8$bY5H#YazEiW(_XDPs#(rREfpV52ik&Kf+ zMIvj^9}TDnbtcmZgVCV4z+ezU&4xS(7r z)2uZziwgvRPW3c5xx%7j10<8=*!P0_@!PAjPPtO9d<@6K!=g)nE+*AhG}BpcWwI;%KSB6CNtu{0fznmkNfHA#dsAM;54R0u`vzQV|!x z%uKp6`HOi+0`w>SQx!&!sr%|J(ojzwLgc%P`npp7-Sm^8@CD$l0qZuf^`QRS#e|6wKT`S1xO= z`OU7!6(X(^Eq5o_%+Fw-SEN(#yL9LEiGEjB@^61|Y297H9|zaZ_ViVbROvquz!-9B zYSrpnE?hHL#&LPRXpd!J6quH09r-X;=h|KF4cjJk+jZ;^*~8VxqGlRjc~EvS!!-HR vbvOCr%;vw(6WdV{{3k9qZ10|>_0QN9&RTBnIiWAU)xUQy>696RVcGmwlP7 z3vd8z&pFrVyX9ExRdfb}!BrTBYV8I$kGIl->jIlU=RzIJP!1iF!Y#S<_PW%9+Mk)q z)xq#(cnMrM0L^N%-Dw>nY_URUTUo0{inw7UpE>rx^262?COc$-9g ze7}=s8NL1PN{wwYt6UM!V%ocHEXy*M7z0Mph)ku6(}eK}jqL_Wnous4Nz$ZvfbaSE zp1-)8umM!AzKjY3bB_ZM&7!kvPlv>e>NOcoVI&leEfQqg_9Qwo+ zxBzh|bnJ2pqLV5uq6Pnb?>TCz+So?V=)yhc+>Z8wdfg-0PY?RZaCV zslWjczO!qwC!@86TiIx}S_f&G>WKtL=f|o5hdIbEjntbeRc$BB;kh2`3v0w-{9~t$ zUTJsQ`@o(9P%T%N8?{xWkW-zPLXw0@6F>kYQDtM=@IV&p4GXZ=&6OTe<`1sA*BD~} zkU~(OtB>`G-`Z=Hd6s4uv40`3#?raF;r0Cm-wiSgG6|BI3IIjdAI`Z5q97s)qRikI z0&Vp3MCUgRG7doZvAa_arYC literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-publicshare.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..a178834d8fd8c1e5494edd92a2c1e476a5dee987 GIT binary patch literal 379 zcmV->0fhdEP)Nr@i+(_v6ApNdc&H^*}y{i;npi%Pv-KMaCEPi$~}aiUUil%VT4;W{oWi_2vF z?2nlq=~CmVaRBVw0QpS5SSc(aEHTx2VF_}6t_ENMbimQMACRVUfUMGq)H;=c3gD%x_HJQM3 zJaC|#FVP7*$s3H3jzl^#c5VUakq#$`zBA6)0L}O2ZpP1Ew!3fcG~u*N4_fWkcG9E2 Z@&W9TTP&PX35xovPDHLkV1jc!rTYK? literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-remote.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..36c9d2df8ff0f44c0e475e791010bb972ea69261 GIT binary patch literal 400 zcmV;B0dM|^P)I2xSy%xsUSy|Zg3T_N9 zU}LGJu`n@~+W7`n6i`sD26Y3gVY9%@wUAv2#0c7XlS$^@bI+GK$pC*W1^~{vV+vCo z0!R<0zBOl^mAA{`M1bRqN*SP&5#^e+KIMYbs7Yy6clMnI0B`^xP<{+e*kz30lE&TW z;SjaTWn~ipb literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-templates.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..70ff730e83ac4b5afae436d0d2238d660c7df414 GIT binary patch literal 364 zcmV-y0h9iTP)V7JrSM+&*ocqt@Juh|6<0L|^4qfNte8H}?4U`%D%FquvmybegR zWF<%xHHK+S=!fLvoH&ik0}KWs1nr>Bc=}Nu(5TfHwJnS9v+|8;o&pfmgYp2^ad92D zFgTY0&@(+=>s-=2V*@04a@O$tr|kXB*(6-ss(p^K(Mj2%f9e~lB2$^}9E7a^0000< KMNUMnLSTZe7N36r literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-videos.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..fc3bb0a100fbcee5bb8c6b282f20e76f20186d29 GIT binary patch literal 364 zcmV-y0h9iTP)sr%|J(ojzwLgc%P`npp7-Sm^8@CD$l0qZuf^`QRS#e|6wKT`S1xO= z`OU7!6(X(^Eq5o_%+Fw-SEN(#yL9LEiGEjB@^61|Y297H9|zaZ_ViVbROvquz!-9B zYSrpnE?hHL#&LPRXpd!J6quH09r-X;=h|KF4cjJk+jZ;^*~8VxqGlRjc~EvS!!-HR vbvOCr%;vw(6WdV{{3k9qZ10|>_0QN9&RTBnIekL3W|^_`2jW-qE=?Luuus48-idd z{(}S}SP7O^YLgVU1_D-M3K1I>6v09iV?aUo>)ttIv3K1|vRM#cm>Fgc?{hw2;6Eh* z=PoUFt4FjhDyS%8f{3CD!wr%re!Tm2cYFKUtui10R1QY)zj2!Re}Y~j9`JF0W&)pEUW>%dGS86i4FlNk)q=$77R3zdw$^L zg9Vz$X60`SBM8wUIe9G1X}>w`c?iTEDjeYyezdT3Vjg|48mW29!0(1|Rd7&ri#~uY$6AyS=BMUw^w{K~XVO z6*I-uP*GG36{EMCWfKAI=jU&}I`rXTs8ky#=9>4jL0p(^G#+GGywLCW<|{NW1`Eu! zT5qBFOb2OoWp%yV>0fhCcyip6uiaU(X`X-99~;)L_WG9@pa1{>07*qoM6N<$f=L?P A^8f$< literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-bookmarks.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..32b774b7e2b5f4d225cc4ea2ded9877f04eb4b79 GIT binary patch literal 355 zcmV-p0i6DcP)4+!r z0OC@}+T#R7Hxk=LTd=jJ({Uj!0jWVGANZ>H^pR(dO1++3*65c9#OQt{9~Yth_-s$QVy2 z0Bm?Q>OtL1@R3hxH+BJd?!A~d0LW)K8Vy>T3p~dq8@_$NE1tQa88-hax5Nfeb2TY7 zU0{)M0DAAe)4&fON8_PeCS3DspZ!t)*mUWyd;!FlPsX~QF`xhd002ovPDHLkV1mN8 BnlAtV literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-desktop.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/16x16/places/user-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..e72b0683d9abf4d0a009d24de6029daa0c831f34 GIT binary patch literal 358 zcmV-s0h#`ZP)5$96c_-Y4|KKQoo8 z-ON8e4~D!z(n}`4OPCT`8R*_Xor0A%HK~y-6rI1Zd!cY{2Kl<8In$`eKY;a=Gpqp^Q#3ghAYIFt0p*60+ z1sI1Wj9m`Iff8b$EtR9>L*Oz%10J@>*tRRBn@Qcrkd9|!=@yvUloOq=Os z5x@qpJo`|iFJ`fY2hnJ?S|>@8$b}8guFul~oaShLtAyN5leBG{9IoTCy|zUqsQj5p zCF|{8`xrQ~0pfDJ-l%ONEV0yjVF{ujY64gQQCQtsHawL^yWaq`c5WcG8_xjxGT==3_o!+TC^{bfLB?=2#1Srzn17`a5Uo^EO7Zme zjPI39HuDd{Qb`hB&R?2lY=HiI|DYUHZU!H(?lNJ&=znH|Y%lN9x%vW8 Wr&rC~>%2e!0000 + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-documents.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-documents.svg new file mode 100644 index 0000000..c42f9e5 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-download.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-download.svg new file mode 100644 index 0000000..8c986b0 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..a30adb1 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-music.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-music.svg new file mode 100644 index 0000000..f4a1b96 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-pictures.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..01d60ca --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..27ed571 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-remote.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-remote.svg new file mode 100644 index 0000000..455e7ff --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-templates.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-templates.svg new file mode 100644 index 0000000..b73706d --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-videos.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-videos.svg new file mode 100644 index 0000000..38bc1d6 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder.svg new file mode 100644 index 0000000..a30adb1 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/network-workgroup.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..aef3017 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..402e093 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-desktop.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-desktop.svg new file mode 100644 index 0000000..230b74b --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-home.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-home.svg new file mode 100644 index 0000000..629f88c --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-take/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-documents.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..8170b1a8d9dbc855de350f3e65f647ef1b3c2804 GIT binary patch literal 372 zcmV-)0gL{LP);2k~QzxUh+AO0x;;QN=RshPV9tk0$Dzb5X5 z@3-D(0d#f#zln zyKMbZ3&f7og|_r^awAX1GQ1R%H9KP>S+uug5 zM}K-+k9VJ%FXzCS3$VGRcJ^vj9O*1IUP=d5iRu6jz(!^5VA=3g!r=b^z<7D4Y&aqt z&VPjU1^~VO7XW47ZnJi>Ow)e1(CMV4pZ$r+lPDFg z22d)rhGyjeXg%&PbpFyb;{vo==}B2dkKJz1UnU%u3d7fS_h{9jx%viHNKO$<2b!n= O0000x=;KqB!3B32nI zYY!ln!qz!X&{8E9vJeg8?4$^hfG*h~ZT|AT_kI6^4I==weh2~;^eu#oq5YVd*1gu9 zpLGHcpeBQXwcLpnZ%w?#X_{UoNn+N1aC`Hpg<$CL`LoSc_gW`oujs<}E5xTqM0??% zoiX|G)A;2QxbOfD4x{!-+(If@s$MBc)C@ZS5@5esKPw9_BP{-}0BpsHUWr@nR!}T3 zCjW8;my8E6nM}_cQ8>!7PrnSjyHovseb0KXrT=CQbeuT3&%MBD00000NkvXXu0mjf Dfm473 literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-music.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-music.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cd34a2253bbfd8992c5341fffd55859a6f0d16 GIT binary patch literal 371 zcmV-(0gV2MP)kq08w=7IHq%`!23N^oq?>~ zL{T%H7hnOD4Ch!WuEkVtNxTKER_iPbLpis>)#Y6z1Q$7)KdQ{!Kaaw;rBm2;fuOO8 zzvBJ5lQP?RXuq5SCl)|;!>{iKH4M{OXuN3}_!X}SU;wOEmiHDlj}Z!c5&)`@%RPnm z4*=r#F8$8|Ns|Sm3{BN*ZZ;;F}q8}(d$R=An(wb`~c?jS*{<) R1Hu3R002ovPDHLkV1h$7q5=Q_ literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-pictures.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-pictures.png new file mode 100644 index 0000000000000000000000000000000000000000..1c79c021d82461f71c2c36fc20b7888c93833847 GIT binary patch literal 367 zcmV-#0g(QQP)D*6N>u8b?! zK7hCsy7qC3A}-oe5eayyni&_;QnjItp4Bk_Ip_ao;Gfa}()7Z%b$dsF`*o=vU-RfZ zO=};c3Jd^8*n3KGrVaEuT05@S>xW5_Kh&Pt- zXWU=2JeOvdh=hi(l(00g18Ic~ToO=0#6Kvhbm2UPe6 zcefp+lmG}_P+gfD>MP_{z+x1zT%AR!KM*J-X*D0{cHgn=;ujVY1mP3_mR0GWOA&aU zgXcMg!EXe4E?-7Ef2fx+06Lwg-D%&w%Ccv3obcp?_13!2w#zOZs!!`bOT~cMJe2?d N002ovPDHLkV1f#XpDq9Z literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-publicshare.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4ad1e694dd8bca1a0592cbe6ec232595322a94 GIT binary patch literal 385 zcmV-{0e=38P)f0(?r z_hLSY0b2grIi&NEmil z-zcR6Shj(ML9vvh)e6%$7|65?vX*qm{a=XVo;*ULfky{uG~RcoT<0nXKFm?f<6~Cm fxfyJy4gFQ0QVCi^{n;i(00000NkvXXu0mjfGn}L= literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-remote.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..9b4adac8a64325355cf5361a7291572be46b34b1 GIT binary patch literal 401 zcmV;C0dD?@P)Sam zkbuM}$=b82xvN59+nWe*esWVF;Nn~9o{_p#Tu{X2tMXBCzXAXp02Cn|Wz#M(_Q*&* zMGi;l?DTdM0OU`j>AHs8+7it9M05aO$=9-T0N_~yfVF6@tmd-dg8#NM06+*1Gn3p1 zS4d})`AN-z7(ovK0BKXX$28ru{}|wX46D-```*w2zCKd*t+{0w`dz2p%Z@ks%C(4? zlEQc&&&T64PQB6F3jwXxOS@KIIOZgbpebplb_A()C~}$T+MQ v1CR=1+b&7prwRZ_4zZlI20=|@EWCdLQf_m-s6G#&00000NkvXXu0mjf>#(p) literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-templates.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..f3a7cb7dfe7cd8d19a244cdae62adfb0032e2489 GIT binary patch literal 370 zcmV-&0ge8NP)=uc2#t?!5Q`Yqx(A$Sg}1Ed5Y1cUW1NiwSrXxfeWtZ8Z?pzs^(dW|rQ0qAs_ z)d7a7W0-njFqZ&G(<~`t<807*qoM6N<$f}i)Gk^lez literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-videos.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..481a716ddc19ccd594c9940abfd56436b977fc9f GIT binary patch literal 371 zcmV-(0gV2MP)tAPC~A4X!TlLLs=Ap?O^m&HeK*s98FPZD%NN zui!6we`aw!+x=;KqB!3B32nI zYY!ln!qz!X&{8E9vJeg8?4$^hfG*h~ZT|AT_kI6^4I==weh2~;^eu#oq5YVd*1gu9 zpLGHcpeBQXwcLpnZ%w?#X_{UoNn+N1aC`Hpg<$CL`LoSc_gW`oujs<}E5xTqM0??% zoiX|G)A;2QxbOfD4x{!-+(If@s$MBc)C@ZS5@5esKPw9_BP{-}0BpsHUWr@nR!}T3 zCjW8;my8E6nM}_cQ8>!7PrnSjyHovseb0KXrT=CQbeuT3&%MBD00000NkvXXu0mjf Dfm473 literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/network-workgroup.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/network-workgroup.png new file mode 100644 index 0000000000000000000000000000000000000000..2a5c51cf41b945291c21e01c8c5d8a619aee7fc1 GIT binary patch literal 506 zcmV3{0XtelL@_uAn4I^5j=|j zpoI#ayn5A3d+=(-A0Sfn(2EEK_0U6vmSP%`?#_DH#C$YqKzv~tm|>oI2WH_v{QUlD8Q=jbb45|DT}DKQPY{=*6GdEx93LIF z&O7h`W`-pLD^&!r9Fl+=LZ$*phFJuG6cXLR5iaF*#!dtgS-P`w?-?pwsn$Nc-F>r{ z9V8=}VZfl@Wo31ZV!ncU#jED%1yx=4Ubg`D_rD}|5f6}3!cs&GL_H2i2sYI+fcHwh zzTn0yj37ivMhJ0~o$NlB@&F+W2xoHuIDz;VFin{dV;ln-HyRH+ox_$_C74?e@d7|; zx literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/user-bookmarks.png b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..8a920531b56b21b905f31a756474114162286975 GIT binary patch literal 360 zcmV-u0hj)XP)i)Us4z#=pU9aHmZeusB zAAd=b_8tZ=r@)B@&}!S=9jAk$Duvdos)F6L`Tz>RMssbiXn28;=t}{}N~v^)+#9?F zBPQcd0BUuE&6ZtG5XUpRy*2>PPht560MpqVApvB0fnjJ2gLeRQBmYFXg7#M9uX0Ni zfFwyLg{CtsG8#Y_ejKga)_oL>^&;VV&6vIpql2uZ`AJ>cApc+=P=(TtXJ0qAL(_WE?qm z0pd`|*sX|@VyzPmbszzM5R>PiRDw1n!Ef|<_nvdlz3@*2Kp0+{rfTj=aHpU0OM6mwrn&PC4G5^+C@qN5-P($WNHr&pm6oYY7@l9{@F90o(Js6*Eq^fy=W z7TrI!G|krUhL4B9fdEmE{dvD8n3DfUfUf2C;&_C=2lhl3?b2f3_vz&sdE(G z;P&PLAq0lept)eN?6>L}IIhK7&j(<99~T-FE-qzORH~N1woS&Pc=~qC`UZZd1-CqA&a2`8Pnge*WsS0LiZICAU) z#G%l!%c*rBrAn%lRIO-kt^=u5ZD@mMc<(vifB1j+rxbuFy0mT0-j(2eJ<7qGs9i)+ zGo1y{0UXudmy&a3g6k*`jaI956o#Rg+2HK-DpC|q3N*i!nYMi#g>600VVVYw%{sxd z_vcTDY^~Go9RdeBK&2Y2Y&2@9nmX5bO;ZWVUK2nCD3$%KdBbBV67w4X(kPUkQ0@-W zRM5TaBSc26Uczy$slMFaotqw&YQXAxm2UTu^ke!pa{-oR5XT9_;RwUf&<(8^pj2K& zW*LL0XI$6Lv*qu=_Z@uS$qoK!AVfBr>HMW}MhA%Fm%XLHyH1imYrWt1lbxbN ar|J`>0#?%+6q_Od0000`>Qwr literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/index.theme b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/index.theme new file mode 100644 index 0000000..bfe7ff6 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/index.theme @@ -0,0 +1,24 @@ +[Icon Theme] +Name=Tanin-ume +Comment=TANINUX Fuji accent (ume) over Adwaita +Inherits=Adwaita,hicolor +Directories=scalable/places,scalable/mimetypes,16x16/places + +[scalable/places] +Context=Places +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[scalable/mimetypes] +Context=MimeTypes +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[16x16/places] +Context=Places +Size=16 +Type=Fixed diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/mimetypes/inode-directory.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/mimetypes/inode-directory.svg new file mode 100644 index 0000000..e167079 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/mimetypes/inode-directory.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-documents.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-documents.svg new file mode 100644 index 0000000..d1a28fd --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-download.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-download.svg new file mode 100644 index 0000000..45636dd --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..e167079 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-music.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-music.svg new file mode 100644 index 0000000..ae92d63 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-pictures.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..edd4280 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..430e1e4 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-remote.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-remote.svg new file mode 100644 index 0000000..f16bab5 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-templates.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-templates.svg new file mode 100644 index 0000000..e8f4b4f --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-videos.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-videos.svg new file mode 100644 index 0000000..da890ac --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder.svg new file mode 100644 index 0000000..e167079 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/network-workgroup.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..18868e3 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..0548086 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-desktop.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-desktop.svg new file mode 100644 index 0000000..f656e88 --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-home.svg b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-home.svg new file mode 100644 index 0000000..667696a --- /dev/null +++ b/packaging/tanin-icons/pkg/tanin-icons/usr/share/icons/Tanin-ume/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-documents.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..7027695b71d9f73cd747a1a8b9bad69f210d3271 GIT binary patch literal 369 zcmV-%0gnEOP)m?rJRF1G9b z@Uk@Bf9(%0feRhL@w~lO_W-HMQs)zJawXXZ-*&$5eLn;LL;&1}VZCnI$AAZrBchH;?s{Q3 zFB2$$x>Tp++n906PJ($sMHlmapdd3 z1#|)8u#j<=Q|M6q0|}^5e0h#+r7BI@Y47y=&bb$Mi~zVZCJ2;w27~|(@fzF3ZQq#B zO#%%NNIk$YURfOv!b`F&8>VS$H!hfrZVZC!3d`rJkSD3Rn(6;OJm2TAmk>6azix%K zNB6V&C2*kuqISCzCtakHrRt?pgsny&Kmr`JqT{mgGEVqQfkSxJ=pSpg1^0qAL1FDj zRq&h90B=t(=V8>CzUR4L2CmiXi~RZVv});}`2um+HnKSHf#3iD002ovPDHLkV1j}t BgU3^h&_vo&YN>)nB=iz)y734(fQp_##FcU3 z0X%@X6uR~}g@P``R-rAenkJLDGF{lA4gNMW@V)oGHv|7f06c`D;~3Tn-~nWaaZ4@k zyJ48D3orp3X`YdBw@l4=6PDlW^)7=TP-`1J-rR-=t_w6D7^yr8!mGZiQ&_gmPRGZq z)&9&rD5RJD_sByuvM=fZfefSgq;Uu2&4($^v+YRC_-ewKp-B%H_rMeRy1S=}LY8;6qoWxFN-> P00000NkvXXu0mjfzLBIE literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-pictures.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-pictures.png new file mode 100644 index 0000000000000000000000000000000000000000..823bff565b48ece2482b192bbd722683e3223d6c GIT binary patch literal 365 zcmV-z0h0cSP)bRk6KB7eD|wp1V;toJ%T3ULaBQYUzmz-(Yxk zO_qIpfKUYsoz7I>FWi!r?=!!=1o96AnUp-lF|VT$x@8p>6cWsNEdX@W%;!o3HOHam zIEBG41X-HC%yfR!XGQ~f8s6{NZsYPfNsKaKvtA!3ceh(rk51J)M{Y>K1yusT00000 LNkvXXu0mjf31pWC literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-publicshare.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e4a13b36f255feedba5630fa248844329cc6b5 GIT binary patch literal 383 zcmV-_0f7FAP)fR4Tc{Blv4CN9R^{@{Nh43z;p+>dI1Z6W zEsZrsLlBL%^$(gHlyDFWlZ)5F>rLPHG?+KJ5ov85c;UR~`QSWofIk)kfU{yT78Axk z0Bbm8o!OLix>+a$6B(Qzl?nhmjsRDha3Z*-s7NbW-7Ox}0DuDkh|NVw1Z*>g8%_K+ z`oMMNqEuQ30Quc&hG}AcIS(tH{(@a`N5Oxc0+?< w$v#Mj|8lt^m7*E|NDi^!Wcwq`U@Yw40nZ|F!S4^SGynhq07*qoM6N<$g6Vj!HUIzs literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-templates.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..d9602eef06325506690c7a088d04fdbfe3140bf4 GIT binary patch literal 367 zcmV-#0g(QQP)&;U%KT_Sn>p-K`(58^n!3B$0cZ18gb5TS5aqxnExiqkN=lZ0FJj`A7C_1 zjHX!{TuA^di(*>2xuI1?1IWjd3)^*GKeEi&CET#AdG?yH N002ovPDHLkV1keboq_-W literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-videos.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..d2277433a4543b450a9178e506a2d7d24e2115c0 GIT binary patch literal 366 zcmV-!0g?WRP)#4D09FdQgRJFo65)&i3Bq2iJ&E=Q z0H5s+-%*4uC0?mGb4Tm_`t;18*Q2W+Bwg3PC M07*qoM6N<$f=_gxj{pDw literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..4a59c5eff57b74a67a2a0b652493c4f7105499d1 GIT binary patch literal 303 zcmV+~0nq-5P)06PJ($sMHlmapdd3 z1#|)8u#j<=Q|M6q0|}^5e0h#+r7BI@Y47y=&bb$Mi~zVZCJ2;w27~|(@fzF3ZQq#B zO#%%NNIk$YURfOv!b`F&8>VS$H!hfrZVZC!3d`rJkSD3Rn(6;OJm2TAmk>6azix%K zNB6V&C2*kuqISCzCtakHrRt?pgsny&Kmr`JqT{mgGEVqQfkSxJ=pSpg1^0qAL1FDj zRq&h90B=t(=V8>CzUR4L2CmiXi~RZVv});}`2um+HnKSHf#3iD002ovPDHLkV1j}t BgU=0eyBsmLb zeuD%eSlQZYlN4za5G?G1SZJk^CWVj(Mo75x?sjJ_-t+l9F9Gr23_HvGX8y}8{HGki zoonshshVqu6P!5I3C=l09D)E4Nqor;p1pnl=5ZC^0j4KtoNU%m5mW>bL0u8WAufmP z?jN>CC-4Afh9v{5+z7>4Koa8ISPMBIC88M!q>$Jf3~@iO9s=goo*!-}& z_t2~0er=@+qFS64C(3?zFa(N4ujuCXjYnB%EMzv-adT%s4_Ymg`ExIZt(l#J$tKSJryn{sXgwCBrP)>eFRCuI8Wh6aBidWJ7)%UH||907*qo IM6N<$f&{4P!2kdN literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-bookmarks.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..9f24b3512f9bc0ddf75feb49647156e77bb3505a GIT binary patch literal 355 zcmV-p0i6DcP)qLtbH{qlcmyiXh=n6y}83!)F z1&Bi-W0zAX;y}_W)IeMNlRQVJg4X8Yncn-I`*Go)XaM(d>^Qo440r$%ZP?6bcbzz1 ztP>ajjxbJ<+%Bz>R5RO2l3YYll&@{@aD5Y_ai!3_rx)TdiZ8pystwb!*xqmB1;LM5 zk>{w17I|GJMsM5<2cK#Dq-7oXW482NcHHid;+!COUGjyL>&MC002ovPDHLkV1fhm BntA{L literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-desktop.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..8934336b720f998c2bd92cebef335b4db886f35d GIT binary patch literal 361 zcmV-v0ha!WP)BJ>u0V=uz5l6;> z3vdDAP{`P==%|qDL~0czphEHGIS^Y=LlQir3-_Myo_pb+5&)M`CN1FR+&KFe8L|B73j*r4HRD}tyPR}9)Cl!(>Mkcqz=y+u2O<1gE8i z-Tu|flv;lnC5OO)3DD|x`@O*i(vYRrOT)nH_(K2*(CoCf%9`iW!kz;(!m8$8DE9~V zH@65Ov1&EyOHKS>xf-DEc?A7604C$RTtM#Onbvu&atR#QWs;1)>PDkFz+VaQ2?`TP zfJ~|P!pR@{_Lu;#)8{>}7V)pnTA74-obOt00000NkvXX Hu0mjfzyG3+ literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-home.png b/packaging/tanin-icons/src/out/Tanin-beni/16x16/places/user-home.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c2a3a99e68bad40fc5d9ac18da1fb1ba886f8c GIT binary patch literal 378 zcmV-=0fqjFP)sii4&!=SQV!zmPg z&0;C5oo4G8IMM(--`^4 z_wYz8CB;e^)3z7tQhRsq?(zI}{LL!;b{jdFWDC$ulc?KcG#nyy1EDXzGBseWR3wgL z-UcsNDfQuxALLw@oa?3 + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-documents.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-documents.svg new file mode 100644 index 0000000..08583f1 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-download.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-download.svg new file mode 100644 index 0000000..4fbc92d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..badd5ad --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-music.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-music.svg new file mode 100644 index 0000000..9a02a94 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-pictures.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..d1c1134 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..8bd77e5 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-remote.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-remote.svg new file mode 100644 index 0000000..899eb22 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-templates.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-templates.svg new file mode 100644 index 0000000..53dca7d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-videos.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-videos.svg new file mode 100644 index 0000000..9240a30 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder.svg new file mode 100644 index 0000000..badd5ad --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/network-workgroup.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..efe0217 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..5a9bccf --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-desktop.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-desktop.svg new file mode 100644 index 0000000..c637d6d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-home.svg b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-home.svg new file mode 100644 index 0000000..a483137 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-beni/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-documents.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..77970fc1c81bef039a8c6ed7593a2d8b9718ebd5 GIT binary patch literal 369 zcmV-%0gnEOP)S{wct#J-J>NZ-2mh1+Xf#6GHjEPl4nPXAzn$FH z>*0JEzyz=jd4@=DzEa!Iq83HbWe^0((gyc;*CB|j49y=6)M0QGHcj1xW#*{t?Bf*N zAD&XlUaQr-0M1Q-qFdVaw`xdfth8QA183c<14w{E!8=$rypTw93qUHczWcM;gxBs1 z{kZ#g8=VoqwQ;=)qw#<^?qml*ijgxKX0lc>=)o{OkbB z%ClLn>I;5I0;qYSKT@O5j0rFpcTb&Z@nJaZ=T-^l?X_t??j2`c`m5dnm0P@W zB&!4(z*6!Yk>Ac+U!Fy)+wETYzMrnV;Nkuz0C8R6@+U=hUyt(Q{a*o`)T1aRHP;kxBnL~hOmxxiS&B-5B=8ZY&f z{s7=Bjta;ys|(U(%18K$5Q19GM(f3BsX%E1$My=Sq`Xux8iquZ&q6jduwAd3z;?aT z(tIR9mZZ^2@>hO38elYeJ9A?1aXcQBzk4nJ}Sg4WNj4z}gj z&d*%&d*A)iSo0k_9@O6X?(juOXTH{ly8WO=@$?m{(qimm`xSgBJu|NUNpp!2qn_h1!KdA4xgWt^aAoF6z yu-og!v#0-!SU=rO#v(VZ?DWxc>&%z)e==75HY-wlpZ6H(T?S8AKbLh*2~7Zu?S32p literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-music.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-music.png new file mode 100644 index 0000000000000000000000000000000000000000..92d7afe1167843acf21ba0cd5fcb8cfd559a3b6d GIT binary patch literal 370 zcmV-&0ge8NP)=Sh_xf z4`3)POsIVv5)&;5VzCnbcV^cDMH898#;>`P+;h&oGXwub0JK`Z<5<=i0v8~Lpj_4W z&8Dx`1=s+NB`*;1-S5=itEjiz?Q75T^x6#`A8vgRHwiX>wup|s6Tf4d6e&B+{y_sb zSNPM5B7NBHcCLU+8z5gO)ykDRQd%30m(s$`7MlPPAeSp1ZE9X3;`A~g7NnB7e=xoQ zz(?@LbTUNgkV>_&QX@^~DAJ6E0lTFdV)0qY0{Xodv{K7z!gz(9{2rm2k+L&{YL+~} z*~;ROU7Ww_DkMOp^u(wz@a?eyM$`VeJI_Cj$Ak1H=8UsF9|Z5GNr$fF6Wu#kRJuOr QUH||907*qoM6N<$f^Y<&YybcN literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-pictures.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-pictures.png new file mode 100644 index 0000000000000000000000000000000000000000..f74f2bac7aa4be3b4edf80b1d75633a990a7fdd5 GIT binary patch literal 365 zcmV-z0h0cSP) zKRc!3&0ep23>=vNmFn__zuH1dBhz^)4cvu#8$behUVS@jcqXCkS%DT<`O;rX{6YWW z7Nr#Mf!tVHpX-x})f#FwpT$NKw9ZeUv}Q2q@iuwIvYo_&M1pd;4uEABW^*orV#%Xe z@)CnT2-GxwPIdm$XT}70dg&jylgjmYJhHQd3r=Y=8b0pjJvvwK&=*N)WgX8W00000 LNkvXXu0mjf1R|R+ literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-publicshare.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..08c5a1363542cab6af3308de7677071e64c19922 GIT binary patch literal 378 zcmV-=0fqjFP);)7ep)3_O*(Y8?>Wyoyzoy6fM(M*O+`6EU;!jBD6d7= zw(W+q04ji~$P+|z^_AM|h(@c`I(Hl=nz`Wi=F$amkzx6hLcH%BxNSAfp{Y8R^)0Mi z;m0kGqguDyJ_AlwfPA4;Ew46^Qkkn>N(F1NXah)qT&}n`Z+I$^=u02S4yw%CBStJht!A4TEZJ`_JE`0_qz(gmK7o=YhcYsBi9O18BO5 z29zpwywO|s1yhh_pe-AzcL@-Okv9wUU7r~h;Ck literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-remote.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..9a274f3f4551655a7357e17047749027a5857694 GIT binary patch literal 403 zcmV;E0c`$>P)uknk`T&^<~;QI0;2Zpl|<4%Fjh2k_CQYt9#9^@+kAp6@iJ78%MkY!-m zFY*BAy_>7k^_{(=2l-t!Lq;&au#A|M{NyPWNav}%51kKfE|BAYfUk zH2~lMU|Ffval=}SA{syp#$@TCHop!2H$cz#tCI$Me#Zd@Ke66=-iVmdTd&=;hnGB0 zr5Q+>QSmZ7pE9Q%&#i51K(W|p+IIDrlZb{(RwBMdq*kD){pN{MtKPT|y#VhW*R8cR xpfA}4DSa=M9%T?v1pp+6$k^#_=oyUZ^9Md=XXY&!By9iy002ovPDHLkV1g{6sBZuO literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-templates.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..203ef0e6fc2fb728cd01f85afdbc44e1588fdd1b GIT binary patch literal 367 zcmV-#0g(QQP)+wM9l=kKf7xMG3p2#Pc_Mz^WAfE;hz!!^?K;KN}VF`01`0e?dYyn z3uns!4uGrVIU>3JN^L)jYNOG(3W6Y7+Th{-CIoSvqWO~|J_?S*reph&aWs{k1H8Qd z!;9l+ztw790v8TI!7uHWx2i~~RvIs*!doxa03<*@Up!njypTxr0+0wY-~H)y%zNjB zuIc>U#%7q0Hh!_faMZ_`*Yp5LkWy0G++#W(rU&G_!s4xz^;p=?n9=lm27nE}lpdgU z7Ok_^;9L?Qo<;U0Et+Q>fc~g+=1mHZgF#oX5?*t2ldgF?NjvmceF9|UPg%8yb2|V4 N002ovPDHLkV1nR~p_2dr literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-videos.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..726d3e18d3fd6b84e618a4f21d5a8d4c097ece11 GIT binary patch literal 365 zcmV-z0h0cSP)ZZ2x{3XG%m z61)Jz;9%0(OK~J=i~|xJ08>Dut&?a1g%Z#Dp7WfO7yc;$P_Mg6Y1$bA10aS@c{{wX z)!gwSfDWKEd4Y)U{!&}7qUw3xwc|M9!Uc~Hw=RgAC6@ad(Xn&lHuNM%M$fWYIlwRr zQ@1Dz_nXbe6>zBo*oBSV@>UfowN&*|Y8b1<8h`{a&Eiqo@I)fcP5`kWvt0UDi9Z17 zcH4Y?4$0&cmbE_9Ba^}}{xgH%gSAqH;UGvXNdyQnZ(9n$&fXy_d4txQk5c(Wz}z2L zb_pxZf@mBL7Wk(0lhFZ!Vf)+|*-!m`FPkQuQ~6P^^M1N)=}dhCIR8kkYa$I}00000 LNkvXXu0mjfwQHSj literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..beba50776401bd8a995e46888839113986d7490e GIT binary patch literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_SJzX3_ zEPCJe?an%sAkjAee*%vg<55Q&McF0h4<{YJ}Sg4WNj4z}gj z&d*%&d*A)iSo0k_9@O6X?(juOXTH{ly8WO=@$?m{(qimm`xSgBJu|NUNpp!2qn_h1!KdA4xgWt^aAoF6z yu-og!v#0-!SU=rO#v(VZ?DWxc>&%z)e==75HY-wlpZ6H(T?S8AKbLh*2~7Zu?S32p literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/network-workgroup.png b/packaging/tanin-icons/src/out/Tanin-chikyu/16x16/places/network-workgroup.png new file mode 100644 index 0000000000000000000000000000000000000000..d8dda14c83ada1771a40cdfede601ce967a77997 GIT binary patch literal 510 zcmV$e}G^qf{+VXL_smXAqbY@ zKS&^gm0)S5Hc62-0U<>SFNj5o6bUvd1lL5H_b>zh zDF<+Cay%Oza&>VICxVK@i9?(qk~Ri|tr`Lut|NWRY65i5kwOtje>{+ zV0(LS;^YlHfSF+-0#;g*Qe8m$03yK*5TXUX?gpH7<7hIA6D+TtAHRO*F)B3AG~O(& zy#9C$u#isM-BE5&U87d7qUupqR6LUAmsI^cz=!vnacKqd05L{_r3l_v5s6sH`*vQ{ z(`pU4;|K)=q8XuUj%8Q;ZBq0g%b;3I5kd)YntJwNp#Yp4xjNl$f0^*AROAgl!b_kytR+ysjf_L1Pm<)BAnr|G4l^1i)Yr+P0=0A#eZ`M4hety5A48 zRRSHr*5nC7T`gNDkD@mm4$p%i$X717y}1lQT+~>8uTks=2jNIBOBlMz#`Z3b>;HHw zio81>kIsNo9l-NjJDtrQQfigXOR3>BntcEX;JVGds^tYjnG1mu81>rUl>R~VGT|eM z0kG@_p0{34kfw85?JfY*r-%9r0J1CvrBJFoQ!3C+i`QrZfMHto1-{?@Yuo|}P-OXg zrSuYC9Ub6pK0S64?=FsKW|gvKuO+kS`LOQMU-<+gCrpOs95*-s0000bksk~9K2A1V89`!%o)$#&ASjR8WKmufo z^tHh54?Z(Gz{}gyu{#g$X0xeTBwVqa`E>GpSoY|jyaRv&LhJ_saB%*apU zv{oCfR^u2rG6DQtalN!s!7$`P>kUKVE)}W(27u=kwigXgC8E?65DBbg=}*P}K&c6x zb`z}?`FxqQ?acHUae&tS4ZdHbxV%ZXbB9uI$pDs>rvKDtG#VgH3n^z`84Ji}3+PDm zJb1*8sSkhrAmeys94|Kbt%07X(Ol;bePm34m)G`zJNB=K!+vU!aN2gp{a$A;>C%~c Y2drvTi+&jVa{vGU07*qoM6N<$f_ZD7SO5S3 literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/index.theme b/packaging/tanin-icons/src/out/Tanin-chikyu/index.theme new file mode 100644 index 0000000..247cf09 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/index.theme @@ -0,0 +1,24 @@ +[Icon Theme] +Name=Tanin-chikyu +Comment=TANINUX Fuji accent (chikyu) over Adwaita +Inherits=Adwaita,hicolor +Directories=scalable/places,scalable/mimetypes,16x16/places + +[scalable/places] +Context=Places +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[scalable/mimetypes] +Context=MimeTypes +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[16x16/places] +Context=Places +Size=16 +Type=Fixed diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/mimetypes/inode-directory.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/mimetypes/inode-directory.svg new file mode 100644 index 0000000..95caf7d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/mimetypes/inode-directory.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-documents.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-documents.svg new file mode 100644 index 0000000..f7a30ef --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-download.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-download.svg new file mode 100644 index 0000000..789194d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..95caf7d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-music.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-music.svg new file mode 100644 index 0000000..c99bfab --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-pictures.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..146b542 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..5d41d6a --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-remote.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-remote.svg new file mode 100644 index 0000000..594dab0 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-templates.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-templates.svg new file mode 100644 index 0000000..934089e --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-videos.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-videos.svg new file mode 100644 index 0000000..ff188ec --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder.svg new file mode 100644 index 0000000..95caf7d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/network-workgroup.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..b114ce6 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..f74f6d9 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-desktop.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-desktop.svg new file mode 100644 index 0000000..9a28d5a --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-home.svg b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-home.svg new file mode 100644 index 0000000..8993416 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-chikyu/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-documents.png b/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..733622f1c476abf40cf695d87a7e06c6a47ed570 GIT binary patch literal 377 zcmV-<0fzpGP)Lcjflhq+BSm&?rjesG$$^iO>P XG%8dmbzz#F|7J1+|C9g-y4|7`7LO6Qc{v;DVdEwU z+Q~A20+8e4X$Bp#Z?4#UZ1sA*OW*hP(haUVw}F7GH8#IbwRISr`+a3{IF8MJbBB$Z z`=_VVwAp_cTmWYZV0+8kZS6I%q+DsdB_(ywZ39SvdaZJ>YWPzbI9~unX3sOt;)Gee z_z}h%07TI{0FG_889Q;Jc^bYV5R^*=l$vif0&HdBRq_v*!V3YTaKhU(%3-#JS1qpx z@Tz5F=%*xr)@iiV`Aa& z^8n#8n01dM=uV;#88IQeb6ZF$l+0Y*_Ak#l-}fKbGXg*j2ELQ|mk7f3*v#~_^`um9 zRU~i$Wu`c}JF?m)39xOf|<=VMWxAD;3b>Vpx&f6y(*27;r zo#*Z0cyb5axB$mT(P`(bg(GFDdPho{QP=~J0L}VAw=Db}1Ky7SVJcSiR_tp31-k`W z=Sy2quhxtU@HSmsMYZrHn}2v^;2@}ezE85gZRwx+01m)0Y2 z0K?LRwZ|dRxR5A@@DLT+Xbbu(O#fSNDn-QmzzlNJ$~*)&V3yA-i_4s`#52thoUqP9;+RLi+~* zFTDY8<4=Mhq_$sLIHT>YqKN)rOu4j$5JDTGHF$jL5{7EN>d-!6JD+7T4Mzg{SX6sQz6#du*sd1m)1w% z0SrqM);@G7OfWy4=mU`-3qR4!Bcn^NE4 z{$YR=5=fA@Hdg2Q(zzA*YgIPuwb}1xAy7)ve~K87Kd|le8>SL?ZW(}lJ~v%wA}Bcp zN=_j)_=7+Q`L@vcOJb5_On^c3a^#ksn|SnUEfX#l^Y6oce2{hMTzvw1y-TI>TYBsO O0000(|a=l(ZbzN6X-Qc`>=_zm)WApn^XuICA+t6hbrfINR zTw*@u{P+`LSZqABPJkmFAh(b%lvna-syb7=rmAGqP6a>($fo9YW;IU~f%yeMi457` zU^t@P@ueVX2mQf_Km_k(oOx$?Uo<}Bt!!Wure e{JprLzv>f$1X~{(7tLn?0000J*cB;t%DJF^lkclaac{c{02}~_Fg}1L+GLE~ z5UJb##W~7{M;EIAAb)zAVZ^XFKaDXnHK6%Q$x5xh1K>^qz|=%$c400L!TE2I0RVz? zWX<%7j*vCe%fp&=Lcq`fFp5QThbr2&{}|Brx{pzfUb|ZddLQZ8X{;rZ#-;1D^5G%X zY37JAC1m0?+)t)P>h?oxO9PzRz5CLh+~*_`!B9G5ZV*>XP}F4V!+LKx*MatC!?W$D zrUrbK?0}R$TUJ^6K2-rga)_CHt`lf6#`OLZ3TkKP)#7#J1;u5+5H99vj4y{XI z0fwOoW0ymsGlCG(lwiZ-whj zUT2*K&;Z2Dyo#Yq{LNJkM(uvTf9JZcOx@t2`{XHbpJDTtNUCSvjXThi9EPED(rQpC zTR&zpiduu$;Vp2j0XT>Dal2VZ6=JV?RS2rK)d3Iy)pGH4-|$v381VUsD6zh4SS(kJ zrvW)#r&cW`f)Z;-6Q71tVIGFOP5kVDI0m5znnw=dVwD|W7Yko!O;r;Ce}1DM%mHv} zx1n T^HS4000000NkvXXu0mjf2PT== literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-videos.png b/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..0e27ea61cab1b3cabc680262391326fa86c4988a GIT binary patch literal 373 zcmV-*0gC>KP)zh44_htpnmKP+H;{-us_(zW;}RN&xuPs-bp`BLvpuIed|w z{I&0w$8iA~fYLKgLZ}e_ z^8n#8n01dM=uV;#88IQeb6ZF$l+0Y*_Ak#l-}fKbGXg*j2ELQ|mk7f3*v#~_^`um9 zRU~i$Wu`c}JF?m)39xOf|<=VMWxAD;3b>Vpx&f6y(*27;r zo#*Z0cyb5axB$mT(P`(bg(GFDdPho{QP=~J0L}VAw=Db}1Ky7SVJcSiR_tp31-k`W z=Sy2quhxtU@HSmsMYZrHn}2v^;2@}ezE85gZRwx+0HYBEryR#m?j~HW95MNl9+1>Y1J>0)J#=GQ4vH95kXZ!6o8zagD;;Sz213we-sb^Dz$30TAxML%ArU(s~VyO~y(s`6|=XQ-olc8NrNTiUn%~vkidvZ{KCM(E>mwd%^*;03yUxHjs}g z+l>0@c^S4)w2{FRWGBYNB=`7A07(4-u~GrR9AZl2zP}KuJQD!UE-u~P-`{HnGX^TF zX#!w0dn)fN06g7%yaj9lT)Tc}HAbK8EajtsQ4RWWH>R|FQud;S34o)+gBM>veYk68 z(ZA)M@$9%~-*~uYCwcx^zW@f&s9}xLZc+dM002ovPDHLkV1lRkW; literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-bookmarks.png b/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..023b7bf429f2f7bfe1e144ec4a3972204a6a5880 GIT binary patch literal 365 zcmV-z0h0cSP)zfMbwSfxlKVq%m9@e*#>c!VB6jqXj1OY0Fh zfMIFUwZ|b*HxdF8ga~4pjtdk?w9>|JHJN$u{V#t8{)qA)&+280F+#3x4KtG7b4MkT?mSfT>}sR#r)bqQu7xI%y|Hs%A|5H5W9nR zx6f!i0bq5-V$*ff1-wl{Dmx_r9-lht8vumiboMoriq*shSe8Ni`Q`H*zoOWHEw}Jj zxj6zr%4nQOy?~s~JO)6g_j=;2+joQE$V_6+<*fH^YjBiy=&yVL@%v7+Yasoh00000 LNkvXXu0mjfG$)-H literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-desktop.png b/packaging/tanin-icons/src/out/Tanin-shinkai/16x16/places/user-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..9899f35d92ab37084043aa8a4d618f09cd50ed4f GIT binary patch literal 364 zcmV-y0h9iTP)rP4}8gD#K-@e*#>c!VB6jqXj1OY0Fh zfMIFE+N-!RBoH+r1Vao&O$^g4!+-6ntp2)woZwBau_Slknj~LbrSMQ|x7#O;Mnf!YaMrvC6*$e%{8|caCp>C&^fZTQ8f;Z7tgd)} z{)Cj(PPcaq9O?i;$zQK+l+jdmsqvbsQuMt#fC^At$!#wi{!jwuCjcceGNr#Mbq7-+ zxVal3grpJ_a2;!|FSYkow?|O&*<1^_y?;Ex7E$niqwFNx_aqXYD#(Y~MeF5}^=xlGt~t+zoh-px96 auHFIP8&|oY + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-documents.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-documents.svg new file mode 100644 index 0000000..b6fc63e --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-download.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-download.svg new file mode 100644 index 0000000..c02b909 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..e86ffba --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-music.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-music.svg new file mode 100644 index 0000000..2732989 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-pictures.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..4fd1fd3 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..88ebe43 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-remote.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-remote.svg new file mode 100644 index 0000000..e65bbcf --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-templates.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-templates.svg new file mode 100644 index 0000000..da22b20 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-videos.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-videos.svg new file mode 100644 index 0000000..68f7342 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder.svg new file mode 100644 index 0000000..e86ffba --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/network-workgroup.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..2ec6338 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..a06b602 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-desktop.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-desktop.svg new file mode 100644 index 0000000..1968d1e --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-home.svg b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-home.svg new file mode 100644 index 0000000..b342940 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-shinkai/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-documents.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..69614a35bba0604ebbdda8e2632f3960026113d4 GIT binary patch literal 367 zcmV-#0g(QQP)ckW{^D8$bY5H#YazEiW(_XDPs#(rREfpV52ik&Kf+ zMIvj^9}TDnbtcmZgVCV4z+ezU&4xS(7r z)2uZziwgvRPW3c5xx%7j10<8=*!P0_@!PAjPPtO9d<@6K!=g)nE+*AhG}BpcWwI;%KSB6CNtu{0fznmkNfHA#dsAM;54R0u`vzQV|!x z%uKp6`HOi+0`w>SQx!&!sr%|J(ojzwLgc%P`npp7-Sm^8@CD$l0qZuf^`QRS#e|6wKT`S1xO= z`OU7!6(X(^Eq5o_%+Fw-SEN(#yL9LEiGEjB@^61|Y297H9|zaZ_ViVbROvquz!-9B zYSrpnE?hHL#&LPRXpd!J6quH09r-X;=h|KF4cjJk+jZ;^*~8VxqGlRjc~EvS!!-HR vbvOCr%;vw(6WdV{{3k9qZ10|>_0QN9&RTBnIiWAU)xUQy>696RVcGmwlP7 z3vd8z&pFrVyX9ExRdfb}!BrTBYV8I$kGIl->jIlU=RzIJP!1iF!Y#S<_PW%9+Mk)q z)xq#(cnMrM0L^N%-Dw>nY_URUTUo0{inw7UpE>rx^262?COc$-9g ze7}=s8NL1PN{wwYt6UM!V%ocHEXy*M7z0Mph)ku6(}eK}jqL_Wnous4Nz$ZvfbaSE zp1-)8umM!AzKjY3bB_ZM&7!kvPlv>e>NOcoVI&leEfQqg_9Qwo+ zxBzh|bnJ2pqLV5uq6Pnb?>TCz+So?V=)yhc+>Z8wdfg-0PY?RZaCV zslWjczO!qwC!@86TiIx}S_f&G>WKtL=f|o5hdIbEjntbeRc$BB;kh2`3v0w-{9~t$ zUTJsQ`@o(9P%T%N8?{xWkW-zPLXw0@6F>kYQDtM=@IV&p4GXZ=&6OTe<`1sA*BD~} zkU~(OtB>`G-`Z=Hd6s4uv40`3#?raF;r0Cm-wiSgG6|BI3IIjdAI`Z5q97s)qRikI z0&Vp3MCUgRG7doZvAa_arYC literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-publicshare.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..a178834d8fd8c1e5494edd92a2c1e476a5dee987 GIT binary patch literal 379 zcmV->0fhdEP)Nr@i+(_v6ApNdc&H^*}y{i;npi%Pv-KMaCEPi$~}aiUUil%VT4;W{oWi_2vF z?2nlq=~CmVaRBVw0QpS5SSc(aEHTx2VF_}6t_ENMbimQMACRVUfUMGq)H;=c3gD%x_HJQM3 zJaC|#FVP7*$s3H3jzl^#c5VUakq#$`zBA6)0L}O2ZpP1Ew!3fcG~u*N4_fWkcG9E2 Z@&W9TTP&PX35xovPDHLkV1jc!rTYK? literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-remote.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..36c9d2df8ff0f44c0e475e791010bb972ea69261 GIT binary patch literal 400 zcmV;B0dM|^P)I2xSy%xsUSy|Zg3T_N9 zU}LGJu`n@~+W7`n6i`sD26Y3gVY9%@wUAv2#0c7XlS$^@bI+GK$pC*W1^~{vV+vCo z0!R<0zBOl^mAA{`M1bRqN*SP&5#^e+KIMYbs7Yy6clMnI0B`^xP<{+e*kz30lE&TW z;SjaTWn~ipb literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-templates.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..70ff730e83ac4b5afae436d0d2238d660c7df414 GIT binary patch literal 364 zcmV-y0h9iTP)V7JrSM+&*ocqt@Juh|6<0L|^4qfNte8H}?4U`%D%FquvmybegR zWF<%xHHK+S=!fLvoH&ik0}KWs1nr>Bc=}Nu(5TfHwJnS9v+|8;o&pfmgYp2^ad92D zFgTY0&@(+=>s-=2V*@04a@O$tr|kXB*(6-ss(p^K(Mj2%f9e~lB2$^}9E7a^0000< KMNUMnLSTZe7N36r literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-videos.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..fc3bb0a100fbcee5bb8c6b282f20e76f20186d29 GIT binary patch literal 364 zcmV-y0h9iTP)sr%|J(ojzwLgc%P`npp7-Sm^8@CD$l0qZuf^`QRS#e|6wKT`S1xO= z`OU7!6(X(^Eq5o_%+Fw-SEN(#yL9LEiGEjB@^61|Y297H9|zaZ_ViVbROvquz!-9B zYSrpnE?hHL#&LPRXpd!J6quH09r-X;=h|KF4cjJk+jZ;^*~8VxqGlRjc~EvS!!-HR vbvOCr%;vw(6WdV{{3k9qZ10|>_0QN9&RTBnIekL3W|^_`2jW-qE=?Luuus48-idd z{(}S}SP7O^YLgVU1_D-M3K1I>6v09iV?aUo>)ttIv3K1|vRM#cm>Fgc?{hw2;6Eh* z=PoUFt4FjhDyS%8f{3CD!wr%re!Tm2cYFKUtui10R1QY)zj2!Re}Y~j9`JF0W&)pEUW>%dGS86i4FlNk)q=$77R3zdw$^L zg9Vz$X60`SBM8wUIe9G1X}>w`c?iTEDjeYyezdT3Vjg|48mW29!0(1|Rd7&ri#~uY$6AyS=BMUw^w{K~XVO z6*I-uP*GG36{EMCWfKAI=jU&}I`rXTs8ky#=9>4jL0p(^G#+GGywLCW<|{NW1`Eu! zT5qBFOb2OoWp%yV>0fhCcyip6uiaU(X`X-99~;)L_WG9@pa1{>07*qoM6N<$f=L?P A^8f$< literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-bookmarks.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..32b774b7e2b5f4d225cc4ea2ded9877f04eb4b79 GIT binary patch literal 355 zcmV-p0i6DcP)4+!r z0OC@}+T#R7Hxk=LTd=jJ({Uj!0jWVGANZ>H^pR(dO1++3*65c9#OQt{9~Yth_-s$QVy2 z0Bm?Q>OtL1@R3hxH+BJd?!A~d0LW)K8Vy>T3p~dq8@_$NE1tQa88-hax5Nfeb2TY7 zU0{)M0DAAe)4&fON8_PeCS3DspZ!t)*mUWyd;!FlPsX~QF`xhd002ovPDHLkV1mN8 BnlAtV literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-desktop.png b/packaging/tanin-icons/src/out/Tanin-take/16x16/places/user-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..e72b0683d9abf4d0a009d24de6029daa0c831f34 GIT binary patch literal 358 zcmV-s0h#`ZP)5$96c_-Y4|KKQoo8 z-ON8e4~D!z(n}`4OPCT`8R*_Xor0A%HK~y-6rI1Zd!cY{2Kl<8In$`eKY;a=Gpqp^Q#3ghAYIFt0p*60+ z1sI1Wj9m`Iff8b$EtR9>L*Oz%10J@>*tRRBn@Qcrkd9|!=@yvUloOq=Os z5x@qpJo`|iFJ`fY2hnJ?S|>@8$b}8guFul~oaShLtAyN5leBG{9IoTCy|zUqsQj5p zCF|{8`xrQ~0pfDJ-l%ONEV0yjVF{ujY64gQQCQtsHawL^yWaq`c5WcG8_xjxGT==3_o!+TC^{bfLB?=2#1Srzn17`a5Uo^EO7Zme zjPI39HuDd{Qb`hB&R?2lY=HiI|DYUHZU!H(?lNJ&=znH|Y%lN9x%vW8 Wr&rC~>%2e!0000 + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-documents.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-documents.svg new file mode 100644 index 0000000..c42f9e5 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-download.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-download.svg new file mode 100644 index 0000000..8c986b0 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..a30adb1 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-music.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-music.svg new file mode 100644 index 0000000..f4a1b96 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-pictures.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..01d60ca --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..27ed571 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-remote.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-remote.svg new file mode 100644 index 0000000..455e7ff --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-templates.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-templates.svg new file mode 100644 index 0000000..b73706d --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-videos.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-videos.svg new file mode 100644 index 0000000..38bc1d6 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder.svg new file mode 100644 index 0000000..a30adb1 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/network-workgroup.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..aef3017 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..402e093 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-desktop.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-desktop.svg new file mode 100644 index 0000000..230b74b --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-home.svg b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-home.svg new file mode 100644 index 0000000..629f88c --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-take/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-documents.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-documents.png new file mode 100644 index 0000000000000000000000000000000000000000..8170b1a8d9dbc855de350f3e65f647ef1b3c2804 GIT binary patch literal 372 zcmV-)0gL{LP);2k~QzxUh+AO0x;;QN=RshPV9tk0$Dzb5X5 z@3-D(0d#f#zln zyKMbZ3&f7og|_r^awAX1GQ1R%H9KP>S+uug5 zM}K-+k9VJ%FXzCS3$VGRcJ^vj9O*1IUP=d5iRu6jz(!^5VA=3g!r=b^z<7D4Y&aqt z&VPjU1^~VO7XW47ZnJi>Ow)e1(CMV4pZ$r+lPDFg z22d)rhGyjeXg%&PbpFyb;{vo==}B2dkKJz1UnU%u3d7fS_h{9jx%viHNKO$<2b!n= O0000x=;KqB!3B32nI zYY!ln!qz!X&{8E9vJeg8?4$^hfG*h~ZT|AT_kI6^4I==weh2~;^eu#oq5YVd*1gu9 zpLGHcpeBQXwcLpnZ%w?#X_{UoNn+N1aC`Hpg<$CL`LoSc_gW`oujs<}E5xTqM0??% zoiX|G)A;2QxbOfD4x{!-+(If@s$MBc)C@ZS5@5esKPw9_BP{-}0BpsHUWr@nR!}T3 zCjW8;my8E6nM}_cQ8>!7PrnSjyHovseb0KXrT=CQbeuT3&%MBD00000NkvXXu0mjf Dfm473 literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-music.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-music.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cd34a2253bbfd8992c5341fffd55859a6f0d16 GIT binary patch literal 371 zcmV-(0gV2MP)kq08w=7IHq%`!23N^oq?>~ zL{T%H7hnOD4Ch!WuEkVtNxTKER_iPbLpis>)#Y6z1Q$7)KdQ{!Kaaw;rBm2;fuOO8 zzvBJ5lQP?RXuq5SCl)|;!>{iKH4M{OXuN3}_!X}SU;wOEmiHDlj}Z!c5&)`@%RPnm z4*=r#F8$8|Ns|Sm3{BN*ZZ;;F}q8}(d$R=An(wb`~c?jS*{<) R1Hu3R002ovPDHLkV1h$7q5=Q_ literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-pictures.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-pictures.png new file mode 100644 index 0000000000000000000000000000000000000000..1c79c021d82461f71c2c36fc20b7888c93833847 GIT binary patch literal 367 zcmV-#0g(QQP)D*6N>u8b?! zK7hCsy7qC3A}-oe5eayyni&_;QnjItp4Bk_Ip_ao;Gfa}()7Z%b$dsF`*o=vU-RfZ zO=};c3Jd^8*n3KGrVaEuT05@S>xW5_Kh&Pt- zXWU=2JeOvdh=hi(l(00g18Ic~ToO=0#6Kvhbm2UPe6 zcefp+lmG}_P+gfD>MP_{z+x1zT%AR!KM*J-X*D0{cHgn=;ujVY1mP3_mR0GWOA&aU zgXcMg!EXe4E?-7Ef2fx+06Lwg-D%&w%Ccv3obcp?_13!2w#zOZs!!`bOT~cMJe2?d N002ovPDHLkV1f#XpDq9Z literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-publicshare.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-publicshare.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4ad1e694dd8bca1a0592cbe6ec232595322a94 GIT binary patch literal 385 zcmV-{0e=38P)f0(?r z_hLSY0b2grIi&NEmil z-zcR6Shj(ML9vvh)e6%$7|65?vX*qm{a=XVo;*ULfky{uG~RcoT<0nXKFm?f<6~Cm fxfyJy4gFQ0QVCi^{n;i(00000NkvXXu0mjfGn}L= literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-remote.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..9b4adac8a64325355cf5361a7291572be46b34b1 GIT binary patch literal 401 zcmV;C0dD?@P)Sam zkbuM}$=b82xvN59+nWe*esWVF;Nn~9o{_p#Tu{X2tMXBCzXAXp02Cn|Wz#M(_Q*&* zMGi;l?DTdM0OU`j>AHs8+7it9M05aO$=9-T0N_~yfVF6@tmd-dg8#NM06+*1Gn3p1 zS4d})`AN-z7(ovK0BKXX$28ru{}|wX46D-```*w2zCKd*t+{0w`dz2p%Z@ks%C(4? zlEQc&&&T64PQB6F3jwXxOS@KIIOZgbpebplb_A()C~}$T+MQ v1CR=1+b&7prwRZ_4zZlI20=|@EWCdLQf_m-s6G#&00000NkvXXu0mjf>#(p) literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-templates.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-templates.png new file mode 100644 index 0000000000000000000000000000000000000000..f3a7cb7dfe7cd8d19a244cdae62adfb0032e2489 GIT binary patch literal 370 zcmV-&0ge8NP)=uc2#t?!5Q`Yqx(A$Sg}1Ed5Y1cUW1NiwSrXxfeWtZ8Z?pzs^(dW|rQ0qAs_ z)d7a7W0-njFqZ&G(<~`t<807*qoM6N<$f}i)Gk^lez literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-videos.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/folder-videos.png new file mode 100644 index 0000000000000000000000000000000000000000..481a716ddc19ccd594c9940abfd56436b977fc9f GIT binary patch literal 371 zcmV-(0gV2MP)tAPC~A4X!TlLLs=Ap?O^m&HeK*s98FPZD%NN zui!6we`aw!+x=;KqB!3B32nI zYY!ln!qz!X&{8E9vJeg8?4$^hfG*h~ZT|AT_kI6^4I==weh2~;^eu#oq5YVd*1gu9 zpLGHcpeBQXwcLpnZ%w?#X_{UoNn+N1aC`Hpg<$CL`LoSc_gW`oujs<}E5xTqM0??% zoiX|G)A;2QxbOfD4x{!-+(If@s$MBc)C@ZS5@5esKPw9_BP{-}0BpsHUWr@nR!}T3 zCjW8;my8E6nM}_cQ8>!7PrnSjyHovseb0KXrT=CQbeuT3&%MBD00000NkvXXu0mjf Dfm473 literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/network-workgroup.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/network-workgroup.png new file mode 100644 index 0000000000000000000000000000000000000000..2a5c51cf41b945291c21e01c8c5d8a619aee7fc1 GIT binary patch literal 506 zcmV3{0XtelL@_uAn4I^5j=|j zpoI#ayn5A3d+=(-A0Sfn(2EEK_0U6vmSP%`?#_DH#C$YqKzv~tm|>oI2WH_v{QUlD8Q=jbb45|DT}DKQPY{=*6GdEx93LIF z&O7h`W`-pLD^&!r9Fl+=LZ$*phFJuG6cXLR5iaF*#!dtgS-P`w?-?pwsn$Nc-F>r{ z9V8=}VZfl@Wo31ZV!ncU#jED%1yx=4Ubg`D_rD}|5f6}3!cs&GL_H2i2sYI+fcHwh zzTn0yj37ivMhJ0~o$NlB@&F+W2xoHuIDz;VFin{dV;ln-HyRH+ox_$_C74?e@d7|; zx literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/user-bookmarks.png b/packaging/tanin-icons/src/out/Tanin-ume/16x16/places/user-bookmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..8a920531b56b21b905f31a756474114162286975 GIT binary patch literal 360 zcmV-u0hj)XP)i)Us4z#=pU9aHmZeusB zAAd=b_8tZ=r@)B@&}!S=9jAk$Duvdos)F6L`Tz>RMssbiXn28;=t}{}N~v^)+#9?F zBPQcd0BUuE&6ZtG5XUpRy*2>PPht560MpqVApvB0fnjJ2gLeRQBmYFXg7#M9uX0Ni zfFwyLg{CtsG8#Y_ejKga)_oL>^&;VV&6vIpql2uZ`AJ>cApc+=P=(TtXJ0qAL(_WE?qm z0pd`|*sX|@VyzPmbszzM5R>PiRDw1n!Ef|<_nvdlz3@*2Kp0+{rfTj=aHpU0OM6mwrn&PC4G5^+C@qN5-P($WNHr&pm6oYY7@l9{@F90o(Js6*Eq^fy=W z7TrI!G|krUhL4B9fdEmE{dvD8n3DfUfUf2C;&_C=2lhl3?b2f3_vz&sdE(G z;P&PLAq0lept)eN?6>L}IIhK7&j(<99~T-FE-qzORH~N1woS&Pc=~qC`UZZd1-CqA&a2`8Pnge*WsS0LiZICAU) z#G%l!%c*rBrAn%lRIO-kt^=u5ZD@mMc<(vifB1j+rxbuFy0mT0-j(2eJ<7qGs9i)+ zGo1y{0UXudmy&a3g6k*`jaI956o#Rg+2HK-DpC|q3N*i!nYMi#g>600VVVYw%{sxd z_vcTDY^~Go9RdeBK&2Y2Y&2@9nmX5bO;ZWVUK2nCD3$%KdBbBV67w4X(kPUkQ0@-W zRM5TaBSc26Uczy$slMFaotqw&YQXAxm2UTu^ke!pa{-oR5XT9_;RwUf&<(8^pj2K& zW*LL0XI$6Lv*qu=_Z@uS$qoK!AVfBr>HMW}MhA%Fm%XLHyH1imYrWt1lbxbN ar|J`>0#?%+6q_Od0000`>Qwr literal 0 HcmV?d00001 diff --git a/packaging/tanin-icons/src/out/Tanin-ume/index.theme b/packaging/tanin-icons/src/out/Tanin-ume/index.theme new file mode 100644 index 0000000..bfe7ff6 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/index.theme @@ -0,0 +1,24 @@ +[Icon Theme] +Name=Tanin-ume +Comment=TANINUX Fuji accent (ume) over Adwaita +Inherits=Adwaita,hicolor +Directories=scalable/places,scalable/mimetypes,16x16/places + +[scalable/places] +Context=Places +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[scalable/mimetypes] +Context=MimeTypes +Size=128 +MinSize=8 +MaxSize=512 +Type=Scalable + +[16x16/places] +Context=Places +Size=16 +Type=Fixed diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/mimetypes/inode-directory.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/mimetypes/inode-directory.svg new file mode 100644 index 0000000..e167079 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/mimetypes/inode-directory.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-documents.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-documents.svg new file mode 100644 index 0000000..d1a28fd --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-download.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-download.svg new file mode 100644 index 0000000..45636dd --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-download.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-drag-accept.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-drag-accept.svg new file mode 100644 index 0000000..e167079 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-drag-accept.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-music.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-music.svg new file mode 100644 index 0000000..ae92d63 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-music.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-pictures.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-pictures.svg new file mode 100644 index 0000000..edd4280 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-pictures.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-publicshare.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-publicshare.svg new file mode 100644 index 0000000..430e1e4 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-publicshare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-remote.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-remote.svg new file mode 100644 index 0000000..f16bab5 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-remote.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-templates.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-templates.svg new file mode 100644 index 0000000..e8f4b4f --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-templates.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-videos.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-videos.svg new file mode 100644 index 0000000..da890ac --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder-videos.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder.svg new file mode 100644 index 0000000..e167079 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/network-workgroup.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..18868e3 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/network-workgroup.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-bookmarks.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-bookmarks.svg new file mode 100644 index 0000000..0548086 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-bookmarks.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-desktop.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-desktop.svg new file mode 100644 index 0000000..f656e88 --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-desktop.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-home.svg b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-home.svg new file mode 100644 index 0000000..667696a --- /dev/null +++ b/packaging/tanin-icons/src/out/Tanin-ume/scalable/places/user-home.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packaging/tanin-icons/tanin-icons-0.1.0-1-any.pkg.tar.zst b/packaging/tanin-icons/tanin-icons-0.1.0-1-any.pkg.tar.zst new file mode 100644 index 0000000000000000000000000000000000000000..6493359ec637981b1562c99bc67722e53a9ebbbb GIT binary patch literal 49860 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 b!2!1Za!zcX2RN{v8(^_>fc@oODe&gHGx6SN literal 0 HcmV?d00001 diff --git a/packaging/tanin-libadwaita/PKGBUILD b/packaging/tanin-libadwaita/PKGBUILD index cc31d57..6694c4c 100644 --- a/packaging/tanin-libadwaita/PKGBUILD +++ b/packaging/tanin-libadwaita/PKGBUILD @@ -12,7 +12,7 @@ pkgdesc="TANINUX Fuji recolour for GTK4/libadwaita + GTK3 — light + dark, pill arch=('any') url="https://taninux.kgva.ch" license=('GPL-3.0-or-later') -depends=('glib2' 'adw-gtk-theme') # gsettings + GTK3 base theme for the recolour +depends=('glib2') # gsettings; GTK3-Theme: taninux-gtk3-theme optdepends=('tanin-setup: seed the variant + start the color-scheme watcher on login') package() { diff --git a/packaging/taninux-gtk3-theme/PKGBUILD b/packaging/taninux-gtk3-theme/PKGBUILD new file mode 100644 index 0000000..d12d0ed --- /dev/null +++ b/packaging/taninux-gtk3-theme/PKGBUILD @@ -0,0 +1,32 @@ +# Maintainer: Karim +# +# taninux-gtk3-theme — eigenständiges GTK3-Theme für die TANINUX Apple-Optik. +# Kein adw-gtk3 als Basis nötig: Adwaita (mit GTK3 geliefert) liefert die +# Struktur, dieses Theme überschreibt alle Farben, Formen und Abstände. +# +# Liefert zwei Varianten: +# /usr/share/themes/TANINUX/gtk-3.0/gtk.css Light +# /usr/share/themes/TANINUX-dark/gtk-3.0/gtk.css Dark +# +# TANINUX wählt die passende Variante via gtk-theme gsetting und schreibt +# bei Akzentänderungen ~/.themes/TANINUX{,-dark}/gtk-3.0/gtk.css als +# User-Override (gleicher Mechanismus wie bei tanin-icons). +# +# CSS neu generieren nach Stil-Änderungen in gui/style.py: +# cd packaging/taninux-gtk3-theme +# PYTHONPATH=../../src python generate.py +pkgname=taninux-gtk3-theme +pkgver=0.1.0 +pkgrel=1 +pkgdesc="TANINUX GTK3 Apple-Optik theme — eigenständig, kein adw-gtk3 nötig" +arch=('any') +url="https://taninux.kgva.ch" +license=('GPL-3.0-or-later') +depends=('gtk3') + +package() { + install -Dm644 "$startdir/TANINUX/gtk-3.0/gtk.css" \ + "$pkgdir/usr/share/themes/TANINUX/gtk-3.0/gtk.css" + install -Dm644 "$startdir/TANINUX-dark/gtk-3.0/gtk.css" \ + "$pkgdir/usr/share/themes/TANINUX-dark/gtk-3.0/gtk.css" +} diff --git a/packaging/taninux-gtk3-theme/TANINUX-dark/gtk-3.0/gtk.css b/packaging/taninux-gtk3-theme/TANINUX-dark/gtk-3.0/gtk.css new file mode 100644 index 0000000..b2c5a3c --- /dev/null +++ b/packaging/taninux-gtk3-theme/TANINUX-dark/gtk-3.0/gtk.css @@ -0,0 +1,368 @@ +/* TANINUX GTK3 Apple-Optik — eigenständig, kein adw-gtk3 nötig. + Basis: Adwaita (strukturell). Alle Farben/Formen kommen von hier. + dark · accent Ume · violet (#8f8aac) */ + +@define-color accent_color #8f8aac; +@define-color accent_bg_color #8f8aac; +@define-color accent_fg_color #ffffff; +@define-color theme_bg_color #0f0f0f; +@define-color theme_fg_color #f0f0f0; +@define-color theme_base_color #141414; +@define-color theme_text_color #f0f0f0; +@define-color theme_selected_bg_color #8f8aac; +@define-color theme_selected_fg_color #ffffff; +@define-color borders rgba(240,240,240,0.10); + +/* ==================================================================== FONT */ +* { + font-family: -apple-system, "SF Pro Text", "Inter", "Cantarell", sans-serif; + -gtk-icon-style: regular; +} + +/* ================================================================= SURFACES */ +window, .background { + background-color: #0f0f0f; + color: #f0f0f0; +} +.csd window > decoration { + background: transparent; + border-radius: 14px; + box-shadow: 0 8px 32px rgba(0,0,0,0.45); +} + +/* ================================================================ HEADERBAR */ +headerbar { + background-color: #0f0f0f; + color: #f0f0f0; + border-bottom: 1px solid rgba(240,240,240,0.10); + padding: 4px 8px; + min-height: 46px; + box-shadow: none; +} +headerbar .title { font-weight: 600; font-size: 0.95em; } +headerbar .subtitle { font-size: 0.8em; color: rgba(235,235,245,0.6); } +headerbar button { + background: transparent; + border: none; + box-shadow: none; + border-radius: 999px; + padding: 4px 8px; + color: #f0f0f0; + min-height: 0; +} +headerbar button:hover { background-color: rgba(255,255,255,0.07); } +headerbar button.close { + background-color: #ff5f57; + border-radius: 50%; + min-width: 14px; + min-height: 14px; + padding: 0; +} + +/* ================================================================== TOOLBAR */ +toolbar { + background-color: #191919; + border-bottom: 1px solid rgba(240,240,240,0.10); + padding: 2px 4px; +} +toolbar button { + background: transparent; + border: none; + box-shadow: none; + border-radius: 8px; + padding: 4px 6px; + color: #f0f0f0; +} +toolbar button:hover { background-color: rgba(255,255,255,0.07); } +toolbar separator { + background-color: rgba(240,240,240,0.10); + margin: 4px 2px; +} + +/* ================================================================== SIDEBAR */ +.sidebar, +placessidebar, +.sidebar > viewport, +placessidebar > viewport, +.sidebar > viewport > contents, +placessidebar > viewport > contents { + background-color: #191919; + color: #f0f0f0; +} +.sidebar, +placessidebar { + border-right: 1px solid rgba(240,240,240,0.10); +} +placessidebar row, +.sidebar row { + padding: 5px 8px; + border-radius: 8px; + margin: 1px 8px; + color: #f0f0f0; + background: transparent; +} +placessidebar row:hover, +.sidebar row:hover { + background-color: rgba(255,255,255,0.07); +} +placessidebar row:selected, +placessidebar row:selected:focus, +.sidebar row:selected, +.sidebar row:selected:focus { + background-color: #8f8aac; + color: #ffffff; + border-radius: 8px; +} +placessidebar row image { color: rgba(235,235,245,0.6); } +placessidebar row:selected image { color: #ffffff; } + +/* ================================================================= TREEVIEW */ +treeview, +treeview.view { + background-color: #141414; + color: #f0f0f0; + border: none; +} +treeview.view:nth-child(even) { background-color: #141414; } +treeview.view:selected, +treeview.view:selected:focus { + background-color: #8f8aac; + color: #ffffff; +} +treeview header button, +columnview header button { + background-color: transparent; + border: none; + box-shadow: none; + border-bottom: 1px solid rgba(240,240,240,0.10); + border-radius: 0; + padding: 4px 8px; + color: rgba(235,235,245,0.6); + font-size: 0.83em; + font-weight: 600; +} +treeview header button:hover { background-color: rgba(255,255,255,0.07); } + +/* =================================================================== FRAME */ +frame > border { border: 1px solid rgba(240,240,240,0.10); border-radius: 12px; } + +/* ================================================================== BUTTON */ +button { + background-color: rgba(255,255,255,0.10); + color: #f0f0f0; + border: none; + border-radius: 999px; + padding: 5px 14px; + box-shadow: none; + transition: background-color 120ms ease; +} +button:hover { background-color: rgba(255,255,255,0.16); } +button:active { background-color: rgba(255,255,255,0.16); } +button:disabled { opacity: 0.45; } +button.suggested-action { + background-color: #8f8aac; + color: #ffffff; +} +button.suggested-action:hover { opacity: 0.92; } +button.destructive-action { + background-color: #ff453a; + color: #ffffff; +} +button.flat, button.flat:hover { + background: transparent; + box-shadow: none; + color: #8f8aac; +} +button.flat:hover { background-color: rgba(255,255,255,0.07); } +combobox button, combobox { + border-radius: 999px; + background-color: rgba(255,255,255,0.10); + border: none; + box-shadow: none; +} + +/* =================================================================== ENTRY */ +entry { + background-color: #191919; + color: #f0f0f0; + border: 1px solid rgba(240,240,240,0.10); + border-radius: 999px; + padding: 5px 12px; + box-shadow: none; + caret-color: #8f8aac; +} +entry:focus { + border-color: #8f8aac; + box-shadow: 0 0 0 2px #8f8aac40; +} +entry image { color: rgba(235,235,245,0.6); } +spinbutton { + background-color: #191919; + color: #f0f0f0; + border: 1px solid rgba(240,240,240,0.10); + border-radius: 999px; + padding: 4px 8px; + box-shadow: none; +} +spinbutton button { + background: transparent; + border: none; + border-radius: 999px; + padding: 2px 6px; + min-width: 0; +} + +/* ================================================================== SWITCH */ +switch { + background-color: rgba(255,255,255,0.15); + border: none; + border-radius: 999px; + box-shadow: none; + min-width: 40px; + min-height: 24px; + transition: background-color 180ms ease; +} +switch:checked { background-color: #8f8aac; } +switch slider { + background-color: white; + border-radius: 50%; + min-width: 20px; + min-height: 20px; + margin: 2px; + box-shadow: 0 1px 4px rgba(0,0,0,0.25); +} + +/* =============================================================== SCROLLBAR */ +scrollbar { + background-color: transparent; + min-width: 8px; + min-height: 8px; + border: none; +} +scrollbar trough { + background-color: transparent; + border-radius: 999px; +} +scrollbar slider { + border-radius: 999px; + background-color: rgba(255,255,255,0.25); + min-width: 6px; + min-height: 6px; + margin: 2px; + transition: background-color 120ms ease; +} +scrollbar slider:hover { background-color: rgba(255,255,255,0.35); } +scrollbar.overlay-indicator { opacity: 0; transition: opacity 200ms; } +scrollbar.overlay-indicator.dragging, +scrollbar.overlay-indicator:hover { opacity: 1; } + +/* =============================================================== SEPARATOR */ +separator { + background-color: rgba(240,240,240,0.10); + min-width: 1px; + min-height: 1px; +} + +/* ================================================================ NOTEBOOK */ +notebook > header { + background-color: #191919; + border-bottom: 1px solid rgba(240,240,240,0.10); + padding: 4px 8px; +} +notebook > header > tabs > tab { + border-radius: 999px; + padding: 4px 14px; + margin: 2px; + color: rgba(235,235,245,0.6); + background: transparent; + border: none; + box-shadow: none; +} +notebook > header > tabs > tab:checked { + background-color: #8f8aac; + color: #ffffff; +} + +/* ==================================================================== MENU */ +menu { + background-color: #191919; + border: 1px solid rgba(240,240,240,0.10); + border-radius: 12px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,0.40); +} +menuitem { + border-radius: 8px; + padding: 5px 10px; + color: #f0f0f0; +} +menuitem:hover { + background-color: #8f8aac; + color: #ffffff; +} +menuitem accelerator { color: rgba(235,235,245,0.6); } +menu separator { + background-color: rgba(240,240,240,0.10); + margin: 4px 8px; + min-height: 1px; +} +menubar { + background-color: #0f0f0f; + border-bottom: 1px solid rgba(240,240,240,0.10); + padding: 0 4px; +} +menubar > menuitem { + padding: 4px 10px; + border-radius: 6px; + color: #f0f0f0; +} +menubar > menuitem:hover { + background-color: #8f8aac; + color: #ffffff; +} + +/* ================================================================= POPOVER */ +popover { + background-color: #191919; + border: 1px solid rgba(240,240,240,0.10); + border-radius: 12px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,0.40); +} +popover > contents { background-color: transparent; border-radius: 12px; } + +/* ================================================================ ICONVIEW */ +iconview { + background-color: #141414; + color: #f0f0f0; +} +iconview item:selected { + background-color: #8f8aac; + color: #ffffff; + border-radius: 8px; +} + +/* =============================================================== SELECTION */ +selection { background-color: #8f8aac; color: #ffffff; } +textview { background-color: #141414; color: #f0f0f0; } +textview text { background-color: #141414; color: #f0f0f0; } +textview text selection { background-color: #8f8aac; color: #ffffff; } + +/* ============================================================= PROGRESSBAR */ +progressbar trough { + background-color: rgba(240,240,240,0.10); + border-radius: 999px; + min-height: 4px; +} +progressbar progress { + background-color: #8f8aac; + border-radius: 999px; +} + +/* ================================================================== DIALOG */ +dialog { + background-color: #0f0f0f; +} +dialog .dialog-action-area button { + min-width: 80px; +} diff --git a/packaging/taninux-gtk3-theme/TANINUX/gtk-3.0/gtk.css b/packaging/taninux-gtk3-theme/TANINUX/gtk-3.0/gtk.css new file mode 100644 index 0000000..917ca1d --- /dev/null +++ b/packaging/taninux-gtk3-theme/TANINUX/gtk-3.0/gtk.css @@ -0,0 +1,368 @@ +/* TANINUX GTK3 Apple-Optik — eigenständig, kein adw-gtk3 nötig. + Basis: Adwaita (strukturell). Alle Farben/Formen kommen von hier. + light · accent Ume · violet (#8f8aac) */ + +@define-color accent_color #8f8aac; +@define-color accent_bg_color #8f8aac; +@define-color accent_fg_color #ffffff; +@define-color theme_bg_color #f0f0f0; +@define-color theme_fg_color #0f0f0f; +@define-color theme_base_color #fafafa; +@define-color theme_text_color #0f0f0f; +@define-color theme_selected_bg_color #8f8aac; +@define-color theme_selected_fg_color #ffffff; +@define-color borders rgba(15,15,15,0.12); + +/* ==================================================================== FONT */ +* { + font-family: -apple-system, "SF Pro Text", "Inter", "Cantarell", sans-serif; + -gtk-icon-style: regular; +} + +/* ================================================================= SURFACES */ +window, .background { + background-color: #f0f0f0; + color: #0f0f0f; +} +.csd window > decoration { + background: transparent; + border-radius: 14px; + box-shadow: 0 8px 32px rgba(0,0,0,0.18); +} + +/* ================================================================ HEADERBAR */ +headerbar { + background-color: #e8e8e8; + color: #0f0f0f; + border-bottom: 1px solid rgba(15,15,15,0.12); + padding: 4px 8px; + min-height: 46px; + box-shadow: none; +} +headerbar .title { font-weight: 600; font-size: 0.95em; } +headerbar .subtitle { font-size: 0.8em; color: rgba(60,60,67,0.6); } +headerbar button { + background: transparent; + border: none; + box-shadow: none; + border-radius: 999px; + padding: 4px 8px; + color: #0f0f0f; + min-height: 0; +} +headerbar button:hover { background-color: rgba(0,0,0,0.05); } +headerbar button.close { + background-color: #ff5f57; + border-radius: 50%; + min-width: 14px; + min-height: 14px; + padding: 0; +} + +/* ================================================================== TOOLBAR */ +toolbar { + background-color: #e4e4e4; + border-bottom: 1px solid rgba(15,15,15,0.12); + padding: 2px 4px; +} +toolbar button { + background: transparent; + border: none; + box-shadow: none; + border-radius: 8px; + padding: 4px 6px; + color: #0f0f0f; +} +toolbar button:hover { background-color: rgba(0,0,0,0.05); } +toolbar separator { + background-color: rgba(15,15,15,0.12); + margin: 4px 2px; +} + +/* ================================================================== SIDEBAR */ +.sidebar, +placessidebar, +.sidebar > viewport, +placessidebar > viewport, +.sidebar > viewport > contents, +placessidebar > viewport > contents { + background-color: #e4e4e4; + color: #0f0f0f; +} +.sidebar, +placessidebar { + border-right: 1px solid rgba(15,15,15,0.12); +} +placessidebar row, +.sidebar row { + padding: 5px 8px; + border-radius: 8px; + margin: 1px 8px; + color: #0f0f0f; + background: transparent; +} +placessidebar row:hover, +.sidebar row:hover { + background-color: rgba(0,0,0,0.05); +} +placessidebar row:selected, +placessidebar row:selected:focus, +.sidebar row:selected, +.sidebar row:selected:focus { + background-color: #8f8aac; + color: #ffffff; + border-radius: 8px; +} +placessidebar row image { color: rgba(60,60,67,0.6); } +placessidebar row:selected image { color: #ffffff; } + +/* ================================================================= TREEVIEW */ +treeview, +treeview.view { + background-color: #fafafa; + color: #0f0f0f; + border: none; +} +treeview.view:nth-child(even) { background-color: #fafafa; } +treeview.view:selected, +treeview.view:selected:focus { + background-color: #8f8aac; + color: #ffffff; +} +treeview header button, +columnview header button { + background-color: transparent; + border: none; + box-shadow: none; + border-bottom: 1px solid rgba(15,15,15,0.12); + border-radius: 0; + padding: 4px 8px; + color: rgba(60,60,67,0.6); + font-size: 0.83em; + font-weight: 600; +} +treeview header button:hover { background-color: rgba(0,0,0,0.05); } + +/* =================================================================== FRAME */ +frame > border { border: 1px solid rgba(15,15,15,0.12); border-radius: 12px; } + +/* ================================================================== BUTTON */ +button { + background-color: rgba(0,0,0,0.08); + color: #0f0f0f; + border: none; + border-radius: 999px; + padding: 5px 14px; + box-shadow: none; + transition: background-color 120ms ease; +} +button:hover { background-color: rgba(0,0,0,0.12); } +button:active { background-color: rgba(0,0,0,0.12); } +button:disabled { opacity: 0.45; } +button.suggested-action { + background-color: #8f8aac; + color: #ffffff; +} +button.suggested-action:hover { opacity: 0.92; } +button.destructive-action { + background-color: #ff3b30; + color: #ffffff; +} +button.flat, button.flat:hover { + background: transparent; + box-shadow: none; + color: #8f8aac; +} +button.flat:hover { background-color: rgba(0,0,0,0.05); } +combobox button, combobox { + border-radius: 999px; + background-color: rgba(0,0,0,0.08); + border: none; + box-shadow: none; +} + +/* =================================================================== ENTRY */ +entry { + background-color: #ffffff; + color: #0f0f0f; + border: 1px solid rgba(15,15,15,0.12); + border-radius: 999px; + padding: 5px 12px; + box-shadow: none; + caret-color: #8f8aac; +} +entry:focus { + border-color: #8f8aac; + box-shadow: 0 0 0 2px #8f8aac40; +} +entry image { color: rgba(60,60,67,0.6); } +spinbutton { + background-color: #ffffff; + color: #0f0f0f; + border: 1px solid rgba(15,15,15,0.12); + border-radius: 999px; + padding: 4px 8px; + box-shadow: none; +} +spinbutton button { + background: transparent; + border: none; + border-radius: 999px; + padding: 2px 6px; + min-width: 0; +} + +/* ================================================================== SWITCH */ +switch { + background-color: rgba(120,120,128,0.36); + border: none; + border-radius: 999px; + box-shadow: none; + min-width: 40px; + min-height: 24px; + transition: background-color 180ms ease; +} +switch:checked { background-color: #8f8aac; } +switch slider { + background-color: white; + border-radius: 50%; + min-width: 20px; + min-height: 20px; + margin: 2px; + box-shadow: 0 1px 4px rgba(0,0,0,0.25); +} + +/* =============================================================== SCROLLBAR */ +scrollbar { + background-color: transparent; + min-width: 8px; + min-height: 8px; + border: none; +} +scrollbar trough { + background-color: transparent; + border-radius: 999px; +} +scrollbar slider { + border-radius: 999px; + background-color: rgba(0,0,0,0.20); + min-width: 6px; + min-height: 6px; + margin: 2px; + transition: background-color 120ms ease; +} +scrollbar slider:hover { background-color: rgba(0,0,0,0.30); } +scrollbar.overlay-indicator { opacity: 0; transition: opacity 200ms; } +scrollbar.overlay-indicator.dragging, +scrollbar.overlay-indicator:hover { opacity: 1; } + +/* =============================================================== SEPARATOR */ +separator { + background-color: rgba(15,15,15,0.12); + min-width: 1px; + min-height: 1px; +} + +/* ================================================================ NOTEBOOK */ +notebook > header { + background-color: #e4e4e4; + border-bottom: 1px solid rgba(15,15,15,0.12); + padding: 4px 8px; +} +notebook > header > tabs > tab { + border-radius: 999px; + padding: 4px 14px; + margin: 2px; + color: rgba(60,60,67,0.6); + background: transparent; + border: none; + box-shadow: none; +} +notebook > header > tabs > tab:checked { + background-color: #8f8aac; + color: #ffffff; +} + +/* ==================================================================== MENU */ +menu { + background-color: #e4e4e4; + border: 1px solid rgba(15,15,15,0.12); + border-radius: 12px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,0.15); +} +menuitem { + border-radius: 8px; + padding: 5px 10px; + color: #0f0f0f; +} +menuitem:hover { + background-color: #8f8aac; + color: #ffffff; +} +menuitem accelerator { color: rgba(60,60,67,0.6); } +menu separator { + background-color: rgba(15,15,15,0.12); + margin: 4px 8px; + min-height: 1px; +} +menubar { + background-color: #e8e8e8; + border-bottom: 1px solid rgba(15,15,15,0.12); + padding: 0 4px; +} +menubar > menuitem { + padding: 4px 10px; + border-radius: 6px; + color: #0f0f0f; +} +menubar > menuitem:hover { + background-color: #8f8aac; + color: #ffffff; +} + +/* ================================================================= POPOVER */ +popover { + background-color: #e4e4e4; + border: 1px solid rgba(15,15,15,0.12); + border-radius: 12px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,0.15); +} +popover > contents { background-color: transparent; border-radius: 12px; } + +/* ================================================================ ICONVIEW */ +iconview { + background-color: #fafafa; + color: #0f0f0f; +} +iconview item:selected { + background-color: #8f8aac; + color: #ffffff; + border-radius: 8px; +} + +/* =============================================================== SELECTION */ +selection { background-color: #8f8aac; color: #ffffff; } +textview { background-color: #fafafa; color: #0f0f0f; } +textview text { background-color: #fafafa; color: #0f0f0f; } +textview text selection { background-color: #8f8aac; color: #ffffff; } + +/* ============================================================= PROGRESSBAR */ +progressbar trough { + background-color: rgba(15,15,15,0.12); + border-radius: 999px; + min-height: 4px; +} +progressbar progress { + background-color: #8f8aac; + border-radius: 999px; +} + +/* ================================================================== DIALOG */ +dialog { + background-color: #f0f0f0; +} +dialog .dialog-action-area button { + min-width: 80px; +} diff --git a/packaging/taninux-gtk3-theme/generate.py b/packaging/taninux-gtk3-theme/generate.py new file mode 100644 index 0000000..1ac8f2e --- /dev/null +++ b/packaging/taninux-gtk3-theme/generate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +"""Regeneriert die statischen CSS-Dateien aus gui/style.py. + + cd packaging/taninux-gtk3-theme + PYTHONPATH=../../src python generate.py +""" +from pathlib import Path +from taninux.gui.style import _gtk3_full +from taninux.gui.accents import by_key + +acc = by_key("ume") +here = Path(__file__).parent + +for dark, name in ((False, "TANINUX"), (True, "TANINUX-dark")): + dst = here / name / "gtk-3.0" / "gtk.css" + dst.parent.mkdir(parents=True, exist_ok=True) + dst.write_text(_gtk3_full(acc, dark)) + print(f" {dst.relative_to(here.parent.parent)}") diff --git a/packaging/taninux/PKGBUILD b/packaging/taninux/PKGBUILD index 9af6e44..6f5eec0 100644 --- a/packaging/taninux/PKGBUILD +++ b/packaging/taninux/PKGBUILD @@ -10,17 +10,19 @@ pkgdesc="Central Linux management for Arch — GTK System Settings, Software Hub arch=('any') url="https://taninux.kgva.ch" license=('GPL-3.0-or-later') -depends=('python' 'python-gobject' 'gtk4' 'libadwaita' 'polkit') +depends=( + 'python' 'python-gobject' 'gtk4' 'libadwaita' 'polkit' + 'networkmanager' # network settings page + 'bluez-utils' # bluetooth settings page + 'wireplumber' # audio settings page (PipeWire/wpctl) + 'brightnessctl' # display brightness settings + 'wlsunset' # display night-light settings + 'cups' # printers settings page + 'pacman-contrib' # update page (checkupdates) and cache cleanup (paccache) +) optdepends=( 'paru: AUR support in the Software Hub (opt-in)' 'flatpak: Flatpak apps and updates' - 'pacman-contrib: update check (checkupdates) and cache cleanup (paccache)' - 'networkmanager: network settings' - 'bluez-utils: bluetooth settings' - 'wireplumber: audio settings (PipeWire)' - 'cups: printer management' - 'brightnessctl: display brightness' - 'wlsunset: night light' 'timeshift: system snapshots/backups' ) makedepends=('python-build' 'python-installer' 'python-wheel' 'python-hatchling' 'git') diff --git a/packaging/taninux/ch.gabrielevarano.Taninux.FlatpakInstall.desktop b/packaging/taninux/ch.gabrielevarano.Taninux.FlatpakInstall.desktop new file mode 100644 index 0000000..d506f4f --- /dev/null +++ b/packaging/taninux/ch.gabrielevarano.Taninux.FlatpakInstall.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Install Flatpak (TANINUX) +Comment=Install a local Flatpak bundle or ref via the TANINUX Software Hub +Exec=foot -T "Flatpak Install" -- /usr/bin/taninux-flatpak-install %f +Icon=system-software-install +Terminal=false +NoDisplay=true +Categories=System;PackageManager; +MimeType=application/vnd.flatpak;application/vnd.flatpak.ref; +Keywords=flatpak;install;bundle;flatpakref; +StartupNotify=true diff --git a/packaging/taninux/flatpak-bundle-handler.sh b/packaging/taninux/flatpak-bundle-handler.sh new file mode 100644 index 0000000..5cbff29 --- /dev/null +++ b/packaging/taninux/flatpak-bundle-handler.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# TANINUX — MimeType-Handler für lokale Flatpak-Dateien. +# +# Wird vom .desktop-Eintrag aufgerufen, wenn man im Dateimanager eine +# *.flatpak (application/vnd.flatpak — Bundle) +# *.flatpakref (application/vnd.flatpak.ref — Remote-Ref) +# doppelklickt. Öffnet ein Terminal und installiert die Datei user-scoped. +# +# Bevorzugt den taninux-Pfad (`taninux flatpak-install `), sobald die +# installierte taninux-Version diesen Unterbefehl kennt. Fällt sonst auf +# einen direkten `flatpak install --user` zurück, damit es auch mit einer +# älteren taninux-Version sofort funktioniert. +set -u + +FILE="${1:-}" +if [[ -z "$FILE" ]]; then + echo "usage: flatpak-bundle-handler.sh " >&2 + exit 2 +fi + +run_install() { + # 1) Über taninux, falls der Unterbefehl existiert (canonical). + if command -v taninux >/dev/null 2>&1 \ + && taninux flatpak-install --help >/dev/null 2>&1; then + exec taninux flatpak-install "$FILE" + fi + + # 2) Fallback: direkt via flatpak (user-scoped, kein root nötig). + if ! command -v flatpak >/dev/null 2>&1; then + echo "flatpak ist nicht installiert." >&2 + sleep 5 + exit 2 + fi + case "${FILE,,}" in + *.flatpakref) set -- flatpak install --user -y --from "$FILE" ;; + *) set -- flatpak install --user -y --bundle "$FILE" ;; + esac + echo "\$ $*" + echo + if "$@"; then + echo + echo "✓ installiert." + else + rc=$? + echo + echo "✗ fehlgeschlagen (exit $rc)." + fi + echo "fenster schliesst in 5s …" + sleep 5 +} + +run_install diff --git a/page_demo.py b/page_demo.py new file mode 100644 index 0000000..3c42a7f --- /dev/null +++ b/page_demo.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Lädt eine ECHTE TANINUX-Seite über die ui-Naht im native-Backend. + + TANINUX_UI=native PYTHONPATH=src python page_demo.py defaultapps [--dark] [--shot=PATH] + +Beweist die API-Gleichheit: derselbe Seiten-Code (gui/pages/.py) rendert +unverändert sowohl mit Adw als auch mit den nativen Apple-Optik-Widgets — je +nach TANINUX_UI. Hier erzwingen wir native. +""" +from __future__ import annotations + +import importlib +import os +import subprocess +import sys + +os.environ.setdefault("TANINUX_UI", "native") + +import gi # noqa: E402 + +gi.require_version("Gtk", "4.0") +from gi.repository import GLib, Gtk # noqa: E402 + +from taninux.gui import ui # noqa: E402 + +PAGE = next((a for a in sys.argv[1:] if not a.startswith("-")), "defaultapps") +DARK = "--dark" in sys.argv +SHOT = next((a for a in sys.argv if a.startswith("--shot=")), None) +SHOT = SHOT.split("=", 1)[1] if SHOT else None + + +def on_activate(app: Gtk.Application) -> None: + ui.ensure_css() + mod = importlib.import_module(f"taninux.gui.pages.{PAGE}") + + win = Gtk.ApplicationWindow(application=app, title=f"{PAGE} · native") + win.set_default_size(720, 760) + ui.set_dark(win, DARK) + win.set_titlebar(Gtk.HeaderBar()) + win.set_child(mod.build()) + win.present() + + if SHOT: + win.fullscreen() + + def _capture() -> bool: + subprocess.run(["grim", SHOT], check=False) + app.quit() + return False + + GLib.timeout_add(900, _capture) + + +def main() -> int: + app = Gtk.Application(application_id="ch.gabrielevarano.Taninux.PageDemo") + app.connect("activate", on_activate) + return app.run([]) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/shell_demo.py b/shell_demo.py new file mode 100644 index 0000000..9574467 --- /dev/null +++ b/shell_demo.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Rendert die NATIVE Shell (ui/shell.py) mit echten Settings-Sektionen. + + TANINUX_UI=native PYTHONPATH=src python shell_demo.py [--shot=PATH] +""" +from __future__ import annotations + +import os +import subprocess +import sys + +os.environ.setdefault("TANINUX_UI", "native") + +import gi # noqa: E402 + +gi.require_version("Gtk", "4.0") +from gi.repository import GLib, Gio # noqa: E402 + +from taninux.gui import style, ui # noqa: E402 +from taninux.gui.pages import SETTINGS_SECTIONS # noqa: E402 + +SHOT = next((a for a in sys.argv if a.startswith("--shot=")), None) +SHOT = SHOT.split("=", 1)[1] if SHOT else None + + +class App(ui.Application): + def __init__(self) -> None: + super().__init__(application_id="ch.gabrielevarano.Taninux.ShellDemo", + flags=Gio.ApplicationFlags.DEFAULT_FLAGS) + + def do_activate(self) -> None: + style.apply_persisted() + win = ui.MainWindow(self, SETTINGS_SECTIONS, "System Settings") + win.present() + if SHOT: + win.fullscreen() + + def _capture() -> bool: + subprocess.run(["grim", SHOT], check=False) + self.quit() + return False + + GLib.timeout_add(1000, _capture) + + +if __name__ == "__main__": + raise SystemExit(App().run([])) diff --git a/src/taninux/cli.py b/src/taninux/cli.py index 2122ca4..1361cd1 100644 --- a/src/taninux/cli.py +++ b/src/taninux/cli.py @@ -339,6 +339,95 @@ def _update_execute() -> int: return rc_total +def _update_json() -> int: + """Maschinenlesbare Update-Liste (für eww-Bar & andere Konsumenten). + + Gibt ein JSON-Objekt aus: + {"count": , "list": [{"name","old","new","src"}, ...]} + src ∈ {"pacman","aur","flatpak","fwupd"}. Keine Rich-Formatierung, + stabiler Vertrag für Skripte. + """ + import json + + from taninux.update.sources import fetch_all + + items = fetch_all() + out = { + "count": len(items), + "list": [ + { + "name": i.name, + "old": i.current_version, + "new": i.new_version, + "src": i.source, + } + for i in items + ], + } + print(json.dumps(out, separators=(",", ":"))) + return 0 + + +# ---------------------------------------------------------------- flatpak-install + + +def _flatpak_install(target: str) -> int: + """Installiere ein lokales Flatpak-Bundle (.flatpak) oder eine .flatpakref. + + Wird vom MimeType-Handler (.desktop) aufgerufen, wenn man eine + Flatpak-Datei im Dateimanager doppelklickt. Läuft user-scoped + (``--user``), damit kein root/polkit nötig ist. + """ + import subprocess + + from taninux.core import theme as T + + console = _console() + + if not target: + console.print(f" [{T.RED}]kein pfad angegeben.[/]") + return 2 + + p = Path(target).expanduser() + if not p.exists(): + console.print(f" [{T.RED}]datei nicht gefunden:[/] [{T.YUKI}]{p}[/]") + return 2 + + try: + subprocess.run(["flatpak", "--version"], + capture_output=True, check=True, timeout=5) + except (FileNotFoundError, subprocess.SubprocessError): + console.print(f" [{T.RED}]flatpak ist nicht installiert.[/]") + return 2 + + suffix = p.suffix.lower() + if suffix == ".flatpakref": + argv = ["flatpak", "install", "--user", "-y", "--from", str(p)] + else: + # .flatpak bundle (oder unbekannt -> als bundle versuchen) + argv = ["flatpak", "install", "--user", "-y", "--bundle", str(p)] + + console.print() + console.print(f" [{T.UME}]● flatpak install[/] [{T.YUKI}]{p.name}[/]") + console.print(f" [{T.SHINKAI}]$ {' '.join(argv)}[/]") + console.print() + + rc = subprocess.run(argv, check=False).returncode + console.print() + if rc == 0: + console.print(f" [{T.TAKE}]✓ installiert.[/]") + else: + console.print(f" [{T.RED}]✗ fehlgeschlagen (exit {rc}).[/]") + console.print(f" [{T.BASE4}]fenster schliesst in 5s …[/]") + console.print() + try: + import time + time.sleep(5) + except KeyboardInterrupt: + pass + return rc + + # ---------------------------------------------------------------- health @@ -746,6 +835,14 @@ def main() -> int: p_update = sub.add_parser("update", help="List pending updates from all sources") p_update.add_argument("--execute", "-y", action="store_true", help="Updates direkt ausführen (paru/flatpak/fwupdmgr, --noconfirm)") + p_update.add_argument("--json", action="store_true", + help="Maschinenlesbar: {count, list:[{name,old,new,src}]}") + + p_fpi = sub.add_parser( + "flatpak-install", + help="Lokales Flatpak-Bundle/.flatpakref installieren (MimeType-Handler)", + ) + p_fpi.add_argument("file", help="Pfad zur .flatpak- oder .flatpakref-Datei") sub.add_parser("health", help="System health summary (headless)") sub.add_parser("suggest", help="System-aware package suggestions") @@ -785,9 +882,13 @@ def main() -> int: if args.cmd == "scan": return _scan(args.category) if args.cmd == "update": + if args.json: + return _update_json() if args.execute: return _update_execute() return _update_list() + if args.cmd == "flatpak-install": + return _flatpak_install(args.file) if args.cmd == "health": return _health_summary() if args.cmd == "suggest": diff --git a/src/taninux/core/lock.py b/src/taninux/core/lock.py index 3693f23..d86be9c 100644 --- a/src/taninux/core/lock.py +++ b/src/taninux/core/lock.py @@ -1,11 +1,15 @@ -"""Screen lock. UI-frei. niri-only — Locker via swaylock (ext-session-lock).""" +"""Screen lock. UI-frei. niri-only — Locker via gtklock (ext-session-lock).""" from __future__ import annotations +import os import shutil -# swaylock spricht das ext-session-lock-Protokoll, das niri implementiert; -# gtklock als Fallback. (hyprlock entfällt — Hyprland-only.) -_LOCKERS = ("swaylock", "gtklock") +# gtklock spricht (via gtk-session-lock) das ext-session-lock-Protokoll, das niri +# implementiert, und sieht im Fuji-Greeter-Look aus (CSS + userinfo/powerbar-Module). +# swaylock bleibt als robuster Fallback. (hyprlock entfällt — Hyprland-only.) +_LOCKERS = ("gtklock", "swaylock") + +_GTKLOCK_CFG = os.path.expanduser("~/.config/gtklock/config.ini") def locker() -> str | None: @@ -16,13 +20,27 @@ def locker() -> str | None: def preferred() -> str: - return "swaylock" + return "gtklock" + + +def _argv_for(name: str) -> list[str]: + """Full launch argv for a locker (config/daemonize flags included).""" + if name == "gtklock": + argv = ["gtklock", "-d"] + if os.path.exists(_GTKLOCK_CFG): + argv += ["-c", _GTKLOCK_CFG] + return argv + if name == "swaylock": + return ["swaylock", "-f"] + return [name] def lock_now_argv() -> list[str] | None: name = locker() - return [name] if name else None + return _argv_for(name) if name else None def install_argv() -> list[str]: - return ["pacman", "-S", "--needed", "--noconfirm", preferred()] + # the styled lock = gtklock + its userinfo/powerbar modules; swaylock as fallback + return ["pacman", "-S", "--needed", "--noconfirm", + "gtklock", "gtklock-userinfo-module", "gtklock-powerbar-module", "swaylock"] diff --git a/src/taninux/core/online_accounts.py b/src/taninux/core/online_accounts.py new file mode 100644 index 0000000..8c7e297 --- /dev/null +++ b/src/taninux/core/online_accounts.py @@ -0,0 +1,165 @@ +"""Online Accounts — UI-free wrapper around GNOME Online Accounts (GOA). + +Reads/removes online accounts via the `Goa` introspection lib. GOA feeds the +accounts (Google/Microsoft/Nextcloud/CalDAV) into the Evolution Data Server, +which then provides calendars & contacts system-wide. + +Robust: if `gnome-online-accounts` (typelib `Goa-1.0`) is not installed, +`available()` returns False and all queries return empty/neutral values — +the GUI then shows a StatusPage instead of crashing. +""" +from __future__ import annotations + +import shutil +import subprocess +from dataclasses import dataclass + +# -- Goa-Import bewusst weich: typelib fehlt, solange das Paket nicht da ist. +try: + import gi + + gi.require_version("Goa", "1.0") + from gi.repository import Goa # type: ignore + + _GOA_OK = True +except (ImportError, ValueError): + Goa = None # type: ignore + _GOA_OK = False + + +@dataclass(frozen=True) +class Account: + """Schnappschuss eines GOA-Kontos (entkoppelt vom Goa.Object für die GUI).""" + + id: str + provider_type: str # z.B. "google", "ms_graph", "owncloud", "webdav" + provider_name: str # menschenlesbar, z.B. "Google" + identity: str # interne Identität (E-Mail/User) + presentation_identity: str # Anzeige-Identität (E-Mail/User) + has_calendar: bool + has_contacts: bool + has_mail: bool + + +def available() -> bool: + """True, wenn die Goa-Typelib importierbar war (Paket installiert).""" + return _GOA_OK + + +def _client(): + """Frischer Goa.Client oder None (wenn GOA fehlt / Verbindung scheitert).""" + if not _GOA_OK: + return None + try: + return Goa.Client.new_sync(None) + except Exception: + return None + + +# Provider-Typ -> (Icon-Name, Capabilities sind dynamisch). Icons sind +# symbolische Standardnamen; fehlt eins, fällt die GUI auf ein Generikum. +PROVIDER_ICONS: dict[str, str] = { + "google": "goa-account-google", + "ms_graph": "goa-account-ms_graph", + "windows_live": "goa-account-windows_live", + "exchange": "goa-account-exchange", + "owncloud": "goa-account-owncloud", + "nextcloud": "goa-account-owncloud", + "webdav": "goa-account-webdav", + "imap_smtp": "goa-account-imap_smtp", + "fedora": "goa-account-fedora", + "kerberos": "goa-account-kerberos", +} + + +def provider_icon(provider_type: str) -> str: + """Symbolisches Icon für den Provider (Fallback: generisches Web-Icon).""" + return PROVIDER_ICONS.get(provider_type, "goa-account") + + +def list_accounts() -> list[Account]: + """Alle bekannten Online-Konten als entkoppelte Account-Snapshots. + + Greift NICHT, wenn GOA fehlt — gibt dann [] zurück. + """ + client = _client() + if client is None: + return [] + out: list[Account] = [] + try: + objects = client.get_accounts() + except Exception: + return [] + for obj in objects: + acc = obj.get_account() + if acc is None: + continue + try: + out.append( + Account( + id=acc.props.id or "", + provider_type=acc.props.provider_type or "", + provider_name=acc.props.provider_name or "", + identity=acc.props.identity or "", + presentation_identity=acc.props.presentation_identity or "", + has_calendar=obj.get_calendar() is not None, + has_contacts=obj.get_contacts() is not None, + has_mail=obj.get_mail() is not None, + ) + ) + except Exception: + # Einzelnes kaputtes Konto soll die Liste nicht killen. + continue + return out + + +def remove_account(account_id: str) -> tuple[bool, str]: + """Entfernt das Konto mit dieser GOA-id via acc.call_remove_sync. + + Rückgabe: (ok, message). Bei Fehler ok=False + Fehlertext (für die GUI). + """ + client = _client() + if client is None: + return False, "GNOME Online Accounts is not available." + try: + for obj in client.get_accounts(): + acc = obj.get_account() + if acc is not None and (acc.props.id or "") == account_id: + acc.call_remove_sync(None) + return True, "" + except Exception as exc: # GLib.Error o.ä. + return False, str(exc) + return False, "Account not found." + + +# -- Hinzufügen: GOA hat keinen sauber einbettbaren Add-Dialog mehr. --------- +# Pragmatisch: das System-UI für Online-Konten starten, falls vorhanden. +# Reihenfolge nach Verbreitung; jeweils (argv). +_ADD_LAUNCHERS: list[list[str]] = [ + ["gnome-control-center", "online-accounts"], + ["gnome-online-accounts-gtk"], # eigenständiges GTK4-UI (manche Distros) +] + + +def add_ui_command() -> list[str] | None: + """Argv des verfügbaren System-UIs zum Konten-Hinzufügen, sonst None.""" + for argv in _ADD_LAUNCHERS: + if shutil.which(argv[0]): + return argv + return None + + +def launch_add_ui() -> tuple[bool, str]: + """Startet das System-UI zum Hinzufügen eines Kontos (best effort).""" + argv = add_ui_command() + if argv is None: + return ( + False, + "No system UI for online accounts found " + "(gnome-control-center or gnome-online-accounts-gtk).", + ) + try: + subprocess.Popen(argv, start_new_session=True) + return True, "" + except OSError as exc: + return False, str(exc) diff --git a/src/taninux/core/panel.py b/src/taninux/core/panel.py index 133d3f8..1ba54d4 100644 --- a/src/taninux/core/panel.py +++ b/src/taninux/core/panel.py @@ -27,6 +27,10 @@ _DEFAULTS = { # Update-Scope: gilt für Hub UND den eww-Update-Zähler. AUR ist Opt-in # (braucht paru/base-devel) — Default Repo + Flatpak. "updates": {"include_aur": False}, + # OSD: macOS-artiges Center-Overlay für Lautstärke + Caps/Num-Lock. + "osd": {"enabled": True}, + # Kalender-Panel: Schwellen für heutige/anstehende Termine (eww konsumiert). + "calendar": {"today_min": 2, "max_entries": 5, "days_ahead": 7}, } @@ -46,6 +50,22 @@ def _apply_script() -> Path: # ----------------------------------------------------------- panel.json +def _clamp_int(value, lo: int, hi: int, default: int) -> int: + try: + n = int(value) + except (TypeError, ValueError): + n = default + return max(lo, min(hi, n)) + + +def _calendar_out(cal: dict) -> dict: + return { + "today_min": _clamp_int(cal.get("today_min", 2), 0, 10, 2), + "max_entries": _clamp_int(cal.get("max_entries", 5), 1, 10, 5), + "days_ahead": _clamp_int(cal.get("days_ahead", 7), 1, 31, 7), + } + + def load() -> dict: """panel.json lesen, mit Defaults auffüllen (defensiv).""" cfg = json.loads(json.dumps(_DEFAULTS)) # deep copy @@ -67,6 +87,17 @@ def load() -> dict: upd = data.get("updates") if isinstance(upd, dict) and "include_aur" in upd: cfg["updates"]["include_aur"] = bool(upd["include_aur"]) + osd = data.get("osd") + if isinstance(osd, dict) and "enabled" in osd: + cfg["osd"]["enabled"] = bool(osd["enabled"]) + cal = data.get("calendar") + if isinstance(cal, dict): + for k in ("today_min", "max_entries", "days_ahead"): + if k in cal: + try: + cfg["calendar"][k] = int(cal[k]) + except (TypeError, ValueError): + pass return cfg @@ -99,6 +130,10 @@ def save(cfg: dict, *, apply: bool = True) -> None: "updates": { "include_aur": bool(cfg.get("updates", {}).get("include_aur", False)), }, + "osd": { + "enabled": bool(cfg.get("osd", {}).get("enabled", True)), + }, + "calendar": _calendar_out(cfg.get("calendar", {})), } p = _panel_path() try: diff --git a/src/taninux/core/wine.py b/src/taninux/core/wine.py new file mode 100644 index 0000000..049b10b --- /dev/null +++ b/src/taninux/core/wine.py @@ -0,0 +1,125 @@ +"""Wine backend — prefix discovery, command helpers, package/component catalog.""" +from __future__ import annotations + +import shutil +from pathlib import Path + +PREFIXES_DIR = Path.home() / ".local" / "share" / "wineprefixes" + +# (pacman-name, display-name, short description) +WINE_PACKAGES: list[tuple[str, str, str]] = [ + ("wine", "Wine", "Run Windows programs — stable 64+32-bit build"), + ("wine-staging", "Wine Staging", "Patched Wine with extra compatibility fixes"), + ("wine-mono", "Wine Mono", ".NET Framework replacement (most apps need this)"), + ("wine-gecko", "Wine Gecko", "Internet Explorer engine replacement"), + ("winetricks", "Winetricks", "Install Windows components into prefixes"), + ("vkd3d-proton", "VKD3D-Proton", "DirectX 12 → Vulkan — best for modern DX12 games"), + ("dxvk", "DXVK", "DirectX 9–11 → Vulkan — big GPU performance boost"), +] + +# (category-label, [(winetricks-verb, display-name, description), …]) +WINETRICKS_COMPONENTS: list[tuple[str, list[tuple[str, str, str]]]] = [ + ("Fonts", [ + ("corefonts", "MS Core Fonts", "Arial, Times New Roman, Verdana, Courier — base for most apps"), + ("tahoma", "Tahoma", "Common Windows UI font (older apps, browsers)"), + ("calibri", "Calibri", "Modern Microsoft Office default font"), + ("consolas", "Consolas", "Monospace font used in IDEs and terminals"), + ]), + ("Visual C++ Runtime", [ + ("vcrun2022", "VC++ 2022", "Latest — needed by most modern apps"), + ("vcrun2019", "VC++ 2019", "Visual Studio 2019 redistributable"), + ("vcrun2017", "VC++ 2017", "Visual Studio 2017 redistributable"), + ("vcrun2015", "VC++ 2015", "Visual Studio 2015 redistributable"), + ("vcrun2013", "VC++ 2013", "Older application runtime"), + ("vcrun2010", "VC++ 2010", "Legacy application runtime"), + ]), + (".NET Framework", [ + ("dotnet48", ".NET 4.8", "Windows .NET 4.8 — many apps require this"), + ("dotnet6", ".NET 6", "Cross-platform .NET 6 runtime"), + ("dotnet7", ".NET 7", "Cross-platform .NET 7 runtime"), + ]), + ("DirectX / GPU", [ + ("dxvk", "DXVK", "DirectX 9–11 → Vulkan (applied to this prefix)"), + ("vkd3d", "VKD3D", "DirectX 12 → Vulkan"), + ("d3dx9_43", "D3DX9", "Direct3D 9 utility library"), + ("xact", "XACT", "DirectX audio component"), + ]), + ("Audio & Input", [ + ("openal", "OpenAL", "3D positional audio"), + ("xinput", "XInput", "Xbox / gamepad controller support"), + ]), +] + + +def is_wine_installed() -> bool: + return shutil.which("wine") is not None + + +def is_winetricks_installed() -> bool: + return shutil.which("winetricks") is not None + + +def prefix_arch(path: Path) -> str: + """Detect 'win64' or 'win32' from prefix system.reg.""" + reg = path / "system.reg" + if not reg.exists(): + return "?" + try: + for line in reg.read_text(errors="replace").splitlines()[:30]: + if "#arch=win32" in line: + return "win32" + if "#arch=win64" in line: + return "win64" + except OSError: + pass + return "win64" + + +def list_prefixes() -> list[Path]: + """Return all known Wine prefixes (default ~/.wine first, then custom ones).""" + found: list[Path] = [] + default = Path.home() / ".wine" + if default.exists() and (default / "system.reg").exists(): + found.append(default) + if PREFIXES_DIR.exists(): + for p in sorted(PREFIXES_DIR.iterdir()): + if p.is_dir() and (p / "system.reg").exists(): + found.append(p) + return found + + +def prefix_display_name(path: Path) -> str: + if path.name == ".wine": + return "~/.wine (default)" + return path.name + + +def create_prefix_argv(path: Path, arch: str = "win64") -> list[str]: + return [ + "env", f"WINEPREFIX={path}", f"WINEARCH={arch}", + "wine", "wineboot", "--init", + ] + + +def run_exe_argv(prefix: Path, exe: Path) -> list[str]: + return ["env", f"WINEPREFIX={prefix}", "wine", str(exe)] + + +def winetricks_argv(prefix: Path, *verbs: str) -> list[str]: + return ["env", f"WINEPREFIX={prefix}", "winetricks", "--unattended", *verbs] + + +def new_prefix_path(name: str) -> Path: + PREFIXES_DIR.mkdir(parents=True, exist_ok=True) + return PREFIXES_DIR / name + + +def safe_to_delete(path: Path) -> bool: + """Only allow deleting paths inside PREFIXES_DIR or the default ~/.wine.""" + default = Path.home() / ".wine" + try: + path.relative_to(PREFIXES_DIR) + return True + except ValueError: + pass + return path == default diff --git a/src/taninux/gui/app.py b/src/taninux/gui/app.py index 118f150..8f6a184 100644 --- a/src/taninux/gui/app.py +++ b/src/taninux/gui/app.py @@ -1,4 +1,4 @@ -"""Adw.Application-Einstiegspunkte fürs GTK-Frontend. +"""GTK-Frontend-Einstiegspunkte. Zwei Apps auf derselben Codebasis (teilen core/ + die GUI-Bausteine): @@ -14,25 +14,23 @@ import sys import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gio # noqa: E402 +from gi.repository import Gio # noqa: E402 -from taninux.gui import APP_ID, style +from taninux.gui import APP_ID, style, ui from taninux.gui.pages import HUB_SECTIONS, SETTINGS_SECTIONS -from taninux.gui.window import MainWindow -class _ShellApp(Adw.Application): +class _ShellApp(ui.Application): def __init__(self, app_id: str, title: str, sections) -> None: super().__init__(application_id=app_id, flags=Gio.ApplicationFlags.DEFAULT_FLAGS) self._title = title self._sections = sections - self._window: MainWindow | None = None + self._window = None def do_activate(self) -> None: if self._window is None: style.apply_persisted() # gespeichertes Farbschema + Fuji-Akzent - self._window = MainWindow(self, self._sections, self._title) + self._window = ui.MainWindow(self, self._sections, self._title) self._window.present() diff --git a/src/taninux/gui/confirm.py b/src/taninux/gui/confirm.py index fc1d976..1907706 100644 --- a/src/taninux/gui/confirm.py +++ b/src/taninux/gui/confirm.py @@ -1,7 +1,7 @@ """Wiederverwendbare Bestätigungs-Dialoge fürs GTK-Frontend. Vor destruktiven Aktionen (Pakete entfernen, Cache leeren, Bootloader ändern) -zeigen die Seiten einen `Adw.AlertDialog` und rufen den Callback nur bei +zeigen die Seiten einen `ui.AlertDialog` und rufen den Callback nur bei „Fortfahren". So liegt das Bestätigungsmuster an EINER Stelle. """ from __future__ import annotations @@ -11,8 +11,8 @@ from typing import Callable import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui def confirm( @@ -27,14 +27,14 @@ def confirm( `parent` ist irgendein Widget im Fenster — der Dialog hängt sich an dessen Root (Gtk.Window) an. Nicht-blockierend (libadwaita-Async-Pattern). """ - dialog = Adw.AlertDialog(heading=heading, body=body) + dialog = ui.AlertDialog(heading=heading, body=body) dialog.add_response("cancel", "Cancel") dialog.add_response("confirm", "Continue") - dialog.set_response_appearance("confirm", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.set_response_appearance("confirm", ui.ResponseAppearance.DESTRUCTIVE) dialog.set_default_response("cancel") dialog.set_close_response("cancel") - def _on_response(_dlg: Adw.AlertDialog, response: str) -> None: + def _on_response(_dlg: ui.AlertDialog, response: str) -> None: if response == "confirm": on_confirm() diff --git a/src/taninux/gui/console.py b/src/taninux/gui/console.py index 47526e7..ee0dbe6 100644 --- a/src/taninux/gui/console.py +++ b/src/taninux/gui/console.py @@ -1,62 +1,49 @@ -"""Scrollbare Monospace-Log-Konsole + Run-Button — wiederverwendbar. +"""Log console — writes to ~/.local/share/taninux/taninux.log (no UI widget). -Kapselt das Muster „Button drücken → privilegierten Befehl async ausführen → -Output live mitlesen". Genutzt von der Update- und Kernel-Seite. +All LogConsole.append_line() calls now write timestamped lines to the log file +instead of displaying them in a scrollable in-app widget. The log_expander() +and collapsible_log() helpers are kept as no-ops so callers can be cleaned up +incrementally, but they return None and must not be added to the page. """ from __future__ import annotations +from datetime import datetime +from pathlib import Path + import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 from taninux.gui import confirm as confirm_mod from taninux.gui.process import ProcessRunner +_LOG_PATH = Path.home() / ".local/share/taninux/taninux.log" -class LogConsole(Gtk.Box): - """TextView in einem ScrolledWindow, das automatisch nach unten scrollt.""" - def __init__(self) -> None: - super().__init__(orientation=Gtk.Orientation.VERTICAL) - self.add_css_class("card") +def _write_log(message: str) -> None: + """Append a timestamped line to the taninux log file.""" + try: + _LOG_PATH.parent.mkdir(parents=True, exist_ok=True) + with _LOG_PATH.open("a") as fh: + fh.write(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] {message}\n") + except OSError: + pass - self._view = Gtk.TextView(editable=False, cursor_visible=False, monospace=True) - self._view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) - self._view.set_left_margin(10) - self._view.set_right_margin(10) - self._view.set_top_margin(8) - self._view.set_bottom_margin(8) - self._buffer = self._view.get_buffer() - # Höhe auf ~5 Zeilen begrenzt (vorher 220px = riesiges Feld), scrollbar. - scroller = Gtk.ScrolledWindow(hexpand=True) - scroller.set_min_content_height(112) - scroller.set_max_content_height(112) - scroller.set_propagate_natural_height(False) - scroller.set_child(self._view) - self.set_vexpand(False) - self.append(scroller) +class LogConsole: + """File-backed log sink — no UI widget, writes to taninux.log.""" def clear(self) -> None: - self._buffer.set_text("") + """No-op: file log is append-only; clearing makes no sense.""" def append_line(self, text: str) -> None: - end = self._buffer.get_end_iter() - self._buffer.insert(end, text + "\n") - # ans Ende scrollen - mark = self._buffer.create_mark(None, self._buffer.get_end_iter(), False) - self._view.scroll_mark_onscreen(mark) - self._buffer.delete_mark(mark) + _write_log(text) -def collapsible_log(console: LogConsole, *, title: str = "Log") -> Gtk.Expander: - """Konsole in einem zugeklappten, dezenten „Log"-Disclosure.""" - exp = Gtk.Expander(label=title) - exp.set_margin_top(6) - exp.set_child(console) - return exp +def collapsible_log(console: LogConsole, *, title: str = "Log") -> None: + """Removed — log is now written to file. Returns None.""" + return None class RunnerCard(Gtk.Box): @@ -75,7 +62,7 @@ class RunnerCard(Gtk.Box): self._privileged = privileged self._confirm = confirm self._runner = ProcessRunner() - self._runner.connect("line", lambda _r, line: self._console.append_line(line)) + self._runner.connect("line", lambda _r, line: _write_log(line)) self._runner.connect("done", self._on_done) self._button = Gtk.Button(label=button_label) @@ -85,12 +72,8 @@ class RunnerCard(Gtk.Box): self._button.connect("clicked", self._on_click) self.append(self._button) - self._console = LogConsole() - self.append(collapsible_log(self._console)) - def _run(self) -> None: self._button.set_sensitive(False) - self._console.clear() self._runner.run(self._argv, privileged=self._privileged) def _on_click(self, _button: Gtk.Button) -> None: @@ -101,23 +84,12 @@ class RunnerCard(Gtk.Box): self._run() def _on_done(self, _runner: ProcessRunner, code: int) -> None: - self._console.append_line("") - self._console.append_line( - f"✔ done (exit {code})" if code == 0 else f"✘ exit {code}" - ) + _write_log("") + _write_log(f"✔ done (exit {code})" if code == 0 else f"✘ exit {code}") self._button.set_sensitive(True) def log_expander(console: LogConsole, *, title: str = "Log", - expanded: bool = False) -> Adw.PreferencesGroup: - """Verpackt eine LogConsole in eine einklappbare „Log"-Zeile (Pfeil). - - Standardmäßig zugeklappt; aufgeklappt zeigt sie die auf ~5 Zeilen - begrenzte, scrollbare Konsole. Rückgabe = PreferencesGroup (für page.add()). - Ersetzt das frühere „große schwarze Log-Feld" auf jeder Seite. - """ - group = Adw.PreferencesGroup() - exp = collapsible_log(console, title=title) - exp.set_expanded(expanded) - group.add(exp) - return group + expanded: bool = False) -> None: + """Removed — log is now written to file. Returns None.""" + return None diff --git a/src/taninux/gui/pages/__init__.py b/src/taninux/gui/pages/__init__.py index f014d7d..3de18b2 100644 --- a/src/taninux/gui/pages/__init__.py +++ b/src/taninux/gui/pages/__init__.py @@ -28,6 +28,7 @@ from taninux.gui.pages import ( lock, maintain, network, + online_accounts, panel, power, printers, @@ -38,6 +39,7 @@ from taninux.gui.pages import ( update, users, vpn, + wine, ) @@ -86,6 +88,11 @@ SETTINGS_SECTIONS: list[Section] = [ Page("shares", "Network Shares", "folder-remote-symbolic", shares.build, ("shares", "smb", "samba", "sftp", "ftp", "webdav", "nextcloud", "nfs", "mount", "nautilus", "cloud")), + Page("online_accounts", "Online Accounts", "goa-account", + online_accounts.build, + ("internet", "konten", "online", "accounts", "google", "microsoft", + "nextcloud", "caldav", "goa", "kalender", "kontakte", "calendar", + "contacts", "mail", "evolution")), ]), Section("Hardware", [ Page("display", "Display", "video-display-symbolic", display.build, @@ -140,6 +147,11 @@ HUB_SECTIONS: list[Section] = [ ("bundles", "packages", "install", "recommended", "suggestions", "dev", "gaming")), ]), + Section("Compatibility", [ + Page("wine", "Wine", "application-x-executable-symbolic", wine.build, + ("wine", "windows", "exe", "msi", "prefix", "winetricks", + "directx", "dxvk", "vkd3d", "proton", "gaming", "emulation")), + ]), ] # Default-Sektionen = System Settings (Rückwärtskompatibilität). diff --git a/src/taninux/gui/pages/about.py b/src/taninux/gui/pages/about.py index 213dd77..1cd4c79 100644 --- a/src/taninux/gui/pages/about.py +++ b/src/taninux/gui/pages/about.py @@ -3,8 +3,9 @@ from __future__ import annotations import gi -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import sysinfo @@ -17,8 +18,8 @@ def _ver() -> str: return "0.2.0" -def _row(title: str, value: str) -> Adw.ActionRow: - row = Adw.ActionRow(title=title) +def _row(title: str, value: str) -> ui.Row: + row = ui.Row(title=title) lbl = Gtk.Label(label=value, xalign=1.0, wrap=True) lbl.add_css_class("dim-label") lbl.set_selectable(True) @@ -39,18 +40,18 @@ def _uptime(sec: float) -> str: def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() mem = sysinfo.memory() gpus = sysinfo.gpus() - g_sys = Adw.PreferencesGroup(title="System") + g_sys = ui.Group(title="System") g_sys.add(_row("Device", sysinfo.hostname())) g_sys.add(_row("Operating system", sysinfo.distro_pretty())) g_sys.add(_row("Kernel", sysinfo.kernel_release())) g_sys.add(_row("TANINUX", _ver())) page.add(g_sys) - g_hw = Adw.PreferencesGroup(title="Hardware") + g_hw = ui.Group(title="Hardware") g_hw.add(_row("Processor", f"{sysinfo.cpu_model()} · {sysinfo.cpu_count()} cores")) g_hw.add(_row("Memory", _fmt_gib(mem.get("MemTotal", 0)))) if gpus: @@ -59,7 +60,7 @@ def build() -> Gtk.Widget: g_hw.add(_row(title, gpu.model)) page.add(g_hw) - g_st = Adw.PreferencesGroup(title="Status") + g_st = ui.Group(title="Status") g_st.add(_row("Uptime", _uptime(sysinfo.uptime_seconds()))) page.add(g_st) diff --git a/src/taninux/gui/pages/accessibility.py b/src/taninux/gui/pages/accessibility.py index d1ee2d0..f1dcab7 100644 --- a/src/taninux/gui/pages/accessibility.py +++ b/src/taninux/gui/pages/accessibility.py @@ -3,30 +3,31 @@ from __future__ import annotations import gi -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import accessibility as a11y -def _switch(title: str, subtitle: str, getter, setter) -> Adw.SwitchRow: - row = Adw.SwitchRow(title=title, subtitle=subtitle) +def _switch(title: str, subtitle: str, getter, setter) -> ui.SwitchRow: + row = ui.SwitchRow(title=title, subtitle=subtitle) row.set_active(getter()) row.connect("notify::active", lambda r, _p: setter(r.get_active())) return row def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() - g_see = Adw.PreferencesGroup(title="Seeing") + g_see = ui.Group(title="Seeing") g_see.add(_switch("Large text", "Increase the size of text", a11y.large_text, a11y.set_large_text)) g_see.add(_switch("High contrast", "Make windows and buttons stand out", a11y.high_contrast, a11y.set_high_contrast)) page.add(g_see) - g_motion = Adw.PreferencesGroup(title="Motion") + g_motion = ui.Group(title="Motion") g_motion.add(_switch("Reduce animations", "Minimise motion across the system", lambda: not a11y.animations(), lambda on: a11y.set_animations(not on))) diff --git a/src/taninux/gui/pages/appdetail.py b/src/taninux/gui/pages/appdetail.py index 101e17d..6ba5f92 100644 --- a/src/taninux/gui/pages/appdetail.py +++ b/src/taninux/gui/pages/appdetail.py @@ -1,4 +1,4 @@ -"""App-Detailansicht (Adw.Dialog) im GNOME-Software-Stil. +"""App-Detailansicht (ui.Dialog) im GNOME-Software-Stil. Zeigt Icon · Name · Beschreibung · Version/Größe/Lizenz/Website und lässt die Installationsquelle wählen (Repo · Flatpak · AUR falls aktiviert). Quellen und @@ -11,11 +11,11 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gdk, GLib, Gtk # noqa: E402 +from gi.repository import Gdk, GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import appinfo, panel -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner @@ -27,7 +27,7 @@ def _icon_name(name: str, fallback: str = "package-x-generic-symbolic") -> str: def open_app_detail(parent: Gtk.Widget, *, title: str, icon: str, repo_name: str | None = None, flatpak_id: str | None = None, remote: str = "flathub", on_changed=None) -> None: - dialog = Adw.Dialog() + dialog = ui.Dialog() dialog.set_title(title) dialog.set_content_width(540) dialog.set_content_height(640) @@ -38,14 +38,14 @@ def open_app_detail(parent: Gtk.Widget, *, title: str, icon: str, cand: list[appinfo.Detail] = [] - page = Adw.PreferencesPage() - tv = Adw.ToolbarView() - tv.add_top_bar(Adw.HeaderBar()) + page = ui.Page() + tv = ui.ToolbarView() + tv.add_top_bar(ui.HeaderBar()) tv.set_content(page) dialog.set_child(tv) # -- Kopf: Icon + Name + Summary -------------------------------------- - hg = Adw.PreferencesGroup() + hg = ui.Group() head = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=16) head.set_margin_top(8) head.set_margin_bottom(8) @@ -65,8 +65,8 @@ def open_app_detail(parent: Gtk.Widget, *, title: str, icon: str, page.add(hg) # -- Quelle + Aktion -------------------------------------------------- - sg = Adw.PreferencesGroup(title="Install") - source_combo = Adw.ComboRow(title="Source") + sg = ui.Group(title="Install") + source_combo = ui.ComboRow(title="Source") sg.add(source_combo) action_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) action_box.set_margin_top(10) @@ -74,7 +74,7 @@ def open_app_detail(parent: Gtk.Widget, *, title: str, icon: str, action_btn.add_css_class("suggested-action") action_btn.add_css_class("pill") action_btn.set_sensitive(False) - spinner = Adw.Spinner() + spinner = ui.Spinner() spinner.set_valign(Gtk.Align.CENTER) spinner.set_visible(False) action_box.append(action_btn) @@ -83,18 +83,16 @@ def open_app_detail(parent: Gtk.Widget, *, title: str, icon: str, page.add(sg) # -- Details ---------------------------------------------------------- - dg = Adw.PreferencesGroup(title="Details") + dg = ui.Group(title="Details") page.add(dg) detail_rows: list[Gtk.Widget] = [] - page.add(log_expander(console)) - def _selected() -> appinfo.Detail | None: idx = source_combo.get_selected() return cand[idx] if 0 <= idx < len(cand) else None def _static_row(label: str, value: str, *, link: bool = False) -> Gtk.Widget: - row = Adw.ActionRow(title=label) + row = ui.Row(title=label) if link: btn = Gtk.LinkButton(uri=value, label=value) btn.set_valign(Gtk.Align.CENTER) diff --git a/src/taninux/gui/pages/audio.py b/src/taninux/gui/pages/audio.py index 7c23880..d727a2e 100644 --- a/src/taninux/gui/pages/audio.py +++ b/src/taninux/gui/pages/audio.py @@ -10,17 +10,17 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import audio def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if not audio.available(): - group = Adw.PreferencesGroup(title="Audio") - group.add(Adw.ActionRow( + group = ui.Group(title="Audio") + group.add(ui.Row( title="No audio server", subtitle="pactl found no PulseAudio/PipeWire server.", )) @@ -39,9 +39,9 @@ def build() -> Gtk.Widget: return page -def _apps_group() -> Adw.PreferencesGroup: +def _apps_group() -> ui.Group: """Per-App-Lautstärke der gerade abspielenden Sink-Inputs.""" - group = Adw.PreferencesGroup(title="Applications") + group = ui.Group(title="Applications") refresh = Gtk.Button(icon_name="view-refresh-symbolic") refresh.add_css_class("flat") @@ -58,7 +58,7 @@ def _apps_group() -> Adw.PreferencesGroup: streams = audio.playback_streams() if not streams: - row = Adw.ActionRow(title="No apps are playing audio") + row = ui.Row(title="No apps are playing audio") group.add(row) rows.append(row) return @@ -72,10 +72,10 @@ def _apps_group() -> Adw.PreferencesGroup: return group -def _stream_row(st: audio.Stream) -> Adw.ActionRow: +def _stream_row(st: audio.Stream) -> ui.Row: icon_on = "audio-volume-high-symbolic" icon_off = "audio-volume-muted-symbolic" - row = Adw.ActionRow(title=st.app) + row = ui.Row(title=st.app) adj = Gtk.Adjustment(value=0, lower=0, upper=150, step_increment=1, page_increment=5) @@ -111,12 +111,12 @@ def _stream_row(st: audio.Stream) -> Adw.ActionRow: return row -def _audio_group(kind: str, title: str, icon_on: str, icon_off: str) -> Adw.PreferencesGroup: +def _audio_group(kind: str, title: str, icon_on: str, icon_off: str) -> ui.Group: devices = audio.list_devices(kind) - group = Adw.PreferencesGroup(title=title) + group = ui.Group(title=title) if not devices: - group.add(Adw.ActionRow(title="No devices found")) + group.add(ui.Row(title="No devices found")) return group names = [d.name for d in devices] @@ -125,12 +125,12 @@ def _audio_group(kind: str, title: str, icon_on: str, icon_off: str) -> Adw.Pref state = {"name": default if default in names else names[0]} # -- Geräte-Auswahl (= Default setzen) ---------------------------- - combo = Adw.ComboRow(title="Device") + combo = ui.ComboRow(title="Device") combo.set_model(Gtk.StringList.new(descs)) combo.set_selected(names.index(state["name"])) # -- Lautstärke-Zeile: Slider + % + Mute -------------------------- - vol_row = Adw.ActionRow(title="Volume") + vol_row = ui.Row(title="Volume") adj = Gtk.Adjustment(value=0, lower=0, upper=150, step_increment=1, page_increment=5) scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=adj) @@ -167,7 +167,7 @@ def _audio_group(kind: str, title: str, icon_on: str, icon_off: str) -> Adw.Pref load(state["name"]) - def on_combo(c: Adw.ComboRow, _p) -> None: + def on_combo(c: ui.ComboRow, _p) -> None: i = c.get_selected() if 0 <= i < len(names): state["name"] = names[i] diff --git a/src/taninux/gui/pages/backup.py b/src/taninux/gui/pages/backup.py index dfd9f3f..1d47862 100644 --- a/src/taninux/gui/pages/backup.py +++ b/src/taninux/gui/pages/backup.py @@ -8,17 +8,18 @@ from __future__ import annotations import gi -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import backup from taninux.gui import confirm -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if not backup.available(): _build_install(page) @@ -31,7 +32,7 @@ def build() -> Gtk.Widget: action_runner.connect("line", lambda _r, line: console.append_line(line)) # Snapshots-Gruppe (wird in-place neu befüllt nach jeder Aktion). - g_snaps = Adw.PreferencesGroup( + g_snaps = ui.Group( title="Snapshots", description="Restore reverts the system and may require a reboot.", ) @@ -54,17 +55,14 @@ def build() -> Gtk.Widget: page.add(g_snaps) reload_snapshots() - # -- Log-Gruppe --------------------------------------------------- - page.add(log_expander(console)) - return page -def _build_install(page: Adw.PreferencesPage) -> None: +def _build_install(page: ui.Page) -> None: """Fallback-UI, wenn timeshift nicht installiert ist.""" from taninux.gui.console import RunnerCard - group = Adw.PreferencesGroup( + group = ui.Group( title="Timeshift not installed", description="Backups use Timeshift for system snapshots.", ) @@ -80,11 +78,11 @@ def _build_install(page: Adw.PreferencesPage) -> None: page.add(group) -def _create_group(console: LogConsole, runner: ProcessRunner) -> Adw.PreferencesGroup: +def _create_group(console: LogConsole, runner: ProcessRunner) -> ui.Group: """EntryRow für Kommentar + Button zum Erstellen eines Snapshots.""" - group = Adw.PreferencesGroup(title="Create snapshot") + group = ui.Group(title="Create snapshot") - entry = Adw.EntryRow(title="Comment") + entry = ui.EntryRow(title="Comment") group.add(entry) button = Gtk.Button(label="Create snapshot") @@ -108,7 +106,7 @@ def _create_group(console: LogConsole, runner: ProcessRunner) -> Adw.Preferences def _load_snapshots( - group: Adw.PreferencesGroup, + group: ui.Group, rows: list[Gtk.Widget], console: LogConsole, action_runner: ProcessRunner, @@ -121,7 +119,7 @@ def _load_snapshots( group.remove(row) rows.clear() - spinner_row = Adw.ActionRow(title="Loading snapshots…") + spinner_row = ui.Row(title="Loading snapshots…") spinner = Gtk.Spinner() spinner.start() spinner_row.add_prefix(spinner) @@ -138,21 +136,21 @@ def _load_snapshots( rows.remove(spinner_row) if code != 0: - err_row = Adw.ActionRow(title="Could not load snapshots") + err_row = ui.Row(title="Could not load snapshots") group.add(err_row) rows.append(err_row) return snapshots = backup.parse_list("\n".join(buffer)) if not snapshots: - empty_row = Adw.ActionRow(title="No snapshots yet") + empty_row = ui.Row(title="No snapshots yet") group.add(empty_row) rows.append(empty_row) return for snap in snapshots: subtitle = " ".join(p for p in (snap.tags, snap.description) if p) - row = Adw.ActionRow(title=snap.name, subtitle=subtitle or None) + row = ui.Row(title=snap.name, subtitle=subtitle or None) row.add_suffix(_restore_button(snap.name, console, action_runner, reload)) row.add_suffix(_delete_button(snap.name, console, action_runner, reload)) group.add(row) diff --git a/src/taninux/gui/pages/bluetooth.py b/src/taninux/gui/pages/bluetooth.py index 5885e41..127710e 100644 --- a/src/taninux/gui/pages/bluetooth.py +++ b/src/taninux/gui/pages/bluetooth.py @@ -10,21 +10,21 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +from gi.repository import GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import bluetooth from taninux.gui import confirm -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if not bluetooth.available(): - g = Adw.PreferencesGroup(title="Bluetooth") - g.add(Adw.ActionRow( + g = ui.Group(title="Bluetooth") + g.add(ui.Row( title="No Bluetooth controller", subtitle="bluetoothctl found no adapter / BlueZ not running.", )) @@ -36,11 +36,11 @@ def build() -> Gtk.Widget: runner.connect("line", lambda _r, ln: console.append_line(ln)) # -- Power + Scan -------------------------------------------------- - g_ctrl = Adw.PreferencesGroup(title="Bluetooth") - power_sw = Adw.SwitchRow(title="Bluetooth", subtitle="Power the adapter") + g_ctrl = ui.Group(title="Bluetooth") + power_sw = ui.SwitchRow(title="Bluetooth", subtitle="Power the adapter") power_sw.set_active(bluetooth.powered()) - def on_power(sw: Adw.SwitchRow, _p) -> None: + def on_power(sw: ui.SwitchRow, _p) -> None: runner.run(bluetooth.set_power_argv(sw.get_active())) power_sw.connect("notify::active", on_power) @@ -48,7 +48,7 @@ def build() -> Gtk.Widget: page.add(g_ctrl) # -- Geräte (async) ------------------------------------------------ - g_dev = Adw.PreferencesGroup( + g_dev = ui.Group( title="Devices", description="Pairing PIN-protected devices may need a Bluetooth agent " "and can fail here — check the log.", @@ -82,7 +82,7 @@ def build() -> Gtk.Widget: def _render(devs: list[bluetooth.BtDevice]) -> None: _clear() if not devs: - empty = Adw.ActionRow(title="No devices", + empty = ui.Row(title="No devices", subtitle="Hit rescan to discover nearby devices.") g_dev.add(empty) rows.append(empty) @@ -91,7 +91,7 @@ def build() -> Gtk.Widget: sub = "connected" if d.connected else ("paired" if d.paired else "available") if d.paired and d.trusted: sub += " · trusted" - row = Adw.ActionRow(title=d.name, subtitle=sub) + row = ui.Row(title=d.name, subtitle=sub) row.add_prefix(Gtk.Image.new_from_icon_name("bluetooth-symbolic")) if not d.paired: @@ -143,8 +143,8 @@ def build() -> Gtk.Widget: def _scan() -> None: _clear() - spin_row = Adw.ActionRow(title="Scanning … (8 s)") - sp = Adw.Spinner() + spin_row = ui.Row(title="Scanning … (8 s)") + sp = ui.Spinner() sp.set_valign(Gtk.Align.CENTER) spin_row.add_prefix(sp) g_dev.add(spin_row) @@ -167,7 +167,4 @@ def build() -> Gtk.Widget: runner.connect("done", on_done) GLib.idle_add(lambda: (_load(), False)[1]) - - # -- Log ----------------------------------------------------------- - page.add(log_expander(console)) return page diff --git a/src/taninux/gui/pages/bundles.py b/src/taninux/gui/pages/bundles.py index 1dda576..f122e21 100644 --- a/src/taninux/gui/pages/bundles.py +++ b/src/taninux/gui/pages/bundles.py @@ -10,31 +10,31 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.bundles.loader import all_bundles from taninux.core import pacman from taninux.gui import confirm -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner from taninux.suggest import rules def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() console = LogConsole() runner = ProcessRunner() runner.connect("line", lambda _r, ln: console.append_line(ln)) - g_rec = Adw.PreferencesGroup( + g_rec = ui.Group( title="Recommended", description="System-aware suggestions for your hardware and tools.", ) page.add(g_rec) - g = Adw.PreferencesGroup(title="Bundles") + g = ui.Group(title="Bundles") page.add(g) rows: list[Gtk.Widget] = [] @@ -66,7 +66,7 @@ def build() -> Gtk.Widget: total = len(b.packages) complete = n_inst == total - row = Adw.ExpanderRow(title=b.label, subtitle=b.tagline) + row = ui.ExpanderRow(title=b.label, subtitle=b.tagline) # Count-Badge als Suffix (statt "Install missing"-Button). badge = Gtk.Label(label=f"{n_inst}/{total}") @@ -78,7 +78,7 @@ def build() -> Gtk.Widget: # Pro Paket eine Zeile: installiert = dim-Tag, fehlend = Install-Button. for pkg in b.packages: - pr = Adw.ActionRow(title=pkg) + pr = ui.Row(title=pkg) if pkg in inst: tag = Gtk.Label(label="installed") tag.add_css_class("dim-label") @@ -102,13 +102,13 @@ def build() -> Gtk.Widget: rec_rows.clear() sugg = rules.all_suggestions(set(pacman.installed_packages())) if not sugg: - row = Adw.ActionRow(title="Nothing recommended", + row = ui.Row(title="Nothing recommended", subtitle="Your system looks complete.") g_rec.add(row) rec_rows.append(row) return for s in sorted(sugg, key=lambda x: x.priority): - row = Adw.ActionRow(title=s.title or s.reason, subtitle=s.reason) + row = ui.Row(title=s.title or s.reason, subtitle=s.reason) btn = Gtk.Button(label="Install") btn.add_css_class("suggested-action") btn.set_valign(Gtk.Align.CENTER) @@ -133,8 +133,5 @@ def build() -> Gtk.Widget: runner.connect("done", on_done) - # -- Log ---------------------------------------------------------- - page.add(log_expander(console)) - _refresh() return page diff --git a/src/taninux/gui/pages/dashboard.py b/src/taninux/gui/pages/dashboard.py index aa50db8..f2d6840 100644 --- a/src/taninux/gui/pages/dashboard.py +++ b/src/taninux/gui/pages/dashboard.py @@ -13,8 +13,7 @@ from pathlib import Path import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +from gi.repository import GLib, Gtk # noqa: E402 from taninux.core import audio, network, packages, power, sysinfo @@ -274,8 +273,13 @@ def build() -> Gtk.Widget: threading.Thread(target=worker, daemon=True).start() - page = Adw.PreferencesPage() - grp = Adw.PreferencesGroup() - grp.add(flow) - page.add(grp) - return page + # Backend-unabhängiger Wrapper: scrollendes Card-Grid mit Rand (kein Adw). + flow.set_margin_top(20) + flow.set_margin_bottom(20) + flow.set_margin_start(20) + flow.set_margin_end(20) + scroller = Gtk.ScrolledWindow(hexpand=True, vexpand=True) + scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroller.add_css_class("tn-page") + scroller.set_child(flow) + return scroller diff --git a/src/taninux/gui/pages/defaultapps.py b/src/taninux/gui/pages/defaultapps.py index cd1e759..8d4d67a 100644 --- a/src/taninux/gui/pages/defaultapps.py +++ b/src/taninux/gui/pages/defaultapps.py @@ -4,10 +4,10 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gdk, Gtk # noqa: E402 +from gi.repository import Gdk, Gtk # noqa: E402 from taninux.core import defaultapps +from taninux.gui import ui def _icon(name: str, fallback: str) -> str: @@ -16,13 +16,13 @@ def _icon(name: str, fallback: str) -> str: def build() -> Gtk.Widget: - page = Adw.PreferencesPage() - group = Adw.PreferencesGroup(title="Default applications") + page = ui.Page() + group = ui.Group(title="Default applications") page.add(group) for label, mime, fallback in defaultapps.CATEGORIES: current, apps = defaultapps.apps_for(mime) - row = Adw.ComboRow(title=label) + row = ui.ComboRow(title=label) row.add_prefix(Gtk.Image.new_from_icon_name(fallback)) if not apps: diff --git a/src/taninux/gui/pages/display.py b/src/taninux/gui/pages/display.py index 7a4dcbf..535c867 100644 --- a/src/taninux/gui/pages/display.py +++ b/src/taninux/gui/pages/display.py @@ -9,8 +9,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import compositor, display from taninux.gui.console import RunnerCard @@ -22,11 +22,11 @@ _ROTATIONS = ["normal", "90", "180", "270"] def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if compositor.detect() == compositor.UNKNOWN: - group = Adw.PreferencesGroup(title="Display") - group.add(Adw.ActionRow( + group = ui.Group(title="Display") + group.add(ui.Row( title="Display settings aren't available in this session", )) page.add(group) @@ -34,8 +34,8 @@ def build() -> Gtk.Widget: outs = display.outputs() if not outs: - group = Adw.PreferencesGroup(title="Display") - group.add(Adw.ActionRow(title="No monitors detected")) + group = ui.Group(title="Display") + group.add(ui.Row(title="No monitors detected")) page.add(group) return page @@ -53,12 +53,12 @@ def build() -> Gtk.Widget: return page -def _output_group(out: display.Output, apply) -> Adw.PreferencesGroup: +def _output_group(out: display.Output, apply) -> ui.Group: desc = f"{out.make} {out.model}".strip() or None - group = Adw.PreferencesGroup(title=out.name, description=desc) + group = ui.Group(title=out.name, description=desc) # -- Enabled ----------------------------------------------------------- - enable_row = Adw.SwitchRow(title="Enabled") + enable_row = ui.SwitchRow(title="Enabled") enable_row.set_active(out.enabled) enable_row.connect( "notify::active", @@ -83,7 +83,7 @@ def _output_group(out: display.Output, apply) -> Adw.PreferencesGroup: if modes: labels = [f"{w}x{h} @ {hz:.2f}Hz" for (w, h, hz) in modes] - res_row = Adw.ComboRow(title="Resolution") + res_row = ui.ComboRow(title="Resolution") res_row.set_model(Gtk.StringList.new(labels)) sel = next( (i for i, m in enumerate(modes) @@ -103,7 +103,7 @@ def _output_group(out: display.Output, apply) -> Adw.PreferencesGroup: if not any(abs(s - out.scale) < 1e-3 for s in scales): scales.append(out.scale) scales.sort() - scale_row = Adw.ComboRow(title="Scale") + scale_row = ui.ComboRow(title="Scale") scale_row.set_model(Gtk.StringList.new([f"{s:g}" for s in scales])) sidx = next( (i for i, s in enumerate(scales) if abs(s - out.scale) < 1e-3), 0, @@ -116,7 +116,7 @@ def _output_group(out: display.Output, apply) -> Adw.PreferencesGroup: group.add(scale_row) # -- Rotation ---------------------------------------------------------- - rot_row = Adw.ComboRow(title="Rotation") + rot_row = ui.ComboRow(title="Rotation") rot_row.set_model(Gtk.StringList.new(_ROTATIONS)) ridx = _ROTATIONS.index(out.transform) if out.transform in _ROTATIONS else 0 rot_row.set_selected(ridx) @@ -129,14 +129,14 @@ def _output_group(out: display.Output, apply) -> Adw.PreferencesGroup: return group -def _night_light_group() -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup( +def _night_light_group() -> ui.Group: + group = ui.Group( title="Night light", description="Warm the screen colours in the evening.", ) if not display.wlsunset_installed(): - group.add(Adw.ActionRow(title="Install to enable night light")) + group.add(ui.Row(title="Install to enable night light")) group.add(RunnerCard( "Install", ["pacman", "-S", "--needed", "--noconfirm", "wlsunset"], @@ -146,27 +146,27 @@ def _night_light_group() -> Adw.PreferencesGroup: nl = display.night_light() - enable_row = Adw.SwitchRow(title="Night light") + enable_row = ui.SwitchRow(title="Night light") enable_row.set_active(nl.enabled) group.add(enable_row) - night_row = Adw.SpinRow.new_with_range(2500, 6500, 100) + night_row = ui.SpinRow.new_with_range(2500, 6500, 100) night_row.set_title("Night temperature") night_row.set_subtitle("Kelvin") night_row.set_value(nl.night_temp) group.add(night_row) - day_row = Adw.SpinRow.new_with_range(3000, 6500, 100) + day_row = ui.SpinRow.new_with_range(3000, 6500, 100) day_row.set_title("Day temperature") day_row.set_subtitle("Kelvin") day_row.set_value(nl.day_temp) group.add(day_row) - lat_row = Adw.EntryRow(title="Latitude") + lat_row = ui.EntryRow(title="Latitude") lat_row.set_text(f"{nl.lat:g}") group.add(lat_row) - lon_row = Adw.EntryRow(title="Longitude") + lon_row = ui.EntryRow(title="Longitude") lon_row.set_text(f"{nl.lon:g}") group.add(lon_row) diff --git a/src/taninux/gui/pages/firewall.py b/src/taninux/gui/pages/firewall.py index 17df9bf..3a93184 100644 --- a/src/taninux/gui/pages/firewall.py +++ b/src/taninux/gui/pages/firewall.py @@ -4,8 +4,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import firewall from taninux.gui.console import RunnerCard @@ -13,12 +13,12 @@ from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() - group = Adw.PreferencesGroup(title="Firewall") + page = ui.Page() + group = ui.Group(title="Firewall") page.add(group) if not firewall.installed(): - group.add(Adw.ActionRow(title="Install to turn on the firewall")) + group.add(ui.Row(title="Install to turn on the firewall")) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box.set_margin_top(6) box.append(RunnerCard("Install", firewall.install_argv(), privileged=True)) @@ -29,7 +29,7 @@ def build() -> Gtk.Widget: status = Gtk.Label(xalign=0.0) status.add_css_class("dim-label") - row = Adw.SwitchRow( + row = ui.SwitchRow( title="Firewall", subtitle="Block unsolicited incoming connections", ) diff --git a/src/taninux/gui/pages/hub_home.py b/src/taninux/gui/pages/hub_home.py index e4675f5..2ce0a40 100644 --- a/src/taninux/gui/pages/hub_home.py +++ b/src/taninux/gui/pages/hub_home.py @@ -11,18 +11,18 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gdk, GLib, Gtk # noqa: E402 +from gi.repository import Gdk, GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import catalog, pacman from taninux.gui.pages.appdetail import open_app_detail def build() -> Gtk.Widget: - page = Adw.PreferencesPage() - groups: list[Adw.PreferencesGroup] = [] + page = ui.Page() + groups: list[ui.Group] = [] - intro = Adw.PreferencesGroup( + intro = ui.Group( title="Recommended apps", description="Popular apps for everyday use — click for details.", ) @@ -76,8 +76,8 @@ def build() -> Gtk.Widget: tile.set_child(row) return tile - def _category_group(category: str, apps: list[catalog.App]) -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup(title=category) + def _category_group(category: str, apps: list[catalog.App]) -> ui.Group: + group = ui.Group(title=category) flow = Gtk.FlowBox() flow.set_selection_mode(Gtk.SelectionMode.NONE) flow.set_max_children_per_line(2) @@ -95,8 +95,8 @@ def build() -> Gtk.Widget: page.remove(g) groups.clear() if not apps: - g = Adw.PreferencesGroup() - g.add(Adw.StatusPage( + g = ui.Group() + g.add(ui.StatusPage( icon_name="emblem-ok-symbolic", title="You're all set", description="All recommended apps are already installed.", diff --git a/src/taninux/gui/pages/kernel.py b/src/taninux/gui/pages/kernel.py index 1c9fcf9..8931555 100644 --- a/src/taninux/gui/pages/kernel.py +++ b/src/taninux/gui/pages/kernel.py @@ -3,11 +3,12 @@ from __future__ import annotations import gi -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +gi.require_version("Gtk", "4.0") +from gi.repository import GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import bootloader, pacman, sysinfo -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner # Paketname → Anzeigename @@ -28,12 +29,11 @@ def _is_running(pkg: str, running: str) -> bool: def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() console = LogConsole() runner = ProcessRunner() runner.connect("line", lambda _r, line: console.append_line(line)) - groups: list[Adw.PreferencesGroup] = [] - log_group = log_expander(console) + groups: list[ui.Group] = [] def _install(pkg: str, btn: Gtk.Button) -> None: btn.set_sensitive(False) @@ -42,7 +42,7 @@ def build() -> Gtk.Widget: runner.run(["pacman", "-S", "--needed", "--noconfirm", pkg, f"{pkg}-headers"], privileged=True) - def _set_default(bl, combo: Adw.ComboRow, btn: Gtk.Button) -> None: + def _set_default(bl, combo: ui.ComboRow, btn: Gtk.Button) -> None: idx = combo.get_selected() if not (0 <= idx < len(bl.entries)): return @@ -58,18 +58,17 @@ def build() -> Gtk.Widget: for g in groups: page.remove(g) groups.clear() - page.remove(log_group) installed = pacman.installed_packages() running = sysinfo.kernel_release() - g_inst = Adw.PreferencesGroup(title="Installed kernels", + g_inst = ui.Group(title="Installed kernels", description=f"Running: {running}") any_inst = False for pkg, label in KNOWN_KERNELS.items(): if pkg in installed: any_inst = True - row = Adw.ActionRow(title=label, subtitle=pkg) + row = ui.Row(title=label, subtitle=pkg) if _is_running(pkg, running): badge = Gtk.Label(label="active") badge.add_css_class("success") @@ -78,15 +77,15 @@ def build() -> Gtk.Widget: row.add_suffix(badge) g_inst.add(row) if not any_inst: - g_inst.add(Adw.ActionRow(title="No known kernel packages found")) + g_inst.add(ui.Row(title="No known kernel packages found")) page.add(g_inst) groups.append(g_inst) available = [(p, l) for p, l in KNOWN_KERNELS.items() if p not in installed] if available: - g_avail = Adw.PreferencesGroup(title="Available to install") + g_avail = ui.Group(title="Available to install") for pkg, label in available: - row = Adw.ActionRow(title=label, subtitle=f"{pkg} · {pkg}-headers") + row = ui.Row(title=label, subtitle=f"{pkg} · {pkg}-headers") btn = Gtk.Button(label="Install") btn.add_css_class("suggested-action") btn.set_valign(Gtk.Align.CENTER) @@ -97,11 +96,11 @@ def build() -> Gtk.Widget: groups.append(g_avail) bl = bootloader.detect() - g_boot = Adw.PreferencesGroup(title="Bootloader") + g_boot = ui.Group(title="Bootloader") g_boot.add(_static_row("Type", bl.kind)) g_boot.add(_static_row("Default entry", bl.default_entry or "—")) if bl.kind == "systemd-boot" and bl.entries: - combo = Adw.ComboRow(title="Default boot entry", + combo = ui.ComboRow(title="Default boot entry", model=Gtk.StringList.new([e.name for e in bl.entries])) for i, e in enumerate(bl.entries): if bootloader.is_default(bl, e): @@ -116,21 +115,18 @@ def build() -> Gtk.Widget: page.add(g_boot) groups.append(g_boot) - page.add(log_group) - def on_done(_r: ProcessRunner, code: int) -> None: console.append_line("✔ done" if code == 0 else f"✘ exit {code}") pacman.invalidate() _render() runner.connect("done", on_done) - page.add(log_group) GLib.idle_add(lambda: (_render(), False)[1]) return page -def _static_row(title: str, value: str) -> Adw.ActionRow: - row = Adw.ActionRow(title=title) +def _static_row(title: str, value: str) -> ui.Row: + row = ui.Row(title=title) label = Gtk.Label(label=value) label.add_css_class("dim-label") label.set_selectable(True) diff --git a/src/taninux/gui/pages/locale.py b/src/taninux/gui/pages/locale.py index 0de5c85..b55764f 100644 --- a/src/taninux/gui/pages/locale.py +++ b/src/taninux/gui/pages/locale.py @@ -14,8 +14,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import localization from taninux.gui import confirm @@ -23,7 +23,7 @@ from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() page.add(_language_group()) page.add(_datetime_group()) page.add(_keyboard_group()) @@ -59,16 +59,16 @@ def _action_block(button_label: str): # -- Sprache -------------------------------------------------------------- -def _language_group() -> Adw.PreferencesGroup: +def _language_group() -> ui.Group: st = localization.status() locales = localization.list_locales() - group = Adw.PreferencesGroup( + group = ui.Group( title="Language", description="Applies on next login.", ) - combo = Adw.ComboRow(title="System language") + combo = ui.ComboRow(title="System language") combo.set_model(Gtk.StringList.new(locales or ["(none generated)"])) combo.set_enable_search(True) if st.locale in locales: @@ -101,14 +101,14 @@ def _language_group() -> Adw.PreferencesGroup: # -- Datum & Zeit --------------------------------------------------------- -def _datetime_group() -> Adw.PreferencesGroup: +def _datetime_group() -> ui.Group: timezones = localization.list_timezones() cur_tz = localization.current_timezone() - group = Adw.PreferencesGroup(title="Date and Time") + group = ui.Group(title="Date and Time") # Aktuelle lokale Zeit (read-only). - cur = Adw.ActionRow(title="Current") + cur = ui.Row(title="Current") cur_label = Gtk.Label(label=localization.local_time() or "—") cur_label.add_css_class("dim-label") cur_label.set_selectable(True) @@ -116,7 +116,7 @@ def _datetime_group() -> Adw.PreferencesGroup: group.add(cur) # Zeitzone wählen + privilegiert anwenden. - tz_combo = Adw.ComboRow(title="Time zone") + tz_combo = ui.ComboRow(title="Time zone") tz_combo.set_model(Gtk.StringList.new(timezones or ["(none)"])) tz_combo.set_enable_search(True) if cur_tz in timezones: @@ -151,13 +151,13 @@ def _datetime_group() -> Adw.PreferencesGroup: lambda _r, code: status.set_label("✔ Applied" if code == 0 else f"✘ exit {code}"), ) - ntp_row = Adw.SwitchRow( + ntp_row = ui.SwitchRow( title="Automatic time (NTP)", subtitle="Synchronize the clock over the network.", ) ntp_row.set_active(localization.ntp_enabled()) - def on_ntp(_row: Adw.SwitchRow, _pspec) -> None: + def on_ntp(_row: ui.SwitchRow, _pspec) -> None: ntp_runner.run(localization.set_ntp_argv(ntp_row.get_active()), privileged=True) ntp_row.connect("notify::active", on_ntp) @@ -169,14 +169,14 @@ def _datetime_group() -> Adw.PreferencesGroup: # -- Tastatur ------------------------------------------------------------- -def _keyboard_group() -> Adw.PreferencesGroup: +def _keyboard_group() -> ui.Group: st = localization.status() layouts = localization.list_x11_layouts() - group = Adw.PreferencesGroup(title="Keyboard") + group = ui.Group(title="Keyboard") # Ist-Zustand sichtbar machen (gerade weil niri davon abweichen kann). - cur = Adw.ActionRow(title="Current (system)") + cur = ui.Row(title="Current (system)") cur_val = f"{st.x11_layout or '—'}" if st.x11_variant: cur_val += f" · {st.x11_variant}" @@ -187,14 +187,14 @@ def _keyboard_group() -> Adw.PreferencesGroup: cur.add_suffix(cur_label) group.add(cur) - layout_combo = Adw.ComboRow(title="Layout") + layout_combo = ui.ComboRow(title="Layout") layout_combo.set_model(Gtk.StringList.new(layouts or ["(none)"])) layout_combo.set_enable_search(True) if st.x11_layout in layouts: layout_combo.set_selected(layouts.index(st.x11_layout)) group.add(layout_combo) - variant_combo = Adw.ComboRow(title="Variant") + variant_combo = ui.ComboRow(title="Variant") group.add(variant_combo) def _selected_layout() -> str: diff --git a/src/taninux/gui/pages/lock.py b/src/taninux/gui/pages/lock.py index d1bdc16..33d67f6 100644 --- a/src/taninux/gui/pages/lock.py +++ b/src/taninux/gui/pages/lock.py @@ -4,8 +4,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import lock from taninux.gui.console import RunnerCard @@ -13,12 +13,12 @@ from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() - group = Adw.PreferencesGroup(title="Screen lock") + page = ui.Page() + group = ui.Group(title="Screen lock") page.add(group) if not lock.locker(): - group.add(Adw.ActionRow(title="Install to enable screen locking")) + group.add(ui.Row(title="Install to enable screen locking")) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box.set_margin_top(6) box.append(RunnerCard("Install", lock.install_argv(), privileged=True)) @@ -26,7 +26,7 @@ def build() -> Gtk.Widget: return page runner = ProcessRunner() - row = Adw.ActionRow( + row = ui.Row( title="Lock now", subtitle="Auto-lock timing is set under Power.", ) diff --git a/src/taninux/gui/pages/maintain.py b/src/taninux/gui/pages/maintain.py index dc8e625..99ecbe9 100644 --- a/src/taninux/gui/pages/maintain.py +++ b/src/taninux/gui/pages/maintain.py @@ -15,13 +15,13 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gdk, GLib, Gtk # noqa: E402 +from gi.repository import Gdk, GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import trash from taninux.core.state import State from taninux.gui import confirm -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner from taninux.maintain.scanners import SCANNERS, ScannerSpec from taninux.maintain.types import Classification, Finding @@ -83,9 +83,9 @@ def _dot(cls: Classification) -> Gtk.Label: return dot -def _legend() -> Adw.ActionRow: +def _legend() -> ui.Row: """Erklärt die farbigen Punkte.""" - row = Adw.ActionRow(title="Legend") + row = ui.Row(title="Legend") box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=14) box.set_valign(Gtk.Align.CENTER) for cls in (Classification.SAFE, Classification.LIKELY_ORPHAN, @@ -118,18 +118,18 @@ def _fill_advice(advice: str, first: Finding | None) -> str: def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() # -- Summary (oben) ----------------------------------------------- - g_summary = Adw.PreferencesGroup(title="Cleanup") + g_summary = ui.Group(title="Cleanup") rescan_btn = Gtk.Button(icon_name="view-refresh-symbolic") rescan_btn.add_css_class("flat") rescan_btn.set_valign(Gtk.Align.CENTER) rescan_btn.set_tooltip_text("Rescan") g_summary.set_header_suffix(rescan_btn) - summary_row = Adw.ActionRow(title="Reclaimable", subtitle="Scanning …") - summary_spin = Adw.Spinner() + summary_row = ui.Row(title="Reclaimable", subtitle="Scanning …") + summary_spin = ui.Spinner() summary_spin.set_valign(Gtk.Align.CENTER) summary_row.add_prefix(summary_spin) g_summary.add(summary_row) @@ -144,17 +144,13 @@ def build() -> Gtk.Widget: scanner_groups: list[Gtk.Widget] = [] # -- Trash-Gruppe (unten) ----------------------------------------- - g_trash = Adw.PreferencesGroup( + g_trash = ui.Group( title="Trash", description="Files moved here are recoverable, nothing is deleted.", ) trash_rows: list[Gtk.Widget] = [] page.add(g_trash) - # -- Log-Gruppe (ganz unten, für Clean-Befehle) ------------------- - g_log = log_expander(console) - page.add(g_log) - def _clean_run(argv: list[str], priv: bool, label: str) -> None: def go() -> None: console.clear() @@ -181,7 +177,7 @@ def build() -> Gtk.Widget: entries = trash.list_trash() if not entries: - empty = Adw.ActionRow( + empty = ui.Row( title="Trash is empty", subtitle="Trashed items will appear here.", ) @@ -190,7 +186,7 @@ def build() -> Gtk.Widget: return for entry in entries: - row = Adw.ActionRow( + row = ui.Row( title=str(entry.original), subtitle=f"{_human(entry.size_bytes)} · {entry.trashed_at}", ) @@ -237,7 +233,7 @@ def build() -> Gtk.Widget: if spec.actions == "view" and spec.advice: desc += "\n" + _fill_advice(spec.advice, findings[0] if findings else None) - grp = Adw.PreferencesGroup(title=spec.label, description=desc) + grp = ui.Group(title=spec.label, description=desc) # view-Scanner: Header mit Copy-Button + (falls sicher) Clean-Button. if spec.actions == "view": @@ -274,11 +270,11 @@ def build() -> Gtk.Widget: if finding.reason: sub += f" · {finding.reason}" - row = Adw.ActionRow(title=title, subtitle=sub) + row = ui.Row(title=title, subtitle=sub) row.add_prefix(_dot(cls)) if spec.actions == "trash": - def _do_trash(f: Finding = finding, r: Adw.ActionRow = row, + def _do_trash(f: Finding = finding, r: ui.Row = row, lb: Gtk.ListBox = listbox) -> None: def confirmed() -> None: def worker() -> None: @@ -296,7 +292,7 @@ def build() -> Gtk.Widget: confirmed, ) - def _do_keep(f: Finding = finding, r: Adw.ActionRow = row, + def _do_keep(f: Finding = finding, r: ui.Row = row, lb: Gtk.ListBox = listbox) -> None: State.load().whitelist_add(f.path) lb.remove(r) @@ -309,7 +305,7 @@ def build() -> Gtk.Widget: listbox.append(row) if hidden > 0: - more = Adw.ActionRow(title=f"… +{hidden} more") + more = ui.Row(title=f"… +{hidden} more") more.add_css_class("dim-label") listbox.append(more) @@ -322,7 +318,7 @@ def build() -> Gtk.Widget: grp.add(scroller) return grp - def _after_trash(row: Adw.ActionRow, listbox: Gtk.ListBox, ok: bool) -> None: + def _after_trash(row: ui.Row, listbox: Gtk.ListBox, ok: bool) -> None: if ok: listbox.remove(row) _refresh_trash() @@ -332,9 +328,8 @@ def build() -> Gtk.Widget: _clear_scanner_groups() summary_spin.set_visible(False) - # Trash + Log temporär entfernen, damit Scanner-Gruppen davor landen. + # Trash temporär entfernen, damit Scanner-Gruppen davor landen. page.remove(g_trash) - page.remove(g_log) total = 0 for spec, findings in results: if not findings: @@ -346,7 +341,6 @@ def build() -> Gtk.Widget: scanner_groups.append(grp) page.add(g_trash) - page.add(g_log) summary_row.set_subtitle(f"{_human(total)} can be reclaimed safely") def _scan() -> None: diff --git a/src/taninux/gui/pages/network.py b/src/taninux/gui/pages/network.py index 0070ca2..4e00600 100644 --- a/src/taninux/gui/pages/network.py +++ b/src/taninux/gui/pages/network.py @@ -11,12 +11,12 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +from gi.repository import GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import network from taninux.gui import confirm -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner @@ -31,11 +31,11 @@ def _signal_icon(sig: int) -> str: def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if not network.available(): - g = Adw.PreferencesGroup(title="Network") - g.add(Adw.ActionRow( + g = ui.Group(title="Network") + g.add(ui.Row( title="NetworkManager not available", subtitle="nmcli found no running NetworkManager.", )) @@ -47,16 +47,16 @@ def build() -> Gtk.Widget: runner.connect("line", lambda _r, ln: console.append_line(ln)) # -- Wi-Fi radio + Status ----------------------------------------- - g_wifi = Adw.PreferencesGroup(title="Wi-Fi") - wifi_sw = Adw.SwitchRow(title="Wi-Fi", subtitle="Enable the wireless radio") + g_wifi = ui.Group(title="Wi-Fi") + wifi_sw = ui.SwitchRow(title="Wi-Fi", subtitle="Enable the wireless radio") wifi_sw.set_active(network.wifi_enabled()) - status_row = Adw.ActionRow(title="Status") + status_row = ui.Row(title="Status") status_lbl = Gtk.Label(label=network.active_summary()) status_lbl.add_css_class("dim-label") status_lbl.set_selectable(True) status_row.add_suffix(status_lbl) - def on_wifi(sw: Adw.SwitchRow, _p) -> None: + def on_wifi(sw: ui.SwitchRow, _p) -> None: runner.run(network.set_wifi_argv(sw.get_active())) wifi_sw.connect("notify::active", on_wifi) @@ -65,7 +65,7 @@ def build() -> Gtk.Widget: page.add(g_wifi) # -- Verfügbare Netze (async) ------------------------------------- - g_nets = Adw.PreferencesGroup(title="Available networks") + g_nets = ui.Group(title="Available networks") refresh = Gtk.Button(icon_name="view-refresh-symbolic") refresh.add_css_class("flat") refresh.set_valign(Gtk.Align.CENTER) @@ -118,7 +118,7 @@ def build() -> Gtk.Widget: def _render(nets: list[network.WifiNet], saved: set[str]) -> None: _clear() for net in nets: - row = Adw.ActionRow(title=net.ssid, + row = ui.Row(title=net.ssid, subtitle=net.security or "open") row.add_prefix(Gtk.Image.new_from_icon_name(_signal_icon(net.signal))) if net.security: @@ -142,8 +142,8 @@ def build() -> Gtk.Widget: def _load(rescan: bool = False) -> None: _clear() - spin_row = Adw.ActionRow(title="Scanning …" if rescan else "Loading …") - sp = Adw.Spinner() + spin_row = ui.Row(title="Scanning …" if rescan else "Loading …") + sp = ui.Spinner() sp.set_valign(Gtk.Align.CENTER) spin_row.add_prefix(sp) g_nets.add(spin_row) @@ -168,12 +168,11 @@ def build() -> Gtk.Widget: _load(rescan=False) runner.connect("done", on_done) - page.add(log_expander(console, title="Connection log")) return page def _ask_password(parent: Gtk.Widget, ssid: str, on_ok) -> None: - dialog = Adw.AlertDialog( + dialog = ui.AlertDialog( heading=f"Connect to {ssid}", body="Enter the Wi-Fi password.", ) @@ -182,11 +181,11 @@ def _ask_password(parent: Gtk.Widget, ssid: str, on_ok) -> None: dialog.set_extra_child(entry) dialog.add_response("cancel", "Cancel") dialog.add_response("connect", "Connect") - dialog.set_response_appearance("connect", Adw.ResponseAppearance.SUGGESTED) + dialog.set_response_appearance("connect", ui.ResponseAppearance.SUGGESTED) dialog.set_default_response("connect") dialog.set_close_response("cancel") - def on_resp(_d: Adw.AlertDialog, resp: str) -> None: + def on_resp(_d: ui.AlertDialog, resp: str) -> None: if resp == "connect": on_ok(entry.get_text()) diff --git a/src/taninux/gui/pages/online_accounts.py b/src/taninux/gui/pages/online_accounts.py new file mode 100644 index 0000000..9507966 --- /dev/null +++ b/src/taninux/gui/pages/online_accounts.py @@ -0,0 +1,171 @@ +"""Online Accounts — online accounts (Google/Microsoft/Nextcloud/CalDAV) via GOA. + +Lists and manages GNOME Online Accounts (core.online_accounts), which feed their +calendars/contacts/mail into the Evolution Data Server. Each row shows the +provider icon, the display identity, and what the account provides; a button +removes it (acc.call_remove_sync). Adding launches the system UI, because GOA's +add dialog can no longer be embedded cleanly. + +If the Goa typelib is missing (package not installed), the page shows an +ui.StatusPage instead of crashing. +""" +from __future__ import annotations + +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui + +from taninux.core import online_accounts as goa + + +def build() -> Gtk.Widget: + if not goa.available(): + return _not_installed_page() + return _accounts_page() + + +# -- GOA missing ----------------------------------------------------------- + +def _not_installed_page() -> Gtk.Widget: + status = ui.StatusPage( + icon_name="goa-account", + title="gnome-online-accounts not installed", + description=( + "Online accounts (Google, Microsoft, Nextcloud, CalDAV) require " + "the gnome-online-accounts package. It feeds calendars and " + "contacts into the Evolution Data Server.\n\n" + "Install it with: sudo pacman -S gnome-online-accounts" + ), + ) + # Wrap it in a Bin so the stack gets a single child. + bin_ = ui.Bin(child=status) + bin_.set_vexpand(True) + bin_.set_hexpand(True) + return bin_ + + +# -- GOA available --------------------------------------------------------- + +def _accounts_page() -> Gtk.Widget: + page = ui.Page() + + group = ui.Group( + title="Online Accounts", + description=( + "Online accounts provide calendars, contacts and mail " + "system-wide (via GNOME Online Accounts → Evolution Data Server)." + ), + ) + page.add(group) + + # Header action: add account (launches the system UI). + add_btn = Gtk.Button(label="Add account") + add_btn.add_css_class("flat") + add_btn.set_valign(Gtk.Align.CENTER) + group.set_header_suffix(add_btn) + + # Status/error line below the accounts. + status = Gtk.Label(xalign=0.0) + status.add_css_class("dim-label") + status.set_wrap(True) + status.set_margin_top(6) + status.set_margin_start(4) + + rows: list[Gtk.Widget] = [] + + def refresh() -> None: + for r in rows: + group.remove(r) + rows.clear() + accounts = goa.list_accounts() + if not accounts: + empty = ui.Row( + title="No accounts", + subtitle="Add an account to connect calendars and contacts.", + ) + empty.add_prefix(Gtk.Image.new_from_icon_name("goa-account")) + group.add(empty) + rows.append(empty) + return + for acc in accounts: + row = _account_row(acc, on_remove) + group.add(row) + rows.append(row) + + def on_remove(account_id: str) -> None: + ok, msg = goa.remove_account(account_id) + if ok: + status.set_label("✔ Account removed.") + else: + status.set_label(f"✘ Removal failed: {msg}") + refresh() + + def on_add(_b: Gtk.Button) -> None: + ok, msg = goa.launch_add_ui() + if ok: + status.set_label( + "The system UI for online accounts was opened. " + "Once added, the account will appear here." + ) + else: + status.set_label(f"✘ {msg}") + + add_btn.connect("clicked", on_add) + + refresh() + + # Refresh + hint in their own group. + actions = ui.Group() + refresh_row = ui.Row( + title="Refresh list", + subtitle="Reload accounts after changes made in the system UI.", + ) + refresh_btn = Gtk.Button(icon_name="view-refresh-symbolic") + refresh_btn.add_css_class("flat") + refresh_btn.set_valign(Gtk.Align.CENTER) + refresh_btn.connect("clicked", lambda _b: (refresh(), status.set_label(""))) + refresh_row.add_suffix(refresh_btn) + refresh_row.set_activatable_widget(refresh_btn) + actions.add(refresh_row) + actions.add(_status_holder(status)) + page.add(actions) + + return page + + +def _status_holder(status: Gtk.Label) -> Gtk.Widget: + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + box.append(status) + return box + + +def _account_row(acc: goa.Account, on_remove) -> ui.Row: + title = acc.presentation_identity or acc.identity or acc.provider_name + row = ui.Row(title=title, subtitle=acc.provider_name) + row.add_prefix(Gtk.Image.new_from_icon_name(goa.provider_icon(acc.provider_type))) + + # Read-only capability indicators (calendar/contacts/mail). + caps = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) + caps.set_valign(Gtk.Align.CENTER) + for active, label, icon in ( + (acc.has_calendar, "Calendar", "x-office-calendar-symbolic"), + (acc.has_contacts, "Contacts", "x-office-address-book-symbolic"), + (acc.has_mail, "Mail", "mail-unread-symbolic"), + ): + if active: + chip = Gtk.Image.new_from_icon_name(icon) + chip.set_tooltip_text(label) + chip.add_css_class("dim-label") + caps.append(chip) + if caps.get_first_child() is not None: + row.add_suffix(caps) + + rm = Gtk.Button(icon_name="user-trash-symbolic") + rm.add_css_class("flat") + rm.set_valign(Gtk.Align.CENTER) + rm.set_tooltip_text("Remove account") + rm.connect("clicked", lambda _b, aid=acc.id: on_remove(aid)) + row.add_suffix(rm) + return row diff --git a/src/taninux/gui/pages/panel.py b/src/taninux/gui/pages/panel.py index 7b54754..6525702 100644 --- a/src/taninux/gui/pages/panel.py +++ b/src/taninux/gui/pages/panel.py @@ -8,8 +8,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import panel @@ -24,7 +24,7 @@ _MODULE_LABELS = { def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() cfg = panel.load() state = {"ready": False} @@ -34,6 +34,7 @@ def build() -> Gtk.Widget: page.add(_topbar_group(cfg, persist)) page.add(_dock_group(cfg, persist)) + page.add(_calendar_group(cfg, persist)) page.add(_pins_group()) state["ready"] = True @@ -42,17 +43,17 @@ def build() -> Gtk.Widget: # -- Top bar -------------------------------------------------------------- -def _topbar_group(cfg: dict, persist) -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup( +def _topbar_group(cfg: dict, persist) -> ui.Group: + group = ui.Group( title="Top bar", description="Which modules the eww bar shows, and the clock format.", ) - clock = Adw.EntryRow(title="Clock format") + clock = ui.EntryRow(title="Clock format") clock.set_text(cfg["bar"]["clock_format"]) clock.set_show_apply_button(True) - def on_clock(_e: Adw.EntryRow) -> None: + def on_clock(_e: ui.EntryRow) -> None: cfg["bar"]["clock_format"] = clock.get_text() persist() @@ -61,47 +62,61 @@ def _topbar_group(cfg: dict, persist) -> Adw.PreferencesGroup: mods = cfg["bar"]["modules"] for key in panel.MODULES: - row = Adw.SwitchRow(title=_MODULE_LABELS.get(key, key)) + row = ui.SwitchRow(title=_MODULE_LABELS.get(key, key)) row.set_active(bool(mods.get(key, True))) - def on_toggle(r: Adw.SwitchRow, _p, k: str = key) -> None: + def on_toggle(r: ui.SwitchRow, _p, k: str = key) -> None: cfg["bar"]["modules"][k] = r.get_active() persist() row.connect("notify::active", on_toggle) group.add(row) + osd = ui.SwitchRow( + title="On-screen display", + subtitle="A macOS-style overlay in the centre of the screen for " + "volume and Caps/Num-Lock changes", + ) + osd.set_active(bool(cfg.get("osd", {}).get("enabled", True))) + + def on_osd(r: ui.SwitchRow, _p) -> None: + cfg.setdefault("osd", {})["enabled"] = r.get_active() + persist() + + osd.connect("notify::active", on_osd) + group.add(osd) + return group # -- Dock ----------------------------------------------------------------- -def _dock_group(cfg: dict, persist) -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup( +def _dock_group(cfg: dict, persist) -> ui.Group: + group = ui.Group( title="Dock", description="Auto-hide, icon size and hide delay. Applies live.", ) dock = cfg["dock"] - autohide = Adw.SwitchRow( + autohide = ui.SwitchRow( title="Auto-hide", subtitle="Hide the dock until you reach the screen edge", ) autohide.set_active(bool(dock.get("autohide", True))) - def on_autohide(r: Adw.SwitchRow, _p) -> None: + def on_autohide(r: ui.SwitchRow, _p) -> None: dock["autohide"] = r.get_active() persist() autohide.connect("notify::active", on_autohide) group.add(autohide) - size = Adw.SpinRow.new_with_range(24, 64, 1) + size = ui.SpinRow.new_with_range(24, 64, 1) size.set_title("Icon size") size.set_subtitle("Pixels (24–64)") size.set_value(int(dock.get("icon_size", 42))) - def on_size(r: Adw.SpinRow, _p) -> None: + def on_size(r: ui.SpinRow, _p) -> None: dock["icon_size"] = int(r.get_value()) persist() @@ -111,10 +126,10 @@ def _dock_group(cfg: dict, persist) -> Adw.PreferencesGroup: delay_adj = Gtk.Adjustment(value=float(dock.get("hide_delay", 1.2)), lower=0.2, upper=5.0, step_increment=0.1, page_increment=0.5) - delay = Adw.SpinRow(title="Hide delay", adjustment=delay_adj, digits=1) + delay = ui.SpinRow(title="Hide delay", adjustment=delay_adj, digits=1) delay.set_subtitle("Seconds before the dock hides (0.2–5.0)") - def on_delay(r: Adw.SpinRow, _p) -> None: + def on_delay(r: ui.SpinRow, _p) -> None: dock["hide_delay"] = round(r.get_value(), 1) persist() @@ -124,10 +139,59 @@ def _dock_group(cfg: dict, persist) -> Adw.PreferencesGroup: return group +# -- Kalender ------------------------------------------------------------- + +def _calendar_group(cfg: dict, persist) -> ui.Group: + group = ui.Group( + title="Calendar", + description="Thresholds for the calendar panel in the eww bar.", + ) + cal = cfg.setdefault("calendar", {}) + + today_min = ui.SpinRow.new_with_range(0, 10, 1) + today_min.set_title("Minimum entries today") + today_min.set_subtitle("Fill up with upcoming events when there are at " + "most this many today (0–10)") + today_min.set_value(int(cal.get("today_min", 2))) + + def on_today_min(r: ui.SpinRow, _p) -> None: + cal["today_min"] = int(r.get_value()) + persist() + + today_min.connect("notify::value", on_today_min) + group.add(today_min) + + max_entries = ui.SpinRow.new_with_range(1, 10, 1) + max_entries.set_title("Maximum entries") + max_entries.set_subtitle("Upper limit on the total number of events shown (1–10)") + max_entries.set_value(int(cal.get("max_entries", 5))) + + def on_max_entries(r: ui.SpinRow, _p) -> None: + cal["max_entries"] = int(r.get_value()) + persist() + + max_entries.connect("notify::value", on_max_entries) + group.add(max_entries) + + days_ahead = ui.SpinRow.new_with_range(1, 31, 1) + days_ahead.set_title("Days to look ahead") + days_ahead.set_subtitle("Pull upcoming events from this many days (1–31)") + days_ahead.set_value(int(cal.get("days_ahead", 7))) + + def on_days_ahead(r: ui.SpinRow, _p) -> None: + cal["days_ahead"] = int(r.get_value()) + persist() + + days_ahead.connect("notify::value", on_days_ahead) + group.add(days_ahead) + + return group + + # -- Pinned apps (dock-pins) ---------------------------------------------- -def _pins_group() -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup( +def _pins_group() -> ui.Group: + group = ui.Group( title="Pinned apps", description="Apps pinned to the dock (exec · class · icon).", ) @@ -143,7 +207,7 @@ def _pins_group() -> Adw.PreferencesGroup: group.remove(r) rows.clear() for i, pin in enumerate(pins): - row = Adw.ActionRow(title=pin.wmclass or pin.exec, subtitle=pin.exec) + row = ui.Row(title=pin.wmclass or pin.exec, subtitle=pin.exec) row.add_prefix(Gtk.Image.new_from_icon_name(pin.icon or "application-x-executable")) rm = Gtk.Button(icon_name="user-trash-symbolic") rm.add_css_class("flat") @@ -162,14 +226,14 @@ def _pins_group() -> Adw.PreferencesGroup: _refresh() # -- Add-Zeile (Expander mit 3 Feldern) -- - _add_row = Adw.ExpanderRow(title="Add app", subtitle="exec · class · icon") - e_exec = Adw.EntryRow(title="Exec (command)") - e_class = Adw.EntryRow(title="Window class (app_id / WM_CLASS)") - e_icon = Adw.EntryRow(title="Icon name (Adwaita)") + _add_row = ui.ExpanderRow(title="Add app", subtitle="exec · class · icon") + e_exec = ui.EntryRow(title="Exec (command)") + e_class = ui.EntryRow(title="Window class (app_id / WM_CLASS)") + e_icon = ui.EntryRow(title="Icon name (Adwaita)") _add_row.add_row(e_exec) _add_row.add_row(e_class) _add_row.add_row(e_icon) - add_btn_row = Adw.ActionRow() + add_btn_row = ui.Row() add_btn = Gtk.Button(label="Add to dock") add_btn.add_css_class("suggested-action") add_btn.add_css_class("pill") diff --git a/src/taninux/gui/pages/power.py b/src/taninux/gui/pages/power.py index 6f3ee24..a79f125 100644 --- a/src/taninux/gui/pages/power.py +++ b/src/taninux/gui/pages/power.py @@ -10,8 +10,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import power from taninux.gui.console import RunnerCard @@ -19,7 +19,7 @@ from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() page.add(_power_source_group()) if power.brightness_available(): page.add(_brightness_group()) @@ -37,14 +37,14 @@ def _card_box(card: Gtk.Widget) -> Gtk.Box: return box -def _energy_mode_group() -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup( +def _energy_mode_group() -> ui.Group: + group = ui.Group( title="Energy mode", description="Most effective on laptops.", ) if not power.ppd_installed(): - group.add(Adw.ActionRow(title="Install to enable energy modes")) + group.add(ui.Row(title="Install to enable energy modes")) group.add(_card_box(RunnerCard( "Install", ["pacman", "-S", "--needed", "--noconfirm", "power-profiles-daemon"], @@ -54,7 +54,7 @@ def _energy_mode_group() -> Adw.PreferencesGroup: profs = power.power_profiles() if not profs: - group.add(Adw.ActionRow(title="Enable to use energy modes")) + group.add(ui.Row(title="Enable to use energy modes")) group.add(_card_box(RunnerCard( "Enable", ["systemctl", "enable", "--now", "power-profiles-daemon"], @@ -63,12 +63,12 @@ def _energy_mode_group() -> Adw.PreferencesGroup: return group cur = power.current_profile() - combo = Adw.ComboRow(title="Power profile") + combo = ui.ComboRow(title="Power profile") combo.set_model(Gtk.StringList.new(profs)) if cur in profs: combo.set_selected(profs.index(cur)) - def on_sel(c: Adw.ComboRow, _p) -> None: + def on_sel(c: ui.ComboRow, _p) -> None: i = c.get_selected() if 0 <= i < len(profs): power.set_profile(profs[i]) @@ -78,12 +78,12 @@ def _energy_mode_group() -> Adw.PreferencesGroup: return group -def _power_source_group() -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup(title="Power source") +def _power_source_group() -> ui.Group: + group = ui.Group(title="Power source") bats = power.batteries() if bats: for b in bats: - row = Adw.ActionRow(title=b.name.upper(), subtitle=b.status) + row = ui.Row(title=b.name.upper(), subtitle=b.status) cap = f"{b.capacity}%" if b.capacity >= 0 else "—" if b.capacity >= 0: level = Gtk.LevelBar.new_for_interval(0, 100) @@ -102,13 +102,13 @@ def _power_source_group() -> Adw.PreferencesGroup: "On AC power" if ac is True else "No battery detected — desktop system" ) - group.add(Adw.ActionRow(title="Battery", subtitle=sub)) + group.add(ui.Row(title="Battery", subtitle=sub)) return group -def _brightness_group() -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup(title="Display brightness") - row = Adw.ActionRow(title="Brightness") +def _brightness_group() -> ui.Group: + group = ui.Group(title="Display brightness") + row = ui.Row(title="Brightness") scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 1, 100, 1) scale.set_hexpand(True) @@ -143,23 +143,23 @@ def _brightness_group() -> Adw.PreferencesGroup: return group -def _idle_group() -> Adw.PreferencesGroup: +def _idle_group() -> ui.Group: t = power.read_idle_timeouts() daemon = power.idle_daemon_name() - group = Adw.PreferencesGroup( + group = ui.Group( title="Idle · sleep", description="Turn the screen off or suspend after inactivity. 0 = never.", ) so_adj = Gtk.Adjustment(value=t["screen_off"] // 60, lower=0, upper=240, step_increment=1, page_increment=5) - so = Adw.SpinRow(title="Screen off after", adjustment=so_adj) + so = ui.SpinRow(title="Screen off after", adjustment=so_adj) so.set_subtitle("minutes (0 = never)") group.add(so) su_adj = Gtk.Adjustment(value=t["suspend"] // 60, lower=0, upper=480, step_increment=1, page_increment=5) - su = Adw.SpinRow(title="Suspend after", adjustment=su_adj) + su = ui.SpinRow(title="Suspend after", adjustment=su_adj) su.set_subtitle("minutes (0 = never)") group.add(su) @@ -196,9 +196,9 @@ def _idle_group() -> Adw.PreferencesGroup: return group -def _install_group() -> Adw.PreferencesGroup: +def _install_group() -> ui.Group: pkg = power.idle_pkg() - group = Adw.PreferencesGroup( + group = ui.Group( title="Idle timers", description="Install to enable the timers above.", ) diff --git a/src/taninux/gui/pages/printers.py b/src/taninux/gui/pages/printers.py index 7d7e06a..5d288e2 100644 --- a/src/taninux/gui/pages/printers.py +++ b/src/taninux/gui/pages/printers.py @@ -13,17 +13,17 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import printers from taninux.gui import confirm as confirm_mod -from taninux.gui.console import LogConsole, RunnerCard, log_expander +from taninux.gui.console import LogConsole, RunnerCard from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if not printers.available(): page.add(_unavailable_group()) @@ -35,12 +35,12 @@ def build() -> Gtk.Widget: runner.connect("line", lambda _r, line: console.append_line(line)) # Druckergruppe wird nach jeder Aktion neu befüllt (live). - printers_group = Adw.PreferencesGroup( + printers_group = ui.Group( title="Printers", description="Adding a printer (driver selection) isn't here — use a " "dedicated tool for that.", ) - queue_group = Adw.PreferencesGroup( + queue_group = ui.Group( title="Print queue", description="Pending jobs across all printers.", ) @@ -58,7 +58,6 @@ def build() -> Gtk.Widget: refresh() page.add(printers_group) page.add(queue_group) - page.add(log_expander(console, title="Output")) return page @@ -70,7 +69,7 @@ def _on_done(console: LogConsole, code: int, refresh) -> None: refresh() -def _clear_group(group: Adw.PreferencesGroup) -> None: +def _clear_group(group: ui.Group) -> None: """Entfernt alle bisher per _track() hinzugefügten Rows der Gruppe.""" rows = getattr(group, "_tani_rows", None) if not rows: @@ -80,7 +79,7 @@ def _clear_group(group: Adw.PreferencesGroup) -> None: rows.clear() -def _track(group: Adw.PreferencesGroup, row: Gtk.Widget) -> None: +def _track(group: ui.Group, row: Gtk.Widget) -> None: rows = getattr(group, "_tani_rows", None) if rows is None: rows = [] @@ -89,11 +88,11 @@ def _track(group: Adw.PreferencesGroup, row: Gtk.Widget) -> None: rows.append(row) -def _fill_printers(group: Adw.PreferencesGroup, run) -> None: +def _fill_printers(group: ui.Group, run) -> None: _clear_group(group) plist = printers.printers() if not plist: - _track(group, Adw.ActionRow( + _track(group, ui.Row( title="No printers configured", subtitle="Add one with a system printer tool.", )) @@ -105,7 +104,7 @@ def _fill_printers(group: Adw.PreferencesGroup, run) -> None: subtitle += " · default" if p.info: subtitle += f" · {p.info}" - row = Adw.ActionRow(title=p.name, subtitle=subtitle) + row = ui.Row(title=p.name, subtitle=subtitle) if not p.is_default: row.add_suffix(_btn( @@ -141,19 +140,19 @@ def _fill_printers(group: Adw.PreferencesGroup, run) -> None: _track(group, row) -def _fill_queue(group: Adw.PreferencesGroup, run) -> None: +def _fill_queue(group: ui.Group, run) -> None: _clear_group(group) jobs = printers.queue() if not jobs: - _track(group, Adw.ActionRow( + _track(group, ui.Row( title="Queue empty", subtitle="No pending print jobs." )) return for job in jobs: - _track(group, Adw.ActionRow(title=job)) + _track(group, ui.Row(title=job)) - clear_row = Adw.ActionRow(title="Clear queue", + clear_row = ui.Row(title="Clear queue", subtitle="Cancel all pending jobs.") clear_row.add_suffix(_btn( "Clear queue", @@ -175,12 +174,12 @@ def _btn(label: str, on_click, *, suggested: bool = False, return btn -def _unavailable_group() -> Adw.PreferencesGroup: - group = Adw.PreferencesGroup( +def _unavailable_group() -> ui.Group: + group = ui.Group( title="Printing", description="CUPS is not installed. Printer management is unavailable.", ) - group.add(Adw.ActionRow( + group.add(ui.Row( title="CUPS not available", subtitle="Install cups to manage printers.", )) diff --git a/src/taninux/gui/pages/settings.py b/src/taninux/gui/pages/settings.py index 19cec2d..81d4523 100644 --- a/src/taninux/gui/pages/settings.py +++ b/src/taninux/gui/pages/settings.py @@ -8,8 +8,9 @@ from __future__ import annotations import gi -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk, Pango # noqa: E402 +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Pango # noqa: E402 +from taninux.gui import ui from taninux.gui import config, desktop, eww, style from taninux.gui.pages.wallpaper import wallpaper_group @@ -20,7 +21,7 @@ _SCHEME_KEYS = ["system", "light", "dark"] def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() page.add(_appearance_group()) page.add(wallpaper_group()) # sehr visuell -> direkt nach Color scheme/Accent page.add(_window_buttons_group()) @@ -32,19 +33,19 @@ def build() -> Gtk.Widget: # -- GTK-Theme + Icon-Theme (nwg-look-Stil, kein root) -------------------- -def _combo(title: str, items: list[str], current_value: str, apply_fn) -> Adw.ComboRow: +def _combo(title: str, items: list[str], current_value: str, apply_fn) -> ui.ComboRow: """ComboRow, die bei Auswahl apply_fn(value) ruft. Initial-Set feuert nicht (connect erst NACH set_selected).""" opts = list(items) if current_value and current_value not in opts: opts.insert(0, current_value) # Ist-Wert immer wählbar halten - row = Adw.ComboRow(title=title) + row = ui.ComboRow(title=title) row.set_model(Gtk.StringList.new(opts or ["(none)"])) row.set_enable_search(True) if current_value in opts: row.set_selected(opts.index(current_value)) - def on_sel(r: Adw.ComboRow, _p) -> None: + def on_sel(r: ui.ComboRow, _p) -> None: i = r.get_selected() if 0 <= i < len(opts): apply_fn(opts[i]) @@ -53,20 +54,20 @@ def _combo(title: str, items: list[str], current_value: str, apply_fn) -> Adw.Co return row -def _window_buttons_group() -> Adw.PreferencesGroup: +def _window_buttons_group() -> ui.Group: """Einzelne Titlebar-Knöpfe an/aus + Platzierung (wie GNOME Tweaks).""" cur = desktop.window_buttons() - group = Adw.PreferencesGroup( + group = ui.Group( title="Window buttons", description="Show or hide the titlebar buttons.", ) state = {"ready": False} - min_row = Adw.SwitchRow(title="Minimize") - max_row = Adw.SwitchRow(title="Maximize") - close_row = Adw.SwitchRow(title="Close") - place_row = Adw.ComboRow(title="Placement") + min_row = ui.SwitchRow(title="Minimize") + max_row = ui.SwitchRow(title="Maximize") + close_row = ui.SwitchRow(title="Close") + place_row = ui.ComboRow(title="Placement") place_row.set_model(Gtk.StringList.new(["Right", "Left"])) def apply(*_a) -> None: @@ -97,9 +98,9 @@ def _window_buttons_group() -> Adw.PreferencesGroup: return group -def _theme_icons_group() -> Adw.PreferencesGroup: +def _theme_icons_group() -> ui.Group: cur = desktop.current() - group = Adw.PreferencesGroup( + group = ui.Group( title="Theme · icons", description="Running apps may need a restart.", ) @@ -114,10 +115,10 @@ def _theme_icons_group() -> Adw.PreferencesGroup: return group -def _font_group() -> Adw.PreferencesGroup: +def _font_group() -> ui.Group: cur = desktop.current() - group = Adw.PreferencesGroup(title="Font") - row = Adw.ActionRow( + group = ui.Group(title="Font") + row = ui.Row( title="System font", subtitle="Default interface font", ) @@ -138,9 +139,9 @@ def _font_group() -> Adw.PreferencesGroup: return group -def _cursor_group() -> Adw.PreferencesGroup: +def _cursor_group() -> ui.Group: cur = desktop.current() - group = Adw.PreferencesGroup( + group = ui.Group( title="Cursor", description="Cursor theme and size.", ) @@ -155,7 +156,7 @@ def _cursor_group() -> Adw.PreferencesGroup: size = 24 adj = Gtk.Adjustment(value=size, lower=8, upper=128, step_increment=2, page_increment=8) - spin = Adw.SpinRow(title="Cursor size", adjustment=adj) + spin = ui.SpinRow(title="Cursor size", adjustment=adj) spin.set_subtitle("Pixel") # connect NACH dem Setzen des Startwerts -> Initial feuert nicht adj.connect("value-changed", lambda a: desktop.apply_cursor_size(int(a.get_value()))) @@ -163,9 +164,9 @@ def _cursor_group() -> Adw.PreferencesGroup: return group -def _appearance_group() -> Adw.PreferencesGroup: +def _appearance_group() -> ui.Group: cfg = config.load() - group = Adw.PreferencesGroup( + group = ui.Group( title="Appearance", description="Color scheme and Fuji accent — applied instantly, saved.", ) @@ -176,20 +177,25 @@ def _appearance_group() -> Adw.PreferencesGroup: def persist() -> None: if state["ready"]: config.save(state["scheme"], state["accent"]) - style.apply(state["scheme"], state["accent"]) # diese App live - style.write_global_theme(state["scheme"], state["accent"]) # ALLE GTK3+GTK4-Apps + gsettings - eww.reload_if_running() # Bar live nachziehen + style.apply(state["scheme"], state["accent"]) + style.write_global_theme(state["scheme"], state["accent"]) + eww.reload_if_running() + # Live dark/light auf dem laufenden Fenster umschalten + root = scheme_row.get_root() + if root: + from taninux.gui.ui import native + native.set_dark(root, style._resolve_dark(state["scheme"])) state["scheme"] = cfg["color_scheme"] state["accent"] = cfg["accent"] # -- Color scheme -------------------------------------------------- - scheme_row = Adw.ComboRow(title="Color scheme") + scheme_row = ui.ComboRow(title="Color scheme") scheme_row.set_model(Gtk.StringList.new(_SCHEME_LABELS)) cur = _SCHEME_KEYS.index(cfg["color_scheme"]) if cfg["color_scheme"] in _SCHEME_KEYS else 0 scheme_row.set_selected(cur) - def on_scheme(row: Adw.ComboRow, _param) -> None: + def on_scheme(row: ui.ComboRow, _param) -> None: state["scheme"] = _SCHEME_KEYS[row.get_selected()] persist() @@ -197,7 +203,7 @@ def _appearance_group() -> Adw.PreferencesGroup: group.add(scheme_row) # -- Fuji accent (Swatch-Reihe) ----------------------------------- - accent_row = Adw.ActionRow( + accent_row = ui.Row( title="Accent color", subtitle="Fuji palette", ) diff --git a/src/taninux/gui/pages/shares.py b/src/taninux/gui/pages/shares.py index 1c39329..3ce0b79 100644 --- a/src/taninux/gui/pages/shares.py +++ b/src/taninux/gui/pages/shares.py @@ -13,13 +13,13 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gtk # noqa: E402 +from gi.repository import Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import nextcloud as nc from taninux.core import shares from taninux.gui import confirm as confirm_mod -from taninux.gui.console import LogConsole, RunnerCard, log_expander +from taninux.gui.console import LogConsole, RunnerCard from taninux.gui.process import ProcessRunner # (Label, scheme). "nextcloud" ist ein Spezial-Scheme (baut den WebDAV-Pfad). @@ -57,7 +57,7 @@ _SCHEME_ICON = { def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() console = LogConsole() runner = ProcessRunner() @@ -66,7 +66,7 @@ def build() -> Gtk.Widget: "✔ done" if code == 0 else f"✘ exit {code}")) # ============================================================ Your shares - list_group = Adw.PreferencesGroup( + list_group = ui.Group( title="Your shares", description="Saved remote locations — they appear in the file manager.", ) @@ -92,13 +92,13 @@ def build() -> Gtk.Widget: _clear_group(list_group) bms = shares.list_remote() if not bms: - _track(list_group, Adw.ActionRow( + _track(list_group, ui.Row( title="No shares yet", subtitle="Add one below — SFTP, SMB, WebDAV, Nextcloud, FTP or NFS.", )) return for bm in bms: - row = Adw.ActionRow(title=bm.label or bm.uri, subtitle=bm.uri) + row = ui.Row(title=bm.label or bm.uri, subtitle=bm.uri) icon = _SCHEME_ICON.get(bm.scheme, "folder-remote-symbolic") row.add_prefix(Gtk.Image.new_from_icon_name(icon)) connect = _btn("Connect", suggested=True) @@ -112,29 +112,29 @@ def build() -> Gtk.Widget: refresh_btn.connect("clicked", lambda _b: _fill_list()) # ============================================================ Add - add_group = Adw.PreferencesGroup( + add_group = ui.Group( title="Add a connection", description="You'll be asked for the password on first connect.", ) - proto_row = Adw.ComboRow(title="Type") + proto_row = ui.ComboRow(title="Type") proto_model = Gtk.StringList() for label, _scheme in _PROTOCOLS: proto_model.append(label) proto_row.set_model(proto_model) add_group.add(proto_row) - host_row = Adw.EntryRow(title="Host") - path_row = Adw.EntryRow(title="Path") - user_row = Adw.EntryRow(title="User") - port_row = Adw.EntryRow(title="Port") + host_row = ui.EntryRow(title="Host") + path_row = ui.EntryRow(title="Path") + user_row = ui.EntryRow(title="User") + port_row = ui.EntryRow(title="Port") field_rows = {"host": host_row, "path": path_row, "user": user_row, "port": port_row} for r in field_rows.values(): add_group.add(r) # Live-Vorschau der Ziel-URI. - preview = Adw.ActionRow(title="Will create") + preview = ui.Row(title="Will create") preview_lbl = Gtk.Label(label="—") preview_lbl.add_css_class("dim-label") preview_lbl.set_selectable(True) @@ -145,7 +145,7 @@ def build() -> Gtk.Widget: add_btn = Gtk.Button(label="Add") add_btn.add_css_class("suggested-action") add_btn.set_valign(Gtk.Align.CENTER) - add_row = Adw.ActionRow(title="Add to file manager") + add_row = ui.Row(title="Add to file manager") add_row.add_suffix(add_btn) add_group.add(add_row) page.add(add_group) @@ -201,12 +201,12 @@ def build() -> Gtk.Widget: # ============================================================ Nextcloud accounts nc_accounts = nc.accounts() if nc_accounts: - nc_group = Adw.PreferencesGroup( + nc_group = ui.Group( title="Detected Nextcloud accounts", description="From the Nextcloud client — add as WebDAV in one click.", ) for acc in nc_accounts: - row = Adw.ActionRow(title=acc.name, subtitle=acc.url) + row = ui.Row(title=acc.name, subtitle=acc.url) row.add_prefix(Gtk.Image.new_from_icon_name("folder-cloud-symbolic")) add = _btn("Add to Files", suggested=True) @@ -229,12 +229,12 @@ def build() -> Gtk.Widget: if not shares.nfs_available(): missing.append(("NFS shares", "gvfs-nfs", shares.install_nfs_argv())) if missing: - be_group = Adw.PreferencesGroup( + be_group = ui.Group( title="More connection types", description="SMB and NFS need an extra component.", ) for label, pkg, argv in missing: - be_group.add(Adw.ActionRow( + be_group.add(ui.Row( title=label, subtitle="Install to enable this connection type.")) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box.set_margin_top(6) @@ -242,9 +242,6 @@ def build() -> Gtk.Widget: be_group.add(box) page.add(be_group) - # ============================================================ Log - page.add(log_expander(console)) - _sync_fields() _fill_list() return page @@ -252,7 +249,7 @@ def build() -> Gtk.Widget: # ----------------------------------------------------------- Helpers -def _clear_group(group: Adw.PreferencesGroup) -> None: +def _clear_group(group: ui.Group) -> None: rows = getattr(group, "_tani_rows", None) if not rows: return @@ -261,7 +258,7 @@ def _clear_group(group: Adw.PreferencesGroup) -> None: rows.clear() -def _track(group: Adw.PreferencesGroup, row: Gtk.Widget) -> None: +def _track(group: ui.Group, row: Gtk.Widget) -> None: rows = getattr(group, "_tani_rows", None) if rows is None: rows = [] diff --git a/src/taninux/gui/pages/storage.py b/src/taninux/gui/pages/storage.py index 0e85a4c..2ae3ee1 100644 --- a/src/taninux/gui/pages/storage.py +++ b/src/taninux/gui/pages/storage.py @@ -4,8 +4,8 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +from gi.repository import GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import storage from taninux.gui.process import ProcessRunner @@ -18,14 +18,14 @@ def _gib(b: int) -> str: def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() runner = ProcessRunner() - groups: list[Adw.PreferencesGroup] = [] + groups: list[ui.Group] = [] - def _row(vol: storage.Volume) -> Adw.ActionRow: + def _row(vol: storage.Volume) -> ui.Row: title = vol.label or vol.dev.split("/")[-1] sub = vol.mountpoint or "Not mounted" - row = Adw.ActionRow(title=title, subtitle=f"{sub} · {_gib(vol.size)} · {vol.fstype}") + row = ui.Row(title=title, subtitle=f"{sub} · {_gib(vol.size)} · {vol.fstype}") if vol.mountpoint and vol.use_pct: level = Gtk.LevelBar.new_for_interval(0, 100) level.set_value(vol.use_pct) @@ -51,17 +51,17 @@ def build() -> Gtk.Widget: internal = [v for v in vols if not v.removable] removable = [v for v in vols if v.removable] - g_int = Adw.PreferencesGroup(title="This device") + g_int = ui.Group(title="This device") if internal: for v in internal: g_int.add(_row(v)) else: - g_int.add(Adw.ActionRow(title="No filesystems found")) + g_int.add(ui.Row(title="No filesystems found")) page.add(g_int) groups.append(g_int) if removable: - g_rm = Adw.PreferencesGroup(title="Removable") + g_rm = ui.Group(title="Removable") for v in removable: g_rm.add(_row(v)) page.add(g_rm) diff --git a/src/taninux/gui/pages/store.py b/src/taninux/gui/pages/store.py index 27e2d9f..0abbd2d 100644 --- a/src/taninux/gui/pages/store.py +++ b/src/taninux/gui/pages/store.py @@ -11,18 +11,18 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +from gi.repository import GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import panel from taninux.core import search as pkgsearch -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.pages.appdetail import open_app_detail from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() console = LogConsole() runner = ProcessRunner() @@ -30,8 +30,8 @@ def build() -> Gtk.Widget: token = {"n": 0} # gegen veraltete Such-Antworten - g_sources = Adw.PreferencesGroup(title="Sources") - aur_row = Adw.SwitchRow( + g_sources = ui.Group(title="Sources") + aur_row = ui.SwitchRow( title="Arch User Repository (AUR)", subtitle="Community packages built from source — requires paru.", ) @@ -39,19 +39,17 @@ def build() -> Gtk.Widget: g_sources.add(aur_row) page.add(g_sources) - g_search = Adw.PreferencesGroup() + g_search = ui.Group() entry = Gtk.SearchEntry() entry.set_placeholder_text("Search apps and packages") entry.set_hexpand(True) g_search.add(entry) page.add(g_search) - g_results = Adw.PreferencesGroup(title="Results") + g_results = ui.Group(title="Results") page.add(g_results) rows: list[Gtk.Widget] = [] - page.add(log_expander(console)) - def _clear() -> None: for r in rows: g_results.remove(r) @@ -59,7 +57,7 @@ def build() -> Gtk.Widget: def _placeholder(text: str) -> None: _clear() - row = Adw.ActionRow(title=text) + row = ui.Row(title=text) g_results.add(row) rows.append(row) @@ -91,7 +89,7 @@ def build() -> Gtk.Widget: subtitle = f"{item.repo} · {item.version}" if item.description: subtitle += f" — {item.description}" - row = Adw.ActionRow(title=item.extra.get("title", item.name)) + row = ui.Row(title=item.extra.get("title", item.name)) row.set_subtitle(subtitle) row.set_subtitle_lines(2) row.set_activatable(True) @@ -147,7 +145,7 @@ def build() -> Gtk.Widget: 400, lambda: (debounce.update(id=0), _run_search(text), False)[-1] ) - def _on_aur(row: Adw.SwitchRow, _pspec) -> None: + def _on_aur(row: ui.SwitchRow, _pspec) -> None: panel.set_include_aur(row.get_active()) _run_search(entry.get_text()) diff --git a/src/taninux/gui/pages/update.py b/src/taninux/gui/pages/update.py index d6faf36..85cbe59 100644 --- a/src/taninux/gui/pages/update.py +++ b/src/taninux/gui/pages/update.py @@ -10,11 +10,11 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, GLib, Gtk # noqa: E402 +from gi.repository import GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import panel -from taninux.gui.console import LogConsole, log_expander +from taninux.gui.console import LogConsole from taninux.gui.process import ProcessRunner from taninux.update.sources import aur, flatpak, pacman from taninux.update.types import UpdateItem @@ -45,7 +45,7 @@ def _all_queue(items: list[UpdateItem]): def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() console = LogConsole() runner = ProcessRunner() @@ -54,14 +54,14 @@ def build() -> Gtk.Widget: items: list[UpdateItem] = [] pending: list = [] - g_sum = Adw.PreferencesGroup(title="Updates") + g_sum = ui.Group(title="Updates") refresh = Gtk.Button(icon_name="view-refresh-symbolic") refresh.add_css_class("flat") refresh.set_valign(Gtk.Align.CENTER) g_sum.set_header_suffix(refresh) def _count_row(name: str): - row = Adw.ActionRow(title=name) + row = ui.Row(title=name) lbl = Gtk.Label(label="…") lbl.add_css_class("numeric") lbl.add_css_class("dim-label") @@ -75,7 +75,7 @@ def build() -> Gtk.Widget: for r in (pac_row, aur_row, fp_row): g_sum.add(r) - all_row = Adw.ActionRow(title="Update all") + all_row = ui.Row(title="Update all") all_btn = Gtk.Button(label="Update") all_btn.add_css_class("suggested-action") all_btn.add_css_class("pill") @@ -85,12 +85,10 @@ def build() -> Gtk.Widget: g_sum.add(all_row) page.add(g_sum) - g_list = Adw.PreferencesGroup(title="Packages") + g_list = ui.Group(title="Packages") page.add(g_list) list_rows: list[Gtk.Widget] = [] - page.add(log_expander(console)) - def _start_next() -> None: if pending: argv, priv = pending.pop(0) @@ -132,13 +130,13 @@ def build() -> Gtk.Widget: _clear_list() if not items: - row = Adw.ActionRow(title="Everything up to date") + row = ui.Row(title="Everything up to date") g_list.add(row) list_rows.append(row) return order = {"pacman": 0, "aur": 1, "flatpak": 2} for item in sorted(items, key=lambda i: (order.get(i.source, 9), i.name)): - row = Adw.ActionRow( + row = ui.Row( title=item.name, subtitle=f"{item.current_version} → {item.new_version}", ) diff --git a/src/taninux/gui/pages/users.py b/src/taninux/gui/pages/users.py index ae8e90e..69e705c 100644 --- a/src/taninux/gui/pages/users.py +++ b/src/taninux/gui/pages/users.py @@ -8,9 +8,9 @@ from pathlib import Path import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") gi.require_version("GdkPixbuf", "2.0") -from gi.repository import Adw, Gdk, GdkPixbuf, Gio, GLib, Gtk # noqa: E402 +from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import account, avatar from taninux.gui import confirm @@ -20,20 +20,20 @@ _NAME_RE = re.compile(r"^[a-z_][a-z0-9_-]*$") def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() me = account.current() runner = ProcessRunner() queue: list = [] - others = Adw.PreferencesGroup(title="Other users") + others = ui.Group(title="Other users") rows: list[Gtk.Widget] = [] # -- eigenes Konto -------------------------------------------------- - g_info = Adw.PreferencesGroup(title="Account") + g_info = ui.Group(title="Account") - av = Adw.Avatar(size=64, text=me.fullname, show_initials=True) + av = ui.Avatar(size=64, text=me.fullname, show_initials=True) _load_avatar(av) - pic_row = Adw.ActionRow(title="Profile picture", subtitle="Click to change") + pic_row = ui.Row(title="Profile picture", subtitle="Click to change") pic_row.add_prefix(av) pic_row.set_activatable(True) pic_row.connect("activated", lambda _r: _pick_avatar(page, av)) @@ -69,12 +69,12 @@ def build() -> Gtk.Widget: rows.clear() rest = [u for u in account.list_users() if u.username != me.username] if not rest: - row = Adw.ActionRow(title="No other users") + row = ui.Row(title="No other users") others.add(row) rows.append(row) return for u in rest: - row = Adw.ActionRow( + row = ui.Row( title=u.fullname, subtitle=f"{u.username} · {'Administrator' if u.admin else 'Standard'}", ) @@ -103,20 +103,20 @@ def build() -> Gtk.Widget: def _add_dialog(parent: Gtk.Widget, start) -> None: - dialog = Adw.Dialog() + dialog = ui.Dialog() dialog.set_title("Add user") dialog.set_content_width(420) - page = Adw.PreferencesPage() - tv = Adw.ToolbarView() - tv.add_top_bar(Adw.HeaderBar()) + page = ui.Page() + tv = ui.ToolbarView() + tv.add_top_bar(ui.HeaderBar()) tv.set_content(page) dialog.set_child(tv) - group = Adw.PreferencesGroup() - full = Adw.EntryRow(title="Full name") - user = Adw.EntryRow(title="Username") - admin = Adw.SwitchRow(title="Administrator", + group = ui.Group() + full = ui.EntryRow(title="Full name") + user = ui.EntryRow(title="Username") + admin = ui.SwitchRow(title="Administrator", subtitle="Can install software and change system settings") group.add(full) group.add(user) @@ -155,11 +155,11 @@ def _add_dialog(parent: Gtk.Widget, start) -> None: dialog.present(parent) -def _password_group() -> Adw.PreferencesGroup: - g_pw = Adw.PreferencesGroup(title="Password") - cur = Adw.PasswordEntryRow(title="Current password") - new = Adw.PasswordEntryRow(title="New password") - confirm_row = Adw.PasswordEntryRow(title="Confirm new password") +def _password_group() -> ui.Group: + g_pw = ui.Group(title="Password") + cur = ui.PasswordEntryRow(title="Current password") + new = ui.PasswordEntryRow(title="New password") + confirm_row = ui.PasswordEntryRow(title="Confirm new password") g_pw.add(cur) g_pw.add(new) g_pw.add(confirm_row) @@ -206,7 +206,7 @@ def _password_group() -> Adw.PreferencesGroup: return g_pw -def _load_avatar(av: Adw.Avatar) -> None: +def _load_avatar(av: ui.Avatar) -> None: path = avatar.current_path() if path: try: @@ -215,7 +215,7 @@ def _load_avatar(av: Adw.Avatar) -> None: pass -def _pick_avatar(parent: Gtk.Widget, av: Adw.Avatar) -> None: +def _pick_avatar(parent: Gtk.Widget, av: ui.Avatar) -> None: dialog = Gtk.FileDialog(title="Choose a picture") filt = Gtk.FileFilter() filt.set_name("Images") @@ -244,8 +244,8 @@ def _pick_avatar(parent: Gtk.Widget, av: Adw.Avatar) -> None: dialog.open(parent.get_root(), None, done) -def _row(title: str, value: str) -> Adw.ActionRow: - row = Adw.ActionRow(title=title) +def _row(title: str, value: str) -> ui.Row: + row = ui.Row(title=title) lbl = Gtk.Label(label=value, xalign=1.0) lbl.add_css_class("dim-label") lbl.set_selectable(True) diff --git a/src/taninux/gui/pages/vpn.py b/src/taninux/gui/pages/vpn.py index 48e9107..513d2c0 100644 --- a/src/taninux/gui/pages/vpn.py +++ b/src/taninux/gui/pages/vpn.py @@ -5,30 +5,30 @@ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gio, GLib, Gtk # noqa: E402 +from gi.repository import Gio, GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import network from taninux.gui.process import ProcessRunner def build() -> Gtk.Widget: - page = Adw.PreferencesPage() + page = ui.Page() if not network.available(): - g = Adw.PreferencesGroup(title="VPN") - g.add(Adw.ActionRow(title="NetworkManager is not running")) + g = ui.Group(title="VPN") + g.add(ui.Row(title="NetworkManager is not running")) page.add(g) return page runner = ProcessRunner() dns_runner = ProcessRunner() dns_queue: list = [] - groups: list[Adw.PreferencesGroup] = [] + groups: list[ui.Group] = [] # ---- VPN ---- - def _vpn_group() -> Adw.PreferencesGroup: - g = Adw.PreferencesGroup(title="VPN") + def _vpn_group() -> ui.Group: + g = ui.Group(title="VPN") add_btn = Gtk.Button(icon_name="list-add-symbolic") add_btn.add_css_class("flat") add_btn.set_valign(Gtk.Align.CENTER) @@ -38,11 +38,11 @@ def build() -> Gtk.Widget: vpns = network.vpn_connections() if not vpns: - g.add(Adw.ActionRow(title="No VPN configured", + g.add(ui.Row(title="No VPN configured", subtitle="Import a .ovpn or WireGuard .conf file")) return g for v in vpns: - row = Adw.ActionRow(title=v.name, subtitle=v.vtype) + row = ui.Row(title=v.name, subtitle=v.vtype) sw = Gtk.Switch() sw.set_active(v.active) sw.set_valign(Gtk.Align.CENTER) @@ -75,14 +75,14 @@ def build() -> Gtk.Widget: dialog.open(page.get_root(), None, done) # ---- DNS ---- - def _dns_group() -> Adw.PreferencesGroup: - g = Adw.PreferencesGroup(title="DNS") + def _dns_group() -> ui.Group: + g = ui.Group(title="DNS") conn = network.primary_connection() if not conn: - g.add(Adw.ActionRow(title="No active connection")) + g.add(ui.Row(title="No active connection")) return g names = list(network.DNS_PRESETS.keys()) + ["Custom"] - row = Adw.ComboRow(title="DNS servers", subtitle=conn) + row = ui.ComboRow(title="DNS servers", subtitle=conn) row.set_model(Gtk.StringList.new(names)) cur = network.get_dns(conn) sel = next((i for i, (_, v) in enumerate(network.DNS_PRESETS.items()) if v == cur), None) @@ -91,7 +91,7 @@ def build() -> Gtk.Widget: row.set_selected(sel if sel is not None else 0) g.add(row) - custom = Adw.EntryRow(title="Custom servers") + custom = ui.EntryRow(title="Custom servers") custom.set_text(cur) custom.set_visible(names[row.get_selected()] == "Custom") g.add(custom) diff --git a/src/taninux/gui/pages/wallpaper.py b/src/taninux/gui/pages/wallpaper.py index 1106c06..8c88df4 100644 --- a/src/taninux/gui/pages/wallpaper.py +++ b/src/taninux/gui/pages/wallpaper.py @@ -19,9 +19,9 @@ import threading import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") gi.require_version("GdkPixbuf", "2.0") -from gi.repository import Adw, Gdk, GdkPixbuf, Gio, GLib, Gtk # noqa: E402 +from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk # noqa: E402 +from taninux.gui import ui from taninux.core import wallpaper from taninux.gui.console import RunnerCard @@ -38,14 +38,14 @@ GRID_MIN_H = 300 GRID_MAX_H = 360 -def wallpaper_group() -> Adw.PreferencesGroup: +def wallpaper_group() -> ui.Group: """Komplette Wallpaper-UI als eine PreferencesGroup (zum Einhängen in Appearance). Enthält die Steuerzeile als Rows und den RAM-sicheren Thumbnail-Grid in einem höhenbegrenzten, intern scrollenden Container.""" if not wallpaper.available(): return _missing_group() - group = Adw.PreferencesGroup( + group = ui.Group( title="Wallpaper", description="Click a thumbnail to set it.", ) @@ -54,13 +54,13 @@ def wallpaper_group() -> Adw.PreferencesGroup: mons = wallpaper.monitors() # -- Steuerzeile: Monitor-Combo als ComboRow --------------------------- - monitor_row = Adw.ComboRow(title="Monitor") + monitor_row = ui.ComboRow(title="Monitor") monitor_row.set_model(Gtk.StringList.new(mons)) monitor_row.set_selected(0) group.add(monitor_row) # -- Steuerzeile: Aktionen als ActionRow mit Suffix-Buttons ------------ - actions_row = Adw.ActionRow(title="Wallpapers") + actions_row = ui.Row(title="Wallpapers") random_btn = Gtk.Button(label="Random") random_btn.add_css_class("pill") random_btn.set_valign(Gtk.Align.CENTER) @@ -80,7 +80,7 @@ def wallpaper_group() -> Adw.PreferencesGroup: status.set_halign(Gtk.Align.START) status.set_margin_top(6) status.set_margin_bottom(6) - spinner = Adw.Spinner() + spinner = ui.Spinner() spinner.set_valign(Gtk.Align.CENTER) status_label = Gtk.Label(xalign=0) status_label.add_css_class("dim-label") @@ -291,18 +291,18 @@ def wallpaper_group() -> Adw.PreferencesGroup: def build() -> Gtk.Widget: """Dünner Wrapper: eigene PreferencesPage, die nur die wallpaper_group() enthält. Bleibt erhalten, falls noch jemand build() aufruft.""" - page = Adw.PreferencesPage() + page = ui.Page() page.add(wallpaper_group()) return page -def _missing_group() -> Adw.PreferencesGroup: +def _missing_group() -> ui.Group: """Gruppe mit Install-Hinweis + RunnerCard, falls waypaper fehlt.""" - group = Adw.PreferencesGroup( + group = ui.Group( title="Wallpaper", description="waypaper is not installed. Install it to manage wallpapers.", ) - row = Adw.ActionRow() + row = ui.Row() row.set_child(RunnerCard( "Install waypaper", ["pacman", "-S", "--needed", "--noconfirm", "waypaper"], diff --git a/src/taninux/gui/pages/wine.py b/src/taninux/gui/pages/wine.py new file mode 100644 index 0000000..c5228ec --- /dev/null +++ b/src/taninux/gui/pages/wine.py @@ -0,0 +1,365 @@ +"""Wine — runtimes, prefix management, Winetricks components. + +Three sections: + Runtime — install/remove Wine packages via pacman (privileged). + Prefixes — create and manage Wine prefixes; run .exe/.msi files. + Winetricks — one-click install of common Windows components (fonts, + VC++ runtimes, .NET, DirectX) into the selected prefix. +""" +from __future__ import annotations + +import os +import shutil +import threading +from pathlib import Path + +import gi + +gi.require_version("Gtk", "4.0") +gi.require_version("Adw", "1") +from gi.repository import Adw, GLib, Gio, Gtk # noqa: E402 +from taninux.gui import ui + +from taninux.core import pacman +from taninux.core import wine as winecore +from taninux.gui import confirm +from taninux.gui.console import LogConsole +from taninux.gui.process import ProcessRunner + + +def build() -> Gtk.Widget: + page = ui.Page() + + console = LogConsole() + runner = ProcessRunner() + runner.connect("line", lambda _r, ln: console.append_line(ln)) + + # ── Runtime ──────────────────────────────────────────────────────────── + g_runtime = ui.Group( + title="Wine Runtime", + description="Install Wine and GPU translation layers via pacman. " + "Start with Wine + Wine Mono for most apps.", + ) + page.add(g_runtime) + _runtime_rows: list[Gtk.Widget] = [] + + # ── Prefixes ─────────────────────────────────────────────────────────── + g_prefixes = ui.Group( + title="Prefixes", + description="Each prefix is an isolated Windows environment. " + "Create one per app or game to avoid conflicts.", + ) + page.add(g_prefixes) + _prefix_rows: list[Gtk.Widget] = [] + + # ── Winetricks components ────────────────────────────────────────────── + g_tricks = ui.Group( + title="Winetricks Components", + description="Install common Windows runtimes and libraries into the selected prefix.", + ) + page.add(g_tricks) + _tricks_rows: list[Gtk.Widget] = [] + + # Shared mutable state + _prefix_paths: list[Path] = [] + _state: dict = {"prefix": None} # Path | None — selected prefix for Winetricks + + # ── Runtime rendering ────────────────────────────────────────────────── + + def _render_runtime() -> None: + for r in _runtime_rows: + g_runtime.remove(r) + _runtime_rows.clear() + + inst = pacman.installed_packages() + for pkg, name, desc in winecore.WINE_PACKAGES: + row = ui.Row(title=name, subtitle=f"{pkg} — {desc}") + if pkg in inst: + tag = Gtk.Label(label="installed") + tag.add_css_class("dim-label") + tag.add_css_class("caption") + tag.set_valign(Gtk.Align.CENTER) + row.add_suffix(tag) + rm = Gtk.Button(label="Remove") + rm.add_css_class("flat") + rm.set_valign(Gtk.Align.CENTER) + rm.connect( + "clicked", + lambda _b, p=pkg: _run_privileged(["pacman", "-Rns", "--noconfirm", p]), + ) + row.add_suffix(rm) + else: + btn = Gtk.Button(label="Install") + btn.add_css_class("suggested-action") + btn.set_valign(Gtk.Align.CENTER) + btn.connect( + "clicked", + lambda _b, p=pkg: _run_privileged( + ["pacman", "-S", "--needed", "--noconfirm", p] + ), + ) + row.add_suffix(btn) + g_runtime.add(row) + _runtime_rows.append(row) + + # ── Prefix rendering ─────────────────────────────────────────────────── + + def _render_prefixes() -> None: + for r in _prefix_rows: + g_prefixes.remove(r) + _prefix_rows.clear() + _prefix_paths.clear() + + # "New Prefix" action always at the top + new_row = ui.Row( + title="New Wine Prefix", + subtitle="Create an isolated Windows environment", + ) + new_btn = Gtk.Button(label="Create…") + new_btn.add_css_class("suggested-action") + new_btn.set_valign(Gtk.Align.CENTER) + new_btn.connect("clicked", _new_prefix_dialog) + new_row.add_suffix(new_btn) + g_prefixes.add(new_row) + _prefix_rows.append(new_row) + + prefixes = winecore.list_prefixes() + if not prefixes: + empty = ui.Row( + title="No prefixes yet", + subtitle="Create one above, or run a .exe — Wine creates ~/.wine automatically", + ) + g_prefixes.add(empty) + _prefix_rows.append(empty) + return + + for ppath in prefixes: + _prefix_paths.append(ppath) + arch = winecore.prefix_arch(ppath) + + row = ui.ExpanderRow( + title=winecore.prefix_display_name(ppath), + subtitle=str(ppath), + ) + + arch_tag = Gtk.Label(label=arch) + arch_tag.add_css_class("caption") + arch_tag.add_css_class("dim-label") + arch_tag.set_valign(Gtk.Align.CENTER) + row.add_suffix(arch_tag) + + # Run .exe / .msi + run_r = ui.Row(title="Run a Windows program", subtitle=".exe or .msi") + run_btn = Gtk.Button(label="Choose file…") + run_btn.add_css_class("flat") + run_btn.set_valign(Gtk.Align.CENTER) + run_btn.connect("clicked", lambda _b, p=ppath: _pick_exe(p)) + run_r.add_suffix(run_btn) + row.add_row(run_r) + + # Delete + del_r = ui.Row( + title="Delete prefix", + subtitle="Removes all files — cannot be undone", + ) + del_btn = Gtk.Button(label="Delete") + del_btn.add_css_class("destructive-action") + del_btn.set_valign(Gtk.Align.CENTER) + del_btn.connect("clicked", lambda _b, p=ppath: _delete_prefix(p, _b)) + del_r.add_suffix(del_btn) + row.add_row(del_r) + + g_prefixes.add(row) + _prefix_rows.append(row) + + # ── Winetricks rendering ─────────────────────────────────────────────── + + def _render_tricks() -> None: + for r in _tricks_rows: + g_tricks.remove(r) + _tricks_rows.clear() + + if not winecore.is_winetricks_installed(): + row = ui.Row( + title="Winetricks not installed", + subtitle="Install it via the Runtime section above first.", + ) + g_tricks.add(row) + _tricks_rows.append(row) + return + + if not _prefix_paths: + row = ui.Row( + title="No prefix available", + subtitle="Create a Wine prefix to install components into.", + ) + g_tricks.add(row) + _tricks_rows.append(row) + return + + # Restore previous selection, fall back to first + prev = _state["prefix"] + initial = 0 + if prev is not None and prev in _prefix_paths: + initial = _prefix_paths.index(prev) + _state["prefix"] = _prefix_paths[initial] + + names = [winecore.prefix_display_name(p) for p in _prefix_paths] + model = Gtk.StringList.new(names) + combo = Gtk.DropDown.new(model, None) + combo.set_selected(initial) + combo.set_valign(Gtk.Align.CENTER) + + def _on_combo(_c, _pspec) -> None: + idx = combo.get_selected() + if idx < len(_prefix_paths): + _state["prefix"] = _prefix_paths[idx] + + combo.connect("notify::selected", _on_combo) + + combo_row = ui.Row(title="Target prefix") + combo_row.set_subtitle("Components are installed into this prefix") + combo_row.add_suffix(combo) + g_tricks.add(combo_row) + _tricks_rows.append(combo_row) + + for cat, components in winecore.WINETRICKS_COMPONENTS: + exp = ui.ExpanderRow(title=cat) + for verb, name, desc in components: + cr = ui.Row(title=name, subtitle=desc) + btn = Gtk.Button(label="Install") + btn.add_css_class("flat") + btn.set_valign(Gtk.Align.CENTER) + btn.connect("clicked", lambda _b, v=verb: _run_winetricks(v)) + cr.add_suffix(btn) + exp.add_row(cr) + g_tricks.add(exp) + _tricks_rows.append(exp) + + # ── Actions ──────────────────────────────────────────────────────────── + + def _run_privileged(argv: list[str]) -> None: + console.clear() + runner.run(argv, privileged=True) + + def _run_winetricks(verb: str) -> None: + prefix = _state["prefix"] + if prefix is None: + return + console.clear() + runner.run(winecore.winetricks_argv(prefix, verb)) + + def _pick_exe(prefix: Path) -> None: + dialog = Gtk.FileDialog.new() + dialog.set_title("Choose a Windows program to run") + + filt = Gtk.FileFilter() + filt.set_name("Windows executables (*.exe, *.msi)") + filt.add_pattern("*.exe") + filt.add_pattern("*.msi") + filt.add_pattern("*.EXE") + filt.add_pattern("*.MSI") + + filt_all = Gtk.FileFilter() + filt_all.set_name("All files") + filt_all.add_pattern("*") + + store = Gio.ListStore.new(Gtk.FileFilter) + store.append(filt) + store.append(filt_all) + dialog.set_filters(store) + + def _done(_dlg, result) -> None: + try: + f = _dlg.open_finish(result) + except GLib.Error: + return + if f is None: + return + exe = Path(f.get_path()) + console.clear() + runner.run(winecore.run_exe_argv(prefix, exe)) + + dialog.open(page.get_root(), None, _done) + + def _new_prefix_dialog(_btn: Gtk.Widget) -> None: + dlg = Adw.AlertDialog.new("New Wine Prefix", None) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) + box.set_margin_top(12) + box.set_margin_bottom(4) + + entry = Gtk.Entry() + entry.set_placeholder_text("Prefix name (e.g. mygame)") + entry.set_width_chars(28) + box.append(entry) + + arch_combo = Gtk.DropDown.new_from_strings([ + "win64 (64-bit — recommended)", + "win32 (32-bit — for old apps)", + ]) + arch_combo.set_margin_top(6) + box.append(arch_combo) + + dlg.set_extra_child(box) + dlg.add_response("cancel", "Cancel") + dlg.add_response("create", "Create") + dlg.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED) + dlg.set_default_response("create") + dlg.set_close_response("cancel") + + def _on_response(_d, resp: str) -> None: + if resp != "create": + return + name = entry.get_text().strip() + if not name: + return + # Sanitise: keep only filesystem-safe chars + name = "".join(c for c in name if c.isalnum() or c in "-_.") + if not name: + return + arch = "win32" if arch_combo.get_selected() == 1 else "win64" + ppath = winecore.new_prefix_path(name) + console.clear() + runner.run(winecore.create_prefix_argv(ppath, arch)) + + dlg.connect("response", _on_response) + dlg.present(page.get_root()) + + def _delete_prefix(ppath: Path, parent: Gtk.Widget) -> None: + if not winecore.safe_to_delete(ppath): + return + + def _do() -> None: + def _worker() -> None: + try: + shutil.rmtree(ppath) + except OSError: + pass + GLib.idle_add(_refresh) + + threading.Thread(target=_worker, daemon=True).start() + + confirm.confirm( + parent, + f'Delete "{winecore.prefix_display_name(ppath)}"?', + f"All files in {ppath} will be permanently removed.", + _do, + ) + + # ── Lifecycle ────────────────────────────────────────────────────────── + + def _refresh() -> None: + pacman.installed_packages.cache_clear() + _render_runtime() + _render_prefixes() + _render_tricks() + return False + + def on_done(_r: ProcessRunner, code: int) -> None: + console.append_line("✔ done" if code == 0 else f"✘ exit {code}") + GLib.idle_add(_refresh) + + runner.connect("done", on_done) + _refresh() + return page diff --git a/src/taninux/gui/style.py b/src/taninux/gui/style.py index 4cbc72e..949f704 100644 --- a/src/taninux/gui/style.py +++ b/src/taninux/gui/style.py @@ -14,8 +14,22 @@ from pathlib import Path import gi gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") -from gi.repository import Adw, Gdk, Gtk # noqa: E402 +from gi.repository import Gdk, Gtk # noqa: E402 + +from taninux.gui.ui import BACKEND + +# Adw.StyleManager nur im adw-Backend laden — im native-Modus kein libadwaita +if BACKEND == "adw": + gi.require_version("Adw", "1") + from gi.repository import Adw as _Adw # noqa: E402 + _SCHEMES = { + "system": _Adw.ColorScheme.DEFAULT, + "light": _Adw.ColorScheme.FORCE_LIGHT, + "dark": _Adw.ColorScheme.FORCE_DARK, + } +else: + _Adw = None # type: ignore[assignment] + _SCHEMES = {} # Akzent-Daten leben GTK-frei in gui.accents (geteilt mit config + eww-Consumer). from taninux.core import theme as T # noqa: F401 (CSS nutzt @window_bg_color etc.) @@ -26,13 +40,6 @@ from taninux.gui.accents import ( # noqa: F401 (re-export für settings.py) by_key as accent, ) -# Mapping Schema-Key → Adwaita ColorScheme -_SCHEMES = { - "system": Adw.ColorScheme.DEFAULT, - "light": Adw.ColorScheme.FORCE_LIGHT, - "dark": Adw.ColorScheme.FORCE_DARK, -} - _provider: Gtk.CssProvider | None = None @@ -49,7 +56,10 @@ def _build_css(acc: Accent) -> bytes: # Akzent-Override + Swatch-Styling (Swatches sind statisch, aber billig # mitzubauen). Swatch = runder Farbknopf, aktiver bekommt einen Ring. swatches = "\n".join( - f".swatch-{a.key} {{ background-color: {a.hex}; }}" + f"button.fuji-swatch.swatch-{a.key} {{" + f" background-color: {a.hex} !important;" + f" background: {a.hex} !important;" + f" border: none !important; }}" for a in FUJI_ACCENTS ) return f""" @@ -67,21 +77,35 @@ combobox button, splitbutton > button, entry, spinbutton, min-height: 26px; padding: 0; border-radius: 50%; - box-shadow: inset 0 0 0 1px alpha(currentColor, 0.15); + background-color: transparent; + border: none; + box-shadow: none; }} .fuji-swatch:checked {{ - box-shadow: inset 0 0 0 2px @window_bg_color, 0 0 0 2px @accent_bg_color; + outline: 2px solid {acc.hex}; + outline-offset: 3px; }} {swatches} """.encode() def apply(color_scheme: str, accent_key: str) -> None: - """Setzt Color-Scheme (StyleManager) + Akzent (CSS-Provider).""" + """Setzt Color-Scheme (StyleManager) + Akzent (CSS-Provider). + + Im native-Backend übernimmt die Shell die Dunkel-Umschaltung via set_dark(); + hier wird nur der Akzent-CSS-Provider aktualisiert. + """ global _provider - mgr = Adw.StyleManager.get_default() - mgr.set_color_scheme(_SCHEMES.get(color_scheme, Adw.ColorScheme.DEFAULT)) + if BACKEND == "adw" and _Adw is not None: + mgr = _Adw.StyleManager.get_default() + mgr.set_color_scheme(_SCHEMES.get(color_scheme, _Adw.ColorScheme.DEFAULT)) + else: + # GTK4 native: prefer-dark steuert Adwaita-Basisfarben (Buttons, Slider…) + dark = _resolve_dark(color_scheme) + settings = Gtk.Settings.get_default() + if settings is not None: + settings.set_property("gtk-application-prefer-dark-theme", dark) if _provider is None: _provider = Gtk.CssProvider() @@ -123,10 +147,6 @@ _PILLS_GTK4 = ( "splitbutton > button, entry, spinbutton, .pill { border-radius: 999px; }\n" ".card, popover > contents, .osd { border-radius: 14px; }\n" ) -_PILLS_GTK3 = ( - "button, combobox button, combobox, entry, spinbutton,\n" - "notebook > header > tabs > tab { border-radius: 999px; }\n" -) def _config_base() -> Path: @@ -180,19 +200,27 @@ def _gtk4_full(acc: Accent, dark: bool) -> str: {_PILLS_GTK4}""" -def _gtk3_full(acc: Accent, dark: bool) -> str: +def _gtk3_full(acc: Accent, dark: bool) -> str: # noqa: PLR0912 + """Eigenständiges GTK3-Theme — Basis Adwaita, kein adw-gtk3 nötig.""" p = _DARK_SURF if dark else _LIGHT_SURF fg = _contrast_fg(acc.hex) - return f"""/* TANINUX-managed — global GTK3 Fuji theme (rides adw-gtk3). Do not edit. */ + hover = "rgba(255,255,255,0.07)" if dark else "rgba(0,0,0,0.05)" + shadow = "0 8px 32px rgba(0,0,0,0.45)" if dark else "0 8px 32px rgba(0,0,0,0.18)" + btn_bg = "rgba(255,255,255,0.10)" if dark else "rgba(0,0,0,0.08)" + btn_hover = "rgba(255,255,255,0.16)" if dark else "rgba(0,0,0,0.12)" + sw_off = "rgba(255,255,255,0.15)" if dark else "rgba(120,120,128,0.36)" + sb_thumb = "rgba(255,255,255,0.25)" if dark else "rgba(0,0,0,0.20)" + sb_hover = "rgba(255,255,255,0.35)" if dark else "rgba(0,0,0,0.30)" + sec = "rgba(235,235,245,0.6)" if dark else "rgba(60,60,67,0.6)" + destr = "#ff453a" if dark else "#ff3b30" + + return f"""/* TANINUX GTK3 Apple-Optik — eigenständig, kein adw-gtk3 nötig. + Basis: Adwaita (strukturell). Alle Farben/Formen kommen von hier. + {'dark' if dark else 'light'} · accent {acc.label} ({acc.hex}) */ + @define-color accent_color {acc.hex}; @define-color accent_bg_color {acc.hex}; @define-color accent_fg_color {fg}; -@define-color window_bg_color {p['bg']}; -@define-color window_fg_color {p['fg']}; -@define-color view_bg_color {p['view']}; -@define-color headerbar_bg_color {p['headerbar']}; -@define-color card_bg_color {p['card']}; -@define-color popover_bg_color {p['bg_alt']}; @define-color theme_bg_color {p['bg']}; @define-color theme_fg_color {p['fg']}; @define-color theme_base_color {p['view']}; @@ -200,9 +228,360 @@ def _gtk3_full(acc: Accent, dark: bool) -> str: @define-color theme_selected_bg_color {acc.hex}; @define-color theme_selected_fg_color {fg}; @define-color borders {p['border']}; -{_PILLS_GTK3}/* The per-widget accent (sliders, tab underline, switches) is driven by the - GNOME accent-color gsetting (adw-gtk3 honours it and wins on specificity); - write_global_theme maps the Fuji hue to the nearest preset. */ + +/* ==================================================================== FONT */ +* {{ + font-family: -apple-system, "SF Pro Text", "Inter", "Cantarell", sans-serif; + -gtk-icon-style: regular; +}} + +/* ================================================================= SURFACES */ +window, .background {{ + background-color: {p['bg']}; + color: {p['fg']}; +}} +.csd window > decoration {{ + background: transparent; + border-radius: 14px; + box-shadow: {shadow}; +}} + +/* ================================================================ HEADERBAR */ +headerbar {{ + background-color: {p['headerbar']}; + color: {p['fg']}; + border-bottom: 1px solid {p['border']}; + padding: 4px 8px; + min-height: 46px; + box-shadow: none; +}} +headerbar .title {{ font-weight: 600; font-size: 0.95em; }} +headerbar .subtitle {{ font-size: 0.8em; color: {sec}; }} +headerbar button {{ + background: transparent; + border: none; + box-shadow: none; + border-radius: 999px; + padding: 4px 8px; + color: {p['fg']}; + min-height: 0; +}} +headerbar button:hover {{ background-color: {hover}; }} +headerbar button.close {{ + background-color: #ff5f57; + border-radius: 50%; + min-width: 14px; + min-height: 14px; + padding: 0; +}} + +/* ================================================================== TOOLBAR */ +toolbar {{ + background-color: {p['bg_alt']}; + border-bottom: 1px solid {p['border']}; + padding: 2px 4px; +}} +toolbar button {{ + background: transparent; + border: none; + box-shadow: none; + border-radius: 8px; + padding: 4px 6px; + color: {p['fg']}; +}} +toolbar button:hover {{ background-color: {hover}; }} +toolbar separator {{ + background-color: {p['border']}; + margin: 4px 2px; +}} + +/* ================================================================== SIDEBAR */ +.sidebar, +placessidebar, +.sidebar > viewport, +placessidebar > viewport, +.sidebar > viewport > contents, +placessidebar > viewport > contents {{ + background-color: {p['bg_alt']}; + color: {p['fg']}; +}} +.sidebar, +placessidebar {{ + border-right: 1px solid {p['border']}; +}} +placessidebar row, +.sidebar row {{ + padding: 5px 8px; + border-radius: 8px; + margin: 1px 8px; + color: {p['fg']}; + background: transparent; +}} +placessidebar row:hover, +.sidebar row:hover {{ + background-color: {hover}; +}} +placessidebar row:selected, +placessidebar row:selected:focus, +.sidebar row:selected, +.sidebar row:selected:focus {{ + background-color: {acc.hex}; + color: {fg}; + border-radius: 8px; +}} +placessidebar row image {{ color: {sec}; }} +placessidebar row:selected image {{ color: {fg}; }} + +/* ================================================================= TREEVIEW */ +treeview, +treeview.view {{ + background-color: {p['view']}; + color: {p['fg']}; + border: none; +}} +treeview.view:nth-child(even) {{ background-color: {p['view']}; }} +treeview.view:selected, +treeview.view:selected:focus {{ + background-color: {acc.hex}; + color: {fg}; +}} +treeview header button, +columnview header button {{ + background-color: transparent; + border: none; + box-shadow: none; + border-bottom: 1px solid {p['border']}; + border-radius: 0; + padding: 4px 8px; + color: {sec}; + font-size: 0.83em; + font-weight: 600; +}} +treeview header button:hover {{ background-color: {hover}; }} + +/* =================================================================== FRAME */ +frame > border {{ border: 1px solid {p['border']}; border-radius: 12px; }} + +/* ================================================================== BUTTON */ +button {{ + background-color: {btn_bg}; + color: {p['fg']}; + border: none; + border-radius: 999px; + padding: 5px 14px; + box-shadow: none; + transition: background-color 120ms ease; +}} +button:hover {{ background-color: {btn_hover}; }} +button:active {{ background-color: {btn_hover}; }} +button:disabled {{ opacity: 0.45; }} +button.suggested-action {{ + background-color: {acc.hex}; + color: {fg}; +}} +button.suggested-action:hover {{ opacity: 0.92; }} +button.destructive-action {{ + background-color: {destr}; + color: #ffffff; +}} +button.flat, button.flat:hover {{ + background: transparent; + box-shadow: none; + color: {acc.hex}; +}} +button.flat:hover {{ background-color: {hover}; }} +combobox button, combobox {{ + border-radius: 999px; + background-color: {btn_bg}; + border: none; + box-shadow: none; +}} + +/* =================================================================== ENTRY */ +entry {{ + background-color: {p['card']}; + color: {p['fg']}; + border: 1px solid {p['border']}; + border-radius: 999px; + padding: 5px 12px; + box-shadow: none; + caret-color: {acc.hex}; +}} +entry:focus {{ + border-color: {acc.hex}; + box-shadow: 0 0 0 2px {acc.hex}40; +}} +entry image {{ color: {sec}; }} +spinbutton {{ + background-color: {p['card']}; + color: {p['fg']}; + border: 1px solid {p['border']}; + border-radius: 999px; + padding: 4px 8px; + box-shadow: none; +}} +spinbutton button {{ + background: transparent; + border: none; + border-radius: 999px; + padding: 2px 6px; + min-width: 0; +}} + +/* ================================================================== SWITCH */ +switch {{ + background-color: {sw_off}; + border: none; + border-radius: 999px; + box-shadow: none; + min-width: 40px; + min-height: 24px; + transition: background-color 180ms ease; +}} +switch:checked {{ background-color: {acc.hex}; }} +switch slider {{ + background-color: white; + border-radius: 50%; + min-width: 20px; + min-height: 20px; + margin: 2px; + box-shadow: 0 1px 4px rgba(0,0,0,0.25); +}} + +/* =============================================================== SCROLLBAR */ +scrollbar {{ + background-color: transparent; + min-width: 8px; + min-height: 8px; + border: none; +}} +scrollbar trough {{ + background-color: transparent; + border-radius: 999px; +}} +scrollbar slider {{ + border-radius: 999px; + background-color: {sb_thumb}; + min-width: 6px; + min-height: 6px; + margin: 2px; + transition: background-color 120ms ease; +}} +scrollbar slider:hover {{ background-color: {sb_hover}; }} +scrollbar.overlay-indicator {{ opacity: 0; transition: opacity 200ms; }} +scrollbar.overlay-indicator.dragging, +scrollbar.overlay-indicator:hover {{ opacity: 1; }} + +/* =============================================================== SEPARATOR */ +separator {{ + background-color: {p['border']}; + min-width: 1px; + min-height: 1px; +}} + +/* ================================================================ NOTEBOOK */ +notebook > header {{ + background-color: {p['bg_alt']}; + border-bottom: 1px solid {p['border']}; + padding: 4px 8px; +}} +notebook > header > tabs > tab {{ + border-radius: 999px; + padding: 4px 14px; + margin: 2px; + color: {sec}; + background: transparent; + border: none; + box-shadow: none; +}} +notebook > header > tabs > tab:checked {{ + background-color: {acc.hex}; + color: {fg}; +}} + +/* ==================================================================== MENU */ +menu {{ + background-color: {p['bg_alt']}; + border: 1px solid {p['border']}; + border-radius: 12px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,{'0.40' if dark else '0.15'}); +}} +menuitem {{ + border-radius: 8px; + padding: 5px 10px; + color: {p['fg']}; +}} +menuitem:hover {{ + background-color: {acc.hex}; + color: {fg}; +}} +menuitem accelerator {{ color: {sec}; }} +menu separator {{ + background-color: {p['border']}; + margin: 4px 8px; + min-height: 1px; +}} +menubar {{ + background-color: {p['headerbar']}; + border-bottom: 1px solid {p['border']}; + padding: 0 4px; +}} +menubar > menuitem {{ + padding: 4px 10px; + border-radius: 6px; + color: {p['fg']}; +}} +menubar > menuitem:hover {{ + background-color: {acc.hex}; + color: {fg}; +}} + +/* ================================================================= POPOVER */ +popover {{ + background-color: {p['bg_alt']}; + border: 1px solid {p['border']}; + border-radius: 12px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,{'0.40' if dark else '0.15'}); +}} +popover > contents {{ background-color: transparent; border-radius: 12px; }} + +/* ================================================================ ICONVIEW */ +iconview {{ + background-color: {p['view']}; + color: {p['fg']}; +}} +iconview item:selected {{ + background-color: {acc.hex}; + color: {fg}; + border-radius: 8px; +}} + +/* =============================================================== SELECTION */ +selection {{ background-color: {acc.hex}; color: {fg}; }} +textview {{ background-color: {p['view']}; color: {p['fg']}; }} +textview text {{ background-color: {p['view']}; color: {p['fg']}; }} +textview text selection {{ background-color: {acc.hex}; color: {fg}; }} + +/* ============================================================= PROGRESSBAR */ +progressbar trough {{ + background-color: {p['border']}; + border-radius: 999px; + min-height: 4px; +}} +progressbar progress {{ + background-color: {acc.hex}; + border-radius: 999px; +}} + +/* ================================================================== DIALOG */ +dialog {{ + background-color: {p['bg']}; +}} +dialog .dialog-action-area button {{ + min-width: 80px; +}} """ @@ -217,7 +596,7 @@ _ACCENT_PRESET = { def _set_gsettings(dark: bool, system: bool, accent_key: str) -> None: pairs = [ ("color-scheme", "default" if system else ("prefer-dark" if dark else "prefer-light")), - ("gtk-theme", "adw-gtk3-dark" if dark else "adw-gtk3"), + ("gtk-theme", "TANINUX-dark" if dark else "TANINUX"), ("accent-color", _ACCENT_PRESET.get(accent_key, "purple")), # Fuji-accent folder icons (tanin-icons): Adwaita overlay whose folders # are recoloured to this hue. Switches live with the accent. @@ -253,17 +632,15 @@ def write_global_theme(color_scheme: str, accent_key: str) -> None: except OSError: pass - # GTK3 → ~/.config/gtk-3.0/gtk.css (overrides on top of adw-gtk3). - try: - g3 = _config_base() / "gtk-3.0" - g3.mkdir(parents=True, exist_ok=True) - dst = g3 / "gtk.css" - backup = g3 / "gtk.css.pre-tanin" - if dst.exists() and not dst.is_symlink() and not backup.exists(): - backup.write_text(dst.read_text()) # keep a real pre-existing file once - dst.write_text(_gtk3_full(acc, dark)) - except OSError: - pass + # GTK3 → ~/.themes/TANINUX{,-dark}/gtk-3.0/gtk.css + for variant_dark in (False, True): + theme_name = "TANINUX-dark" if variant_dark else "TANINUX" + try: + t = Path.home() / ".themes" / theme_name / "gtk-3.0" + t.mkdir(parents=True, exist_ok=True) + (t / "gtk.css").write_text(_gtk3_full(acc, variant_dark)) + except OSError: + pass _set_gsettings(dark, color_scheme == "system", accent_key) diff --git a/src/taninux/gui/ui/__init__.py b/src/taninux/gui/ui/__init__.py new file mode 100644 index 0000000..0fc07d4 --- /dev/null +++ b/src/taninux/gui/ui/__init__.py @@ -0,0 +1,96 @@ +"""ui-Naht — ein Widget-Vokabular, zwei Backends. + +Die Seiten importieren nur noch ``from taninux.gui import ui`` und bauen mit +``ui.Page() / ui.Group(...) / ui.Row(...)``. *Was* dahinter steckt, entscheidet +EIN Schalter: + + TANINUX_UI=adw → libadwaita (Adw.PreferencesPage/Group/ActionRow) + TANINUX_UI=native → eigene GTK4-Widgets mit Apple-Optik (ui/native.py) + +Default ist ``adw`` — heute ändert sich also nichts. Der Sinn der Naht: die +~430 Adw-Zeilen-/Gruppen-Instanzen über 34 Dateien laufen ab jetzt durch EINE +Stelle. Damit wird „eigene Design-Sprache statt libadwaita“ ein Umbau hier +statt überall — und Seite für Seite migrierbar, weil beide Backends dieselbe +API sprechen. +""" +from __future__ import annotations + +import os + +BACKEND = os.environ.get("TANINUX_UI", "native") + +if BACKEND == "native": + import gi as _gi + + _gi.require_version("Gtk", "4.0") + from gi.repository import Gtk as _Gtk # noqa: E402 + + from taninux.gui.ui.native import ( # noqa: F401 + AlertDialog, + Avatar, + Bin, + ComboRow, + Dialog, + EntryRow, + ExpanderRow, + Group, + HeaderBar, + Page, + PasswordEntryRow, + ResponseAppearance, + Row, + SpinRow, + Spinner, + StatusPage, + SwitchRow, + ToolbarView, + ensure_css, + set_dark, + ) + + Application = _Gtk.Application +else: + import gi + + gi.require_version("Gtk", "4.0") + gi.require_version("Adw", "1") + from gi.repository import Adw, Gtk # noqa: E402 + + # Adw-Pendants sind API-gleich — direkte Aliase, kein Wrapper-Overhead. + Page = Adw.PreferencesPage + Group = Adw.PreferencesGroup + Row = Adw.ActionRow + SwitchRow = Adw.SwitchRow + ComboRow = Adw.ComboRow + EntryRow = Adw.EntryRow + SpinRow = Adw.SpinRow + Spinner = Adw.Spinner + StatusPage = Adw.StatusPage + Bin = Adw.Bin + ExpanderRow = Adw.ExpanderRow + AlertDialog = Adw.AlertDialog + ResponseAppearance = Adw.ResponseAppearance + Dialog = Adw.Dialog + Avatar = Adw.Avatar + PasswordEntryRow = Adw.PasswordEntryRow + HeaderBar = Adw.HeaderBar + ToolbarView = Adw.ToolbarView + Application = Adw.Application + + def ensure_css() -> None: + """No-op im Adw-Backend (Styling kommt aus style.py / libadwaita).""" + + def set_dark(window, dark: bool) -> None: # noqa: ANN001 + """No-op im Adw-Backend (Dunkel kommt über Adw.StyleManager).""" + + +def __getattr__(name: str): + """Lazy MainWindow — vermeidet Zirkular-Import (window/shell ziehen pages, + pages ziehen wiederum ui). Erst bei ``ui.MainWindow`` aufgelöst.""" + if name == "MainWindow": + if BACKEND == "native": + from taninux.gui.ui.shell import MainWindow + else: + from taninux.gui.window import MainWindow + return MainWindow + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/taninux/gui/ui/native.py b/src/taninux/gui/ui/native.py new file mode 100644 index 0000000..cab2cbc --- /dev/null +++ b/src/taninux/gui/ui/native.py @@ -0,0 +1,683 @@ +"""Natives GTK4-Widget-Vokabular — libadwaita-frei, eigene Apple-Optik. + +Ersetzt die libadwaita-Idiomatik (PreferencesPage / PreferencesGroup / +ActionRow) durch eigene GTK4-Widgets. Die öffentliche API ist absichtlich +*Adw-kompatibel* (gleiche Konstruktor-Kwargs, gleiche Methodennamen), damit die +Seiten über die ui-Naht (siehe ``ui/__init__.py``) ohne Umschreiben von Adw auf +native Widgets wechseln können — Seite für Seite, ohne die App zu brechen. +""" +from __future__ import annotations + +import enum as _enum +from pathlib import Path + +import gi + +gi.require_version("Gdk", "4.0") +gi.require_version("Gtk", "4.0") +from gi.repository import Gdk, GObject, Gtk # noqa: E402 + +# Apples Content-Spalte ist fix-ish; wir clampen statt voll zu dehnen. +CLAMP_WIDTH = 640 + +_css_loaded = False + + +def ensure_css() -> None: + """Lädt das Apple-Stylesheet einmalig (USER-Priorität, überschreibt Stock).""" + global _css_loaded + if _css_loaded: + return + display = Gdk.Display.get_default() + if display is None: + return + # Fallback-Akzent (Fuji Ume) auf THEME-Priorität: gui/style.py überschreibt + # @accent_color später auf USER-Priorität mit dem echten Hue und gewinnt. + fallback = Gtk.CssProvider() + fallback.load_from_data(b"@define-color accent_color #8f8aac;") + Gtk.StyleContext.add_provider_for_display( + display, fallback, Gtk.STYLE_PROVIDER_PRIORITY_THEME + ) + + provider = Gtk.CssProvider() + provider.load_from_path(str(Path(__file__).with_name("style_apple.css"))) + Gtk.StyleContext.add_provider_for_display( + display, provider, Gtk.STYLE_PROVIDER_PRIORITY_USER + ) + _css_loaded = True + + +def set_dark(window: Gtk.Window, dark: bool) -> None: + """Schaltet die Dunkel-Variante (CSS hängt an window.tn-dark ).""" + window.add_css_class("tn") + if dark: + window.add_css_class("tn-dark") + else: + window.remove_css_class("tn-dark") + + +class Row(Gtk.ListBoxRow): + """Adw.ActionRow-Ersatz: Titel/Untertitel + Prefix-/Suffix-Slots.""" + + __gsignals__ = {"activated": (GObject.SignalFlags.RUN_FIRST, None, ())} + + def __init__(self, title: str = "", subtitle: str = "") -> None: + super().__init__() + self.set_activatable(True) + self.connect("activate", lambda _: self.emit("activated")) + + box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + box.add_css_class("tn-row") + self.set_child(box) + + self._prefix = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + self._prefix.set_valign(Gtk.Align.CENTER) + box.append(self._prefix) + + text = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + text.set_valign(Gtk.Align.CENTER) + text.set_hexpand(True) + self._title = Gtk.Label(label=title, xalign=0.0) + self._title.add_css_class("tn-title") + self._subtitle = Gtk.Label(label=subtitle, xalign=0.0) + self._subtitle.add_css_class("tn-subtitle") + self._subtitle.set_wrap(True) + self._subtitle.set_visible(bool(subtitle)) + text.append(self._title) + text.append(self._subtitle) + box.append(text) + + self._suffix = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + self._suffix.set_valign(Gtk.Align.CENTER) + box.append(self._suffix) + + # -- Adw.ActionRow-kompatible API ------------------------------------ + def set_title(self, title: str) -> None: + self._title.set_label(title) + + def set_subtitle(self, subtitle: str) -> None: + self._subtitle.set_label(subtitle or "") + self._subtitle.set_visible(bool(subtitle)) + + def add_prefix(self, widget: Gtk.Widget) -> None: + self._prefix.append(widget) + + def add_suffix(self, widget: Gtk.Widget) -> None: + self._suffix.append(widget) + + +class SwitchRow(Row): + """Adw.SwitchRow-Ersatz: Zeile mit Toggle im Suffix (property ``active``).""" + + def __init__(self, title: str = "", subtitle: str = "") -> None: + super().__init__(title, subtitle) + self._switch = Gtk.Switch(valign=Gtk.Align.CENTER) + self.add_suffix(self._switch) + self._switch.connect("notify::active", lambda *_: self.notify("active")) + + @GObject.Property(type=bool, default=False) + def active(self) -> bool: # type: ignore[override] + return self._switch.get_active() + + @active.setter + def active(self, value: bool) -> None: + self._switch.set_active(value) + + def get_active(self) -> bool: + return self._switch.get_active() + + def set_active(self, value: bool) -> None: + self._switch.set_active(value) + + +class ComboRow(Row): + """Adw.ComboRow-Ersatz: Zeile mit DropDown im Suffix (property ``selected``).""" + + def __init__(self, title: str = "", subtitle: str = "") -> None: + super().__init__(title, subtitle) + self._dd = Gtk.DropDown(valign=Gtk.Align.CENTER) + self._dd.add_css_class("flat") + self.add_suffix(self._dd) + self._dd.connect("notify::selected", lambda *_: self.notify("selected")) + + @GObject.Property(type=int, default=0) + def selected(self) -> int: # type: ignore[override] + return self._dd.get_selected() + + @selected.setter + def selected(self, index: int) -> None: + self._dd.set_selected(index) + + def set_model(self, model) -> None: # noqa: ANN001 — StringList | Sequence[str] + if isinstance(model, (list, tuple)): + model = Gtk.StringList.new(list(model)) + self._dd.set_model(model) + + def get_selected(self) -> int: + return self._dd.get_selected() + + def set_selected(self, index: int) -> None: + self._dd.set_selected(index) + + def set_enable_search(self, _enable: bool) -> None: + """Adw.ComboRow-Kompatibilität — DropDown hat keine eigene Suche.""" + + +class EntryRow(Row): + """Adw.EntryRow-Ersatz: Zeile mit rahmenlosem Eingabefeld im Suffix.""" + + __gsignals__ = { + "changed": (GObject.SignalFlags.RUN_FIRST, None, ()), + "apply": (GObject.SignalFlags.RUN_FIRST, None, ()), + } + + def __init__(self, title: str = "", subtitle: str = "") -> None: + super().__init__(title, subtitle) + self._entry = Gtk.Entry(valign=Gtk.Align.CENTER, hexpand=True, xalign=1.0) + self._entry.set_has_frame(False) + self.add_suffix(self._entry) + self._entry.connect("changed", lambda *_: self.emit("changed")) + + def get_text(self) -> str: + return self._entry.get_text() + + def set_text(self, text: str) -> None: + self._entry.set_text(text) + + def set_show_apply_button(self, _show: bool) -> None: + """Adw.EntryRow-Kompatibilität — kein separater Apply-Button im native Row.""" + + +class SpinRow(Row): + """Adw.SpinRow-Ersatz: Zeile mit SpinButton im Suffix.""" + + def __init__(self, title: str = "", subtitle: str = "", + adjustment: Gtk.Adjustment | None = None, digits: int = 0) -> None: + super().__init__(title, subtitle) + adj = adjustment or Gtk.Adjustment( + lower=0, upper=100, step_increment=1, page_increment=10 + ) + self._spin = Gtk.SpinButton(adjustment=adj, valign=Gtk.Align.CENTER) + self._spin.set_digits(digits) + self.add_suffix(self._spin) + + @classmethod + def new_with_range(cls, lower: float, upper: float, step: float, + title: str = "", subtitle: str = "") -> "SpinRow": + """Adw.SpinRow.new_with_range()-Kompatibilität.""" + adj = Gtk.Adjustment(lower=lower, upper=upper, + step_increment=step, page_increment=step * 10) + obj = cls(title=title, subtitle=subtitle, adjustment=adj) + return obj + + def get_value(self) -> float: + return self._spin.get_value() + + def set_value(self, value: float) -> None: + self._spin.set_value(value) + + def set_range(self, lower: float, upper: float) -> None: + self._spin.set_range(lower, upper) + + +class Group(Gtk.Box): + """Adw.PreferencesGroup-Ersatz: optionaler Header + runde Card mit Zeilen.""" + + def __init__(self, title: str = "", description: str = "") -> None: + super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=6) + self.add_css_class("tn-group") + + self._header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + self._title = Gtk.Label(label=title, xalign=0.0, hexpand=True) + self._title.add_css_class("tn-group-title") + self._header.append(self._title) + self._suffix_slot = Gtk.Box() + self._suffix_slot.set_valign(Gtk.Align.CENTER) + self._header.append(self._suffix_slot) + self._header.set_visible(bool(title)) + self.append(self._header) + + if description: + self._desc = Gtk.Label(label=description, xalign=0.0) + self._desc.add_css_class("tn-group-desc") + self._desc.set_wrap(True) + self.append(self._desc) + else: + self._desc = None + + self._card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self._card.add_css_class("tn-card") + self.append(self._card) + + self._list = Gtk.ListBox() + self._list.add_css_class("tn-list") + self._list.set_selection_mode(Gtk.SelectionMode.NONE) + self._card.append(self._list) + + # -- Adw.PreferencesGroup-kompatible API ----------------------------- + def add(self, child: Gtk.Widget) -> None: + # Zeilen wandern in die ListBox; alles andere (z.B. ScrolledWindow mit + # eigener Liste, wie maintain.py es nutzt) wird unter die Card gehängt. + if isinstance(child, Gtk.ListBoxRow): + self._list.append(child) + else: + self._card.append(child) + + def remove(self, child: Gtk.Widget) -> None: + parent = child.get_parent() + if parent is self._list: + self._list.remove(child) + elif parent is self._card: + self._card.remove(child) + + def set_description(self, description: str) -> None: + if self._desc is not None: + self._desc.set_label(description or "") + self._desc.set_visible(bool(description)) + + def set_header_suffix(self, widget: Gtk.Widget | None) -> None: + old = self._suffix_slot.get_first_child() + while old is not None: + nxt = old.get_next_sibling() + self._suffix_slot.remove(old) + old = nxt + if widget is not None: + self._suffix_slot.append(widget) + self._header.set_visible(True) + + +class Page(Gtk.ScrolledWindow): + """Adw.PreferencesPage-Ersatz: scrollende, zentrierte Gruppen-Spalte.""" + + def __init__(self) -> None: + super().__init__(hexpand=True, vexpand=True) + self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.add_css_class("tn-page") + + self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=28) + self._box.set_halign(Gtk.Align.CENTER) + self._box.set_size_request(CLAMP_WIDTH, -1) + self._box.set_margin_top(28) + self._box.set_margin_bottom(28) + self._box.set_margin_start(18) + self._box.set_margin_end(18) + self.set_child(self._box) + + # -- Adw.PreferencesPage-kompatible API ------------------------------ + def add(self, group: Gtk.Widget) -> None: + self._box.append(group) + + def remove(self, group: Gtk.Widget) -> None: + self._box.remove(group) + + +# ============================================================ Phase-1-Widgets + +# Spinner — GTK hat Gtk.Spinner, direkt nutzbar +Spinner = Gtk.Spinner + + +class Bin(Gtk.Box): + """Adw.Bin-Ersatz: Einzel-Kind-Container ohne eigenes Layout.""" + + def __init__(self, child: Gtk.Widget | None = None) -> None: + super().__init__() + if child is not None: + self.append(child) + + def set_child(self, child: Gtk.Widget | None) -> None: # type: ignore[override] + old = self.get_first_child() + while old is not None: + nxt = old.get_next_sibling() + self.remove(old) + old = nxt + if child is not None: + self.append(child) + + def get_child(self) -> Gtk.Widget | None: + return self.get_first_child() + + +class StatusPage(Gtk.Box): + """Adw.StatusPage-Ersatz: zentrierte Leer-/Statusanzeige mit Icon + Text.""" + + def __init__( + self, + icon_name: str = "", + title: str = "", + description: str = "", + ) -> None: + super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=12) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.CENTER) + self.add_css_class("tn-status-page") + + self._icon = Gtk.Image() + self._icon.set_pixel_size(96) + self._icon.add_css_class("tn-status-icon") + if icon_name: + self._icon.set_from_icon_name(icon_name) + self._icon.set_visible(bool(icon_name)) + self.append(self._icon) + + self._title = Gtk.Label(label=title) + self._title.add_css_class("title-1") + self._title.set_wrap(True) + self._title.set_justify(Gtk.Justification.CENTER) + self._title.set_visible(bool(title)) + self.append(self._title) + + self._desc = Gtk.Label(label=description) + self._desc.add_css_class("body") + self._desc.set_wrap(True) + self._desc.set_justify(Gtk.Justification.CENTER) + self._desc.set_visible(bool(description)) + self.append(self._desc) + + def set_icon_name(self, icon_name: str) -> None: + if icon_name: + self._icon.set_from_icon_name(icon_name) + self._icon.set_visible(bool(icon_name)) + + def set_title(self, title: str) -> None: + self._title.set_label(title) + self._title.set_visible(bool(title)) + + def set_description(self, description: str) -> None: + self._desc.set_label(description) + self._desc.set_visible(bool(description)) + + +class ExpanderRow(Gtk.ListBoxRow): + """Adw.ExpanderRow-Ersatz: aufklappbare Zeile mit Unterzeilen.""" + + def __init__(self, title: str = "", subtitle: str = "") -> None: + super().__init__() + self.set_activatable(False) + self.add_css_class("tn-expander-row") + + outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.set_child(outer) + + # Header-Box (kein ListBoxRow — sitzt als Kind im äußeren ListBoxRow) + header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + header.add_css_class("tn-row") + + self._prefix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + self._prefix_box.set_valign(Gtk.Align.CENTER) + header.append(self._prefix_box) + + text = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + text.set_valign(Gtk.Align.CENTER) + text.set_hexpand(True) + self._title_lbl = Gtk.Label(label=title, xalign=0.0) + self._title_lbl.add_css_class("tn-title") + self._subtitle_lbl = Gtk.Label(label=subtitle, xalign=0.0) + self._subtitle_lbl.add_css_class("tn-subtitle") + self._subtitle_lbl.set_visible(bool(subtitle)) + text.append(self._title_lbl) + text.append(self._subtitle_lbl) + header.append(text) + + self._suffix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + self._suffix_box.set_valign(Gtk.Align.CENTER) + header.append(self._suffix_box) + + self._chevron = Gtk.Image(icon_name="pan-end-symbolic") + self._chevron.add_css_class("tn-expander-chevron") + self._suffix_box.append(self._chevron) + + gesture = Gtk.GestureClick() + gesture.connect("released", lambda _g, _n, _x, _y: self._toggle()) + header.add_controller(gesture) + outer.append(header) + + # Aufgeklappte Unterzeilen + self._rows_list = Gtk.ListBox() + self._rows_list.add_css_class("tn-list") + self._rows_list.add_css_class("tn-expander-list") + self._rows_list.set_selection_mode(Gtk.SelectionMode.NONE) + self._rows_list.set_visible(False) + outer.append(self._rows_list) + + self._expanded = False + + def _toggle(self) -> None: + self.set_expanded(not self._expanded) + + def set_expanded(self, expanded: bool) -> None: + self._expanded = expanded + self._rows_list.set_visible(expanded) + self._chevron.set_from_icon_name( + "pan-down-symbolic" if expanded else "pan-end-symbolic" + ) + + def get_expanded(self) -> bool: + return self._expanded + + def add_row(self, widget: Gtk.Widget) -> None: + if isinstance(widget, Gtk.ListBoxRow): + self._rows_list.append(widget) + else: + row = Gtk.ListBoxRow() + row.set_child(widget) + self._rows_list.append(row) + + def add_action(self, widget: Gtk.Widget) -> None: + self._suffix_box.prepend(widget) + + def add_suffix(self, widget: Gtk.Widget) -> None: + self._suffix_box.prepend(widget) + + def add_prefix(self, widget: Gtk.Widget) -> None: + self._prefix_box.append(widget) + + def set_title(self, title: str) -> None: + self._title_lbl.set_label(title) + + def set_subtitle(self, subtitle: str) -> None: + self._subtitle_lbl.set_label(subtitle or "") + self._subtitle_lbl.set_visible(bool(subtitle)) + + +class ResponseAppearance(_enum.Enum): + """Adw.ResponseAppearance-Ersatz: Styling-Hinweis für AlertDialog-Buttons.""" + + DEFAULT = "default" + SUGGESTED = "suggested" + DESTRUCTIVE = "destructive" + + +class AlertDialog(GObject.Object): + """Adw.AlertDialog-Ersatz: nicht-blockierender Bestätigungs-Dialog.""" + + __gsignals__ = { + "response": (GObject.SignalFlags.RUN_FIRST, None, (str,)), + } + + def __init__(self, heading: str = "", body: str = "") -> None: + super().__init__() + self._heading = heading + self._body = body + self._responses: list[tuple[str, str]] = [] + self._appearances: dict[str, ResponseAppearance] = {} + self._default: str | None = None + self._close_resp: str | None = None + self._extra: Gtk.Widget | None = None + + def add_response(self, resp_id: str, label: str) -> None: + self._responses.append((resp_id, label)) + + def set_response_appearance( + self, resp_id: str, appearance: ResponseAppearance + ) -> None: + self._appearances[resp_id] = appearance + + def set_default_response(self, resp_id: str) -> None: + self._default = resp_id + + def set_close_response(self, resp_id: str) -> None: + self._close_resp = resp_id + + def set_extra_child(self, widget: Gtk.Widget | None) -> None: + self._extra = widget + + def present(self, parent: Gtk.Widget) -> None: + root = parent.get_root() if hasattr(parent, "get_root") else None + win = root if isinstance(root, Gtk.Window) else None + + dlg = Gtk.Window(modal=True, resizable=False, decorated=False) + dlg.add_css_class("tn-alert-dialog") + if win: + dlg.set_transient_for(win) + dlg.set_default_size(380, -1) + + outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + outer.add_css_class("tn-alert-box") + dlg.set_child(outer) + + heading_lbl = Gtk.Label(label=self._heading) + heading_lbl.add_css_class("tn-alert-heading") + heading_lbl.set_wrap(True) + heading_lbl.set_xalign(0.5) + outer.append(heading_lbl) + + if self._body: + body_lbl = Gtk.Label(label=self._body) + body_lbl.add_css_class("tn-alert-body") + body_lbl.set_wrap(True) + body_lbl.set_xalign(0.5) + outer.append(body_lbl) + + if self._extra is not None: + outer.append(self._extra) + + btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + btn_box.add_css_class("tn-alert-buttons") + btn_box.set_halign(Gtk.Align.CENTER) + outer.append(btn_box) + + close_resp = self._close_resp + + def _emit(resp_id: str) -> None: + dlg.destroy() + self.emit("response", resp_id) + + for resp_id, label in self._responses: + btn = Gtk.Button(label=label) + btn.add_css_class("pill") + appearance = self._appearances.get(resp_id, ResponseAppearance.DEFAULT) + if appearance == ResponseAppearance.DESTRUCTIVE: + btn.add_css_class("destructive-action") + elif appearance == ResponseAppearance.SUGGESTED: + btn.add_css_class("suggested-action") + btn.connect("clicked", lambda _b, rid=resp_id: _emit(rid)) + btn_box.append(btn) + + if close_resp: + dlg.connect("close-request", lambda *_: (_emit(close_resp or ""), True)[1]) + + dlg.present() + + +class Dialog(Gtk.Window): + """Adw.Dialog-Ersatz: modales Overlay-Fenster.""" + + def __init__(self) -> None: + super().__init__(modal=True) + self.add_css_class("tn-dialog") + self._w = -1 + self._h = -1 + + def set_content_width(self, width: int) -> None: + self._w = width + self.set_default_size(self._w, self._h) + + def set_content_height(self, height: int) -> None: + self._h = height + self.set_default_size(self._w, self._h) + + def present(self, parent: Gtk.Widget) -> None: # type: ignore[override] + root = parent.get_root() if hasattr(parent, "get_root") else None + if isinstance(root, Gtk.Window): + self.set_transient_for(root) + super().present() + + def close(self) -> None: + self.destroy() + + +class Avatar(Gtk.Stack): + """Adw.Avatar-Ersatz: runder Avatar mit Initialen oder Nutzerbild.""" + + def __init__( + self, size: int = 48, text: str = "", show_initials: bool = True + ) -> None: + super().__init__() + self.set_size_request(size, size) + self.add_css_class("tn-avatar") + self.set_transition_type(Gtk.StackTransitionType.CROSSFADE) + self.set_transition_duration(120) + + initials = ( + "".join(w[0].upper() for w in text.split()[:2]) + if (show_initials and text) + else "" + ) + self._init_lbl = Gtk.Label(label=initials) + self._init_lbl.add_css_class("tn-avatar-initials") + self._init_lbl.set_halign(Gtk.Align.CENTER) + self._init_lbl.set_valign(Gtk.Align.CENTER) + self.add_named(self._init_lbl, "initials") + + # Gdk.Texture implementiert Gdk.Paintable → set_paintable() nimmt es direkt + self._pic = Gtk.Picture() + self._pic.set_content_fit(Gtk.ContentFit.COVER) + self.add_named(self._pic, "image") + + self.set_visible_child_name("initials") + + def set_custom_image(self, paintable) -> None: # noqa: ANN001 — Gdk.Paintable + if paintable is not None: + self._pic.set_paintable(paintable) + self.set_visible_child_name("image") + else: + self.set_visible_child_name("initials") + + +class PasswordEntryRow(EntryRow): + """Adw.PasswordEntryRow-Ersatz: Eingabezeile mit verborgenem Text.""" + + def __init__(self, title: str = "", subtitle: str = "") -> None: + super().__init__(title, subtitle) + self._entry.set_visibility(False) + self._entry.set_input_purpose(Gtk.InputPurpose.PASSWORD) + peek = Gtk.ToggleButton() + peek.add_css_class("flat") + peek.set_child(Gtk.Image(icon_name="view-reveal-symbolic")) + peek.connect("toggled", lambda b: self._entry.set_visibility(b.get_active())) + self.add_suffix(peek) + + +# Alias: GTK4 hat Gtk.HeaderBar, das passt direkt +HeaderBar = Gtk.HeaderBar + + +class ToolbarView(Gtk.Box): + """Adw.ToolbarView-Ersatz: Inhalt-Box; im nativen Backend liefert die WM- + Titelleiste den Header, deshalb ist add_top_bar() ein No-op.""" + + def __init__(self) -> None: + super().__init__(orientation=Gtk.Orientation.VERTICAL) + + def add_top_bar(self, widget: Gtk.Widget) -> None: + pass # WM-Dekoration übernimmt die Titelleiste + + def add_bottom_bar(self, widget: Gtk.Widget) -> None: + self.append(widget) + + def set_content(self, widget: Gtk.Widget) -> None: + self.append(widget) + + def get_content(self) -> Gtk.Widget | None: + return self.get_last_child() diff --git a/src/taninux/gui/ui/shell.py b/src/taninux/gui/ui/shell.py new file mode 100644 index 0000000..3735c3f --- /dev/null +++ b/src/taninux/gui/ui/shell.py @@ -0,0 +1,134 @@ +"""Native Shell — Sidebar + Content, libadwaita-frei, Apple-Optik. + +API-gleicher Ersatz für taninux.gui.window.MainWindow (gleicher Konstruktor, +gleiches Verhalten: Sektions-Sidebar mit Suche, lazy Content-Stack). Ersetzt +Adw.ApplicationWindow / NavigationSplitView / ToolbarView / HeaderBar / +NavigationPage / WindowTitle durch eigene GTK4-Container. + +Die responsive Zuklapp-Mechanik von Adw.NavigationSplitView ist hier (noch) +ein fester Zwei-Spalten-Split — wie macOS System Settings am Desktop. Narrow/ +Collapse kommt später, falls nötig. +""" +from __future__ import annotations + +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk # noqa: E402 + +from taninux.gui import config, style +from taninux.gui.pages import SECTIONS +from taninux.gui.ui import native + + +class MainWindow(Gtk.ApplicationWindow): + def __init__(self, app, sections=None, title: str = "TANINUX") -> None: # noqa: ANN001 + super().__init__(application=app, title=title) + self.set_default_size(960, 680) + + native.ensure_css() + native.set_dark(self, style._resolve_dark(config.load()["color_scheme"])) + + self._sections = sections if sections is not None else SECTIONS + self._pages = [p for s in self._sections for p in s.pages] + self._app_title = title + self._rows: dict[str, Gtk.ListBoxRow] = {} + self._built: set[str] = set() + self._query = "" + + self._stack = Gtk.Stack(hexpand=True, vexpand=True) + self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) + + # Content-Titel lebt in der Fenster-HeaderBar (statt Adw.WindowTitle). + self._title = Gtk.Label(label=title) + self._title.add_css_class("tn-content-title") + header = Gtk.HeaderBar() + header.set_title_widget(self._title) + self.set_titlebar(header) + + split = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + split.add_css_class("tn-shell") + split.append(self._build_sidebar()) + sep = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) + split.append(sep) + split.append(self._stack) + self.set_child(split) + + if self._pages: + self._listbox.select_row(self._rows[self._pages[0].key]) + + # -- Sidebar ------------------------------------------------------- + def _build_sidebar(self) -> Gtk.Widget: + self._listbox = Gtk.ListBox() + self._listbox.add_css_class("tn-nav") + self._listbox.set_selection_mode(Gtk.SelectionMode.SINGLE) + self._listbox.connect("row-selected", self._on_row_selected) + self._listbox.set_filter_func(self._filter_row) + + for section in self._sections: + if section.title: + self._listbox.append(self._header_row(section.title)) + for page in section.pages: + row = Gtk.ListBoxRow() + row._page = page # type: ignore[attr-defined] + box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + box.append(Gtk.Image.new_from_icon_name(page.icon)) + box.append(Gtk.Label(label=page.label, xalign=0.0)) + row.set_child(box) + self._rows[page.key] = row + self._listbox.append(row) + + scroller = Gtk.ScrolledWindow(vexpand=True) + scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroller.set_child(self._listbox) + + search = Gtk.SearchEntry() + search.set_placeholder_text("Search settings") + search.add_css_class("tn-search") + search.set_margin_top(8) + search.set_margin_bottom(4) + search.set_margin_start(8) + search.set_margin_end(8) + search.connect("search-changed", self._on_search) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + box.add_css_class("tn-sidebar") + box.append(search) + box.append(scroller) + return box + + def _header_row(self, title: str) -> Gtk.ListBoxRow: + row = Gtk.ListBoxRow() + row.set_selectable(False) + row.set_activatable(False) + label = Gtk.Label(label=title, xalign=0.0) + label.add_css_class("tn-nav-header") + row.set_child(label) + return row + + # -- Suche --------------------------------------------------------- + def _on_search(self, entry: Gtk.SearchEntry) -> None: + self._query = entry.get_text().strip().lower() + self._listbox.invalidate_filter() + + def _filter_row(self, row: Gtk.ListBoxRow) -> bool: + page = getattr(row, "_page", None) + if page is None: # Sektions-Header + return not self._query + if not self._query: + return True + hay = (page.label + " " + " ".join(page.keywords)).lower() + return self._query in hay + + # -- Navigation ---------------------------------------------------- + def _on_row_selected(self, _box: Gtk.ListBox, row: Gtk.ListBoxRow | None) -> None: + page = getattr(row, "_page", None) if row is not None else None + if page is not None: + self._select(page) + + def _select(self, page) -> None: # noqa: ANN001 + if page.key not in self._built: + self._stack.add_named(page.build(), page.key) + self._built.add(page.key) + self._stack.set_visible_child_name(page.key) + self._title.set_label(page.label) diff --git a/src/taninux/gui/ui/style_apple.css b/src/taninux/gui/ui/style_apple.css new file mode 100644 index 0000000..91b17f3 --- /dev/null +++ b/src/taninux/gui/ui/style_apple.css @@ -0,0 +1,442 @@ +/* TANINUX UI — eigene Design-Sprache, Apple-Optik (macOS System Settings). + * + * libadwaita-frei: gilt nur für die nativen ui.* -Widgets (.tn-*). Bewusst + * lokal gehalten, damit es Adwaita-Seiten nicht stört, solange beide Backends + * parallel laufen. Karim tweakt hier visuell — Werte sind absichtlich an einer + * Stelle gebündelt. + * + * Hell ist Default; Dunkel hängt an window.tn-dark (vom Code gesetzt). + */ + +/* --------------------------------------------------------- Window / Fläche */ +.tn-page { + background-color: #f6f6f6; +} +.tn-page, +.tn-title, +.tn-row, +.tn-group-title { + font-family: -apple-system, "SF Pro Text", "Inter", "Cantarell", sans-serif; +} + +/* ----------------------------------------------------------------- Gruppe */ +/* Kleiner, sekundärer Header über der Card — macOS-Ventura-Stil (kein Caps). */ +.tn-group-title { + font-size: 0.92em; + font-weight: 600; + color: rgba(60, 60, 67, 0.65); + margin: 0 10px 2px; +} +.tn-group-desc { + font-size: 0.85em; + color: rgba(60, 60, 67, 0.55); + margin: 2px 10px 0; +} + +/* ------------------------------------------------------------------- Card */ +.tn-card { + background-color: #ffffff; + border-radius: 14px; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.07), + 0 4px 12px rgba(0, 0, 0, 0.05); +} +.tn-card > list, +.tn-list { + background: transparent; + border-radius: 14px; +} + +/* ------------------------------------------------------------------- Zeile */ +.tn-row { + padding: 9px 16px; + min-height: 26px; + background: transparent; + transition: background-color 120ms ease; +} +.tn-list > row:hover > .tn-row { + background-color: rgba(0, 0, 0, 0.035); +} +/* Hairline-Trenner zwischen Zeilen, dezent eingerückt (Apple-Inset). */ +.tn-list > row:not(:last-child) > .tn-row { + border-bottom: 1px solid rgba(60, 60, 67, 0.13); +} +/* Erste/letzte Zeile folgen der Card-Rundung, damit Hover nicht übersteht. */ +.tn-list > row:first-child > .tn-row { + border-top-left-radius: 14px; + border-top-right-radius: 14px; +} +.tn-list > row:last-child > .tn-row { + border-bottom-left-radius: 14px; + border-bottom-right-radius: 14px; +} + +.tn-title { + color: #1d1d1f; + font-weight: 500; +} +.tn-subtitle { + font-size: 0.84em; + color: rgba(60, 60, 67, 0.6); +} + +/* Toggle folgt dem Fuji-Akzent (@accent_color kommt aus gui/style.py; im Demo- + * Fall liefert ensure_css einen Fallback-Hue). So bleibt die Optik *eure*. */ +.tn-row switch:checked { + background-color: @accent_color; +} +.tn-row button { + border-radius: 999px; +} +.tn-row button.flat { + color: @accent_color; + font-weight: 500; +} +.tn-row button.flat:hover { + background-color: alpha(@accent_color, 0.10); +} +.tn-row button.destructive-action { + color: #ff3b30; + font-weight: 500; +} +.tn-row button.destructive-action:hover { + background-color: rgba(255, 59, 48, 0.10); +} + +/* Entry/Spin rahmenlos & rechtsbündig, Wert in Sekundärfarbe (wie iOS). */ +.tn-row entry, +.tn-row spinbutton, +.tn-row spinbutton text { + background: transparent; + border: none; + box-shadow: none; + min-height: 0; + color: rgba(60, 60, 67, 0.6); +} +.tn-row entry:focus-within, +.tn-row spinbutton:focus-within { + color: #1d1d1f; +} +/* DropDown als ruhiger, runder Button statt voller Combobox. */ +.tn-row dropdown > button { + background: transparent; + box-shadow: none; + border: none; + color: rgba(60, 60, 67, 0.7); +} +.tn-row dropdown > button:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* ============================================== KARTEN & TYPOGRAFIE (native) + * Ersetzt die libadwaita-Klassen, auf die dashboard.py & Co. bauen, damit die + * Seiten ohne libadwaita korrekt aussehen. (Im adw-Backend liefert libadwaita + * diese Klassen; dann wird dieses Stylesheet gar nicht geladen.) */ +.card { + background-color: #ffffff; + border-radius: 14px; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.07), + 0 4px 12px rgba(0, 0, 0, 0.05); +} +.title-2 { + font-size: 1.7em; + font-weight: 700; +} +.caption-heading { + font-size: 0.82em; + font-weight: 700; +} +.caption { + font-size: 0.82em; +} +.accent { + color: @accent_color; +} + +/* =================================================================== SHELL */ +/* Sidebar = schwebendes Glaspanel, kein harter Separator nötig. */ +.tn-shell > separator { + background-color: transparent; + min-width: 0; +} +.tn-sidebar { + background-color: #ebebeb !important; + border-right: 1px solid rgba(0, 0, 0, 0.07); + min-width: 232px; +} +headerbar { + background-color: #ebebeb !important; + background: #ebebeb !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + box-shadow: none !important; +} +.tn-search { + border-radius: 999px; + background-color: rgba(0, 0, 0, 0.06); + border: none; + box-shadow: none; +} +.tn-search:focus { + background-color: rgba(255, 255, 255, 0.80); + box-shadow: 0 0 0 2px @accent_color; +} +.tn-nav { + background: transparent; + padding: 4px 0; +} +.tn-nav > row { + margin: 2px 10px; + border-radius: 14px; + min-height: 0; +} +.tn-nav > row > box { + padding: 8px 10px; +} +.tn-nav > row:hover { + background-color: rgba(0, 0, 0, 0.05); +} +/* Hell: solider Akzent-Pill (macOS System Settings) */ +.tn-nav > row:selected { + background-color: @accent_color; + box-shadow: + 0 2px 8px alpha(@accent_color, 0.40), + inset 0 1px 0 rgba(255, 255, 255, 0.28); +} +.tn-nav > row:selected label, +.tn-nav > row:selected image { + color: #ffffff; +} +/* Nicht-wählbare Sektions-Überschrift im Sidebar (macOS-klein, grau). */ +.tn-nav-header { + font-size: 0.78em; + font-weight: 700; + color: rgba(60, 60, 67, 0.5); + margin: 12px 12px 2px; +} +.tn-content-title { + font-weight: 600; +} + +/* Explizite Hell-Farben: GTK4 Adwaita kann beim Systemstart noch dunkel sein. */ +window.tn:not(.tn-dark) { + color: #1d1d1f; +} +window.tn:not(.tn-dark) headerbar, +window.tn:not(.tn-dark) headerbar label, +window.tn:not(.tn-dark) headerbar button { + color: #1d1d1f; +} +window.tn:not(.tn-dark) .tn-nav > row label, +window.tn:not(.tn-dark) .tn-nav > row image { + color: #1d1d1f; +} +window.tn:not(.tn-dark) .tn-nav-header { + color: rgba(60, 60, 67, 0.5); +} +window.tn:not(.tn-dark) .tn-group-title { + color: rgba(60, 60, 67, 0.65); +} +window.tn:not(.tn-dark) .tn-group-desc { + color: rgba(60, 60, 67, 0.55); +} + +/* ------------------------------------------- Suggested-action + Progress */ +/* GTK4 Adwaita nutzt @blue_3 für suggested-action; wir überschreiben mit dem + * Fuji-Akzent. !important schlägt Adwaita-Interna bei gleicher Priorität. */ +button.suggested-action, +button.suggested-action:not(:disabled) { + background-color: @accent_color !important; + background: @accent_color !important; + color: @accent_fg_color !important; + box-shadow: none !important; +} +button.suggested-action:hover { + background-color: shade(@accent_color, 0.92) !important; + background: shade(@accent_color, 0.92) !important; +} +button.suggested-action:active { + background-color: shade(@accent_color, 0.85) !important; + background: shade(@accent_color, 0.85) !important; +} + +/* Progress-Bars (Storage, Memory, CPU…) — Fuji-Akzent statt Adwaita-Blau */ +progress, trough > progress, progressbar > trough > progress { + background-color: @accent_color !important; + background: @accent_color !important; +} + +/* =================================================================== DUNKEL */ +window.tn-dark .tn-page { + background-color: #0f0f0f; +} +/* Sidebar + Topbar so dunkel wie die eww-Panels ($bg #0f0f0f), satt & opak. */ +window.tn-dark headerbar { + background-color: #0f0f0f !important; + background: #0f0f0f !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important; + box-shadow: none !important; +} +window.tn-dark .tn-sidebar { + background-color: #0f0f0f !important; + border-right-color: rgba(255, 255, 255, 0.06); +} +window.tn-dark .tn-search { + background-color: rgba(255, 255, 255, 0.06); +} +window.tn-dark .tn-search:focus { + background-color: rgba(255, 255, 255, 0.10); +} +window.tn-dark .tn-nav > row:hover { + background-color: rgba(255, 255, 255, 0.06); + box-shadow: none; +} +/* Dunkel: Akzent-Wash + Border (wie CC-Tiles) statt Solid-Block */ +window.tn-dark .tn-nav > row:selected { + background-color: alpha(@accent_color, 0.18); + border: 1px solid alpha(@accent_color, 0.32); + box-shadow: + 0 2px 8px rgba(0, 0, 0, 0.35), + inset 0 1px 0 rgba(255, 255, 255, 0.10); +} +window.tn-dark .tn-nav > row:selected label, +window.tn-dark .tn-nav > row:selected image { + color: @accent_color; +} +window.tn-dark .tn-nav-header { + color: rgba(235, 235, 245, 0.45); +} +window.tn-dark .tn-card { + background-color: rgba(44, 44, 46, 0.88); + border-color: rgba(255, 255, 255, 0.07); + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.40), + 0 8px 28px rgba(0, 0, 0, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.09); /* Glasrand */ +} +window.tn-dark .card { + background-color: rgba(44, 44, 46, 0.88); + border-color: rgba(255, 255, 255, 0.07); + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.40), + 0 8px 28px rgba(0, 0, 0, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.09); +} +window.tn-dark .tn-list > row:hover > .tn-row { + background-color: rgba(255, 255, 255, 0.05); +} +window.tn-dark .tn-list > row:not(:last-child) > .tn-row { + border-bottom-color: rgba(255, 255, 255, 0.08); +} +window.tn-dark .tn-title { + color: #f5f5f7; +} +window.tn-dark .tn-subtitle { + color: rgba(235, 235, 245, 0.55); +} +window.tn-dark .tn-group-title { + color: rgba(235, 235, 245, 0.55); +} +window.tn-dark .tn-group-desc { + color: rgba(235, 235, 245, 0.45); +} +window.tn-dark .tn-row entry, +window.tn-dark .tn-row spinbutton, +window.tn-dark .tn-row spinbutton text, +window.tn-dark .tn-row dropdown > button { + color: rgba(235, 235, 245, 0.55); +} +window.tn-dark .tn-row entry:focus-within, +window.tn-dark .tn-row spinbutton:focus-within { + color: #f5f5f7; +} +window.tn-dark .tn-row dropdown > button:hover { + background-color: rgba(255, 255, 255, 0.07); +} +window.tn-dark .tn-row button.flat { + color: @accent_color; +} +window.tn-dark .tn-row button.destructive-action { + color: #ff453a; +} + +/* ================================================================= STATUS PAGE */ +.tn-status-page { + padding: 36px 24px; +} +.tn-status-icon { + color: rgba(60, 60, 67, 0.35); + -gtk-icon-size: 96px; +} +window.tn-dark .tn-status-icon { + color: rgba(235, 235, 245, 0.30); +} + +/* ================================================================= EXPANDER ROW */ +/* Unterzeilen leicht eingerückt und durch obere Linie vom Header getrennt. */ +.tn-expander-list { + border-top: 1px solid rgba(60, 60, 67, 0.10); + margin-left: 16px; +} +.tn-expander-chevron { + color: rgba(60, 60, 67, 0.45); + transition: transform 150ms ease; +} +window.tn-dark .tn-expander-list { + border-top-color: rgba(255, 255, 255, 0.08); +} +window.tn-dark .tn-expander-chevron { + color: rgba(235, 235, 245, 0.40); +} + +/* ================================================================= AVATAR */ +/* GTK4 unterstützt overflow:hidden nicht in CSS — Bild-Clipping läuft über + * den Cairo-Pfad in Avatar._draw(); hier nur Hintergrund + Radius für + * die Initialen-Variante. */ +.tn-avatar { + background-color: @accent_color; + border-radius: 50%; +} +.tn-avatar-initials { + color: #ffffff; + font-weight: 600; + font-size: 1.1em; +} + +/* ================================================================= ALERT DIALOG */ +.tn-alert-dialog { + border-radius: 14px; +} +.tn-alert-box { + background-color: #f2f2f7; + border-radius: 14px; + padding: 24px 20px 20px; + min-width: 300px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); +} +.tn-alert-heading { + font-size: 1.05em; + font-weight: 600; + color: #1d1d1f; + margin-bottom: 4px; +} +.tn-alert-body { + font-size: 0.92em; + color: rgba(60, 60, 67, 0.75); + margin-bottom: 4px; +} +.tn-alert-buttons { + margin-top: 16px; +} +window.tn-dark .tn-alert-box { + background-color: #2c2c2e; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45); +} +window.tn-dark .tn-alert-heading { + color: #f5f5f7; +} +window.tn-dark .tn-alert-body { + color: rgba(235, 235, 245, 0.65); +} diff --git a/ui_demo.py b/ui_demo.py new file mode 100644 index 0000000..84a689f --- /dev/null +++ b/ui_demo.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Visuelle Demo der nativen ui.* -Widgets (Apple-Optik, libadwaita-frei). + + PYTHONPATH=src python ui_demo.py # hell + PYTHONPATH=src python ui_demo.py --dark # dunkel + +Baut bewusst dieselben Zeilen-Typen nach, die maintain.py / defaultapps.py über +Adw nutzen (Titel/Untertitel, Prefix-Icon, farbiger Punkt, Switch, Dropdown, +flat-Buttons) — damit der Schnitt 1:1 vergleichbar ist. +""" +from __future__ import annotations + +import subprocess +import sys + +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import GLib, Gtk # noqa: E402 + +from taninux.gui.ui import native as ui # noqa: E402 + +DARK = "--dark" in sys.argv +SHOT = next((a for a in sys.argv if a.startswith("--shot=")), None) +SHOT = SHOT.split("=", 1)[1] if SHOT else None +TOGGLE = next((a for a in sys.argv if a.startswith("--toggle=")), None) +TOGGLE = TOGGLE.split("=", 1)[1] if TOGGLE else None + + +def _dot(css_class: str) -> Gtk.Label: + dot = Gtk.Label(label="●") + dot.add_css_class(css_class) + dot.set_valign(Gtk.Align.CENTER) + return dot + + +def _flat(label: str, destructive: bool = False) -> Gtk.Button: + btn = Gtk.Button(label=label) + btn.add_css_class("flat") + if destructive: + btn.add_css_class("destructive-action") + btn.set_valign(Gtk.Align.CENTER) + return btn + + +def build_page() -> ui.Page: + page = ui.Page() + + # --- Gruppe 1: Cleanup-Summary (wie maintain.py) --------------------- + g1 = ui.Group(title="Cleanup") + rescan = Gtk.Button(icon_name="view-refresh-symbolic") + rescan.add_css_class("flat") + rescan.set_valign(Gtk.Align.CENTER) + g1.set_header_suffix(rescan) + + r = ui.Row(title="Reclaimable", subtitle="2.4 GiB can be reclaimed safely") + r.add_prefix(_dot("success")) + g1.add(r) + + legend = ui.Row(title="Legend") + box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=14) + for cls, txt in (("success", "safe"), ("warning", "likely orphan"), + ("dim-label", "unknown")): + item = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + item.append(_dot(cls)) + lbl = Gtk.Label(label=txt) + lbl.add_css_class("dim-label") + item.append(lbl) + box.append(item) + legend.add_suffix(box) + g1.add(legend) + page.add(g1) + + # --- Gruppe 2: Findings mit flat-Buttons ---------------------------- + g2 = ui.Group(title="Orphan packages", + description="6 findings · 410 MiB reclaimable") + for name, sub, cls in ( + ("python-debug-symbols", "safe · 182 MiB", "success"), + ("lib32-mesa-demos", "likely orphan · 96 MiB", "warning"), + ("ttf-ancient-fonts", "unknown · 132 MiB", "dim-label"), + ): + row = ui.Row(title=name, subtitle=sub) + row.add_prefix(_dot(cls)) + row.add_suffix(_flat("Keep")) + row.add_suffix(_flat("Trash", destructive=True)) + g2.add(row) + page.add(g2) + + # --- Gruppe 3: echte Control-Rows (Switch/Combo/Entry/Spin) --------- + g3 = ui.Group(title="Default applications") + + sw = ui.SwitchRow(title="Empty Trash automatically", + subtitle="Remove items older than 30 days") + sw.set_active(True) + g3.add(sw) + + combo = ui.ComboRow(title="Web Browser") + combo.add_prefix(Gtk.Image.new_from_icon_name("web-browser-symbolic")) + combo.set_model(["Firefox", "Chromium", "Zen"]) + g3.add(combo) + + entry = ui.EntryRow(title="Hostname") + entry.set_text("taninux") + g3.add(entry) + + spin = ui.SpinRow(title="Keep snapshots") + spin.set_range(1, 30) + spin.set_value(7) + g3.add(spin) + + page.add(g3) + + return page + + +def _override_toggle(hex_color: str) -> None: + """Optionaler Provider: Toggle-Farbe überschreiben (z.B. Fuji-Akzent).""" + from gi.repository import Gdk + + provider = Gtk.CssProvider() + provider.load_from_data( + f".tn-row switch:checked {{ background-color: {hex_color}; }}".encode() + ) + Gtk.StyleContext.add_provider_for_display( + Gdk.Display.get_default(), provider, + Gtk.STYLE_PROVIDER_PRIORITY_USER + 1, + ) + + +def on_activate(app: Gtk.Application) -> None: + ui.ensure_css() + if TOGGLE: + _override_toggle(TOGGLE) + win = Gtk.ApplicationWindow(application=app, title="TANINUX · ui demo") + win.set_default_size(720, 720) + ui.set_dark(win, DARK) + + header = Gtk.HeaderBar() + win.set_titlebar(header) + win.set_child(build_page()) + win.present() + + if SHOT: + win.fullscreen() + + def _capture() -> bool: + subprocess.run(["grim", SHOT], check=False) + app.quit() + return False + + GLib.timeout_add(800, _capture) + + +def main() -> int: + app = Gtk.Application(application_id="ch.gabrielevarano.Taninux.UiDemo") + app.connect("activate", on_activate) + return app.run([]) + + +if __name__ == "__main__": + raise SystemExit(main())