diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-11-15 13:47:09 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-11-15 13:58:34 +0900 |
| commit | a91920310d20e1fc472dea044fce8fd9438835df (patch) | |
| tree | 378cba47d8586e798130a2855f91a685e05244a3 /internal/wayland/conn.go | |
| parent | 16e674782abbda8c85eac2e7d0c5c22e8cfbf5de (diff) | |
internal: relocate packages
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/wayland/conn.go')
| -rw-r--r-- | internal/wayland/conn.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/internal/wayland/conn.go b/internal/wayland/conn.go new file mode 100644 index 00000000..c55fe704 --- /dev/null +++ b/internal/wayland/conn.go @@ -0,0 +1,122 @@ +package wayland + +import ( + "errors" + "net" + "os" + "runtime" + "sync" + "syscall" +) + +// Conn represents a connection to the wayland display server. +type Conn struct { + conn *net.UnixConn + + done chan struct{} + doneOnce sync.Once + + mu sync.Mutex +} + +// Attach connects Conn to a wayland socket. +func (c *Conn) Attach(p string) (err error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn != nil { + return errors.New("socket already attached") + } + + c.conn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: p, Net: "unix"}) + return +} + +// Close releases resources and closes the connection to the wayland compositor. +func (c *Conn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.done == nil { + return errors.New("no socket bound") + } + + c.doneOnce.Do(func() { + c.done <- struct{}{} + <-c.done + }) + + // closed by wayland + runtime.SetFinalizer(c.conn, nil) + return nil +} + +// Bind binds the new socket to pathname. +func (c *Conn) Bind(pathname, appID, instanceID string) (*os.File, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn == nil { + return nil, errors.New("socket not attached") + } + if c.done != nil { + return nil, errors.New("socket already bound") + } + + if rc, err := c.conn.SyscallConn(); err != nil { + // unreachable + return nil, err + } else { + c.done = make(chan struct{}) + return bindRawConn(c.done, rc, pathname, appID, instanceID) + } +} + +func bindRawConn(done chan struct{}, rc syscall.RawConn, p, appID, instanceID string) (*os.File, error) { + var syncPipe [2]*os.File + + if r, w, err := os.Pipe(); err != nil { + return nil, err + } else { + syncPipe[0] = r + syncPipe[1] = w + } + + setupDone := make(chan error, 1) // does not block with c.done + + go func() { + if err := rc.Control(func(fd uintptr) { + // prevent runtime from closing the read end of sync fd + runtime.SetFinalizer(syncPipe[0], nil) + + // allow the Bind method to return after setup + setupDone <- bind(fd, p, appID, instanceID, syncPipe[0].Fd()) + close(setupDone) + + // keep socket alive until done is requested + <-done + runtime.KeepAlive(syncPipe[1]) + }); err != nil { + setupDone <- err + } + + // notify Close that rc.Control has returned + close(done) + }() + + // return write end of the pipe + return syncPipe[1], <-setupDone +} + +func bind(fd uintptr, p, appID, instanceID string, syncFd uintptr) error { + // ensure p is available + if f, err := os.Create(p); err != nil { + return err + } else if err = f.Close(); err != nil { + return err + } else if err = os.Remove(p); err != nil { + return err + } + + return bindWaylandFd(p, fd, appID, instanceID, syncFd) +} |
