improve i3: xrandr-toggle, named workspaces

This commit is contained in:
Frieder Schlesier 2016-03-17 23:34:24 +01:00
parent f4f9afacab
commit ea16c9e60d
2 changed files with 356 additions and 28 deletions

View File

@ -26,6 +26,8 @@ font pango:DejaVu Sans Mono 8
# Use Mouse+$mod to drag floating windows to their wanted position # Use Mouse+$mod to drag floating windows to their wanted position
floating_modifier $mod floating_modifier $mod
hide_edge_borders both # none|vertical|horizontal|both
# start a terminal # start a terminal
bindsym $mod+Return exec i3-sensible-terminal bindsym $mod+Return exec i3-sensible-terminal
@ -80,40 +82,61 @@ bindsym $mod+Shift+space floating toggle
# change focus between tiling / floating windows # change focus between tiling / floating windows
bindsym $mod+space focus mode_toggle bindsym $mod+space focus mode_toggle
# focus the parent container
bindsym $mod+a focus parent
# focus the child container
#bindsym $mod+d focus child
# float some applications # float some applications
for_window [class="Tor Browser"] floating enable for_window [class="Tor Browser"] floating enable
bindsym $mod+a focus parent # focus the parent container
#bindsym $mod+d focus child # focus the child container
bindsym $mod+p exec xrandr-toggle
# manage workspaces
set $WS1 "1 web"
set $WS2 "2 emacs"
set $WS3 "3 term"
set $WS4 "4 mail"
set $WS5 "5 IDE"
set $WS9 "9 misc"
assign [class="^Iceweasel$"] $WS1
assign [class="^emacs$"] $WS2
assign [class="^Eclipse$"] $WS5
assign [class="^Icedove$"] $WS4
workspace $WS1 output VGA1
workspace $WS2 output VGA1
workspace $WS3 output LVDS1
workspace $WS4 output LVDS1
workspace $WS5 output VGA1
workspace $WS9 output LVDS1
# switch to workspace # switch to workspace
bindsym $mod+1 workspace 1 bindsym $mod+1 workspace $WS1
bindsym $mod+2 workspace 2 bindsym $mod+2 workspace $WS2
bindsym $mod+3 workspace 3 bindsym $mod+3 workspace $WS3
bindsym $mod+4 workspace 4 bindsym $mod+4 workspace $WS4
bindsym $mod+5 workspace 5 bindsym $mod+5 workspace $WS5
bindsym $mod+6 workspace 6 bindsym $mod+6 workspace 6
bindsym $mod+7 workspace 7 bindsym $mod+7 workspace 7
bindsym $mod+8 workspace 8 bindsym $mod+8 workspace 8
bindsym $mod+9 workspace 9 bindsym $mod+9 workspace $WS9
bindsym $mod+0 workspace 10 bindsym $mod+0 workspace 10
# move focused container to workspace # move focused container to workspace
bindsym $mod+Shift+1 move container to workspace 1 bindsym $mod+Shift+1 move container to workspace $WS1
bindsym $mod+Shift+2 move container to workspace 2 bindsym $mod+Shift+2 move container to workspace $WS2
bindsym $mod+Shift+3 move container to workspace 3 bindsym $mod+Shift+3 move container to workspace $WS3
bindsym $mod+Shift+4 move container to workspace 4 bindsym $mod+Shift+4 move container to workspace $WS4
bindsym $mod+Shift+5 move container to workspace 5 bindsym $mod+Shift+5 move container to workspace $WS5
bindsym $mod+Shift+6 move container to workspace 6 bindsym $mod+Shift+6 move container to workspace 6
bindsym $mod+Shift+7 move container to workspace 7 bindsym $mod+Shift+7 move container to workspace 7
bindsym $mod+Shift+8 move container to workspace 8 bindsym $mod+Shift+8 move container to workspace 8
bindsym $mod+Shift+9 move container to workspace 9 bindsym $mod+Shift+9 move container to workspace $WS9
bindsym $mod+Shift+0 move container to workspace 10 bindsym $mod+Shift+0 move container to workspace 10
# audio controls # audio controls
bindsym XF86AudioRaiseVolume exec amixer set Master 5+ #increase sound volume bindsym XF86AudioRaiseVolume exec amixer set Master 5+ #increase sound volume
bindsym XF86AudioLowerVolume exec amixer set Master 5- #decrease sound volume bindsym XF86AudioLowerVolume exec amixer set Master 5- #decrease sound volume
@ -169,16 +192,10 @@ bar {
# run the merge for good colors # run the merge for good colors
#exec xrdb -merge /home/jessie/.Xresources #exec xrdb -merge /home/jessie/.Xresources
#exec xrdb -merge /home/jessie/.Xdefaults #exec xrdb -merge /home/jessie/.Xdefaults
bindsym $mod+p exec xrandr --output LVDS1 --left-of VGA1 --auto
bindsym $mod+shift+p exec xrandr --output VGA1 --off
assign [class="^Iceweasel$"] 1
assign [class="^emacs$"] 2
assign [class="^Eclipse$"] 3
assign [class="^Icedove$"] 4
# startup programs # startup programs
exec --no-startup-id xrandr --output LVDS1 --left-of VGA1 --auto exec --no-startup-id xrandr --output LVDS1 --left-of VGA1 --auto
exec --no-startup-id xrandr --output VGA1 --auto exec --no-startup-id xrandr --output VGA1 --auto
exec --no-startup-id i3-msg 'exec iceweasel;' exec --no-startup-id i3-msg 'workspace $WS1; exec iceweasel;'
exec --no-startup-id i3-msg 'workspace 2; exec emacs24' exec --no-startup-id i3-msg 'workspace $WS2; exec emacs24'
exec --no-startup-id i3-msg 'workspace $WS3; exec i3-sensible-terminal; exec i3-sensible-terminal'

311
bin/xrandr-toggle Executable file
View File

@ -0,0 +1,311 @@
#!/usr/bin/python
# Copyright 2009 David Smith. GNU GPLv2 or, at your option, a later version.
"""Fn-F7 toggle script for xrandr.
How does it work?
First, it's stateless, so it runs xrandr -q and parses the output into a
RandRState object. The RandRState object defines attributes and methods to
query and set xrandr options.
Second, it proceeds through the state machine toggling to the next state in
order. The RandRStateMachine controls the transitions (i.e. it defines the
states).
These steps are executed by the RandRToggleCommand that parses command line
arguments, instantiates the RandRState and RandRStateMachine instances, and
handles errors.
This is one of those programs that should have existed eons ago...
"""
# ... it's a bit over engineered, I guess
__author__ = 'david.daniel.smith@gmail.com (David Smith)'
import logging
import subprocess
import re
import sys
def VecSum(*vecs):
# promise to never send me different sized vectors, ok?
size = len(vecs[0])
result = []
for i in range(0, size):
result.append(sum([vec[i] for vec in vecs]))
return result
def VecScale(scalar, vec):
return [el * scalar for el in vec]
class RandRState(object):
"""XRandR state representation."""
# TODO: implement reflections, too, someday
REFLECT_NORMAL = 'reflect_normal'
REFLECT_X = 'reflect_x'
REFLECT_Y = 'reflect_y'
REFLECT_XY = 'reflect_xy'
ROTATE_NORMAL = 'rotate_normal'
ROTATE_INVERTED = 'rotate_inverted'
ROTATE_LEFT = 'rotate_left'
ROTATE_RIGHT = 'rotate_right'
LEFT_OF = 'left_of'
RIGHT_OF = 'right_of'
ABOVE = 'above'
BELOW = 'below'
SAME_AS = 'same_as'
def __init__(self):
"""Constructor."""
self._displays = set() # the list of display names
self._connected = set() # set of connected display names
self._position = {} # display -> (x,y)
self._resolution = {} # display -> (x,y)
def _IterDisplays(self):
return iter(self._displays)
def _SetDisplays(self, displays):
self._displays = set(displays)
displays = property(_IterDisplays, _SetDisplays, None, 'display names')
def AddDisplay(self, display):
self._displays.add(display)
def RemoveDisplay(self, display):
self._displays.remove(display)
def IsConnected(self, display):
return display in self._connected
def SetConnected(self, connected):
self._connected = set(connected)
def NumConnections(self):
return len(self._connected)
def GetPosition(self, display):
result = self._position.get(display, None)
return result and tuple(result) or None
def AddConnected(self, display):
self._connected.add(display)
def RemoveConnected(self, display):
self._connected.remove(display)
def SetPosition(self, display, position):
self._position[display] = tuple(position)
def SetRelativePosition(self, display, relative_display, position):
logging.debug('setting %s %s %s' % (display, position, relative_display))
rel_pos = self.GetPosition(relative_display)
rel_res = self.GetResolution(relative_display)
dis_res = self.GetResolution(display)
if position is self.LEFT_OF:
dis_pos, rel_pos = rel_pos, VecSum((dis_res[0], rel_pos[1]),
(rel_pos[0], 0))
elif position is self.ABOVE:
dis_pos, rel_pos = rel_pos, VecSum((rel_pos[0], dis_res[1]),
(0, rel_pos[1]))
elif position is self.RIGHT_OF:
dis_pos = (rel_res[0], rel_pos[0])
elif position is self.BELOW:
dis_pos = (rel_pos[0], rel_res[1])
elif position is self.SAME_AS:
dis_pos = rel_pos
else:
raise RuntimeError('invalid position')
self.SetPosition(display, dis_pos)
self.SetPosition(relative_display, rel_pos)
def GetResolution(self, display):
result = self._resolution.get(display, None)
return result and tuple(result) or None
def SetResolution(self, display, resolution):
self._resolution[display] = tuple(resolution)
def __str__(self):
result = 'XRandR State:\n'
for display in self.displays:
connected = ['disconnected', 'connected'][self.IsConnected(display)]
geometry = ''
resolution = self.GetResolution(display)
position = self.GetPosition(display)
if resolution is not None:
geometry += '%dx%d' % resolution
if position is not None:
geometry += '+%d+%d' % position
result += ' * ' + ' '.join((display, connected, geometry)) + '\n'
return result.strip()
def Commit(self):
logging.info('Commiting %s' % self)
args = ['xrandr']
for display in self.displays:
if self.IsConnected(display):
args.extend(['--output', display,
'--pos', '%dx%d' % self.GetPosition(display),
'--mode', '%dx%d' % self.GetResolution(display)])
else:
args.extend(['--output', display, '--off'])
logging.debug('running "%s"' % ' '.join(args))
proc = subprocess.Popen(' '.join(args), stderr=subprocess.PIPE, shell=1)
if proc.wait():
error_msg = proc.stderr.read()
logging.error(error_msg)
raise RuntimeError(error_msg)
@classmethod
def ParseXRandROutput(cls, xrandr_output):
screen_re = re.compile(r'^Screen')
display_re = re.compile(r'^(?P<name>\S+)\s+((?P<disconnected>dis))?'
+ r'connected\s+((?P<res_x>\d+)x(?P<res_y>\d+)'
+ r'\+(?P<pos_x>\d+)\+(?P<pos_y>\d+))?')
mode_re = re.compile(r'^\s+(?P<res_x>\d+)x(?P<res_y>\d+)\s+\d+')
state = cls()
line = xrandr_output.readline()
while line:
# look for a display line
match = display_re.search(line)
if not match:
line = xrandr_output.readline()
continue
# parse out the name and connection status
display = match.group('name')
connected = not bool(match.group('disconnected'))
logging.debug('Found %s display %s' %
(['disconnected', 'connected'][connected],
display))
state.AddDisplay(display)
res = tuple((int(match.group(x) or 0) for x in ('res_x', 'res_y')))
pos = tuple((int(match.group(x) or 0) for x in ('pos_x', 'pos_y')))
if connected and res != (0, 0):
state.AddConnected(display)
state.SetResolution(display, res)
state.SetPosition(display, pos)
# skip until the next display line
line = xrandr_output.readline()
while line and not display_re.search(line):
line = xrandr_output.readline()
# else, look for a mode line and set the res to that
else:
while True:
line = xrandr_output.readline()
match = mode_re.match(line)
# if no mode lines, give up
if not match:
break
res = tuple((int(match.group(x)) for x in ('res_x', 'res_y')))
state.AddConnected(display)
state.SetResolution(display, res)
state.SetPosition(display, (0, 0))
# only need one so might as well end the loop here
break
logging.debug('Parsed %s' % state)
return state
class RandRStateMachine(object):
"""State manipulator."""
def __init__(self, state):
"""Constructor."""
self._state = state
def GetCurrentState(self):
return self._state
def NextState(self):
# if we have a clone, then next pair to the left
if self.IsClone():
logging.info('CLONE -> PAIR_LEFT')
self.SetPairExtLeft()
# if we have a pair to the left, then next pair to the right
elif self.IsPairExtLeft():
logging.info('PAIR_LEFT -> PAIR_RIGHT')
self.SetPairExtRight()
# else, just set cloning
else:
logging.info('UNKNOWN (or PAIR_RIGHT) -> CLONE')
self.SetClone()
def IsClone(self):
found_origin_count = 0
for disp in self._state.displays:
if not self._state.IsConnected(disp):
continue
if self._state.GetPosition(disp) == (0, 0):
found_origin_count += 1
return found_origin_count >= 2
def SetClone(self):
for disp in self._state.displays:
logging.info('Cloning display %s' % disp)
if not self._state.IsConnected(disp):
logging.info('Skipping disconnected display %s' % disp)
continue
self._state.SetPosition(disp, (0, 0))
self._state.Commit()
def _IsPairCmp(self):
if self._state.NumConnections() < 2:
return 0
for disp in self._state.displays:
if not self._state.IsConnected(disp):
continue
if disp.startswith('LVDS'):
if self._state.GetPosition(disp) == (0, 0):
return 1
else:
return -1
return 0
def IsPairExtLeft(self):
return self._IsPairCmp() < 0
def IsPairExtRight(self):
return self._IsPairCmp() > 0
def _SetPairExt(self, direction):
lvds = ''
ext = ''
for disp in self._state.displays:
if not self._state.IsConnected(disp):
continue
if disp.startswith('LVDS'):
lvds = disp
else:
ext = disp
self._state.SetRelativePosition(ext, lvds, direction)
self._state.Commit()
def SetPairExtLeft(self):
self._SetPairExt(self._state.LEFT_OF)
def SetPairExtRight(self):
for disp in self._state.displays:
if disp.startswith('LVDS'):
self._state.SetPosition(disp, (0, 0))
self._state.Commit()
self._SetPairExt(self._state.RIGHT_OF)
def main():
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
xrandr = subprocess.Popen('xrandr -q', stdout=subprocess.PIPE, shell=True)
xrandr.wait()
state = RandRState.ParseXRandROutput(xrandr.stdout)
sm = RandRStateMachine(state)
sm.NextState()
if __name__ == '__main__':
main()