diff --git a/common/config.go b/common/config.go index 636c48f..bc9a998 100644 --- a/common/config.go +++ b/common/config.go @@ -16,13 +16,16 @@ import ( var Config ConfigMapper type ConfigMapper struct { - Keybindings map[string]string - WindowsToIgnore [][]string `toml:"ignore"` - Gap int - Division float64 - Proportion float64 - HideDecor bool `toml:"remove_decorations"` - StartupTiling bool `toml:"startup_tiling"` + TilingEnabled bool `toml:"tiling_enabled"` // Tile windows on startup + TilingLayout string `toml:"tiling_layout"` // Tile windows on startup + Proportion float64 `toml:"proportion"` // Master-slave area initial proportion + ProportionMin float64 `toml:"proportion_min"` // Master-slave area minimum proportion + ProportionMax float64 `toml:"proportion_max"` // Master-slave area maximum proportion + ProportionStep float64 `toml:"proportion_step"` // Master-slave area step size proportion + WindowGap int `toml:"window_gap"` // Gap size between windows + WindowDecoration bool `toml:"window_decoration"` // Show window decorations + WindowIgnore [][]string `toml:"window_ignore"` // Regex to ignore windows + Keys map[string]string `toml:"keys"` // Key bindings for shortcuts } func init() { @@ -31,10 +34,12 @@ func init() { } func writeDefaultConfig() { + // Create config folder if _, err := os.Stat(configFolderPath()); os.IsNotExist(err) { os.MkdirAll(configFolderPath(), 0700) } + // Write default config if _, err := os.Stat(configFilePath()); os.IsNotExist(err) { defaultConfig, err := ioutil.ReadFile("config.toml") if err != nil { @@ -44,12 +49,9 @@ func writeDefaultConfig() { } } -func configFilePath() string { - return filepath.Join(configFolderPath(), "config.toml") -} - func configFolderPath() string { var configFolder string + switch runtime.GOOS { case "linux": xdgConfigHome := os.Getenv("XDG_CONFIG_HOME") @@ -64,3 +66,7 @@ func configFolderPath() string { return configFolder } + +func configFilePath() string { + return filepath.Join(configFolderPath(), "config.toml") +} diff --git a/common/corner.go b/common/corner.go index 09dbbc3..35f192e 100644 --- a/common/corner.go +++ b/common/corner.go @@ -1,12 +1,5 @@ package common -type Area struct { - X1 uint - Y1 uint - X2 uint - Y2 uint -} - type Corner struct { Name string // Corner name used in config Enabled bool // Corner events are handled @@ -14,6 +7,13 @@ type Corner struct { Area Area // Area of the corner section } +type Area struct { + X1 uint // Rectangle top left x position + Y1 uint // Rectangle top left y position + X2 uint // Rectangle bottom right x position + Y2 uint // Rectangle bottom right y position +} + func CreateCorner(name string, enabled bool, x1 int, y1 int, x2 int, y2 int) (c Corner) { c = Corner{ Name: name, @@ -33,29 +33,29 @@ func CreateCorner(name string, enabled bool, x1 int, y1 int, x2 int, y2 int) (c func CreateCorners() []Corner { xw, yw, ww, hw := ScreenDimensions() - // TODO: load from config + // TODO: Load from config enabled := true wcs, hcs := 10, 10 wcl, hcl := 100, 100 + // Define corners and positions tl := CreateCorner("top_left", enabled, xw, yw, xw+wcs, yw+hcs) tc := CreateCorner("top_center", enabled, (xw+ww)/2-wcl/2, yw, (xw+ww)/2+wcl/2, yw+hcs) tr := CreateCorner("top_right", enabled, xw+ww-wcs, yw, xw+ww, yw+hcs) - cr := CreateCorner("center_right", enabled, xw+ww-wcs, (yw+hw)/2-hcl/2, xw+ww, (yw+hw)/2+hcl/2) - br := CreateCorner("bottom_right", enabled, xw+ww-wcs, yw+hw-hcs, xw+ww, yw+hw) bc := CreateCorner("bottom_center", enabled, (xw+ww)/2-wcl/2, yw+hw-hcs, (xw+ww)/2+wcl/2, yw+hw) bl := CreateCorner("bottom_left", enabled, xw, yw+hw-hcs, xw+wcs, yw+hw) - cl := CreateCorner("center_left", enabled, xw, (yw+hw)/2-hcl/2, xw+wcs, (yw+hw)/2+hcl/2) return []Corner{tl, tc, tr, cr, br, bc, bl, cl} } -// Check if corner is active func (c *Corner) IsActive(x uint, y uint) bool { x1, y1, x2, y2 := c.Area.X1, c.Area.Y1, c.Area.X2, c.Area.Y2 + + // Check if enabled and inside rectangle c.Active = c.Enabled && x >= x1 && x <= x2 && y >= y1 && y <= y2 + return c.Active } diff --git a/common/state.go b/common/state.go index 0ab61cf..d87d784 100644 --- a/common/state.go +++ b/common/state.go @@ -134,25 +134,6 @@ func ScreenDimensions() (x, y, w, h int) { return } -func checkEwmhCompliance() { - _, err := ewmh.GetEwmhWM(X) - if err != nil { - log.Fatal("Window manager is not EWMH complaint!") - } -} - -func checkFatal(err error) { - if err != nil { - log.Fatal("Error populating state ", err) - } -} - -func checkError(err error) { - if err != nil { - log.Error("Warning populating state ", err) - } -} - func stateUpdate(X *xgbutil.XUtil, e xevent.PropertyNotifyEvent) { var err error @@ -160,6 +141,7 @@ func stateUpdate(X *xgbutil.XUtil, e xevent.PropertyNotifyEvent) { log.Debug("State event ", aname) + // Update common state variables if aname == "_NET_NUMBER_OF_DESKTOPS" { DeskCount, err = ewmh.NumberOfDesktopsGet(X) } else if aname == "_NET_CURRENT_DESKTOP" { @@ -179,3 +161,22 @@ func stateUpdate(X *xgbutil.XUtil, e xevent.PropertyNotifyEvent) { log.Warn("Warning updating state ", err) } } + +func checkEwmhCompliance() { + _, err := ewmh.GetEwmhWM(X) + if err != nil { + log.Fatal("Window manager is not EWMH complaint!") + } +} + +func checkFatal(err error) { + if err != nil { + log.Fatal("Error populating state ", err) + } +} + +func checkError(err error) { + if err != nil { + log.Error("Warning populating state ", err) + } +} diff --git a/config.toml b/config.toml index db9a5f0..1f301f0 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,30 @@ -# The WM_CLASS (class="ignore all", title="but allow main") of the window used for perl regex match. -# You can find WM_CLASS string name by running "xprop WM_CLASS". -ignore = [ +# Tiling will be enabled on application start if set to true. +tiling_enabled = true + +# Initial tiling layout ('vertical', 'horizontal', 'fullscreen') +tiling_layout = "vertical" + +# Initial division of master-slave area. +proportion = 0.6 + +# Minimum division of master-slave area. +proportion_min = 0.1 + +# Maximum division of master-slave area. +proportion_max = 0.9 + +# How much to increment/decrement master-slave area. +proportion_step = 0.05 + +# How much space should be left between windows. +window_gap = 4 + +# Window decorations will be removed if set to false. +window_decoration = true + +# Perl regex to ignore windows (['WM_CLASS', 'WM_NAME'] = ['ignore all windows with this class', 'but allow those with this name']). +# The WM_CLASS string name can be found by running 'xprop WM_CLASS'. +window_ignore = [ ['xf.*', ''], ['nm.*', ''], ['gcr.*', ''], @@ -9,30 +33,11 @@ ignore = [ ['lightdm.*', ''], ['blueman.*', ''], ['pavucontrol.*', ''], - ['thunderbird.*', ''], ['engrampa.*', ''], - ['gimp.*', '.*GIMP'], - ['inkscape.*', '.*Inkscape'], ['firefox.*', '.*Mozilla Firefox'], ] -# Tiling will be enabled on application start if set to true -startup_tiling = true - -# Window decorations will be removed when tiling if set to true -remove_decorations = false - -# Adds spacing between windows -gap = 4 - -# Initial division of master area size. -division = 0.5 - -# How much to increment the master area size. -proportion = 0.1 - - -[keybindings] +[keys] # You can view which keys activate which modifier using the 'xmodmap' program. # Key symbols can be found by pressing keys using the 'xev' program. diff --git a/desktop/tracker.go b/desktop/tracker.go index 6f97830..c01b2a6 100644 --- a/desktop/tracker.go +++ b/desktop/tracker.go @@ -18,8 +18,8 @@ import ( ) type Tracker struct { - Clients map[xproto.Window]store.Client // List of clients that are being tracked. - Workspaces map[uint]*Workspace // List of workspaces used. + Clients map[xproto.Window]store.Client // List of clients that are being tracked + Workspaces map[uint]*Workspace // List of workspaces used } func CreateTracker(ws map[uint]*Workspace) *Tracker { @@ -28,45 +28,13 @@ func CreateTracker(ws map[uint]*Workspace) *Tracker { Workspaces: ws, } + // Init clients xevent.PropertyNotifyFun(t.handleClientUpdates).Connect(common.X, common.X.RootWin()) t.populateClients() return &t } -// Adds window to tracked clients and layouts. -func (tr *Tracker) trackWindow(w xproto.Window) { - if tr.isTracked(w) { - return - } - - // Add new client - c := store.CreateClient(w) - tr.Clients[c.Win.Id] = c - ws := tr.Workspaces[c.Desk] - ws.AddClient(c) - - // Wait with handler attachment, as some applications load saved geometry delayed - time.AfterFunc(time.Millisecond*800, func() { - tr.attachHandlers(&c) - tr.Workspaces[common.CurrentDesk].Tile() - }) -} - -// Remove window from tracked clients and layouts. -func (tr *Tracker) untrackWindow(w xproto.Window) { - if tr.isTracked(w) { - c := tr.Clients[w] - ws := tr.Workspaces[c.Desk] - - // Remove client - ws.RemoveClient(c) - xevent.Detach(common.X, w) - delete(tr.Clients, w) - } -} - -// UpdateClients updates the list of tracked clients with the most up to date list of clients. func (tr *Tracker) populateClients() { // Add trackable windows @@ -81,7 +49,7 @@ func (tr *Tracker) populateClients() { trackable := false for _, w2 := range common.Stacking { if w1 == w2 { - trackable = tr.isTrackable(w1) // true + trackable = tr.isTrackable(w1) break } } @@ -91,72 +59,82 @@ func (tr *Tracker) populateClients() { } } -// isTracked returns true if the window is already tracked. -func (tr *Tracker) isTracked(w xproto.Window) bool { - _, ok := tr.Clients[w] - return ok -} +func (tr *Tracker) trackWindow(w xproto.Window) { + if tr.isTracked(w) { + return + } -// isTrackable returns true if the window should be tracked. -func (tr *Tracker) isTrackable(w xproto.Window) bool { - return !store.IsHidden(w) && !store.IsModal(w) && !store.IsIgnored(w) && store.IsInsideViewPort(w) -} + // Add new client + c := store.CreateClient(w) + tr.Clients[c.Win.Id] = c + ws := tr.Workspaces[c.Desk] + ws.AddClient(c) -// Handle new clients and viewport changes -func (tr *Tracker) handleClientUpdates(X *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { - aname, _ := xprop.AtomName(common.X, ev.Atom) - if aname == "_NET_CLIENT_LIST_STACKING" || aname == "_NET_DESKTOP_VIEWPORT" { - tr.populateClients() + // Wait with handler attachment, as some applications load geometry delayed + time.AfterFunc(1000*time.Millisecond, func() { + tr.attachHandlers(&c) tr.Workspaces[common.CurrentDesk].Tile() + }) +} + +func (tr *Tracker) untrackWindow(w xproto.Window) { + if tr.isTracked(w) { + c := tr.Clients[w] + ws := tr.Workspaces[c.Desk] + + // Remove client + ws.RemoveClient(c) + xevent.Detach(common.X, w) + delete(tr.Clients, w) } } func (tr *Tracker) handleResizeClient(c *store.Client) { - // previous dimensions + // Previous dimensions pGeom := c.CurrentProp.Geom pw, ph := pGeom.Width(), pGeom.Height() - // current dimensions + // Current dimensions cGeom, err := c.Win.DecorGeometry() if err != nil { return } cw, ch := cGeom.Width(), cGeom.Height() - // update dimensions + // Update dimensions success := c.Update() if !success { return } - // re-tile on width or height change + // Re-tile on width or height change dw, dh := 0.0, 0.0 tile := (math.Abs(float64(cw-pw)) > dw || math.Abs(float64(ch-ph)) > dh) - // tile workspace + // Tile workspace if tile { ws := tr.Workspaces[c.Desk] l := ws.ActiveLayout() s := l.GetManager() - // ignore master only windows + // Ignore master only windows if len(s.Slaves) == 0 { return } - // ignore fullscreen windows + // Ignore fullscreen windows if store.IsMaximized(c.Win.Id) { return } - gap := common.Config.Gap - proportion := common.Config.Proportion + proportion := 0.0 + gap := common.Config.WindowGap isMaster := ws.IsMaster(*c) layoutType := l.GetType() _, _, dw, dh := common.DesktopDimensions() - // calculate proportion based on resized window width (TODO: LTR/RTL gap support) + // Calculate proportion based on resized window width (TODO: LTR/RTL gap support) if layoutType == "vertical" { proportion = float64(cw+gap) / float64(dw) if isMaster { @@ -164,7 +142,7 @@ func (tr *Tracker) handleResizeClient(c *store.Client) { } } - // calculate proportion based on resized window height (TODO: LTR/RTL gap support) + // Calculate proportion based on resized window height (TODO: LTR/RTL gap support) if layoutType == "horizontal" { proportion = 1.0 - float64(ch+gap)/float64(dh) if isMaster { @@ -174,7 +152,7 @@ func (tr *Tracker) handleResizeClient(c *store.Client) { log.Debug("Proportion set to ", proportion, " [", c.Class, "]") - // set proportion based on resized window + // Set proportion based on resized window l.SetProportion(proportion) ws.Tile() } @@ -182,6 +160,8 @@ func (tr *Tracker) handleResizeClient(c *store.Client) { func (tr *Tracker) handleMaximizedClient(c *store.Client) { states, _ := ewmh.WmStateGet(common.X, c.Win.Id) + + // Client maximized for _, state := range states { if strings.Contains(state, "_NET_WM_STATE_MAXIMIZED") { ws := tr.Workspaces[c.Desk] @@ -197,6 +177,8 @@ func (tr *Tracker) handleMaximizedClient(c *store.Client) { func (tr *Tracker) handleMinimizedClient(c *store.Client) { states, _ := ewmh.WmStateGet(common.X, c.Win.Id) + + // Client minimized for _, state := range states { if state == "_NET_WM_STATE_HIDDEN" { tr.Workspaces[c.Desk].RemoveClient(*c) @@ -207,27 +189,35 @@ func (tr *Tracker) handleMinimizedClient(c *store.Client) { } func (tr *Tracker) handleDesktopChange(c *store.Client) { + // Remove client from current workspace tr.Workspaces[c.Desk].RemoveClient(*c) - if tr.Workspaces[c.Desk].IsTiling { + if tr.Workspaces[c.Desk].TilingEnabled { tr.Workspaces[c.Desk].Tile() } - success := c.Update() - if !success { - return - } - + // Add client to new workspace tr.Workspaces[c.Desk].AddClient(*c) - if tr.Workspaces[c.Desk].IsTiling { + if tr.Workspaces[c.Desk].TilingEnabled { tr.Workspaces[c.Desk].Tile() } else { c.Restore() } } +func (tr *Tracker) handleClientUpdates(X *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { + aname, _ := xprop.AtomName(common.X, ev.Atom) + + // Client added or workspace changed + if aname == "_NET_CLIENT_LIST_STACKING" || aname == "_NET_DESKTOP_VIEWPORT" { + tr.populateClients() + tr.Workspaces[common.CurrentDesk].Tile() + } +} + func (tr *Tracker) attachHandlers(c *store.Client) { c.Win.Listen(xproto.EventMaskStructureNotify | xproto.EventMaskPropertyChange) + // Attach resize events xevent.ConfigureNotifyFun(func(x *xgbutil.XUtil, ev xevent.ConfigureNotifyEvent) { log.Debug("Client configure event [", c.Class, "]") @@ -238,6 +228,7 @@ func (tr *Tracker) attachHandlers(c *store.Client) { } }).Connect(common.X, c.Win.Id) + // Attach state events xevent.PropertyNotifyFun(func(x *xgbutil.XUtil, ev xevent.PropertyNotifyEvent) { aname, _ := xprop.AtomName(common.X, ev.Atom) log.Debug("Client property event ", aname, " [", c.Class, "]") @@ -246,7 +237,6 @@ func (tr *Tracker) attachHandlers(c *store.Client) { if aname == "_NET_WM_STATE" { tr.handleMaximizedClient(c) tr.handleMinimizedClient(c) - } else if aname == "_NET_WM_DESKTOP" { tr.handleDesktopChange(c) } @@ -255,3 +245,12 @@ func (tr *Tracker) attachHandlers(c *store.Client) { } }).Connect(common.X, c.Win.Id) } + +func (tr *Tracker) isTracked(w xproto.Window) bool { + _, ok := tr.Clients[w] + return ok +} + +func (tr *Tracker) isTrackable(w xproto.Window) bool { + return !store.IsHidden(w) && !store.IsModal(w) && !store.IsIgnored(w) && store.IsInsideViewPort(w) +} diff --git a/desktop/workspace.go b/desktop/workspace.go index 8c879e0..c626fb5 100644 --- a/desktop/workspace.go +++ b/desktop/workspace.go @@ -9,20 +9,30 @@ import ( ) type Workspace struct { - Layouts []Layout - IsTiling bool - ActiveLayoutNum uint + Layouts []Layout // List of vailable layouts + TilingEnabled bool // Tiling is enabled or not + ActiveLayoutNum uint // Active layout index } func CreateWorkspaces() map[uint]*Workspace { workspaces := make(map[uint]*Workspace) for i := uint(0); i < common.DeskCount; i++ { + + // Create layouts for each workspace + layouts := CreateLayouts(i) ws := Workspace{ - Layouts: CreateLayouts(i), - IsTiling: common.Config.StartupTiling, - ActiveLayoutNum: 0, // TODO: add to config + Layouts: layouts, + TilingEnabled: common.Config.TilingEnabled, + } + + // Activate default layout + for i, l := range layouts { + if l.GetType() == common.Config.TilingLayout { + ws.SetLayout(uint(i)) + } } + workspaces[i] = &ws } @@ -45,48 +55,48 @@ func (ws *Workspace) ActiveLayout() Layout { return ws.Layouts[ws.ActiveLayoutNum] } -// Cycle through the available layouts func (ws *Workspace) SwitchLayout() { ws.ActiveLayoutNum = (ws.ActiveLayoutNum + 1) % uint(len(ws.Layouts)) ws.ActiveLayout().Do() } -// Adds client to all the layouts in a workspace func (ws *Workspace) AddClient(c store.Client) { log.Debug("Add client [", c.Class, "]") + + // Add client to all layouts for _, l := range ws.Layouts { l.Add(c) } } -// Removes client from all the layouts in a workspace func (ws *Workspace) RemoveClient(c store.Client) { log.Debug("Remove client [", c.Class, "]") + + // Remove client from all layouts for _, l := range ws.Layouts { l.Remove(c) } } -// Check if client is master in active layout func (ws *Workspace) IsMaster(c store.Client) bool { s := ws.ActiveLayout().GetManager() + + // Check if window is master for _, m := range s.Masters { if c.Win.Id == m.Win.Id { return true } } + return false } -// Tiles the active layout in a workspace func (ws *Workspace) Tile() { - if ws.IsTiling { + if ws.TilingEnabled { ws.ActiveLayout().Do() } } -// Untiles the active layout in a workspace. func (ws *Workspace) UnTile() { - ws.IsTiling = false ws.ActiveLayout().Undo() } diff --git a/input/keybinding.go b/input/keybinding.go index 110ddd3..93d772e 100644 --- a/input/keybinding.go +++ b/input/keybinding.go @@ -11,21 +11,22 @@ import ( log "github.com/sirupsen/logrus" ) -type KeyMapper struct { -} +type KeyMapper struct{} func BindKeys(t *desktop.Tracker) { workspaces := t.Workspaces keybind.Initialize(common.X) k := KeyMapper{} + // Bind keyboard shortcuts k.bind("tile", func() { ws := workspaces[common.CurrentDesk] - ws.IsTiling = true + ws.TilingEnabled = true ws.Tile() }) k.bind("untile", func() { ws := workspaces[common.CurrentDesk] + ws.TilingEnabled = false ws.UnTile() }) k.bind("make_active_window_master", func() { @@ -70,9 +71,9 @@ func BindKeys(t *desktop.Tracker) { func (k KeyMapper) bind(action string, f func()) { err := keybind.KeyPressFun( func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) { - // callback + // Callback f() - }).Connect(common.X, common.X.RootWin(), common.Config.Keybindings[action], true) + }).Connect(common.X, common.X.RootWin(), common.Config.Keys[action], true) if err != nil { log.Warn(err) diff --git a/input/mousebinding.go b/input/mousebinding.go index 0d3029d..f74ff45 100644 --- a/input/mousebinding.go +++ b/input/mousebinding.go @@ -14,13 +14,10 @@ import ( func BindMouse(t *desktop.Tracker) { backgroundTask(common.X, 100, func() { - // query mouse pointer pointer, _ := xproto.QueryPointer(common.X.Conn(), common.X.RootWin()).Reply() x, y := pointer.RootX, pointer.RootY - //log.Trace("Pointer at ", "x=", x, ", y=", y) - - // update hotcorner states + // Check corner states for i := range common.Corners { hc := &common.Corners[i] @@ -28,58 +25,50 @@ func BindMouse(t *desktop.Tracker) { isActive := hc.IsActive(uint(x), uint(y)) if !wasActive && isActive { - // corner was entered log.Debug("Corner at position ", hc.Area, " is hot [", hc.Name, "]") - // get active clients and workspace + // Get active clients and workspace c := t.Clients[common.ActiveWin] ws := t.Workspaces[common.CurrentDesk] - // TODO: load from config - - // execute hotcorner actions + // TODO: Load from config switch hc.Name { case "top_left": - // switch_layout ws.SwitchLayout() case "top_center": - // TODO: top center + // TODO: Add top-center case "top_right": - // make active window master ws.ActiveLayout().MakeMaster(c) ws.Tile() case "center_right": - // TODO: center right + // TODO: Add center-right case "bottom_right": - // increase master ws.ActiveLayout().IncreaseMaster() ws.Tile() case "bottom_center": - // TODO: bottom center + // TODO: Add bottom-center case "bottom_left": - // decrease master ws.ActiveLayout().DecreaseMaster() ws.Tile() case "center_left": - // TODO: center left + // TODO: Add center-left } } else if wasActive && !isActive { - // corner was leaved log.Debug("Corner at position ", hc.Area, " is cold [", hc.Name, "]") } } }) } -// Poll X events in background func backgroundTask(X *xgbutil.XUtil, t time.Duration, f func()) { go func() { - for range time.Tick(time.Millisecond * t) { + // Poll X events in background + for range time.Tick(t * time.Millisecond) { _, err := X.Conn().PollForEvent() if err != nil { continue } - // callback + // Callback f() } }() diff --git a/layout/fullscreen.go b/layout/fullscreen.go index 7891ce1..3b30700 100644 --- a/layout/fullscreen.go +++ b/layout/fullscreen.go @@ -8,9 +8,9 @@ import ( ) type FullscreenLayout struct { - *store.Manager - WorkspaceNum uint - Type string + *store.Manager // Layout store manager + WorkspaceNum uint // Active workspace index + Type string // Layout name } func CreateFullscreenLayout(workspaceNum uint) *FullscreenLayout { @@ -22,10 +22,10 @@ func CreateFullscreenLayout(workspaceNum uint) *FullscreenLayout { } func (l *FullscreenLayout) Do() { - log.Info("Tile ", len(l.All()), " windows with ", l.GetType(), " layout") + log.Info("Tile ", len(l.Clients()), " windows with ", l.GetType(), " layout") - gap := common.Config.Gap - for _, c := range l.All() { + gap := common.Config.WindowGap + for _, c := range l.Clients() { dx, dy, dw, dh := common.DesktopDimensions() c.MoveResize(dx+gap, dy+gap, dw-2*gap, dh-2*gap) } diff --git a/layout/horizontal.go b/layout/horizontal.go index 3e76ef7..9758317 100644 --- a/layout/horizontal.go +++ b/layout/horizontal.go @@ -10,23 +10,23 @@ import ( ) type HorizontalLayout struct { - *store.Manager - Proportion float64 - WorkspaceNum uint - Type string + *store.Manager // Layout store manager + Proportion float64 // Master-slave proportion + WorkspaceNum uint // Active workspace index + Type string // Layout name } func CreateHorizontalLayout(workspaceNum uint) *HorizontalLayout { return &HorizontalLayout{ Manager: store.CreateManager(), - Proportion: common.Config.Division, // TODO: LTR/RTL support + Proportion: common.Config.Proportion, // TODO: LTR/RTL support WorkspaceNum: workspaceNum, Type: "horizontal", } } func (l *HorizontalLayout) Do() { - log.Info("Tile ", len(l.All()), " windows with ", l.GetType(), " layout") + log.Info("Tile ", len(l.Clients()), " windows with ", l.GetType(), " layout") dx, dy, dw, dh := common.DesktopDimensions() msize := len(l.Masters) @@ -36,15 +36,14 @@ func (l *HorizontalLayout) Do() { mh := int(math.Round(float64(dh) * l.Proportion)) sy := my + mh sh := dh - mh - gap := common.Config.Gap + gap := common.Config.WindowGap - asize := len(l.All()) + asize := len(l.Clients()) fsize := l.AllowedMasters - // swap master-slave area for LTR/RTL support (TODO: add to config) - swap := false + ltr := false // TODO: Load from config - if swap && asize > fsize { + if ltr && asize > fsize { mytmp := my mhtmp := mh sytmp := sy @@ -63,7 +62,7 @@ func (l *HorizontalLayout) Do() { } for i, c := range l.Masters { - if common.Config.HideDecor { + if !common.Config.WindowDecoration { c.UnDecorate() } c.MoveResize(gap+dx+i*(mw+gap), my+gap, mw, mh-2*gap) @@ -77,7 +76,7 @@ func (l *HorizontalLayout) Do() { } for i, c := range l.Slaves { - if common.Config.HideDecor { + if !common.Config.WindowDecoration { c.UnDecorate() } c.MoveResize(gap+dx+i*(sw+gap), sy, sw, sh-gap) @@ -102,19 +101,19 @@ func (l *HorizontalLayout) PreviousClient() { } func (l *HorizontalLayout) IncrementProportion() { - precision := 1.0 / common.Config.Proportion - proportion := math.Round(l.Proportion*precision)/precision + common.Config.Proportion + precision := 1.0 / common.Config.ProportionStep + proportion := math.Round(l.Proportion*precision)/precision + common.Config.ProportionStep l.SetProportion(proportion) } func (l *HorizontalLayout) DecrementProportion() { - precision := 1.0 / common.Config.Proportion - proportion := math.Round(l.Proportion*precision)/precision - common.Config.Proportion + precision := 1.0 / common.Config.ProportionStep + proportion := math.Round(l.Proportion*precision)/precision - common.Config.ProportionStep l.SetProportion(proportion) } func (l *HorizontalLayout) SetProportion(p float64) { - l.Proportion = math.Min(math.Max(p, 0.1), 0.9) + l.Proportion = math.Min(math.Max(p, common.Config.ProportionMin), common.Config.ProportionMax) } func (l *HorizontalLayout) GetType() string { diff --git a/layout/vertical.go b/layout/vertical.go index ee048d5..ff270f9 100644 --- a/layout/vertical.go +++ b/layout/vertical.go @@ -10,23 +10,23 @@ import ( ) type VerticalLayout struct { - *store.Manager - Proportion float64 - WorkspaceNum uint - Type string + *store.Manager // Layout store manager + Proportion float64 // Master-slave proportion + WorkspaceNum uint // Active workspace index + Type string // Layout name } func CreateVerticalLayout(workspaceNum uint) *VerticalLayout { return &VerticalLayout{ Manager: store.CreateManager(), - Proportion: 1.0 - common.Config.Division, // TODO: LTR/RTL support + Proportion: 1.0 - common.Config.Proportion, // TODO: LTR/RTL support WorkspaceNum: workspaceNum, Type: "vertical", } } func (l *VerticalLayout) Do() { - log.Info("Tile ", len(l.All()), " windows with ", l.GetType(), " layout") + log.Info("Tile ", len(l.Clients()), " windows with ", l.GetType(), " layout") dx, dy, dw, dh := common.DesktopDimensions() msize := len(l.Masters) @@ -36,15 +36,14 @@ func (l *VerticalLayout) Do() { mw := int(math.Round(float64(dw) * l.Proportion)) sx := mx + mw sw := dw - mw - gap := common.Config.Gap + gap := common.Config.WindowGap - asize := len(l.All()) + asize := len(l.Clients()) fsize := l.AllowedMasters - // swap master-slave area for LTR/RTL support (TODO: add to config) - swap := true + ltr := true // TODO: Load from config - if swap && asize > fsize { + if ltr && asize > fsize { mxtmp := mx mwtmp := mw sxtmp := sx @@ -63,7 +62,7 @@ func (l *VerticalLayout) Do() { } for i, c := range l.Masters { - if common.Config.HideDecor { + if !common.Config.WindowDecoration { c.UnDecorate() } c.MoveResize(mx+gap, gap+dy+i*(mh+gap), mw-2*gap, mh) @@ -77,7 +76,7 @@ func (l *VerticalLayout) Do() { } for i, c := range l.Slaves { - if common.Config.HideDecor { + if !common.Config.WindowDecoration { c.UnDecorate() } c.MoveResize(sx, gap+dy+i*(sh+gap), sw-gap, sh) @@ -102,19 +101,19 @@ func (l *VerticalLayout) PreviousClient() { } func (l *VerticalLayout) IncrementProportion() { - precision := 1.0 / common.Config.Proportion - proportion := math.Round(l.Proportion*precision)/precision + common.Config.Proportion + precision := 1.0 / common.Config.ProportionStep + proportion := math.Round(l.Proportion*precision)/precision + common.Config.ProportionStep l.SetProportion(proportion) } func (l *VerticalLayout) DecrementProportion() { - precision := 1.0 / common.Config.Proportion - proportion := math.Round(l.Proportion*precision)/precision - common.Config.Proportion + precision := 1.0 / common.Config.ProportionStep + proportion := math.Round(l.Proportion*precision)/precision - common.Config.ProportionStep l.SetProportion(proportion) } func (l *VerticalLayout) SetProportion(p float64) { - l.Proportion = math.Min(math.Max(p, 0.1), 0.9) + l.Proportion = math.Min(math.Max(p, common.Config.ProportionMin), common.Config.ProportionMax) } func (l *VerticalLayout) GetType() string { diff --git a/main.go b/main.go index b47d6f3..6177e13 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ func main() { tracker := desktop.CreateTracker(workspaces) // Tile on startup - if common.Config.StartupTiling { + if common.Config.TilingEnabled { for _, ws := range workspaces { ws.Tile() } diff --git a/store/client.go b/store/client.go index 190a73e..c9097f4 100644 --- a/store/client.go +++ b/store/client.go @@ -21,17 +21,17 @@ import ( var UNKNOWN = "" type Client struct { - Win *xwindow.Window - Desk uint // Desktop the client is currently in. - Name string // Window title name. - Class string // Window application name. - CurrentProp Prop // Properties that the client has at the moment. - SavedProp Prop // Properties that the client had before it was tiled. + Win *xwindow.Window // X window object + Desk uint // Desktop the client is currently in + Name string // Client window title name + Class string // Client window application name + CurrentProp Prop // Properties that the client has at the moment + SavedProp Prop // Properties that the client had before tiling } type Prop struct { - Geom xrect.Rect - Decoration bool + Geom xrect.Rect // Client rectangle geometry + Decoration bool // Decoration active or not } func CreateClient(w xproto.Window) (c Client) { @@ -112,6 +112,10 @@ func (c *Client) Update() (success bool) { return true } +func (c Client) Activate() { + ewmh.ActiveWindowReq(common.X, c.Win.Id) +} + func (c Client) Unmaximize() { ewmh.WmStateReq(common.X, c.Win.Id, 0, "_NET_WM_STATE_MAXIMIZED_VERT") ewmh.WmStateReq(common.X, c.Win.Id, 0, "_NET_WM_STATE_MAXIMIZED_HORZ") @@ -137,7 +141,6 @@ func (c Client) Decorate() { }) } -// Restore resizes and decorates window to pre-tiling state. func (c Client) Restore() { c.Decorate() @@ -147,12 +150,6 @@ func (c Client) Restore() { log.Info("Restoring window position x=", geom.X(), ", y=", geom.Y(), " [", c.Class, "]") } -// Activate makes the client the currently active window. -func (c Client) Activate() { - ewmh.ActiveWindowReq(common.X, c.Win.Id) -} - -// Get window info. func GetInfo(w xproto.Window) (class string, name string, desk uint, states []string, hints *motif.Hints) { var err error var wmClass *icccm.WmClass @@ -192,13 +189,11 @@ func GetInfo(w xproto.Window) (class string, name string, desk uint, states []st return class, name, desk, states, hints } -// hasDecoration returns true if the window has client decorations. func HasDecoration(w xproto.Window) bool { _, _, _, _, hints := GetInfo(w) return motif.Decor(hints) } -// isMaximized returns true if the window has been maximized. func IsMaximized(w xproto.Window) bool { class, name, _, states, _ := GetInfo(w) if class == UNKNOWN { @@ -215,7 +210,6 @@ func IsMaximized(w xproto.Window) bool { return false } -// isHidden returns true if the window has been minimized. func IsHidden(w xproto.Window) bool { class, name, _, states, _ := GetInfo(w) if class == UNKNOWN { @@ -232,7 +226,6 @@ func IsHidden(w xproto.Window) bool { return false } -// isModal returns true if the window is a modal dialog. func IsModal(w xproto.Window) bool { class, name, _, states, _ := GetInfo(w) if class == UNKNOWN { @@ -249,24 +242,23 @@ func IsModal(w xproto.Window) bool { return false } -// isIgnored returns true if the window is ignored by config. func IsIgnored(w xproto.Window) bool { class, name, _, _, _ := GetInfo(w) if class == UNKNOWN { return true } - for _, s := range common.Config.WindowsToIgnore { + for _, s := range common.Config.WindowIgnore { conf_class := s[0] conf_name := s[1] reg_class := regexp.MustCompile(strings.ToLower(conf_class)) reg_name := regexp.MustCompile(strings.ToLower(conf_name)) - // ignore all windows with this class... + // Ignore all windows with this class class_match := reg_class.MatchString(strings.ToLower(class)) - // ...except the window with a special name + // But allow the window with a special name name_match := conf_name != "" && reg_name.MatchString(strings.ToLower(name)) if class_match && !name_match { @@ -278,7 +270,6 @@ func IsIgnored(w xproto.Window) bool { return false } -// isInsideViewPort returns true if the window is partially inside viewport. func IsInsideViewPort(w xproto.Window) bool { class, _, desk, _, _ := GetInfo(w) if class == UNKNOWN { diff --git a/store/manager.go b/store/manager.go index 7878d33..f04164c 100644 --- a/store/manager.go +++ b/store/manager.go @@ -7,9 +7,9 @@ import ( ) type Manager struct { - Masters []Client - Slaves []Client - AllowedMasters int + Masters []Client // List of master window clients + Slaves []Client // List of slave window clients + AllowedMasters int // Number of maximal allowed masters } func CreateManager() *Manager { @@ -22,55 +22,44 @@ func CreateManager() *Manager { func (st *Manager) Add(c Client) { if len(st.Masters) < st.AllowedMasters { - st.Masters = addItem(st.Masters, c) + // Fill up master area + st.Masters = addClient(st.Masters, c) } else { - st.Slaves = addItem(st.Slaves, c) + // Add remaining to slave area + st.Slaves = addClient(st.Slaves, c) } } -func addItem(cs []Client, c Client) []Client { - return append([]Client{c}, cs...) -} - func (st *Manager) Remove(c Client) { + if c.Win == nil { + return + } - log.Error("--- len masters 1 --- : ", len(st.Masters), ", id=", c.Win.Id) - log.Error("--- len slaves 1 --- : ", len(st.Slaves), ", id=", c.Win.Id) - - for i, m := range st.Masters { - if m.Win.Id == c.Win.Id { - if len(st.Slaves) > 0 { - log.Error("--- remove 1 --- : ", c) - st.Masters[i] = st.Slaves[0] - st.Slaves = st.Slaves[1:] - } else { - log.Error("--- remove 2 --- : ", c) - st.Masters = removeItem(st.Masters, i) - } - - log.Error("--- len masters 2 --- : ", len(st.Masters)) - log.Error("--- len slaves 2 --- : ", len(st.Slaves)) + log.Info("Remove window [", c.Class, "]") - return + // Remove master window + mi := getIndex(st.Masters, c) + if mi >= 0 { + if len(st.Slaves) > 0 { + st.Masters[mi] = st.Slaves[0] + st.Slaves = st.Slaves[1:] + } else { + st.Masters = removeClient(st.Masters, mi) } + return } - for i, s := range st.Slaves { - if s.Win.Id == c.Win.Id { - log.Error("--- remove 3 --- : ", c) - st.Slaves = removeItem(st.Slaves, i) - log.Error("--- len masters 3 --- : ", len(st.Masters)) - log.Error("--- len slaves 3 --- : ", len(st.Slaves)) - return - } + // Remove slave window + si := getIndex(st.Slaves, c) + if si >= 0 { + st.Slaves = removeClient(st.Slaves, si) + return } } -func removeItem(cs []Client, i int) []Client { - return append(cs[:i], cs[i+1:]...) -} - func (st *Manager) IncreaseMaster() { + + // Increase master area if len(st.Slaves) > 1 { st.AllowedMasters = st.AllowedMasters + 1 st.Masters = append(st.Masters, st.Slaves[0]) @@ -81,11 +70,12 @@ func (st *Manager) IncreaseMaster() { } func (st *Manager) DecreaseMaster() { + + // Decrease master area if len(st.Masters) > 1 { st.AllowedMasters = st.AllowedMasters - 1 - mlen := len(st.Masters) - st.Slaves = append([]Client{st.Masters[mlen-1]}, st.Slaves...) - st.Masters = st.Masters[:mlen-1] + st.Slaves = append([]Client{st.Masters[len(st.Masters)-1]}, st.Slaves...) + st.Masters = st.Masters[:len(st.Masters)-1] } log.Info("Decrease masters to ", st.AllowedMasters) @@ -98,27 +88,26 @@ func (st *Manager) MakeMaster(c Client) { log.Info("Make window master [", c.Class, "]") - for i, master := range st.Masters { - if master.Win.Id == c.Win.Id { - st.Masters[0], st.Masters[i] = st.Masters[i], st.Masters[0] - } + // Swap master with master + mi := getIndex(st.Masters, c) + if mi >= 0 { + st.Masters[0], st.Masters[mi] = st.Masters[mi], st.Masters[0] + return } - for i, slave := range st.Slaves { - if slave.Win.Id == c.Win.Id { - st.Masters[0], st.Slaves[i] = st.Slaves[i], st.Masters[0] - } + // Swap slave with master + si := getIndex(st.Slaves, c) + if si >= 0 { + st.Masters[0], st.Slaves[si] = st.Slaves[si], st.Masters[0] + return } } -func (st *Manager) All() []Client { - return append(st.Masters, st.Slaves...) -} - func (st *Manager) Next() Client { - clients := st.All() + clients := st.Clients() lastIndex := len(clients) - 1 + // Get next window for i, c := range clients { if c.Win.Id == common.ActiveWin { next := i + 1 @@ -133,9 +122,10 @@ func (st *Manager) Next() Client { } func (st *Manager) Previous() Client { - clients := st.All() + clients := st.Clients() lastIndex := len(clients) - 1 + // Get previous window for i, c := range clients { if c.Win.Id == common.ActiveWin { prev := i - 1 @@ -148,3 +138,24 @@ func (st *Manager) Previous() Client { return Client{} } + +func (st *Manager) Clients() []Client { + return append(st.Masters, st.Slaves...) +} + +func addClient(cs []Client, c Client) []Client { + return append([]Client{c}, cs...) +} + +func removeClient(cs []Client, i int) []Client { + return append(cs[:i], cs[i+1:]...) +} + +func getIndex(cs []Client, c Client) int { + for i, m := range cs { + if m.Win.Id == c.Win.Id { + return i + } + } + return -1 +}