This is a small example on how to draw some text, on a X11 Desktop, with Python and XLib. Nothing fancy... the script can output any text given in the command line and also use colors and a font you prefer. I made it, as a way to display info on the desktop, even when using fullscreen applications like mpv
, but after i made it, i realized, that you can do the same with notify-send
and even dmenu
. But still, some may find it useful.
If no text is given in the command line, it will display the current time, for 5 sec and exit. Feel free to customize the script as you like.
Da script...
#!/usr/bin/python3
from Xlib import X, display
import time
import argparse
from datetime import datetime
def main():
# Add argument parsing
parser = argparse.ArgumentParser(description='Display text using X11')
parser.add_argument('--text', help='Text to display')
parser.add_argument('--fg-color', default='white',
help='Text color (white/black or hex color like #FFBB00)')
parser.add_argument('--bg-color', default='black',
help='Background color (white/black or hex color like #000000)')
parser.add_argument('--delay', type=int, default=3,
help='Display for n seconds')
parser.add_argument('--font', type=str,
default='-misc-fixed-medium-r-normal--13-120-75-75-c-70-iso8859-1',
help='X11 font name')
args = parser.parse_args()
#Update text to show current time if no text argument provided
#print(args.text)
if not args.text:
current_time = datetime.now().strftime('%H:%M:%S')
text = current_time.encode()
else:
text = args.text.encode()
# Connect to the X server
# Connect to the X server
disp = display.Display()
screen = disp.screen()
root = screen.root
# Load font first to calculate text dimensions
try:
font = disp.open_font(args.font)
#print(f"Successfully loaded font")
except Exception as e:
#print(f"Error loading font: {e}")
return
# Calculate text dimensions before creating window
text_extents = font.query_text_extents(text)
text_width = text_extents.overall_width
text_height = text_extents.font_ascent + text_extents.font_descent
# Add padding to the window size
padding_x = 20 # 10px padding on each side
padding_y = 20 # 10px padding on top and bottom
width = text_width + padding_x
height = text_height + padding_y
# Calculate position for top right corner
screen_width = screen.width_in_pixels
x_pos = screen_width - width - 10
y_pos = 10
# Create window with calculated dimensions
win = root.create_window(
x_pos, y_pos, width, height, 0,
screen.root_depth,
X.InputOutput,
X.CopyFromParent,
override_redirect=True
)
# Set window type to remove borders
win.change_property(
disp.intern_atom("_NET_WM_WINDOW_TYPE"),
disp.intern_atom("ATOM"), 32,
[disp.intern_atom("_NET_WM_WINDOW_TYPE_DIALOG")]
)
win.map()
def parse_color(color_str):
if color_str.lower() == 'white':
return disp.screen().white_pixel
elif color_str.lower() == 'black':
return disp.screen().black_pixel
elif color_str.startswith('#'):
# Convert hex color to RGB values
r = int(color_str[1:3], 16) << 16
g = int(color_str[3:5], 16) << 8
b = int(color_str[5:7], 16)
return r | g | b
return disp.screen().black_pixel
fg_pixel = parse_color(args.fg_color)
bg_pixel = parse_color(args.bg_color)
gc = win.create_gc(
foreground=fg_pixel,
background=bg_pixel
)
gc.font = font.id
#Get text dimensions
text_extents = font.query_text_extents(text)
text_width = text_extents.overall_width
text_height = text_extents.font_ascent + text_extents.font_descent
# Update text position calculation
x = padding_x // 2 # Center horizontally with padding
y = (height + text_height) // 2 # Center vertically
win.change_attributes(background_pixel=bg_pixel)
win.clear_area(0, 0, width, height)
# Draw text at calculated center position
win.poly_text(gc, x, y, [(0, text)])
# Flush changes
disp.flush()
disp.sync()
# Keep the window open for 5 seconds
try:
time.sleep(args.delay)
finally:
win.destroy()
if __name__ == "__main__":
main()