Skip to content
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
23 changes: 9 additions & 14 deletions cmd/nirilayout/main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package main

import (
"errors"
"flag"
"fmt"
"io/fs"
"nirilayout"
"os"
"path/filepath"
"slices"

"github.com/diamondburned/gotk4/pkg/gio/v2"
Expand All @@ -25,27 +22,25 @@ func main() {
flag.Parse()

var layouts []nirilayout.Layout
var current string

configDir, err := nirilayout.GetNiriConfigDir()
if err == nil {
layouts, err = nirilayout.GatherLayouts(configDir)
}

// Preselect the active layout. Detection works whether nirilayout.kdl is a
// symlink (classic setup) or a regular file (e.g. noctalia 5); if it can't
// be determined we just fall back to the first layout, never an error.
index := 0
if err == nil {
current, err = os.Readlink(filepath.Join(configDir, "nirilayout.kdl"))
if errors.Is(err, fs.ErrNotExist) {
err = nil
current := nirilayout.CurrentLayoutPath(configDir, layouts)
if i := slices.IndexFunc(layouts, func(l nirilayout.Layout) bool {
return l.Path == current
}); i != -1 {
index = i
}
}

index := slices.IndexFunc(layouts, func(l nirilayout.Layout) bool {
return l.Path == current
})
if index == -1 {
index = 0
}

app := gtk.NewApplication("co.calebc.nirilayout", gio.ApplicationDefaultFlags)
app.ConnectActivate(func() {
nirilayout.Run(app, layouts, index, err)
Expand Down
51 changes: 50 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,72 @@ func GatherLayouts(configDir string) ([]Layout, error) {
return layouts, nil
}

// CurrentLayoutPath returns the Path of the layout currently written to
// nirilayout.kdl, or "" if it can't be determined.
//
// SetCurrentLayout now always writes nirilayout.kdl as a regular file, so the
// answer normally comes from matching its contents against the known layout
// files. We still try Readlink first for backward compatibility: older
// nirilayout versions (and the classic setup) left nirilayout.kdl as a symlink
// to the active layout, which a successful Readlink resolves directly. Once the
// user switches layouts under this version, it becomes a regular file.
func CurrentLayoutPath(configDir string, layouts []Layout) string {
path := filepath.Join(configDir, "nirilayout.kdl")

if target, err := os.Readlink(path); err == nil {
if !filepath.IsAbs(target) {
target = filepath.Join(configDir, target)
}
return target
}

// Not a symlink (or an unreadable link): compare file contents instead.
data, err := os.ReadFile(path)
if err != nil {
return ""
}
for _, layout := range layouts {
other, err := os.ReadFile(layout.Path)
if err != nil {
continue
}
if bytes.Equal(data, other) {
return layout.Path
}
}
return ""
}

func SetCurrentLayout(layout Layout) {
configDir, err := GetNiriConfigDir()
if err != nil {
log.Fatal(err)
return
}

// Write the selected layout's contents into nirilayout.kdl as a regular
// file, atomically (temp file + rename). This used to create a symlink,
// but some setups (e.g. noctalia) manage nirilayout.kdl as a regular file
// and never followed a symlink swap, so switching layouts did nothing. A
// plain copy works for every setup: niri's `include` reads the contents
// either way, and CurrentLayoutPath matches the active layout by contents.
data, err := os.ReadFile(layout.Path)
if err != nil {
log.Fatal(err)
return
}

temp := filepath.Join(configDir, fmt.Sprintf("nirilayout-%d.kdl", unix.Getpid()))

err = os.Symlink(layout.Path, temp)
err = os.WriteFile(temp, data, 0o644)
if err != nil {
log.Fatal(err)
return
}

err = os.Rename(temp, filepath.Join(configDir, "nirilayout.kdl"))
if err != nil {
os.Remove(temp)
log.Fatal(err)
return
}
Expand Down
3 changes: 3 additions & 0 deletions nirilayout.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ func Run(app *gtk.Application, layouts []Layout, startIndex int, err error) {
}

for i, layout := range layouts {
if selector == nil {
break
}
button := gtk.NewButton()
b := gtk.NewBox(gtk.OrientationVertical, 8)
container := gtk.NewCenterBox()
Expand Down