improve i3: xrandr-toggle, named workspaces
This commit is contained in:
parent
f4f9afacab
commit
ea16c9e60d
73
.i3/config
73
.i3/config
|
@ -26,6 +26,8 @@ font pango:DejaVu Sans Mono 8
|
|||
# Use Mouse+$mod to drag floating windows to their wanted position
|
||||
floating_modifier $mod
|
||||
|
||||
hide_edge_borders both # none|vertical|horizontal|both
|
||||
|
||||
# start a terminal
|
||||
bindsym $mod+Return exec i3-sensible-terminal
|
||||
|
||||
|
@ -80,40 +82,61 @@ bindsym $mod+Shift+space floating toggle
|
|||
|
||||
# change focus between tiling / floating windows
|
||||
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
|
||||
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
|
||||
bindsym $mod+1 workspace 1
|
||||
bindsym $mod+2 workspace 2
|
||||
bindsym $mod+3 workspace 3
|
||||
bindsym $mod+4 workspace 4
|
||||
bindsym $mod+5 workspace 5
|
||||
bindsym $mod+1 workspace $WS1
|
||||
bindsym $mod+2 workspace $WS2
|
||||
bindsym $mod+3 workspace $WS3
|
||||
bindsym $mod+4 workspace $WS4
|
||||
bindsym $mod+5 workspace $WS5
|
||||
bindsym $mod+6 workspace 6
|
||||
bindsym $mod+7 workspace 7
|
||||
bindsym $mod+8 workspace 8
|
||||
bindsym $mod+9 workspace 9
|
||||
bindsym $mod+9 workspace $WS9
|
||||
bindsym $mod+0 workspace 10
|
||||
|
||||
# move focused container to workspace
|
||||
bindsym $mod+Shift+1 move container to workspace 1
|
||||
bindsym $mod+Shift+2 move container to workspace 2
|
||||
bindsym $mod+Shift+3 move container to workspace 3
|
||||
bindsym $mod+Shift+4 move container to workspace 4
|
||||
bindsym $mod+Shift+5 move container to workspace 5
|
||||
bindsym $mod+Shift+1 move container to workspace $WS1
|
||||
bindsym $mod+Shift+2 move container to workspace $WS2
|
||||
bindsym $mod+Shift+3 move container to workspace $WS3
|
||||
bindsym $mod+Shift+4 move container to workspace $WS4
|
||||
bindsym $mod+Shift+5 move container to workspace $WS5
|
||||
bindsym $mod+Shift+6 move container to workspace 6
|
||||
bindsym $mod+Shift+7 move container to workspace 7
|
||||
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
|
||||
|
||||
|
||||
# audio controls
|
||||
bindsym XF86AudioRaiseVolume exec amixer set Master 5+ #increase sound volume
|
||||
bindsym XF86AudioLowerVolume exec amixer set Master 5- #decrease sound volume
|
||||
|
@ -169,16 +192,10 @@ bar {
|
|||
# run the merge for good colors
|
||||
#exec xrdb -merge /home/jessie/.Xresources
|
||||
#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
|
||||
exec --no-startup-id xrandr --output LVDS1 --left-of 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 2; exec emacs24'
|
||||
exec --no-startup-id i3-msg 'workspace $WS1; exec iceweasel;'
|
||||
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'
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue