#! /usr/bin/python from Xlib import display, Xatom, X, Xutil, rdb from Xlib.xobject.drawable import Window import Xlib.protocol.event import sys import struct class KaClient: def __init__(self, parent, win): self.client = win self.parent = parent self.screen = parent.screen self.depth = parent.parent.depth self.display = parent.display self.window = self.make_window() self.update_position() self.update_state() win.change_attributes(event_mask = ( X.PropertyChangeMask | X.StructureNotifyMask) ) def __del__(self): self.window.unmap() self.window.destroy() def make_window(self): w = self.parent.window.create_window( 0, 0, 2, 2, border_width = 1, depth = self.depth, window_class = X.InputOutput, event_mask = ( X.ButtonReleaseMask | X.ButtonPressMask | X.Button1MotionMask ), border_pixel = self.screen.black_pixel, background_pixel = self.screen.white_pixel, ) return w def get_virtual_pos(self, window): pos = window.get_full_property(self.parent.parent._WAIMEA_NET_VIRTUAL_POS, Xatom.INTEGER) if not pos: return 0, 0 return pos.value[0], pos.value[1] def update_position(self, width = None, height = None): x, y = self.get_virtual_pos(self.client) x = struct.unpack("i", struct.pack("I", x))[0] y = struct.unpack("i", struct.pack("I", y))[0] r_x = int(self.parent.f_w * x) r_y = int(self.parent.f_h * y) geom = self.client.get_geometry() if width is None: width = geom.width if height is None: height = geom.height r_w = int(self.parent.f_w * width) r_h = int(self.parent.f_h * height) if r_x < 0: r_x = 0 if r_y < 0: r_y = 0 if r_w + r_x >= self.parent.w: r_w = self.parent.w - r_x if r_h + r_y >= self.parent.h: r_h = self.parent.h - r_y self.window.configure(x = self.parent.x + r_x, y = self.parent.y + r_y, width = r_w, height = r_h) def update_state(self): state = self.client.get_full_property(self.parent.parent._WM_STATE, 0) if (not state) or state.value[0] == Xutil.NormalState: self.window.map() else: self.window.unmap() def client_raise(self): self.client.configure(stack_mode = X.Above) self.client.set_input_focus(X.RevertToNone, X.CurrentTime) class KaDesktop: def __init__(self, parent, number, x, y, w, h, d_w, d_h): self.parent = parent self.display = parent.display self.screen = parent.screen self.root = parent.root self.window = parent.window self.number = number self.x = x self.y = y self.w = w self.h = h self.d_w = d_w self.d_h = d_h self.f_w = float(w) / float(d_w) self.f_h = float(h) / float(d_h) self.clients = {} def update(self): stack = self.parent.get_stacking_list() id_stack = [win.id for win in stack] restack = [] for win in stack: mask = self.get_desktop_mask(win) if mask & (1L << self.number): if win.id in self.clients: self.clients[win.id].update_position() else: client = KaClient(self, win) self.clients[win.id] = client restack.append(self.clients[win.id].window) else: if win.id in self.clients: self.clients[win.id].window.unmap() del self.clients[win.id] for client_id in self.clients.keys()[:]: client = self.clients[client_id] client_win = client.window if not client_win in restack: client.window.destroy() del self.clients[client_id] restack.reverse() for win in restack: win.configure(stack_mode = X.Below) def get_desktop_mask(self, window): mask = window.get_full_property(self.parent._WAIMEA_NET_WM_DESKTOP_MASK, Xatom.CARDINAL) return mask.value[0] def update_state(self, window): if window.id in self.clients.keys(): self.clients[window.id].update_state() def update_position(self, window, width = None, height = None): if window.id in self.clients.keys(): self.clients[window.id].update_position(width, height) def window_raise(self, window): for client in self.clients.values(): if client.window.id == window.id: client.client_raise() self.update() def find_window(self, window): for client in self.clients.values(): if client.window.id == window.id: return client return None class KaPager: def __init__(self): self.display = display.Display() self.screen = self.display.screen() self.root = self.screen.root self.depth = self.screen.root_depth self.active = None self.root.change_attributes(event_mask = X.PropertyChangeMask) self.get_atoms() if not self.wm_check(): raise RuntimeError, "WM does not support NET properties" if not self.check_atoms(): raise RuntimeError, "Required properties not supported" num_desktops = self.get_num_desktops() self.w = w = 66 self.h = h = 65 * num_desktops + 1 d_w, d_h = self.get_desktop_geometry() self.window = self.make_window(w, h) self.desktops = [] for number in range(num_desktops): x = 1 y = number * 65 + 1 desktop = KaDesktop(self, number, x, y, 64, 64, d_w, d_h) desktop.update() self.desktops.append(desktop) def get_atoms(self): self._NET_SUPPORTED = self.display.intern_atom("_NET_SUPPORTED") self._NET_SUPPORTING_WM_CHECK = self.display.intern_atom("_NET_SUPPORTING_WM_CHECK") self._NET_CLIENT_LIST = self.display.intern_atom("_NET_CLIENT_LIST") self._NET_CLIENT_LIST_STACKING = self.display.intern_atom("_NET_CLIENT_LIST_STACKING") self._NET_NUMBER_OF_DESKTOPS = self.display.intern_atom("_NET_NUMBER_OF_DESKTOPS") self._NET_DESKTOP_GEOMETRY = self.display.intern_atom("_NET_DESKTOP_GEOMETRY") self._WAIMEA_NET_VIRTUAL_POS = self.display.intern_atom('_WAIMEA_NET_VIRTUAL_POS') self._WAIMEA_NET_WM_DESKTOP_MASK = self.display.intern_atom("_WAIMEA_NET_WM_DESKTOP_MASK") self._WM_STATE = self.display.intern_atom("WM_STATE") def wm_check(self): wm_check = self.root.get_full_property(self._NET_SUPPORTING_WM_CHECK, Xatom.WINDOW) win = Window(self.display.display, wm_check.value[0]) wm_check = win.get_full_property(self._NET_SUPPORTING_WM_CHECK, Xatom.WINDOW) if win.id == wm_check.value[0]: return True return False def check_atoms(self): supported = self.root.get_full_property(self._NET_SUPPORTED, Xatom.ATOM) if self._NET_NUMBER_OF_DESKTOPS not in supported.value: return False return True def get_num_desktops(self): desktops = self.root.get_full_property(self._NET_NUMBER_OF_DESKTOPS, Xatom.CARDINAL) return desktops.value[0] def get_desktop_geometry(self): viewport = self.root.get_full_property(self._NET_DESKTOP_GEOMETRY, Xatom.CARDINAL) return viewport.value[0], viewport.value[1] def get_stacking_list(self): stack = self.root.get_full_property(self._NET_CLIENT_LIST_STACKING, Xatom.WINDOW) winlist = [] for w_id in stack.value: win = Window(self.display.display, w_id) winlist.append(win) return winlist def make_window(self, w, h): w = self.root.create_window( 0, 0, w, h, border_width = 0, depth = self.depth, window_class = X.InputOutput, background_pixel = self.screen.black_pixel, event_mask = (X.StructureNotifyMask | X.ExposureMask | X.ButtonReleaseMask | X.PointerMotionHintMask ) ) w.set_wm_name("KaPager") w.set_wm_icon_name("KaPager") w.set_wm_class("pager", "KaPager") # w.set_wm_hints(flags = Xutil.StateHint, initial_state = Xutil.WithdrawnState) w.map() return w def send_client_message(self, window, type, fmt, *data): data = list(data[:5]) data += [0] * (160 / fmt - len(data)) ev = Xlib.protocol.event.ClientMessage(window = window, client_type = type, data = (fmt, data)) self.root.send_event(ev, event_mask=X.SubstructureRedirectMask) def run(self): while 1: e = self.display.next_event() if e.type == X.PropertyNotify: if e.atom == self._NET_CLIENT_LIST_STACKING: for desktop in self.desktops: desktop.update() elif e.atom == self._WM_STATE: for desktop in self.desktops: desktop.update_state(e.window) elif e.atom == self._WAIMEA_NET_VIRTUAL_POS: for desktop in self.desktops: desktop.update_position(e.window) elif e.type == X.ConfigureNotify: for desktop in self.desktops: desktop.update_position(e.window) elif e.type == X.ButtonPress: for desktop in self.desktops: win = desktop.find_window(e.window) if win: break else: self.active = None continue self.active = win self.active_x = e.event_x self.active_y = e.event_y self.motion = 0 elif e.type == X.MotionNotify: if self.active: geom = self.active.window.get_geometry() x = geom.x + e.event_x - self.active_x y = geom.y + e.event_y - self.active_y self.active.window.configure(x = x, y = y) self.motion = 1 elif e.type == X.ButtonRelease: if self.active: if self.motion: geom = self.active.window.get_geometry() x = geom.x + e.event_x y = geom.y + e.event_y for desktop in self.desktops: if x>=desktop.x and x<=desktop.x+desktop.w and y>=desktop.y and y<=desktop.y+desktop.w: break else: continue if desktop != self.active.parent: new_mask = desktop.get_desktop_mask(self.active.client) new_mask -= new_mask & (1L << self.active.parent.number) new_mask |= 1L << desktop.number self.send_client_message(self.active.client, self._WAIMEA_NET_WM_DESKTOP_MASK, 32, new_mask) x -= self.active_x + desktop.x y -= self.active_y + desktop.y x /= desktop.f_w y /= desktop.f_h self.active.client.configure(x=int(x), y=int(y)) self.active.client_raise() pager = KaPager() pager.run()