#!/usr/bin/env python3
'''
xpybar – xmobar replacement written in python
Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Mattias Andrée (m@maandreese)
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 <http://www.gnu.org/licenses/>.
'''
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()