terminal.go RawHistoryFolder
1package core
2
3import (
4 "crypto/rand"
5 "slices"
6 "time"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// The terminal is the desktop: a workspace on screen is never empty. When
12// one is, core spawns the configured terminal with a SlotEnv token, and the
13// window carrying it fills that workspace, wherever the focus is by then.
14
15// termRetry is the least time between two terminals for one workspace: a
16// terminal that fails, or maps and exits at once, is not respawned in a loop.
17const termRetry = 5 * time.Second
18
19type termSpawn struct {
20 w *Workspace
21 at time.Time
22}
23
24// keepsTerminal reports whether empty workspaces get a terminal.
25func (c *Core) keepsTerminal() bool { return c.ch.Terminal && len(c.cfg.Terminal.Command) > 0 }
26
27// fillEmpty returns the terminals to spawn for empty workspaces on screen.
28// Workspaces with slots are left to them.
29func (c *Core) fillEmpty() []ports.SpawnRequest {
30 now := time.Now()
31 // Expired spawns go first, whatever is on screen: pending tokens keep
32 // wayland reading /proc on every map.
33 for token, t := range c.terms {
34 if now.Sub(t.at) > termRetry {
35 delete(c.terms, token)
36 }
37 }
38 if !c.keepsTerminal() {
39 return nil
40 }
41 var reqs []ports.SpawnRequest
42 for _, s := range c.screens {
43 w := s.mon.Current()
44 if s.name() == "" || !w.empty() || c.hasSlots(w) || c.termPending(w) || now.Sub(w.termAt) < termRetry {
45 continue
46 }
47 token := rand.Text()
48 w.termAt = now
49 c.terms[token] = &termSpawn{w: w, at: now}
50 reqs = append(reqs, ports.SpawnRequest{Argv: slices.Clone(c.cfg.Terminal.Command), Env: []string{ports.SlotEnv + "=" + token}})
51 }
52 return reqs
53}
54
55// termPending reports whether a terminal spawn waits for w.
56func (c *Core) termPending(w *Workspace) bool {
57 for _, t := range c.terms {
58 if t.w == w {
59 return true
60 }
61 }
62 return false
63}
64
65func (c *Core) hasSlots(w *Workspace) bool {
66 for key := range c.slots {
67 if w.Name != "" && key.workspace == w.Name {
68 return true
69 }
70 }
71 return false
72}
73
74// placeTerminal puts the window of a pending terminal on its workspace. It
75// returns false for any other window, or when the workspace is gone.
76func (c *Core) placeTerminal(id WindowID, token string) bool {
77 t := c.terms[token]
78 if t == nil {
79 return false
80 }
81 delete(c.terms, token)
82 for _, s := range c.screens {
83 if slices.Contains(s.mon.all(), t.w) {
84 t.w.AddWindow(id)
85 s.mon.normalize()
86 return true
87 }
88 }
89 return false
90}