#!/usr/bin/env python # Revision A # Created: 2006-12-02 22:40:00 CET # Author: Danny Milosavljevic # TODO mtime # TODO struts (in TScreen, in root coordinates, non-xinerama-aware) import exceptions import time TWindow = int class TEnumerated(object): pass class TEvent(object): def __init__(self, *args): self._listeners = set() self._frozen_emits = [] self._frozen_count = 0 self._limit_frozen_emit_to_one_p = False pass def connect(self, value): self._listeners.add(value) def disconnect(self, value): try: self._listeners.remove(value) except: pass def _frozen_emit(sender, *args, **kwargs): for listener in self._listeners: listener(sender, *args, **kwargs) def emit(self, sender, *args, **kwargs): if self._frozen_count > 0: if _limit_frozen_emit_to_one_p and len(self._frozen_emits) >= 1: # already marked. return self._frozen_emits.append(lambda: self._frozen_emit(sender, *args, **kwargs)) else: for listener in self._listeners: listener(sender, *args, **kwargs) def freeze(self): self._frozen_count = self._frozen_count + 1 def thaw(self): assert(self._frozen_count > 0) self._frozen_count = self._frozen_count - 1 if len(self._frozen_emits) > 0: for frozen_emit in self._frozen_emits: frozen_emit() self._frozen_emits = [] def relax(self): assert(self._frozen_count > 0) self._frozen_emits = [] class TMonitorableList(object): def __init__(self, supposed_class): self._supposed_class = supposed_class self._list = list() self.item_added = TEvent(int, supposed_class) self.item_removed = TEvent(int, supposed_class) def append(self, value): self._list.append(value) self.item_added.emit(self, len(self._list) - 1, value) def remove(self, value): try: i = self._list.index(value) del self._list[i] self.item_removed.emit(self, i, value) except exceptions.ValueError, e: return def delete(self, i): value = self._list[i] del self._list[i] self.item_removed.emit(self, i, value) def __iter__(self): for item in self._list: yield item # ctor supposed_class class TMonitorableDictionary(object): def __init__(self, supposed_class): self._supposed_class = supposed_class self._dict = dict() self.item_added = TEvent(str, supposed_class) self.item_removed = TEvent(str, supposed_class) def __setitem__(self, name, value): if name in self._dict: self.delete(name) assert(isinstance(value, self._supposed_class)) self._dict[name] = value self.item_added.emit(self, name, value) def __getitem__(self, name): return self._dict[name] def keys(self): for key in self._dict.keys(): yield key def delete(self, name): try: value = self._dict[name] del self._dict[name] self.item_removed.emit(self, name, value) except: pass def __repr__(self): return "{ %s }" % (", ".join([k + ": " + repr(x) for k, x in self._dict.items()])) class TMonitorableProperty(object): # TODO mtime? def __init__(self, supposed_class, writeable, default_value = None): self._supposed_class = supposed_class self.property_written = TEvent() self.property_read = TEvent() self.access_time = time.time() self.writeable = writeable if writeable == False and default_value is None: raise exceptions.ValueError("writeable == False and default_value is None, which is probably not what you want") self._value = default_value def _get_value(self): return self._value def __getattr__(self, name): if name == "value": self.access_time = time.time() return self._get_value() else: return super(TMonitorableProperty, self).__getattr__(name) def __setattr__( self, name, value ): if name == "value": if isinstance(value, int) and isinstance(self._supposed_class(), TEnumerated): self._value = value else: assert(isinstance(value, self._supposed_class)) self._value = value else: return super(TMonitorableProperty, self).__setattr__(name, value) class TMonitorableRecursiveCalculatedProperty(TMonitorableProperty): def __init__(self, supposed_class, list_instance, summary_callback, child_symbol, fallback_value): self._fallback_value = fallback_value self._list_instance = list_instance self._summary_callback = summary_callback self._child_symbol = child_symbol TMonitorableProperty.__init__(self, supposed_class, True, self._get_value()) self.writeable = False def _get_value(self): child_values = [getattr(item, self._child_symbol).value for item in self._list_instance] if child_values <> []: return self._summary_callback(child_values) else: return self._fallback_value class TX(object): def __init__(self): self.display = TMonitorableProperty(TDisplay, False) class TDisplay(object): def __init__(self): self.screens = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TScreen)) # FIXME right position here? self.keyboard_focus = TMonitorableProperty(TClient, True, None) self.hovered_clients = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TClient)) # depending on the number of pointing devices #self.active_client = TMonitorableProperty((depending on the number of mouses). def weak_uplink(supposed_class): # TODO return supposed_class class TStruts(object): def __init__(self): self.left_border = TMonitorableProperty(int, True, 32) self.top_border = TMonitorableProperty(int, True, 0) self.right_border = TMonitorableProperty(int, True, 0) self.bottom_border = TMonitorableProperty(int, True, 0) # TODO "partial" covers class TScreen(object): def __init__(self, parent): self.display = TMonitorableProperty(weak_uplink(TDisplay), False, parent) x_window = 7 # TODO self.root_window = TMonitorableProperty(TWindow, False, x_window) self.visible_areas = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TVisibleArea)) self.clients = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TClient)) self.rubberband = TMonitorableProperty(TRubberband, False, TRubberband()) self.struts = TMonitorableProperty(TStruts, False, TStruts()) # TODO handle it when struts change #self.struts.left_border #self.struts.top_border #self.struts.right_border #self.struts.bottom_border self.visible_areas.value.item_added.connect(self._visible_areas_item_added_callback) self.visible_areas.value.item_removed.connect(self._visible_areas_item_removed_callback) def _visible_areas_item_added_callback(self, sender, index, value): pass def _visible_areas_item_removed_callback(self, sender, index, value): pass class TPoint(object): # : immutable def __init__(self, x, y): self.x = x self.y = y def copy(self): return TPoint(self.x, self.y) def __eq__(self, other): return self.x == other.y and self.y == other.y def __repr__(self): return "(%d, %d)" % (self.x, self.y) class TAreaManagementMode(TEnumerated): Tiled = 0 Stacked = 1 Chaotic = 2 class TFluidRectangle(object): def __init__(self): self.upper_left = TMonitorableProperty(TPoint, True, TPoint(0, 0)) self.lower_right = TMonitorableProperty(TPoint, True, TPoint(0, 0)) self.volatile_upper_left = TMonitorableProperty(TPoint, True, TPoint(0, 0)) self.volatile_lower_right = TMonitorableProperty(TPoint, True, TPoint(0, 0)) self.step_distance = TMonitorableProperty(int, True, 10000000) self.step_time = TMonitorableProperty(int, True, 0) self.moving_p = TMonitorableProperty(bool, True, False) self.finished_moving = TEvent() self.started_moving = TEvent() self.moving = TEvent() def __repr__(self): return "goal (%s, %s), intermediate (%s, %s), moving? %s" % (self.upper_left.value, self.lower_right.value, self.volatile_upper_left.value, self.volatile_lower_right.value, self.moving_p.value) class TRubberband(TFluidRectangle): def __init__(self): self.step_distance = TMonitorableProperty(int, True, 100000) self.step_time = TMonitorableProperty(int, True, 0) self.visible_p = TMonitorableProperty(bool, True, False) class TDirection(TEnumerated): North = 0 North_East = 1 East = 2 South_East = 3 South = 4 South_West = 5 West = 6 North_West = 7 def __init__(self, value): self._value = value class TInterconnectionAnchor(object): def __init__(self): self.direction = TMonitorableProperty(TDirection, True, TDirection()) self.other_area = TMonitorableProperty(TVisibleArea, True, None) class TVisibleArea(object): def __init__(self, parent): self.screen = TMonitorableProperty(weak_uplink(TScreen), False, parent) self.current_p = TMonitorableProperty(bool, True, False) # A "Current" Visible Area is the one preferred for new Client Placement. self.area = TMonitorableProperty(TFluidRectangle, False, TFluidRectangle()) self.interconnection_anchors = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TInterconnectionAnchor)) self.management_mode = TMonitorableProperty(TAreaManagementMode, True, TAreaManagementMode.Tiled) self.status_line = TMonitorableProperty(TClient, True) # (slow scrolling, v-resizeable). # Each Status Line can either be visible or not. self.views = TMonitorableProperty(TMonitorableDictionary, False, TMonitorableDictionary(TView)) # Each Visible Area has 1 preallocated "Unsorted" View. # Each Visible Area has 1 preallocated "Fullscreen" View. (later, the "Current" Visible Area is used) # Each Visible Area has 1 Active View. self.views.value["unsorted"] = TView(self) full_screen_view = TView(self) full_screen_view.default_packing_mode.value = TPackingMode.Tabs self.views.value["full_screen"] = full_screen_view self.active_view = TMonitorableProperty(str, True, "unsorted") self.offset_from_root = TMonitorableProperty(TPoint, True, TPoint(0, 0)) self.area.value.upper_left.connect(self._area_upper_left_changed_callback) self.area.value.lower_right.connect(self._area_lower_right_changed_callback) def _area_upper_left_changed_callback(self, sender): for view in self.views.value: view.box.value.area.value.upper_left = self.area.value.upper_left.value.copy() def _area_lower_right_changed_callback(self, sender): # TODO keep some space for the status line etc? for view in self.views.value: view.box.value.area.value.lower_right = self.area.value.lower_right.value.copy() def __repr__(self): return "TVisibleArea(%s)" % (self.area.value) class TPackingMode(TEnumerated): Tabs = 0 Tiles = 1 Sides = 2 Floating = 3 # near Client X class TView(object): def __init__(self, parent): self.visible_area = TMonitorableProperty(weak_uplink(TVisibleArea), False, parent) self.default_packing_mode = TMonitorableProperty(TPackingMode, True, TPackingMode.Tiles) self.default_create_near = TMonitorableProperty(TTab, True, None) # (Maybe that also makes sense for "Clients named XYZ" and/or process executable name, I don't know yet) self.id = TMonitorableProperty(str, True, id(self)) # tag name self.current_active_pointer_tab = TMonitorableProperty(TTab, True, None) self.current_active_keyboard_tab = TMonitorableProperty(TTab, True, None) self.frame_stack = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TFrame)) self.box = TMonitorableProperty(TBox, False, TBox()) # public # self.floatings = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TFrame)) def __repr__(self): return "View(%s)" % self.id.value class TBoxMode(object): Horizontal = 0 Vertical = 1 class TItem(object): def __init__(self): self.area = TMonitorableProperty(TFluidRectangle, False, TFluidRectangle()) # Each Frame has variable Height that makes sense class TGrip(object): def __init__(self): self.position = TMonitorableProperty(int, True, 0) # TODO position-changed? class TBox(TItem): def __init__(self): TItem.__init__(self) self.mode = TMonitorableProperty(TBoxMode, True, TBoxMode.Vertical) # Each Horizontal Box has a Height. # Each Vertical Box has a Width. self.items = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TItem)) self.grips = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TGrip)) # Between Frames there is a Resize Grip. Grip 0 is after the first. self.area.value.upper_left.connect(self._box_area_changed_callback) self.area.value.lower_right.connect(self._box_area_changed_callback) def _box_area_changed_callback(self, sender): box_upper_left = self.area.value.upper_left.value box_lower_right = self.area.value.lower_right.value # TODO resize those items for item in self.items.value: pass for grip in self.grips.value: pass def add_item(self, item): grip = TGrip() #grip.position.property_written.connect() self.items.value.append(item) self.grips.value.append(grip) def __repr__(self): for v in self.items.value: print v return "Box(%s\n)" % (", \n".join([repr(item) for item in self.items.value])) TSize = TPoint class TFrame(TItem): def __init__(self, parent): TItem.__init__(self) self.visible_area = TMonitorableProperty(weak_uplink(TVisibleArea), False, parent) self.tabs = TMonitorableProperty(TMonitorableList, False, TMonitorableList(TTab)) self.shaded_p = TMonitorableProperty(bool, True, False) self.tags = TMonitorableProperty(TMonitorableDictionary, False, TMonitorableDictionary(str)) # inherited area: TFluidRectangle # Each Frame has variable Height that makes sense self.action = TMonitorableProperty(TMonitorableDictionary, False, TMonitorableDictionary(str)) # Custom Action Buttons (usual are "Shade/Unshade", "Iconify/Deiconify"). self.ideal_height = TMonitorableProperty(int, True, 1) # Each Frame has a Greed Factor (Ideal, but usually unattainable height ?). self.minimum_size = TMonitorableRecursiveCalculatedProperty(TSize, self.tabs.value, max, "minimum_size", TSize(1, 1)) self.default_size = TMonitorableRecursiveCalculatedProperty(TSize, self.tabs.value, max, "default_size", TSize(1, 1)) self.maximum_size = TMonitorableRecursiveCalculatedProperty(TSize, self.tabs.value, min, "maximum_size", TSize(1, 1)) self.resize_step = TMonitorableRecursiveCalculatedProperty(TSize, self.tabs.value, max, "resize_step", TSize(1, 1)) # not that easy, actually. But sufficing for now. def __repr__(self): return "Frame(shaded? %s, tabs: %s)" % (self.shaded_p.value, ", \n".join([repr(item) for item in self.tabs.value])) class TAction(object): # TODO id # TODO icon-name # TODO caption # TODO "callback" pass class TTab(object): def __init__(self, parent): self.frame = TMonitorableProperty(weak_uplink(TFrame), False, parent) self.client = TMonitorableProperty(TClient, True, None) # foreign reference, same Client can be referenced many times, i.e. each Client is in n Tabs. #self.visible? # i. e. client mapped #close_button: TButton # visible on the active tab # most likely a menu: self.actions = TMonitorableProperty(TMonitorableDictionary, False, TMonitorableDictionary(TAction)) # Custom Action Buttons (usual are "Close", "Float", "Move Below", "Move Right"). self.geometry = TMonitorableProperty(TFluidRectangle, True, TFluidRectangle()) self.urgent_p = TMonitorableProperty(bool, True, False) #self.empty_p = TMonitorableProperty(bool, calculated client is not nil self.active_p = TMonitorableProperty(bool, True, False) self.selected_p = TMonitorableProperty(bool, True, False) def __repr__(self): return "\nTab(client %s, actions %s, geometry %s, urgent? %s, active? %s, selected? %s)" % ( self.client.value, repr(self.actions.value), self.geometry.value, self.urgent_p.value, self.active_p.value, self.selected_p.value, ) class TDisplayMode(object): Normal = 0 Iconified = 1 class TAtom(object): def __init__(self, name, create_p): self.name = TMonitorableProperty(str, False, name) self.create_p = TMonitorableProperty(bool, False, create_p) self.symbol = None # TMonitorableProperty(int, False, display.get_atom(name)) NET_WM_WINDOW_TYPE = TAtom("_NET_WM_WINDOW_TYPE", True) NET_WM_WINDOW_TYPE_NORMAL = TAtom("_NET_WM_WINDOW_TYPE_NORMAL", True) NET_WM_WINDOW_TYPE_DESKTOP = TAtom("_NET_WM_WINDOW_TYPE_DESKTOP", True) NET_WM_WINDOW_TYPE_DOCK = TAtom("_NET_WM_WINDOW_TYPE_DOCK", True) NET_WM_WINDOW_TYPE_TOOLBAR = TAtom("_NET_WM_WINDOW_TYPE_TOOLBAR", True) NET_WM_WINDOW_TYPE_MENU = TAtom("_NET_WM_WINDOW_TYPE_MENU", True) NET_WM_WINDOW_TYPE_UTILITY = TAtom("_NET_WM_WINDOW_TYPE_UTILITY", True) NET_WM_WINDOW_TYPE_SPLASH = TAtom("_NET_WM_WINDOW_TYPE_SPLASH", True) NET_WM_WINDOW_TYPE_DIALOG = TAtom("_NET_WM_WINDOW_TYPE_DIALOG", True) NET_WM_STATE = TAtom("_NET_WM_STATE", True) # list NET_WM_STATE_MODAL = TAtom("_NET_WM_STATE_MODAL", True) NET_WM_STATE_STICKY = TAtom("_NET_WM_STATE_STICKY", True) NET_WM_STATE_MAXIMIZED_VERT = TAtom("_NET_WM_STATE_MAXIMIZED_VERT", True) NET_WM_STATE_MAXIMIZED_HORZ = TAtom("_NET_WM_STATE_MAXIMIZED_HORZ", True) NET_WM_STATE_SHADED = TAtom("_NET_WM_STATE_SHADED", True) NET_WM_STATE_SKIP_TASKBAR = TAtom("_NET_WM_STATE_SKIP_TASKBAR", True) NET_WM_STATE_SKIP_PAGER = TAtom("_NET_WM_STATE_SKIP_PAGER", True) NET_WM_STATE_HIDDEN = TAtom("_NET_WM_STATE_HIDDEN", True) NET_WM_STATE_FULLSCREEN = TAtom("_NET_WM_STATE_FULLSCREEN", True) NET_WM_STATE_ABOVE = TAtom("_NET_WM_STATE_ABOVE", True) NET_WM_STATE_BELOW = TAtom("_NET_WM_STATE_BELOW", True) NET_WM_STATE_DEMANDS_ATTENTION = TAtom("_NET_WM_STATE_DEMANDS_ATTENTION", True) class TClient(object): def __init__(self, x_window): self.minimum_size = TMonitorableProperty(TSize, True, TSize(1, 1)) self.default_size = TMonitorableProperty(TSize, True, TSize(1, 1)) self.maximum_size = TMonitorableProperty(TSize, True, TSize(1, 1)) self.resize_step = TMonitorableProperty(TSize, True, TSize(1, 1)) self._sizes = { TDisplayMode.Normal: TSize(1, 1), TDisplayMode.Iconified: TSize(1, 1) } self._titles = { TDisplayMode.Normal: TSize(1, 1), TDisplayMode.Iconified: TSize(1, 1) } # An "Iconified Client" just has a smaller (modifyable) Client area than a "Not Iconified Client". # A "Shaded Client" would have zero (unmodifyable) Client area. However, shading is done on Frames, not Tabs self.urgent_p = TMonitorableProperty(bool, True, False) self.iconified_p = TMonitorableProperty(bool, True, False) self.want_keyboard_focus_p = TMonitorableProperty(bool, True, True) self.want_frame_p = TMonitorableProperty(bool, True, True) # calculated, actually # (Each Client has n Tags.) self.tags = TMonitorableProperty(TMonitorableList, False, TMonitorableList(str)) # see TView self.window = TMonitorableProperty(TWindow, False, x_window) self.window_type = TMonitorableProperty(TAtom, True, NET_WM_WINDOW_TYPE_NORMAL) def mapped(self, screen): # TODO if window type changed, change packing mode pass def __repr__(self): # TODO the rest return "Client(%d)" % self.window.value """ Stuff the Window Manager needs to keep visible at times: ======================================================== - Rubberband, sometimes - Current Selection, always - Ideal, but usually unattainable height of Frames when dragging Grip Bar - Minimum Height of Frames when dragging Grip Bar - Maximum Height of Frames when dragging Grip Bar - Step (Resize Granularity) of Frames when dragging Grip Bar - Current Topmost (Active) Window Mark? - Warning that a new window is going to open and chance to move mouse to where it should. """ if __name__ == "__main__": display = TDisplay() screen_1 = TScreen(display) display.screens.value.append(screen_1) visible_area_1 = TVisibleArea(screen_1) visible_area_1.area.value.upper_left = TPoint(0, 0) visible_area_1.area.value.lower_right = TPoint(0, 0) screen_1.visible_areas.value.append(visible_area_1) window_id = 2 for view_tag in visible_area_1.views.value.keys(): view = visible_area_1.views.value[view_tag] frame = TFrame(visible_area_1) view.box.value.add_item(frame) client_1 = TClient(window_id) window_id = window_id + 1 tab = TTab(frame) tab.client.value = client_1 frame.tabs.value.append(tab) #print view.visible_area.value #print view.box.value print frame