1// Package daemon holds vev's server-side session multiplexer use case.
2package daemon
3
4import (
5 "github.com/bnema/vev/internal/domain"
6 "github.com/bnema/vev/internal/protocol"
7 "github.com/bnema/vev/internal/usecase/picker"
8)
9
10// moveSourceLocator captures the exact source of one move at command time. The
11// presenting client never supplies it: only the daemon resolves it, and only
12// for the interaction that captured it.
13type moveSessionLocator struct {
14 ID domain.SessionID
15 Incarnation domain.IncarnationID
16 Name string
17}
18
19type moveSourceLocator struct {
20 Session moveSessionLocator
21 TabID domain.TabStableID
22 PaneID domain.PaneStableID
23 Attachment *attachedClient
24 AttachmentCapability attachmentCapability
25}
26
27func (d *Daemon) movePickerSourceError(source moveSourceLocator) error {
28 if d == nil || source.Session.ID == "" || source.TabID == "" {
29 return domain.UserWarn(domain.NoticeSessionUnavailable, "Source session is no longer available.", errMovePaneInvalid)
30 }
31 sess := d.sessionByID(source.Session.ID)
32 if sess == nil || sess.incarnation != source.Session.Incarnation {
33 return domain.UserWarn(domain.NoticeSessionUnavailable, "Source session is no longer available.", errMovePaneInvalid)
34 }
35 sess.mu.Lock()
36 defer sess.mu.Unlock()
37 if source.AttachmentCapability.ac != nil && !source.AttachmentCapability.currentInSessionLocked(sess) {
38 return domain.UserWarn(domain.NoticeSessionUnavailable, "Source attachment is no longer active.", errMovePaneInvalid)
39 }
40 if source.Attachment != nil && !attachmentRegisteredLocked(sess, source.Attachment) {
41 return domain.UserWarn(domain.NoticeSessionUnavailable, "Source attachment is no longer active.", errMovePaneInvalid)
42 }
43 tb := findMoveTabLocked(sess, source.TabID)
44 if tb == nil {
45 return domain.UserWarn(domain.NoticeSessionUnavailable, "Tab no longer exists.", errMovePaneInvalid)
46 }
47 if source.PaneID == "" {
48 return nil
49 }
50 tb.mu.Lock()
51 defer tb.mu.Unlock()
52 if paneByStableIDLocked(tb, string(source.PaneID)) == nil {
53 return domain.UserWarn(domain.NoticeSessionUnavailable, "Pane no longer exists.", errMovePaneInvalid)
54 }
55 return nil
56}
57
58// commitMovePickerSelection performs the move the owning source authorised.
59// beforeFollow runs once admission proves the move will follow; afterFollow
60// receives the fresh effect on the published destination capability that the
61// move postcommit admits, so a composite follow can report its completion
62// after the committing effect ended.
63func (d *Daemon) commitMovePickerSelection(intent protocol.PickerIntent, source moveSourceLocator, target picker.Target, beforeFollow func() error, afterFollow func(*attachmentEffect)) error {
64 destination := moveSessionLocator{ID: target.Session, Incarnation: target.Incarnation, Name: target.Name}
65 switch intent {
66 case protocol.PickerIntentMovePane:
67 if target.TabID == "" {
68 return errMovePaneInvalid
69 }
70 return d.movePane(movePaneRequest{
71 Follow: true,
72 BeforeFollow: beforeFollow,
73 AfterFollow: afterFollow,
74 Attachment: source.Attachment,
75 AttachmentCapability: source.AttachmentCapability,
76 Source: source.Session,
77 SourceTabID: source.TabID,
78 SourcePaneID: source.PaneID,
79 Destination: destination,
80 DestinationTabID: target.TabID,
81 })
82 case protocol.PickerIntentMoveTab:
83 return d.moveTab(moveTabRequest{
84 Follow: true,
85 BeforeFollow: beforeFollow,
86 AfterFollow: afterFollow,
87 Attachment: source.Attachment,
88 AttachmentCapability: source.AttachmentCapability,
89 Source: source.Session,
90 SourceTabID: source.TabID,
91 Destination: destination,
92 })
93 default:
94 return errMovePaneInvalid
95 }
96}