refactor(core): share spawn dispatch and workspace placement
Core.dispatch publishes the pending flag, sends the requests and cancels the claims of those not issued; spawnEmpty and spawnStash both use it. place handles terminal and stash claims in one case.
- commit
- baa0832f0cb43c99c03cf02c1f891a2da46ed4df
- author
- brice · · 2026-10-14 20:36 UTC
- parent
- f3346fa
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +25 -28
M internal/core/core.go +18 -8
| @@ -1247,19 +1247,29 @@ func (c *Core) spawnEmpty(ctx context.Context) error { | |||
| 1247 | 1247 | } | |
| 1248 | 1248 | first := c.firstTerminalResolved | |
| 1249 | 1249 | terms := c.fillEmpty() | |
| 1250 | - | err := c.publishPending(ctx) | |
| 1251 | - | for i, req := range terms { | |
| 1250 | + | issued, err := c.dispatch(ctx, terms) | |
| 1251 | + | if len(terms) > 0 && issued == 0 { | |
| 1252 | + | c.firstTerminalResolved = first | |
| 1253 | + | } | |
| 1254 | + | return err | |
| 1255 | + | } | |
| 1256 | + | ||
| 1257 | + | // dispatch sends reqs to the launcher and reports how many it issued. | |
| 1258 | + | // Wayland reads claim tokens only while it knows spawns are pending, so the | |
| 1259 | + | // flag is published before the first send. From the first request that is | |
| 1260 | + | // not issued (publication failed, or the gate suppressed the send), the | |
| 1261 | + | // rest are not sent and their claims are cancelled. | |
| 1262 | + | func (c *Core) dispatch(ctx context.Context, reqs []ports.SpawnRequest) (issued int, err error) { | |
| 1263 | + | err = c.publishPending(ctx) | |
| 1264 | + | for i, req := range reqs { | |
| 1252 | 1265 | if err != nil || !c.trySpawn(ctx, req) { | |
| 1253 | - | for _, suppressed := range terms[i:] { | |
| 1266 | + | for _, suppressed := range reqs[i:] { | |
| 1254 | 1267 | c.cancelClaim(suppressed) | |
| 1255 | 1268 | } | |
| 1256 | - | if i == 0 { | |
| 1257 | - | c.firstTerminalResolved = first | |
| 1258 | - | } | |
| 1259 | - | return err | |
| 1269 | + | return i, err | |
| 1260 | 1270 | } | |
| 1261 | 1271 | } | |
| 1262 | - | return err | |
| 1272 | + | return len(reqs), err | |
| 1263 | 1273 | } | |
| 1264 | 1274 | ||
| 1265 | 1275 | // activate brings a window forward for a valid xdg-activation token. The | |
| @@ -1247,19 +1247,29 @@ func (c *Core) spawnEmpty(ctx context.Context) error { | |||||
| 1247 | } | 1247 | } | ||
| 1248 | first := c.firstTerminalResolved | 1248 | first := c.firstTerminalResolved | ||
| 1249 | terms := c.fillEmpty() | 1249 | terms := c.fillEmpty() | ||
| 1250 | - | err := c.publishPending(ctx) | 1250 | + | issued, err := c.dispatch(ctx, terms) |
| 1251 | - | for i, req := range terms { | 1251 | + | if len(terms) > 0 && issued == 0 { |
| 1252 | + | c.firstTerminalResolved = first | |||
| 1253 | + | } | |||
| 1254 | + | return err | |||
| 1255 | + | } | |||
| 1256 | + | ||||
| 1257 | + | // dispatch sends reqs to the launcher and reports how many it issued. | |||
| 1258 | + | // Wayland reads claim tokens only while it knows spawns are pending, so the | |||
| 1259 | + | // flag is published before the first send. From the first request that is | |||
| 1260 | + | // not issued (publication failed, or the gate suppressed the send), the | |||
| 1261 | + | // rest are not sent and their claims are cancelled. | |||
| 1262 | + | func (c *Core) dispatch(ctx context.Context, reqs []ports.SpawnRequest) (issued int, err error) { | |||
| 1263 | + | err = c.publishPending(ctx) | |||
| 1264 | + | for i, req := range reqs { | |||
| 1252 | if err != nil || !c.trySpawn(ctx, req) { | 1265 | if err != nil || !c.trySpawn(ctx, req) { | ||
| 1253 | - | for _, suppressed := range terms[i:] { | 1266 | + | for _, suppressed := range reqs[i:] { |
| 1254 | c.cancelClaim(suppressed) | 1267 | c.cancelClaim(suppressed) | ||
| 1255 | } | 1268 | } | ||
| 1256 | - | if i == 0 { | 1269 | + | return i, err |
| 1257 | - | c.firstTerminalResolved = first | |||
| 1258 | - | } | |||
| 1259 | - | return err | |||
| 1260 | } | 1270 | } | ||
| 1261 | } | 1271 | } | ||
| 1262 | - | return err | 1272 | + | return len(reqs), err |
| 1263 | } | 1273 | } | ||
| 1264 | 1274 | ||||
| 1265 | // activate brings a window forward for a valid xdg-activation token. The | 1275 | // activate brings a window forward for a valid xdg-activation token. The | ||
M internal/core/input.go +1 -9
| @@ -398,15 +398,7 @@ func (c *Core) spawnStash(ctx context.Context, w *Workspace, commands [][]string | |||
| 398 | 398 | for i, argv := range commands { | |
| 399 | 399 | reqs[i] = c.placement.request(argv, spawnTarget{kind: spawnStash, ws: w, at: now}) | |
| 400 | 400 | } | |
| 401 | - | err := c.publishPending(ctx) | |
| 402 | - | for i, req := range reqs { | |
| 403 | - | if err != nil || !c.trySpawn(ctx, req) { | |
| 404 | - | for _, suppressed := range reqs[i:] { | |
| 405 | - | c.cancelClaim(suppressed) | |
| 406 | - | } | |
| 407 | - | break | |
| 408 | - | } | |
| 409 | - | } | |
| 401 | + | _, err := c.dispatch(ctx, reqs) | |
| 410 | 402 | return err | |
| 411 | 403 | } | |
| 412 | 404 | ||
| @@ -398,15 +398,7 @@ func (c *Core) spawnStash(ctx context.Context, w *Workspace, commands [][]string | |||||
| 398 | for i, argv := range commands { | 398 | for i, argv := range commands { | ||
| 399 | reqs[i] = c.placement.request(argv, spawnTarget{kind: spawnStash, ws: w, at: now}) | 399 | reqs[i] = c.placement.request(argv, spawnTarget{kind: spawnStash, ws: w, at: now}) | ||
| 400 | } | 400 | } | ||
| 401 | - | err := c.publishPending(ctx) | 401 | + | _, err := c.dispatch(ctx, reqs) |
| 402 | - | for i, req := range reqs { | |||
| 403 | - | if err != nil || !c.trySpawn(ctx, req) { | |||
| 404 | - | for _, suppressed := range reqs[i:] { | |||
| 405 | - | c.cancelClaim(suppressed) | |||
| 406 | - | } | |||
| 407 | - | break | |||
| 408 | - | } | |||
| 409 | - | } | |||
| 410 | return err | 402 | return err | ||
| 411 | } | 403 | } | ||
| 412 | 404 | ||||
M internal/core/spawn_placement.go +6 -11
| @@ -112,20 +112,15 @@ func (p *spawnPlacement) place(c *Core, v ports.WindowMapped) { | |||
| 112 | 112 | p.expireWorkspaceClaims(c.now()) | |
| 113 | 113 | if target, ok := p.pending[v.Slot]; ok { | |
| 114 | 114 | switch target.kind { | |
| 115 | - | case spawnTerminal: | |
| 115 | + | case spawnTerminal, spawnStash: | |
| 116 | 116 | delete(p.pending, v.Slot) | |
| 117 | 117 | for _, s := range c.screens { | |
| 118 | 118 | if s.mon.has(target.ws) { | |
| 119 | - | target.ws.AddWindow(v.ID) | |
| 120 | - | s.mon.normalize() | |
| 121 | - | return | |
| 122 | - | } | |
| 123 | - | } | |
| 124 | - | case spawnStash: | |
| 125 | - | delete(p.pending, v.Slot) | |
| 126 | - | for _, s := range c.screens { | |
| 127 | - | if s.mon.has(target.ws) { | |
| 128 | - | target.ws.stashSpawned(v.ID) | |
| 119 | + | if target.kind == spawnStash { | |
| 120 | + | target.ws.stashSpawned(v.ID) | |
| 121 | + | } else { | |
| 122 | + | target.ws.AddWindow(v.ID) | |
| 123 | + | } | |
| 129 | 124 | s.mon.normalize() | |
| 130 | 125 | return | |
| 131 | 126 | } | |
| @@ -112,20 +112,15 @@ func (p *spawnPlacement) place(c *Core, v ports.WindowMapped) { | |||||
| 112 | p.expireWorkspaceClaims(c.now()) | 112 | p.expireWorkspaceClaims(c.now()) | ||
| 113 | if target, ok := p.pending[v.Slot]; ok { | 113 | if target, ok := p.pending[v.Slot]; ok { | ||
| 114 | switch target.kind { | 114 | switch target.kind { | ||
| 115 | - | case spawnTerminal: | 115 | + | case spawnTerminal, spawnStash: |
| 116 | delete(p.pending, v.Slot) | 116 | delete(p.pending, v.Slot) | ||
| 117 | for _, s := range c.screens { | 117 | for _, s := range c.screens { | ||
| 118 | if s.mon.has(target.ws) { | 118 | if s.mon.has(target.ws) { | ||
| 119 | - | target.ws.AddWindow(v.ID) | 119 | + | if target.kind == spawnStash { |
| 120 | - | s.mon.normalize() | 120 | + | target.ws.stashSpawned(v.ID) |
| 121 | - | return | 121 | + | } else { |
| 122 | - | } | 122 | + | target.ws.AddWindow(v.ID) |
| 123 | - | } | 123 | + | } |
| 124 | - | case spawnStash: | |||
| 125 | - | delete(p.pending, v.Slot) | |||
| 126 | - | for _, s := range c.screens { | |||
| 127 | - | if s.mon.has(target.ws) { | |||
| 128 | - | target.ws.stashSpawned(v.ID) | |||
| 129 | s.mon.normalize() | 124 | s.mon.normalize() | ||
| 130 | return | 125 | return | ||
| 131 | } | 126 | } | ||