roundabout,
created on Friday, 12 December 2025, 21:27:27 (1765574847),
received on Friday, 12 December 2025, 21:27:31 (1765574851)
Author identity: Vlad <vlad.muntoiu@gmail.com>
ca137dba898ba73db1ad3058459c737bf58983a1
applet-listing.ui
@@ -0,0 +1,38 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.96.3 -->
<interface>
<requires lib="gtk" version="4.18"/>
<template class="AppletListing" parent="GtkWindow">
<property name="title" translatable="yes">Applets</property>
<child>
<object class="GtkBox">
<property name="margin-bottom">16</property>
<property name="margin-end">16</property>
<property name="margin-start">16</property>
<property name="margin-top">16</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkViewport">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkFlowBox" id="flowbox">
<property name="activate-on-single-click">False</property>
<property name="hexpand">True</property>
<property name="homogeneous">True</property>
<property name="max-children-per-line">8</property>
<property name="vexpand">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>
applets/launchers/__init__.py
@@ -83,7 +83,8 @@ class DesktopFileButton(panorama_panel.Applet):
self.append(self.button)
def execute(self, button, *args):
self.app_info.launch()
if self.app_info:
self.app_info.launch()
def get_config(self):
return {"desktop_file": self.desktop_file, "app_id": self.app_id, "icon_size": self.icon_size}
config.yaml
@@ -64,6 +64,49 @@ panels:
- NotifierApplet:
icon_size: 24
empty_icon_size: 16
- AppMenu:
category_mappings:
Utility:
menu_name: Accessories
icon: applications-accessories
Development:
menu_name: Programming
icon: applications-development
Game:
menu_name: Games
icon: applications-games
Graphics:
menu_name: Graphics
icon: applications-graphics
Network:
menu_name: Network
icon: applications-internet
AudioVideo:
menu_name: Multimedia
icon: applications-multimedia
Office:
menu_name: Office
icon: applications-office
Science:
menu_name: Science
icon: applications-science
Education:
menu_name: Education
icon: applications-education
System:
menu_name: System
icon: applications-system
Settings:
menu_name: Settings
icon: preferences-desktop
Other:
menu_name: Other
icon: applications-other
trigger_name: app-menu
icon_name: start-here-symbolic
icon_size: 24
- ClockApplet:
formatting: '%c'
right:
- BatteryMonitor:
icon_size: 24
@@ -95,4 +138,8 @@ panels:
centre: []
right:
- FileListingApplet:
directory: "~"
directory: /home/vlad
icon_name:
icon_size: 24
label:
show_hidden: false
main.py
@@ -53,7 +53,7 @@ import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Gtk4LayerShell", "1.0")
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio, GObject
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio, GObject, Pango
from gi.events import GLibEventLoopPolicy
import ctypes
@@ -276,6 +276,50 @@ class PanelManager(Gtk.Window):
self.get_application().save_config()
class AppletChoice(Gtk.Box):
def __init__(self, applet_class, **kwargs):
super().__init__(**kwargs, orientation=Gtk.Orientation.VERTICAL)
self.label = Gtk.Label(label=applet_class.name, halign=Gtk.Align.CENTER, xalign=0.5, wrap_mode=Pango.WrapMode.WORD)
self.append(self.label)
self.AppletClass = applet_class
self.drag_source = Gtk.DragSource(actions=Gdk.DragAction.COPY)
self.add_controller(self.drag_source)
self.drag_source.connect("prepare", self.provide_drag_data)
self.drag_source.connect("drag-begin", self.drag_begin)
def provide_drag_data(self, source: Gtk.DragSource, x: float, y: float):
app = self.get_root().get_application()
print(self.AppletClass.name)
app.drags[id(self)] = self.AppletClass
value = GObject.Value()
value.init(GObject.TYPE_UINT64)
value.set_uint64(id(self))
print(value)
return Gdk.ContentProvider.new_for_value(value)
def drag_begin(self, source: Gtk.DragSource, drag: Gdk.Drag):
paintable = Gtk.WidgetPaintable.new(self).get_current_image()
source.set_icon(paintable, 0, 0)
@Gtk.Template(filename=str(module_directory / "applet-listing.ui"))
class AppletListing(Gtk.Window):
__gtype_name__ = "AppletListing"
flowbox: Gtk.FlowBox = Gtk.Template.Child()
def __init__(self, application: PanoramaPanel, **kwargs):
super().__init__(application=application, **kwargs)
self.connect("close-request", lambda *args: self.destroy())
for class_name, applet in sorted(application.applets_by_name.items(), key=lambda x: x[0]):
applet_widget = AppletChoice(applet)
self.flowbox.append(applet_widget)
def get_applet_directories():
data_home = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share"))
data_dirs = [Path(d) for d in os.getenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share").split(":")]
@@ -294,14 +338,25 @@ class AppletArea(Gtk.Box):
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL):
super().__init__()
self.drop_target = Gtk.DropTarget.new(GObject.TYPE_UINT64, Gdk.DragAction.MOVE)
self.drop_target = Gtk.DropTarget.new(GObject.TYPE_UINT64, Gdk.DragAction.MOVE | Gdk.DragAction.COPY)
self.drop_target.set_gtypes([GObject.TYPE_UINT64])
self.drop_target.connect("drop", self.drop_applet)
def drop_applet(self, drop_target: Gtk.DropTarget, value: int, x: float, y: float):
applet = self.get_root().get_application().drags[value]
print(value)
obj = self.get_root().get_application().drags[value]
if isinstance(obj, type):
applet = obj()
if self.get_root().get_application().edit_mode:
applet.make_draggable()
else:
applet.restore_drag()
applet.set_opacity(0.75 if value else 1)
else:
applet = obj
old_area: AppletArea = applet.get_parent()
old_area.remove(applet)
if old_area:
old_area.remove(applet)
# Find the position where to insert the applet
child = self.get_first_child()
while child:
@@ -408,6 +463,7 @@ class Panel(Gtk.Window):
menu = Gio.Menu()
menu.append(_("Open _manager"), "panel.manager")
menu.append(_("_Applets"), "panel.applets")
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
self.context_menu.set_has_arrow(False)
@@ -426,6 +482,9 @@ class Panel(Gtk.Window):
manager_action = Gio.SimpleAction.new("manager", None)
manager_action.connect("activate", self.show_manager)
action_group.add_action(manager_action)
applets_action = Gio.SimpleAction.new("applets", None)
applets_action.connect("activate", self.show_applets)
action_group.add_action(applets_action)
self.insert_action_group("panel", action_group)
def reset_margins(self):
@@ -475,6 +534,13 @@ class Panel(Gtk.Window):
self.get_application().manager_window.connect("close-request", self.get_application().reset_manager_window)
self.get_application().manager_window.present()
def show_applets(self, _0=None, _1=None):
if self.get_application():
if not self.get_application().applets_window:
self.get_application().applets_window = AppletListing(self.get_application())
self.get_application().applets_window.connect("close-request", self.get_application().reset_applets_window)
self.get_application().applets_window.present()
def get_orientation(self):
box = self.get_first_child()
return box.get_orientation()
@@ -684,6 +750,7 @@ class PanoramaPanel(Gtk.Application):
self.foreign_toplevel_manager = None
self.screencopy_manager = None
self.manager_window = None
self.applets_window = None
self.edit_mode = False
self.output_proxies = []
self.drags = {}
@@ -997,6 +1064,9 @@ class PanoramaPanel(Gtk.Application):
def reset_manager_window(self, *args):
self.manager_window = None
def reset_applets_window(self, *args):
self.applets_window = None
if __name__ == "__main__":
app = PanoramaPanel()
panel-manager.cmb
@@ -1,7 +1,8 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<!-- Created with Cambalache 0.96.1 -->
<!-- Created with Cambalache 0.96.3 -->
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
<ui template-class="PanelManager" filename="panel-manager.ui" sha256="df92559d8accc37e028b8179a4148ed28d5d3c9cf508c65a97d130668b3fc968"/>
<ui template-class="PanelConfigurator" filename="panel-configurator.ui" sha256="06dd4c9b4de9a20683b991a44b38f97b6668401e84466a2ebe057ddfbc31d924"/>
<ui template-class="AppletListing" filename="applet-listing.ui" sha256="2d30e2172e34e3a859b18aeeb562931496da25c60777e8a140c2d4973ea255c8"/>
</cambalache-project>
shared/panorama_panel.py
@@ -53,6 +53,8 @@ class Applet(Gtk.Box):
def drag_cancel(self, source: Gtk.DragSource, drag: Gdk.Drag, reason: Gdk.DragCancelReason):
self.show()
app = self.get_root().get_application()
del app.drags[id(self)]
return False
def get_config(self):