@user79758, animosd.py
, -: http://hefesto.intra.ial.sp.gov.br/share/pyshared/quodlibet/plugins./events/animosd.py ().
, quodlibet
, Ubuntu 18.04;
sudo apt install quodlibet
Then it is not entirely trivial to get a minimal example; mine is included below as animosd_test.py
; just run it with:
python2 animosd_test.py
You will get this:

animosd_test.py
:
import sys
import os
from collections import namedtuple
from quodlibet.packages.senf import environ, argv as sys_argv
from quodlibet.cli import process_arguments, exit_
import quodlibet
try:
assert "gi.repository.Gtk" not in sys.modules
sys.modules["gi.repository.Gtk"] = None
startup_actions, cmds_todo = process_arguments(sys_argv)
finally:
sys.modules.pop("gi.repository.Gtk", None)
quodlibet.init()
from quodlibet.ext.events.animosd import osdwindow
from quodlibet.ext.events.animosd.config import get_config
from quodlibet.ext.events.animosd.osdwindow import OSDWindow
from quodlibet.util import cached_property, print_exc
from gi.repository import Gtk, GObject, GLib, Pango, PangoCairo, Gdk
from quodlibet import qltk
import cairo
from math import pi
class OSDWindowMin(Gtk.Window):
__gsignals__ = {
'fade-finished': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
}
POS_X = 0.5
"""position of window 0--1 horizontal"""
MARGIN = 50
"""never any closer to the screen edge than this"""
BORDER = 20
"""text/cover this far apart, from edge"""
FADETIME = 0.3
"""take this many seconds to fade in or out"""
MS = 40
"""wait this many milliseconds between steps"""
@cached_property
def Conf(self):
return get_config('animosd')
def __init__(self):
Gtk.Window.__init__(self, Gtk.WindowType.POPUP)
self.set_type_hint(Gdk.WindowTypeHint.NOTIFICATION)
screen = self.get_screen()
rgba = screen.get_rgba_visual()
if rgba is not None:
self.set_visual(rgba)
self.conf = self.Conf
self.iteration_source = None
self.fading_in = False
self.fade_start_time = 0
mgeo = screen.get_monitor_geometry(self.conf.monitor)
textwidth = mgeo.width - 2 * (self.BORDER + self.MARGIN)
scale_factor = self.get_scale_factor()
coverheight = 0
coverwidth = 0
self.cover_surface = None
layout = self.create_pango_layout('')
layout.set_alignment((Pango.Alignment.LEFT, Pango.Alignment.CENTER,
Pango.Alignment.RIGHT)[self.conf.align])
layout.set_spacing(Pango.SCALE * 7)
layout.set_font_description(Pango.FontDescription(self.conf.font))
layout.set_markup("AAAA")
layout.set_width(Pango.SCALE * textwidth)
layoutsize = layout.get_pixel_size()
if layoutsize[0] < textwidth:
layout.set_width(Pango.SCALE * layoutsize[0])
layoutsize = layout.get_pixel_size()
self.title_layout = layout
winw = layoutsize[0] + 2 * self.BORDER
if coverwidth:
winw += coverwidth + self.BORDER
winh = max(coverheight, layoutsize[1]) + 2 * self.BORDER
self.set_default_size(winw, winh)
rect = namedtuple("Rect", ["x", "y", "width", "height"])
rect.x = self.BORDER
rect.y = (winh - coverheight) // 2
rect.width = coverwidth
rect.height = coverheight
self.cover_rectangle = rect
winx = int((mgeo.width - winw) * self.POS_X)
winx = max(self.MARGIN, min(mgeo.width - self.MARGIN - winw, winx))
winy = int((mgeo.height - winh) * self.conf.pos_y)
winy = max(self.MARGIN, min(mgeo.height - self.MARGIN - winh, winy))
self.move(winx + mgeo.x, winy + mgeo.y)
def do_draw(self, cr):
if self.is_composited():
self.draw_title_info(cr)
else:
walloc = self.get_allocation()
wpos = self.get_position()
if not getattr(self, "_bg_sf", None):
root_win = self.get_root_window()
bg_sf = cairo.ImageSurface(cairo.FORMAT_ARGB32,
walloc.width, walloc.height)
pb = Gdk.pixbuf_get_from_window(
root_win, wpos[0], wpos[1], walloc.width, walloc.height)
bg_cr = cairo.Context(bg_sf)
Gdk.cairo_set_source_pixbuf(bg_cr, pb, 0, 0)
bg_cr.paint()
self._bg_sf = bg_sf
if not getattr(self, "_fg_sf", None):
fg_sf = cairo.ImageSurface(cairo.FORMAT_ARGB32,
walloc.width, walloc.height)
fg_cr = cairo.Context(fg_sf)
fg_cr.set_source_surface(fg_sf)
self.draw_title_info(fg_cr)
self._fg_sf = fg_sf
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.set_source_surface(self._bg_sf)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
cr.set_source_surface(self._fg_sf)
cr.paint_with_alpha(self.get_opacity())
@staticmethod
def rounded_rectangle(cr, x, y, radius, width, height):
cr.move_to(x + radius, y)
cr.line_to(x + width - radius, y)
cr.arc(x + width - radius, y + radius, radius,
- 90.0 * pi / 180.0, 0.0 * pi / 180.0)
cr.line_to(x + width, y + height - radius)
cr.arc(x + width - radius, y + height - radius, radius,
0.0 * pi / 180.0, 90.0 * pi / 180.0)
cr.line_to(x + radius, y + height)
cr.arc(x + radius, y + height - radius, radius,
90.0 * pi / 180.0, 180.0 * pi / 180.0)
cr.line_to(x, y + radius)
cr.arc(x + radius, y + radius, radius,
180.0 * pi / 180.0, 270.0 * pi / 180.0)
cr.close_path()
@property
def corners_factor(self):
if self.conf.corners != 0:
return 0.14
return 0.0
def draw_conf_rect(self, cr, x, y, width, height, radius):
if self.conf.corners != 0:
self.rounded_rectangle(cr, x, y, radius, width, height)
else:
cr.rectangle(x, y, width, height)
def draw_title_info(self, cr):
cr.save()
do_shadow = (self.conf.shadow[0] != -1.0)
do_outline = (self.conf.outline[0] != -1.0)
self.set_name("osd_bubble")
qltk.add_css(self, """
#osd_bubble {
background-color:rgba(0,0,0,0);
}
""")
cr.set_operator(cairo.OPERATOR_OVER)
cr.set_source_rgba(*self.conf.fill)
radius = min(25, self.corners_factor * min(*self.get_size()))
self.draw_conf_rect(cr, 0, 0, self.get_size()[0],
self.get_size()[1], radius)
cr.fill()
if do_outline:
f = self.conf.fill
rgba = (f[0] / 1.25, f[1] / 1.25, f[2] / 1.25, f[3] / 2.0)
cr.set_source_rgba(*rgba)
self.draw_conf_rect(cr,
1, 1,
self.get_size()[0] - 2, self.get_size()[1] - 2,
radius)
cr.set_line_width(2.0)
cr.stroke()
textx = self.BORDER
if self.cover_surface is not None:
rect = self.cover_rectangle
textx += rect.width + self.BORDER
surface = self.cover_surface
transmat = cairo.Matrix()
if do_shadow:
cr.set_source_rgba(*self.conf.shadow)
self.draw_conf_rect(cr,
rect.x + 2, rect.y + 2,
rect.width, rect.height,
0.6 * self.corners_factor * rect.width)
cr.fill()
if do_outline:
cr.set_source_rgba(*self.conf.outline)
self.draw_conf_rect(cr,
rect.x, rect.y,
rect.width, rect.height,
0.6 * self.corners_factor * rect.width)
cr.stroke()
cr.set_source_surface(surface, 0, 0)
width, height = get_surface_extents(surface)[2:]
transmat.scale(width / float(rect.width),
height / float(rect.height))
transmat.translate(-rect.x, -rect.y)
cr.get_source().set_matrix(transmat)
self.draw_conf_rect(cr,
rect.x, rect.y,
rect.width, rect.height,
0.6 * self.corners_factor * rect.width)
cr.fill()
PangoCairo.update_layout(cr, self.title_layout)
height = self.title_layout.get_pixel_size()[1]
texty = (self.get_size()[1] - height) // 2
if do_shadow:
cr.set_source_rgba(*self.conf.shadow)
cr.move_to(textx + 2, texty + 2)
PangoCairo.show_layout(cr, self.title_layout)
if do_outline:
cr.set_source_rgba(*self.conf.outline)
cr.move_to(textx, texty)
PangoCairo.layout_path(cr, self.title_layout)
cr.stroke()
cr.set_source_rgb(*self.conf.text[:3])
cr.move_to(textx, texty)
PangoCairo.show_layout(cr, self.title_layout)
cr.restore()
def fade_in(self):
self.do_fade_inout(True)
def fade_out(self):
self.do_fade_inout(False)
def do_fade_inout(self, fadein):
fadein = bool(fadein)
self.fading_in = fadein
now = GObject.get_current_time()
fraction = self.get_opacity()
if not fadein:
fraction = 1.0 - fraction
self.fade_start_time = now - fraction * self.FADETIME
if self.iteration_source is None:
self.iteration_source = GLib.timeout_add(self.MS,
self.fade_iteration_callback)
def fade_iteration_callback(self):
delta = GObject.get_current_time() - self.fade_start_time
fraction = delta / self.FADETIME
if self.fading_in:
self.set_opacity(fraction)
else:
self.set_opacity(1.0 - fraction)
if not self.is_composited():
self.queue_draw()
if fraction >= 1.0:
self.iteration_source = None
self.emit('fade-finished', self.fading_in)
return False
return True
def __buttonpress(window, event):
window.hide()
window.destroy()
window = OSDWindowMin()
window.connect("destroy", Gtk.main_quit)
window.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
window.connect('button-press-event', __buttonpress)
window.set_opacity(0.0)
window.show()
window.fade_in()
Gtk.main()