[Python] I am passing a window ID to a script that returns the window's icon, but it returns...

Discussão em 'Python' iniciado por Stack, Junho 22, 2025.

  1. Stack

    Stack Membro Participativo

    I have been working on a window manager and was able to get the icon from a X window via its ID. This script properly returns a Pillow Image object that I can use to get the icon of a window. However, trying to use this script in my window manager doesn't work properly. I pass the window's ID (I have confirmed that it is correct) to the script and it returns None. Here is the script I am using:

    import ctypes
    import os
    from PIL import Image
    import io
    class IconData(ctypes.Structure):
    _fields_ = [
    ("width", ctypes.c_ulong),
    ("height", ctypes.c_ulong),
    ("pixels", ctypes.POINTER(ctypes.c_ulong)),
    ("num_icons", ctypes.c_int),
    ]
    lib_paths = [
    os.path.join(os.path.dirname(__file__), "libgeticon.so"), # Linux
    os.path.join(os.path.dirname(__file__), "libgeticon.dylib"), # macOS (ewwww)
    os.path.join(os.path.dirname(__file__), "geticon.dll"), # Windows (EWWWWW)
    ]
    lib = None
    for path in lib_paths:
    if os.path.exists(path):
    try:
    lib = ctypes.CDLL(path)
    break
    except OSError as e:
    print(f"Warning: Could not load {path}: {e}")
    lib = None
    if lib is None:
    raise RuntimeError("Could not find or load libgeticon shared library. "
    "Please ensure it's compiled and in the same directory as xicon.py "
    "or in a system-wide library path.")
    lib.get_window_icon.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
    lib.get_window_icon.restype = ctypes.POINTER(IconData)
    lib.free_icon_data.argtypes = [ctypes.POINTER(IconData)]
    lib.free_icon_data.restype = None
    def get_window_icon(window_id: int) -> Image.Image | None:
    """
    Retrieves the icon for a given X Window ID.

    Args:
    window_id: The ID of the X Window (integer).

    Returns:
    A PIL.Image.Image object if an icon is found, otherwise None.
    The image will be in RGBA format.
    """
    dpy = ctypes.cdll.LoadLibrary('libX11.so.6').XOpenDisplay(None) # Linux
    if not dpy:
    print("Error: Could not open X display.")
    return None
    icon_data_ptr = lib.get_window_icon(dpy, ctypes.c_ulong(window_id))
    if icon_data_ptr and icon_data_ptr.contents.pixels:
    icon_data = icon_data_ptr.contents
    width = icon_data.width
    height = icon_data.height
    pixels_ptr = icon_data.pixels
    raw_pixels = [pixels_ptr for i in range(width * height)]
    rgba_pixels = []
    for pixel_val in raw_pixels:
    a = (pixel_val >> 24) & 0xFF
    r = (pixel_val >> 16) & 0xFF
    g = (pixel_val >> 8) & 0xFF
    b = pixel_val & 0xFF
    rgba_pixels.extend([r, g, b, a])
    img = Image.frombytes("RGBA", (width, height), bytes(rgba_pixels))
    lib.free_icon_data(icon_data_ptr)
    ctypes.cdll.LoadLibrary('libX11.so.6').XCloseDisplay(dpy)
    return img
    else:
    ctypes.cdll.LoadLibrary('libX11.so.6').XCloseDisplay(dpy)
    print(f"No icon found for window ID: {window_id}")
    return None

    if __name__ == '__main__':
    try:
    test_window_id = input("Enter a window ID: ")
    icon_image = get_window_icon(test_window_id)
    if icon_image:
    print(f"Icon is {icon_image.width}x{icon_image.height} pixels")
    icon_image.save("window_icon.png")
    else:
    print("Failed to retrieve icon")

    except Exception as e:
    print(e)


    And the geticon library:

    // geticon.c

    #include "geticon.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <X11/Xatom.h> // <--- ADDED THIS LINE

    // Helper function to ignore X errors
    int (*old_error_handler)(Display *dpy, XErrorEvent *ev);

    static int ignore_x_errors(Display *dpy, XErrorEvent *ev) {
    // Optionally print error, but for library, might want to be quiet.
    // fprintf(stderr, "X Error: serial %lu, error_code %d, request_code %d, minor_code %d\n",
    // ev->serial, ev->error_code, ev->request_code, ev->minor_code);
    return 0;
    }

    IconData* get_window_icon(Display *dpy, Window win) {
    Atom actual_type;
    int actual_format;
    unsigned long nitems;
    unsigned long bytes_after;
    unsigned char *prop = NULL;
    IconData *icon_data = NULL;

    // Set error handler to avoid Xlib exiting on BadWindow
    old_error_handler = XSetErrorHandler(ignore_x_errors);

    Atom net_wm_icon_atom = XInternAtom(dpy, "_NET_WM_ICON", False);
    if (net_wm_icon_atom == None) {
    fprintf(stderr, "Error: _NET_WM_ICON atom not found.\n");
    goto cleanup;
    }

    // Get the _NET_WM_ICON property
    // The property data is typically an array of CARDINAL, where each CARDINAL
    // represents a width, then a height, followed by width*height CARDINALs for the pixels.
    // This can repeat for multiple icon sizes.
    int status = XGetWindowProperty(dpy, win, net_wm_icon_atom,
    0, // offset
    ~0L, // length (get all data)
    False, // delete
    XA_CARDINAL, // desired type
    &actual_type,
    &actual_format,
    &nitems,
    &bytes_after,
    &prop);

    // Restore old error handler
    XSetErrorHandler(old_error_handler);

    if (status != Success || prop == NULL || actual_type != XA_CARDINAL || actual_format != 32) {
    if (status == BadWindow) {
    fprintf(stderr, "Error: Window ID 0x%lx does not exist.\n", win);
    } else if (status != Success) {
    fprintf(stderr, "Error: XGetWindowProperty failed with status %d.\n", status);
    } else if (prop == NULL) {
    fprintf(stderr, "Error: No _NET_WM_ICON property found or property is empty.\n");
    } else if (actual_type != XA_CARDINAL) {
    fprintf(stderr, "Error: _NET_WM_ICON property has wrong type (expected XA_CARDINAL, got %lu).\n", actual_type);
    } else if (actual_format != 32) {
    fprintf(stderr, "Error: _NET_WM_ICON property has wrong format (expected 32-bit, got %d-bit).\n", actual_format);
    }
    goto cleanup;
    }

    if (nitems == 0) {
    fprintf(stderr, "Warning: _NET_WM_ICON property is empty.\n");
    goto cleanup; // No icon data
    }

    icon_data = (IconData*) malloc(sizeof(IconData));
    if (!icon_data) {
    perror("Failed to allocate IconData");
    goto cleanup;
    }
    icon_data->width = 0;
    icon_data->height = 0;
    icon_data->pixels = NULL;
    icon_data->num_icons = 0;

    unsigned long *data_ptr = (unsigned long *)prop;
    unsigned long remaining_items = nitems;

    // The _NET_WM_ICON property can contain multiple icon images.
    // Each image starts with width, then height, then width*height pixels.
    // We will extract the first valid icon found.
    while (remaining_items >= 2) { // Need at least width and height
    unsigned long current_width = *data_ptr++;
    unsigned long current_height = *data_ptr++;
    remaining_items -= 2;

    if (current_width == 0 || current_height == 0) {
    fprintf(stderr, "Warning: Encountered icon with zero dimension (%lu x %lu).\n", current_width, current_height);
    // Skip to the next potential icon if dimensions are invalid
    data_ptr += (current_width * current_height); // Attempt to skip based on reported size
    remaining_items -= (current_width * current_height);
    continue;
    }

    unsigned long pixel_count = current_width * current_height;

    if (remaining_items < pixel_count) {
    fprintf(stderr, "Error: Incomplete icon data. Expected %lu pixels, but only %lu remaining.\n",
    pixel_count, remaining_items);
    // This indicates a malformed property, try to clean up.
    if (icon_data->pixels) free(icon_data->pixels);
    free(icon_data);
    icon_data = NULL;
    XFree(prop); // Free the X property data
    return NULL;
    }

    // We'll take the first valid icon we find. If the user wants to choose,
    // we'd return a list of IconData, but the prompt asked for "the icon".
    // It's common to take the largest or a specific size. For simplicity, we'll take the first.
    icon_data->width = current_width;
    icon_data->height = current_height;
    icon_data->pixels = (unsigned long *) malloc(pixel_count * sizeof(unsigned long));
    if (!icon_data->pixels) {
    perror("Failed to allocate pixels");
    if (icon_data) free(icon_data);
    icon_data = NULL;
    goto cleanup;
    }
    memcpy(icon_data->pixels, data_ptr, pixel_count * sizeof(unsigned long));
    icon_data->num_icons = 1; // We extracted one icon

    // Successfully extracted one icon, we can stop here.
    break;
    }

    cleanup:
    if (prop) {
    XFree(prop); // Always free data returned by XGetWindowProperty
    }
    return icon_data;
    }

    void free_icon_data(IconData *data) {
    if (data) {
    if (data->pixels) {
    free(data->pixels);
    }
    free(data);
    }
    }


    This code works fine, but in my window manager it doesn't (I only show this, because the thing is like over 1000 lines):

    def _draw_text_on_window(self, target_window, text, x, y, max_width=None, text_color=None, target_window_for_icon=None):
    """
    Draws text on a given Xlib window.
    Clears the area before drawing.
    If text_color is provided, sets the GC's foreground to that color.
    """
    if not target_window or not text:
    return

    target_window.clear_area(0, 0, 0, 0, False) # Clear full window for simplicity

    if text_color is not None:
    self.gc.change(foreground=text_color)
    else:
    # If no text_color is specified, use a default black (monochrome text is required)
    self.gc.change(foreground=self.screen.black_pixel)

    text_bytes = text.encode('utf-8')

    # Simple truncation if text is too long (can be improved with font metrics)
    # Assuming ~6 pixels per character for a rough estimate
    if max_width:
    max_chars = int(max_width / 6) # Rough character width
    if len(text_bytes) > max_chars:
    text_bytes = text_bytes[:max_chars - 3] + b".."

    target_window.draw_text(self.gc, x + 18, y, text_bytes)

    import utils.xicon as xr

    print(target_window_for_icon.id)
    print(target_window_for_icon.get_wm_name())

    icon_image = xr.get_window_icon(int(target_window_for_icon.id))

    if icon_image:
    icon_image = icon_image.resize((16, 16), resample=Image.Resampling.NEAREST)
    gcx = target_window.create_gc(foreground = 0xFFFFFF, background = 0x000000)
    try:
    target_window.put_pil_image(gcx, BUTTON_PADDING, BUTTON_PADDING, icon_image)
    except Exception as e:
    print(f"Error putting PIL image on window: {e}", file=sys.stderr)
    else:
    print("No icon image available to draw.", file=sys.stderr)

    self.disp.flush()

    Continue reading...

Compartilhe esta Página