You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

X窗口管理器调用窗口图标获取脚本返回None问题求助

问题

开发X窗口管理器过程中遇到一个问题:单独运行窗口图标获取脚本时,传入窗口ID能正常返回Pillow Image对象;但在窗口管理器内部调用该脚本,传入已确认有效的窗口ID,却返回None。


Python 图标获取脚本

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[i] 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)

C 语言库代码(geticon.c)

// 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);
    }
}

头文件(geticon.h)

// geticon.h

#ifndef GET_ICON_H
#define GET_ICON_H

#include <X11/Xlib.h>
#include <X11/Xutil.h>

// Structure to hold icon data
typedef struct {
    unsigned long width;
    unsigned long height;
    unsigned long *pixels; // ARGB pixel data
    int num_icons;         // Number of icon images in the property
} IconData;

// Function to get window icon data
// Returns an IconData struct. Caller is responsible for freeing pixels.
// Returns NULL on error.
IconData* get_window_icon(Display *dpy, Window win);

// Function to free IconData
void free_icon_data(IconData *data);

#endif // GET_ICON_H

窗口管理器中的调用片段

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()

内容的提问来源于stack exchange,提问作者SpinningCubes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 19:55:54