#!/usr/bin/env python3 ''' xpybar – xmobar replacement written in python Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Mattias Andrée (maandree@kth.se) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ''' import os, subprocess import Xlib.display, Xlib.Xatom, Xlib.ext.randr, Xlib.X display = None ''' The connection the X display ''' screen = None ''' The screen the panel is placed in ''' screen_i = None ''' The index of the screen `screen` ''' def open_x(screen_no = None): ''' Open connection the X and select screen @param screen_no:int? The index of the screen to use, `None` for default ''' global display, screen, screen_i display = Xlib.display.Display() screen_i = screen_no if screen_no is not None else display.get_default_screen() screen = display.screen(screen_i) def get_monitors(): ''' Returns the pixel size and position of the monitors in the screen @return :list<(width:int, height:int, left:int, top:int)> The width, height, left position and top position of each monitor, starting with the primary output ''' global screen_i p = subprocess.Popen(['xrandr'], stdout = subprocess.PIPE) p = p.communicate()[0].decode('utf-8', 'replace') s = -1 rc = [] prim = None for line in p.split('\n'): if line.startswith('Screen '): s = int(line[len('Screen '):].split(':')[0]) elif s == screen_i: if ' connected ' in line: try: m = line.replace('-', '+-').replace('++', '+') p = ' primary ' in m m = m.replace(' primary ', ' ') m = m.split(' ')[2].replace('+', 'x').split('x') m = [int(x) for x in m] if p and (prim is None): prim = m else: rc.append(m) except: pass # Output not used if prim is not None: rc = [prim] + rc return rc def get_display(): ''' Returns the X display ''' global display return display def get_screen(): ''' Returns the X screen ''' global screen return screen def get_event_mask(): ''' For `create_panel`, return which events should be caught @return :int Mask of events that should be caught ''' return (Xlib.X.StructureNotifyMask | Xlib.X.ButtonPressMask | Xlib.X.ButtonReleaseMask | Xlib.X.ExposureMask) def get_override_redirect(): ''' For `create_panel`, figure out whether override_redirect should be set @return :bool Whether override_redirect should be set ''' if 'DESKTOP_SESSION' in os.environ: desktop = os.environ['DESKTOP_SESSION'] if desktop in ('xmonad',): return False return True def create_panel(width, height, left, ypos, panel_height, at_top): ''' Create a docked panel, not mapped yet @param width:int The width of the output @param height:int The height of the output @param left:int The left position of the output @param ypos:int The position of the panel in relation to either the top or bottom edge of the output @param panel_height:int The height of the panel @param at_top:bool Whether the panel is to be docked to the top of the output, otherwise to the bottom @return The window ''' global display, screen ypos = ypos if at_top else (height - ypos - panel_height) window = screen.root.create_window(left, ypos, width, panel_height, 0, Xlib.X.CopyFromParent, Xlib.X.InputOutput, Xlib.X.CopyFromParent, event_mask = get_event_mask(), colormap = Xlib.X.CopyFromParent, override_redirect = get_override_redirect()) top_ = lambda x, y, w, h : [0, 0, y + h, 0, 0, 0, 0, 0, x, x + w - 1, 0, 0] bottom_ = lambda x, y, w, h : [0, 0, 0, y + h, 0, 0, 0, 0, 0, 0, x, x + w - 1] window.set_wm_name('xpybar') window.set_wm_icon_name('xpybar') window.set_wm_class('bar', 'xpybar') window.set_wm_client_machine(os.uname().nodename) position = (top_ if at_top else bottom_)(left, ypos, width, panel_height) _CARD = display.intern_atom("CARDINAL") _PSTRUT = display.intern_atom("_NET_WM_STRUT_PARTIAL") _STRUT = display.intern_atom("_NET_WM_STRUT") _DESKTOP = display.intern_atom("_NET_WM_DESKTOP") _PID = display.intern_atom("_NET_WM_PID") window.change_property(_PSTRUT, _CARD, 32, position) window.change_property(_STRUT, _CARD, 32, position[:4]) window.change_property(_DESKTOP, _CARD, 32, [0xFFFFFFFF]) window.change_property(_PID, _CARD, 32, [os.getpid()]) _ATOM = display.intern_atom("ATOM") _TYPE = display.intern_atom("_NET_WM_WINDOW_TYPE") _DOCK = display.intern_atom("_NET_WM_WINDOW_TYPE_DOCK") window.change_property(_TYPE, _ATOM, 32, [_DOCK]) return window def draw_text(window, gc, x, y, text): ''' Draw a text on a window @param window The window @param gc The window's graphics context @param x:int The left position of the text @param y:int The Y position of the bottom of the text @param text:str The text to draw ''' text_ = text.encode('utf-16')[2:] text = [] for i in range(len(text_)): if (i & 1) == 0: text.append(text_[i]) else: text[-1] |= text_[i] << 8 window.image_text_16(gc, x, y, text) def close_x(): ''' Closes the connection to X, but flushes it first ''' global display display.flush() display.close()