#!/usr/bin/env python3.7 from os import system from Xlib import X, XK from Xlib.display import Display class FreedoWM(object): def __init__(self): self.display = Display() self.event = self.display.next_event() self.root = self.display.screen().root self.colormap = self.display.screen().default_colormap self.currently_focused = None self.start = None self.NET_WM_NAME = self.display.intern_atom('_NET_WM_NAME') self.NET_ACTIVE_WINDOW = self.display.intern_atom('_NET_ACTIVE_WINDOW') def set_listeners(self): # Listen for window changes self.root.change_attributes( event_mask=X.PropertyChangeMask | X.FocusChangeMask | X.SubstructureNotifyMask | X.PointerMotionMask ) # Keyboard listener self.root.grab_key(X.AnyKey, X.Mod4Mask, 1, X.GrabModeAsync, X.GrabModeAsync) # Button (Mouse) listeners self.root.grab_button(X.AnyButton, X.Mod4Mask, 1, X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) def is_key(self, key_name): return self.event.type == X.KeyPress \ and self.event.detail == self.display.keysym_to_keycode(XK.string_to_keysym(key_name)) def window_focused(self): return hasattr(self.event, "child") and self.event.child != X.NONE or self.root.query_pointer().child != 0 def set_border(self, child, color): if child is not None and child is not X.NONE: border_color = self.colormap.alloc_named_color(color).pixel child.configure(border_width=1) child.change_attributes(None, border_pixel=border_color) def update_windows(self): # Only update if the self.event has relevance (focus/title change) # if self.event.type != X.PropertyNotify: # return new_focus = False # Set focused window "in focus" if self.window_focused(): if hasattr(self.event, "child") and self.event.child != self.currently_focused: new_focus = True self.currently_focused = self.event.child self.event.child.configure(stack_mode=X.Above) elif self.root.query_pointer().child != self.currently_focused: new_focus = True self.currently_focused = self.root.query_pointer().child self.root.query_pointer().child.configure(stack_mode=X.Above) # Set all windows to un-focused borders if self.event.type == X.FocusOut or new_focus: for child in self.root.query_tree().children: print("RESET FOCUS") self.set_border(child, "#000") # Set focused window border if self.event.type == X.FocusIn or new_focus: child = self.root.query_pointer().child self.currently_focused = child if child != 0: print("FOCUS") child.configure(stack_mode=X.Above) self.set_border(child, "#fff") self.display.sync() # Check for actions until exit def main_loop(self): self.set_listeners() while 1: self.event = self.display.next_event() self.update_windows() # Resize window (MOD + right click) if self.event.type == X.ButtonPress and self.event.child != X.NONE: attribute = self.event.child.get_geometry() self.start = self.event # Move window (MOD + left click) elif self.event.type == X.MotionNotify and self.start: x_diff = self.event.root_x - self.start.root_x y_diff = self.event.root_y - self.start.root_y self.start.child.configure( x=attribute.x + (self.start.detail == 1 and x_diff or 0), y=attribute.y + (self.start.detail == 1 and y_diff or 0), width=max(1, attribute.width + (self.start.detail == 3 and x_diff or 0)), height=max(1, attribute.height + (self.start.detail == 3 and y_diff or 0)) ) # Cycle between windows (MOD + Tab) // X11's "tab" keysym is 0, but it's 23 if self.event.type == X.KeyPress and self.event.detail == 23: self.event.child.configure(stack_mode=X.Below) # Close window (MOD + Q) elif self.is_key("q") and self.window_focused(): self.event.child.destroy() # Open terminal (MOD + Enter) // X11's "enter" keysym is 0, but it's 36 elif self.event.type == X.KeyPress and self.event.detail == 36: system("st &") # Open dmenu (MOD + D) elif self.is_key("d"): system("dmenu_run &") # Exit window manager (MOD + C) elif self.is_key("c"): self.display.close() elif self.event.type == X.ButtonRelease: self.start = None FreedoWM = FreedoWM() FreedoWM.main_loop()