Skip to content

Instantly share code, notes, and snippets.

@s-zeid
Last active December 28, 2021 23:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s-zeid/52767b52d36cfa055d8681987ef2ea9b to your computer and use it in GitHub Desktop.
Save s-zeid/52767b52d36cfa055d8681987ef2ea9b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# vim: set fdm=marker:
import atexit
import os
import sys
from typing import *
class TransparentWindow: #{{{1
def __init__(self,
x: int, y: int, w: int, h: int,
*,
warn_if_unable: bool = True,
wm_class: str = None,
):
raise NotImplementedError
def _tick(self):
raise NotImplementedError
@property # is_transparent #{{{2
def is_transparent(self):
return self._is_transparent
def loop(self, inner=None): #{{{2
self._running = True
while self._running:
if not self.tick(inner=inner):
break
def stop(self): #{{{2
self._running = False
def tick(self, inner=None) -> bool: #{{{2
if inner:
inner()
return self._tick()
def __del__(self): #{{{2
pass
@classmethod # _x_match_visual_info_reimpl #{{{2
def _x_match_visual_info_reimpl(self,
_display: "Xlib.display.Display", # type: ignore
screen: "Xlib.display.Display.screen()", # type: ignore
visual_depth: int,
visual_class: int,
):
"""A reimplementation of `XMatchVisualInfo()`."""
visuals = None
for i in screen.allowed_depths:
if i["depth"] == visual_depth:
visuals = i["visuals"]
break
if not visuals:
raise RuntimeError(f"no depth {visual_depth} in screen.allowed_depths")
visual = None
for i in visuals:
if i["visual_class"] == visual_class:
visual = i
break
if not visual:
raise RuntimeError(f"no visual with class {visual_class} in depth {visual_depth}")
visual["_visual_depth"] = visual_depth
return visual
class TransparentSDLWindow(TransparentWindow): #{{{1
def __init__(self, #{{{2
x: int, y: int, w: int, h: int,
*,
warn_if_unable: bool = True,
flags: int = 0,
wm_class: str = None,
):
import sdl2 # type: ignore # pip3: PySDL2; apt: python3-sdl2; apk: sdl2, then use pip3
try:
import Xlib.X # type: ignore # pip3: python-xlib; apt: python3-xlib; apk: py3-xlib
import Xlib.Xatom # type: ignore
import Xlib.display # type: ignore
except ImportError:
Xlib = None # type: ignore
if os.environ.get("XAUTHORITY") and os.environ.get("DISPLAY"):
for line in [
"warning: cannot make X window transparent: python-xlib is not installed",
"hint: run `pip3 install python-xlib`, `apt install python3-xlib`,",
" or `apk add py3-xlib`",
]:
print(line, file=sys.stderr)
self._is_transparent = False
supported_syswm_types = []
# make window transparent on supported windowing systems
if Xlib and self._have_x():
supported_syswm_types += [sdl2.SDL_SYSWM_X11]
self.x_display = Xlib.display.Display()
self.x_screen = self.x_display.screen()
try:
visual = self._x_match_visual_info_reimpl(
self.x_display, self.x_screen,
32, Xlib.X.TrueColor,
)
self._is_transparent = True
except RuntimeError as exc:
if warn_if_unable:
print(f"warning: could not make window transparent: {exc}", file=sys.stderr)
visual = None
if visual:
os.environ["SDL_VIDEO_X11_VISUALID"] = str(visual["visual_id"])
if wm_class and "SDL_VIDEO_X11_WMCLASS" not in os.environ:
os.environ["SDL_VIDEO_X11_WMCLASS"] = wm_class
# XXX: do not call self._get_default_syswm_type() before this line;
# SDL_* environment variables set above must be present before SDL_Init() is called
syswm_type = self._get_default_syswm_type()
syswm_types = {
getattr(sdl2, attr): attr.replace("SDL_SYSWM_", "")
for attr in sdl2.__dict__.keys()
if attr.startswith("SDL_SYSWM_") and attr != "SDL_SYSWM_TYPE"
}
syswm_name = syswm_types.get(syswm_type, str(syswm_type))
if warn_if_unable and syswm_type not in supported_syswm_types:
print(
"warning: could not make window transparent: unsupported windowing system:",
syswm_name,
file=sys.stderr,
)
sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
atexit.register(sdl2.SDL_Quit)
self._tick_event = sdl2.SDL_Event()
forced_flags = sdl2.SDL_WINDOW_HIDDEN
if syswm_type == sdl2.SDL_SYSWM_X11:
sdl2.SDL_SetHint(sdl2.SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, b"0")
self.sdl_window = sdl2.SDL_CreateWindow(b"", x, y, w, h, flags | forced_flags)
self.sdl_window_id = sdl2.SDL_GetWindowID(self.sdl_window)
self.sdl_wm_info = sdl2.SDL_SysWMinfo()
sdl2.SDL_VERSION(self.sdl_wm_info.version)
sdl2.SDL_GetWindowWMInfo(self.sdl_window, self.sdl_wm_info)
if hasattr(self, "x_display") and self.sdl_wm_info.subsystem == sdl2.SDL_SYSWM_X11:
window_id = self.sdl_wm_info.info.x11.window
self.x_window = self.x_display.create_resource_object("window", window_id)
self._sdl2 = sdl2
def _tick(self): #{{{2
import sdl2 # type: ignore
if sdl2.SDL_PollEvent(self._tick_event) != 0:
if self._tick_event.type == sdl2.SDL_WINDOWEVENT:
if self._tick_event.window.event == sdl2.SDL_WINDOWEVENT_CLOSE:
if self._tick_event.window.windowID == self.sdl_window_id:
return False
if self._tick_event.type == sdl2.SDL_QUIT:
return False
return True
def __del__(self): #{{{2
self._sdl2.SDL_DestroyWindow(self.sdl_window)
super().__del__()
@classmethod # _get_default_syswm_type #{{{2
def _get_default_syswm_type(cls) -> int:
import sdl2 # type: ignore
sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
test_window = sdl2.SDL_CreateWindow(
b"TransparentSDLWindow test window",
0, 0,
1, 1,
sdl2.SDL_WINDOW_HIDDEN,
)
wm_info = sdl2.SDL_SysWMinfo()
sdl2.SDL_VERSION(wm_info.version)
sdl2.SDL_GetWindowWMInfo(test_window, wm_info)
sdl2.SDL_DestroyWindow(test_window)
return wm_info.subsystem
@classmethod # _have_x #{{{2
def _have_x(cls) -> bool:
try:
import Xlib.display # type: ignore
import Xlib.error # type: ignore
try:
test_display = Xlib.display.Display()
return True
except (Xlib.error.XError, Xlib.error.DisplayError):
pass
except ImportError:
pass
return False
class TransparentXWindow(TransparentWindow): #{{{1
def __init__(self, #{{{2
x: int, y: int, w: int, h: int,
display=None,
*,
warn_if_unable: bool = True,
wm_class: str = None,
wm_protocols: list = None,
**attributes,
):
import Xlib.X # type: ignore # pip3: python-xlib; apt: python3-xlib; apk: py3-xlib
import Xlib.Xatom # type: ignore
import Xlib.display # type: ignore
self.x_display = display or Xlib.display.Display()
self.x_screen = self.x_display.screen()
self.WM_PROTOCOLS = self.x_display.intern_atom("WM_PROTOCOLS")
self.WM_DELETE_WINDOW = self.x_display.intern_atom("WM_DELETE_WINDOW")
try:
visual = self._x_match_visual_info_reimpl(
self.x_display, self.x_screen,
32, Xlib.X.TrueColor,
)
self._is_transparent = True
except RuntimeError as exc:
if warn_if_unable:
print(f"warning: could not make window transparent: {exc}", file=sys.stderr)
visual = None
self._is_transparent = False
if visual:
depth = visual["_visual_depth"]
self._visual_id = visual_id = visual["visual_id"]
colormap = self.x_screen.root.create_colormap(visual_id, Xlib.X.AllocNone)
else:
depth = self.x_screen.root_depth
self._visual_id = visual_id = Xlib.X.CopyFromParent
colormap = Xlib.X.CopyFromParent
attributes["border_pixel"] = attributes.get("border_pixel", 0)
attributes["background_pixel"] = attributes.get("background_pixel", 0)
self.x_window = self.x_screen.root.create_window(
x, y, w, h,
0, # border_width
depth,
Xlib.X.InputOutput, # window_class
visual_id,
colormap=colormap,
**attributes,
)
self.x_window.set_wm_protocols([self.WM_DELETE_WINDOW] + (wm_protocols or []))
if wm_class is not None:
wm_class = wm_class.strip("\0")
if "\0" in wm_class:
wm_class_instance, wm_class = wm_class.split("\0", 1)
else:
wm_class_instance = wm_class
self.x_window.set_wm_class(wm_class_instance, wm_class)
_NET_WM_PID = self.x_display.intern_atom("_NET_WM_PID")
self.x_window.change_property(_NET_WM_PID, Xlib.Xatom.CARDINAL, 32, [os.getpid()])
def _tick(self) -> bool: #{{{2
import Xlib.X # type: ignore
pending_events = self.x_display.pending_events()
while pending_events > 0:
event = self.x_display.next_event()
if event.type == Xlib.X.DestroyNotify:
return False
elif event.type == Xlib.X.ClientMessage:
if event.client_type == self.WM_PROTOCOLS:
format, data = event.data
if format == 32 and data[0] == self.WM_DELETE_WINDOW:
return False
pending_events -= 1
return True
if __name__ == "__main__": #{{{1
t: TransparentWindow
kind = sys.argv[1] if len(sys.argv) > 1 else "sdl-renderer"
kinds = {
"sdl-renderer": "SDL (with renderer)",
"sdl-surface": "SDL (with surface)",
"x": "X",
}
kind_name = kinds.get(kind, kind)
print(f"making a transparent {kind_name} window", file=sys.stderr)
if False:
pass
elif kind in ("sdl-renderer", "sdl-surface"): #{{{2
import sdl2
wm_name = "Transparent SDL Window"
wm_class = "transparentsdlwindow"
t = TransparentSDLWindow(
x=sdl2.SDL_WINDOWPOS_CENTERED, y=sdl2.SDL_WINDOWPOS_CENTERED,
w=400, h=200,
wm_class=wm_class,
)
sdl2.SDL_SetWindowTitle(t.sdl_window, wm_name.encode("utf-8"))
sdl2.SDL_ShowWindow(t.sdl_window)
rect = sdl2.SDL_Rect(x=0, y=0, w=200, h=200)
if kind == "sdl-renderer":
renderer = sdl2.SDL_CreateRenderer(t.sdl_window, -1, 0)
sdl2.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 127)
sdl2.SDL_RenderFillRect(renderer, rect)
sdl2.SDL_RenderPresent(renderer)
if kind == "sdl-surface":
surface = sdl2.SDL_GetWindowSurface(t.sdl_window)
surface_format = surface.contents.format
sdl2.SDL_FillRect(surface, rect, sdl2.SDL_MapRGBA(surface_format, 0, 0, 0, 127))
sdl2.SDL_UpdateWindowSurface(t.sdl_window)
print(
"window's pixel format:",
sdl2.SDL_GetPixelFormatName(sdl2.SDL_GetWindowPixelFormat(t.sdl_window))
.decode("utf-8"),
file=sys.stderr,
)
t.loop(inner=None)
if kind == "sdl-renderer":
sdl2.SDL_DestroyRenderer(renderer)
if kind == "sdl-surface":
sdl2.SDL_FreeSurface(surface)
elif kind == "x": #{{{2
import tkinter
import tkinter.ttk
wm_name = "Transparent X Window"
wm_class = "transparentxwindow"
t = TransparentXWindow(
x=0, y=0,
w=400, h=200,
wm_class=wm_class,
)
t.x_window.set_wm_name(wm_name)
t.x_window.map()
root = tkinter.Tk(use=str(t.x_window.id))
frame = tkinter.ttk.Frame(root, padding=100)
frame.grid()
# XXX: non-empty text leads to a BadValue X error:
#
# X Error of failed request: BadValue (integer parameter out of range for operation)
# Major opcode of failed request: 91 (X_QueryColors)
# Value in failed request: 0xff000000
# Serial number of failed request: [...]
# Current serial number in output stream: [...]
tkinter.ttk.Label(frame, text="").grid(column=0, row=0)
def tk_tick():
root.update_idletasks()
root.update()
t.loop(inner=tk_tick)
else: #{{{2
print("invalid kind argument:", kind, file=sys.stderr)
print("valid values:", ", ".join(kinds.keys()), file=sys.stderr)
sys.exit(2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment