Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi monitor #41

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ On-demand tiling for Openbox, Xfce and other [EWMH Compliant Window Managers](ht

### Features
- Workspace based tiling. You can enable tiling in one workspace and leave others untouched.
- Ships with two simple tiling layouts (Vertical & Horizontal)
- Ships with four simple tiling layouts:
Vertical, Horizontal, Square, and FullScreen
- Customizable gap between tiling windows.
- Autodetection of panels and docks.
- Support for multiple monitors (Square Layout)

### Installation

Expand Down
36 changes: 34 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ type cfg struct {
WindowsToIgnore []string `toml:"ignore"`
Gap int
Proportion float64
HideDecor bool `toml:"remove_decorations"`
HideDecor bool `toml:"remove_decorations"`
MMRegions [][4]int `toml:"multi_region_geometry"`
DefaultLayout uint `toml:"default_layout"`
}

func init() {
Expand Down Expand Up @@ -56,7 +58,16 @@ func configFilePath() string {
return filepath.Join(configFolderPath(), "config.toml")
}

var defaultConfig = `# Window decorations will be removed when tiling if set to true
var defaultConfig = `## General Config

# Startup Layout - preferred layout to begin with
# 0 - Vertical
# 1 - Horizontal
# 2 - Square
# 3 - Full Screen
default_layout = 0

# Window decorations will be removed when tiling if set to true
remove_decorations = false

# Zentile will ignore windows added to this list.
Expand All @@ -70,6 +81,27 @@ gap = 5
# How much to increment the master area size.
proportion = 0.1


## Square Layout Config
# Multiple Monitor Support (optional)
#
# You can use this to describe one or more areas where you want windows to be
# handled separately. This is most commonly because you have multiple monitors
# displaying portions of, but not the full, workarea dimensions.
#
# This is a list of region descriptions in the form (x, y, width, height)
# where x and y represent the Top-Left corner of the region within the workarea
#
# Here is an example with two monitors: one portrait and one landscape, with the
# landscape display at a vertical offset of 630 pixels and to the right edge of the
# portrait display

# multi_region_geometry = [
# [0, 0, 1080, 1920],
# [1080, 630, 1920, 1080]
#]


[keybindings]
# key sequences can have zero or more modifiers and exactly one key.
# example: Control-Shift-t has two modifiers and one key.
Expand Down
3 changes: 3 additions & 0 deletions keybinding.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ func bindKeys(t *tracker) {

k.bind("tile", func() {
ws := workspaces[state.CurrentDesk]
if !ws.IsTiling {
ws.activeLayoutNum = Config.DefaultLayout % uint(len(ws.layouts))
}
ws.IsTiling = true
ws.Tile()
})
Expand Down
1 change: 1 addition & 0 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,6 @@ func WorkAreaDimensions(num uint) (x, y, width, height int) {
y = w.Y
width = int(w.Width)
height = int(w.Height)
// log.Info("workArea ", num, " - X: ", x, " Y: ", y, " W: ", width, " H: ", height)
return
}
21 changes: 20 additions & 1 deletion store.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,28 @@ func (st *Store) DecreaseMaster() {
}

func (st *Store) MakeMaster(c Client) {
if len(st.masters) > 0 && st.masters[0].window.Id == c.window.Id {
// Mastering the first master demotes it
s := st.masters[0]
slen := len(st.slaves)
if slen == 0 {
st.slaves = []Client{s}
st.masters = st.masters[1:]
} else {
st.masters[0], st.slaves[slen-1] = st.slaves[slen-1], st.masters[0]
}
return
}

for i, slave := range st.slaves {
if slave.window.Id == c.window.Id {
st.masters[0], st.slaves[i] = st.slaves[i], st.masters[0]
if len(st.masters) > 0 {
st.masters[0], st.slaves[i] = st.slaves[i], st.masters[0]
} else {
st.masters = []Client{st.slaves[i]}
st.slaves = append(st.slaves[:i], st.slaves[i+1:]...)
}
break
}
}
}
Expand Down
127 changes: 127 additions & 0 deletions vertical_horizonal_layout.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"math"

"github.com/blrsn/zentile/state"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -98,3 +100,128 @@ func (l *HorizontalLayout) Do() {

state.X.Conn().Sync()
}

type SquareLayout struct {
*VertHorz
}

func (l *SquareLayout) Do() {
// intended for lots of small windows
log.Info("Switching to Square Layout")
wx, wy, ww, wh := state.WorkAreaDimensions(l.WorkspaceNum)

gap := Config.Gap

// concatenating all the window clients into a single list
// The master window proportional zoom is not supported in this layout.
// Selecting a window as a master using the hotkey has the effect of
// swapping it with whatever window was currently in the number
// one (top-left) position.
allClients := append(l.masters, l.slaves...)

// sub-regions of the main work area (to describe multiple monitors)
// regions is a slice of (x,y,width,height) arrays
// clients get divided evenly among regions then regions are
// rendered serially
var regions [][4]int = Config.MMRegions

if len(regions) == 0 {
regions = append(regions, [4]int{wx, wy, ww, wh})
}

nregions := len(regions)
segsize := len(allClients) / nregions

for i := 0; i < nregions; i += 1 {
region := regions[i]
rx := region[0]
ry := region[1]
rw := region[2]
rh := region[3]

var regionClients []Client
if i+1 == nregions {
regionClients = allClients[i*segsize:]
} else {
regionClients = allClients[i*segsize : (i+1)*segsize]
}
csize := len(regionClients)

if csize == 0 {
continue
}
cols := int(math.Floor(math.Sqrt(float64(csize))))
rows := cols // default to perfect square

// cols^2 + 2*cols + 1 === (cols + 1)^2
extras := int(math.Ceil(float64(csize)/float64(cols))) - cols // 0..2

// if taller than wide, add additional rows first
// if wider than tall, add additional columns first
if extras > 0 {
if rh >= rw {
rows = rows + 1
} else {
cols = cols + 1
}
}
if extras == 2 {
if rh >= rw {
cols = cols + 1
} else {
rows = rows + 1
}
}

colsize := rw/cols - gap
rowsize := rh/rows - gap

padx := 0
pady := 0

// here is an algo for auto-padding (gap becomes minimum pad)
// i ended up not liking it and figured it would add too
// much complexity to the config, but here it is...

// if rowsize < colsize && cols > 1 {
// padx = (colsize - rowsize) * cols / (cols - 1)
// colsize = rowsize
// for padx > colsize {
// colsize = colsize * 3 / 2
// padx = (rw - colsize*cols) / (cols - 1)
// }
// } else if rowsize > colsize && rows > 1 {
// pady = (rowsize - colsize) * rows / (rows - 1)
// rowsize = colsize
// for pady > rowsize {
// rowsize = rowsize * 3 / 2
// pady = (rh - rowsize*rows) / (rows - 1)
// }
// }

mx := rx
my := ry

log.Info("cols: ", cols, " rows: ", rows, " colsize: ", colsize, " rowsize: ", rowsize, " padx: ", padx, " pady: ", pady)

currcol := 1
for _, c := range regionClients {
if Config.HideDecor {
c.UnDecorate()
}

log.Info("Moving ", c.name(), ": ", " X: ", mx, " Y: ", my)
c.MoveResize(mx, my, colsize, rowsize)

mx = mx + colsize + padx + gap
currcol = currcol + 1
if currcol > cols {
mx = rx
my = my + rowsize + pady + gap
currcol = 1
}
}
}

state.X.Conn().Sync()
}
4 changes: 4 additions & 0 deletions workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func createLayouts(workspaceNum uint) []Layout {
Proportion: 0.5,
WorkspaceNum: workspaceNum,
}},
&SquareLayout{&VertHorz{
Store: buildStore(),
WorkspaceNum: workspaceNum,
}},
&FullScreen{
Store: buildStore(),
WorkspaceNum: workspaceNum,
Expand Down