ArkGraphicsServer.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. //
  2. // Copyright 2026 Aarav Ravindra Kharade
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. // ═══════════════════════════════════════════════════════════════════
  17. // ARK-Graphics Server (Display + Registry)
  18. // ═══════════════════════════════════════════════════════════════════
  19. // Implements wl_display (object ID 1) and wl_registry.
  20. // Manages global interface registration and client lifecycle.
  21. // ═══════════════════════════════════════════════════════════════════
  22. import ark.sys.libc
  23. // MARK: - Global Interface
  24. /// A registered global interface that clients can bind to.
  25. public struct WaylandGlobal {
  26. public let name: UInt32 // Unique numeric name for this global
  27. public let interface: String // Interface name (e.g., "wl_compositor")
  28. public let version: UInt32 // Interface version
  29. public let factory: (ArkClient, UInt32, UInt32) -> WaylandObject? // (client, id, version) → object
  30. public init(name: UInt32, interface: String, version: UInt32,
  31. factory: @escaping (ArkClient, UInt32, UInt32) -> WaylandObject?) {
  32. self.name = name
  33. self.interface = interface
  34. self.version = version
  35. self.factory = factory
  36. }
  37. }
  38. // MARK: - WlDisplay Object
  39. /// The wl_display server-side object. Always object ID 1 for every client.
  40. public class WlDisplay: WaylandObject {
  41. public let interfaceName = "wl_display"
  42. public weak var server: ArkGraphicsServer?
  43. public init(server: ArkGraphicsServer) {
  44. self.server = server
  45. }
  46. public func handleRequest(client: ArkClient, opcode: UInt16, decoder: inout WaylandDecoder) -> Bool {
  47. guard let req = WlDisplayRequest(rawValue: opcode) else { return false }
  48. switch req {
  49. case .sync:
  50. // Client sends: sync(callback: new_id<wl_callback>)
  51. guard let callbackID = decoder.readNewID() else { return false }
  52. let callback = WlCallback()
  53. _ = client.registerObject(id: callbackID, object: callback)
  54. // Immediately fire the callback with current serial
  55. var encoder = WaylandEncoder()
  56. encoder.writeUInt32(server?.nextSerial() ?? 0)
  57. let event = encoder.buildMessage(objectID: callbackID, opcode: WlCallbackEvent.done.rawValue)
  58. client.queueEvent(event)
  59. // The callback is now consumed — destroy it
  60. client.destroyObject(id: callbackID)
  61. return true
  62. case .getRegistry:
  63. // Client sends: get_registry(registry: new_id<wl_registry>)
  64. guard let registryID = decoder.readNewID() else { return false }
  65. guard let server = server else { return false }
  66. let registry = WlRegistry(server: server)
  67. _ = client.registerObject(id: registryID, object: registry)
  68. // Send all current globals to the new registry
  69. for global in server.globals {
  70. var encoder = WaylandEncoder()
  71. encoder.writeUInt32(global.name)
  72. encoder.writeString(global.interface)
  73. encoder.writeUInt32(global.version)
  74. let event = encoder.buildMessage(objectID: registryID, opcode: WlRegistryEvent.global.rawValue)
  75. client.queueEvent(event)
  76. }
  77. return true
  78. }
  79. }
  80. public func destroy(client: ArkClient) {
  81. // wl_display is never truly destroyed
  82. }
  83. }
  84. // MARK: - WlCallback Object
  85. /// A one-shot callback object (used by wl_display.sync and wl_surface.frame).
  86. public class WlCallback: WaylandObject {
  87. public let interfaceName = "wl_callback"
  88. public func handleRequest(client: ArkClient, opcode: UInt16, decoder: inout WaylandDecoder) -> Bool {
  89. // wl_callback has no requests
  90. return false
  91. }
  92. }
  93. // MARK: - WlRegistry Object
  94. /// The wl_registry object — handles global binding.
  95. public class WlRegistry: WaylandObject {
  96. public let interfaceName = "wl_registry"
  97. public weak var server: ArkGraphicsServer?
  98. public init(server: ArkGraphicsServer) {
  99. self.server = server
  100. }
  101. public func handleRequest(client: ArkClient, opcode: UInt16, decoder: inout WaylandDecoder) -> Bool {
  102. guard let req = WlRegistryRequest(rawValue: opcode) else { return false }
  103. switch req {
  104. case .bind:
  105. // Client sends: bind(name: uint, interface: string, version: uint, id: new_id)
  106. // Note: for wl_registry.bind, the new_id arg includes interface+version
  107. guard let name = decoder.readUInt32(),
  108. let interface = decoder.readString(),
  109. let version = decoder.readUInt32(),
  110. let newID = decoder.readNewID(),
  111. let server = server else { return false }
  112. // Find the global
  113. guard let global = server.globals.first(where: { $0.name == name }) else {
  114. client.sendError(objectID: 1, code: .invalidObject,
  115. message: "No global with name \(name)")
  116. return true
  117. }
  118. // Verify interface matches
  119. guard global.interface == interface else {
  120. client.sendError(objectID: 1, code: .invalidObject,
  121. message: "Global \(name) is \(global.interface), not \(interface)")
  122. return true
  123. }
  124. // Create the bound object
  125. let boundVersion = min(version, global.version)
  126. guard let object = global.factory(client, newID, boundVersion) else {
  127. client.sendError(objectID: 1, code: .noMemory,
  128. message: "Failed to create \(interface)")
  129. return true
  130. }
  131. _ = client.registerObject(id: newID, object: object)
  132. return true
  133. }
  134. }
  135. }
  136. // MARK: - ArkGraphicsServer
  137. /// The top-level display server. Manages globals, clients, and the event loop.
  138. public class ArkGraphicsServer {
  139. public let socket: ArkSocket
  140. public var clients: [Int32: ArkClient] = [:]
  141. public var globals: [WaylandGlobal] = []
  142. public var running: Bool = true
  143. public weak var compositor: ArkCompositor?
  144. /// Monotonically increasing serial number for events.
  145. private var serial: UInt32 = 0
  146. /// Next global name (auto-increment).
  147. private var nextGlobalName: UInt32 = 1
  148. public init?(socketPath: String = "/run/ark-graphics.sock") {
  149. guard let sock = ArkSocket(path: socketPath) else {
  150. return nil
  151. }
  152. self.socket = sock
  153. }
  154. /// Allocates the next event serial number.
  155. public func nextSerial() -> UInt32 {
  156. serial += 1
  157. return serial
  158. }
  159. // MARK: - Global Registration
  160. /// Registers a new global interface.
  161. public func addGlobal(interface: String, version: UInt32,
  162. factory: @escaping (ArkClient, UInt32, UInt32) -> WaylandObject?) {
  163. let name = nextGlobalName
  164. nextGlobalName += 1
  165. let global = WaylandGlobal(name: name, interface: interface,
  166. version: version, factory: factory)
  167. globals.append(global)
  168. // Notify all existing clients' registries
  169. for (_, client) in clients {
  170. for (id, obj) in client.objects {
  171. if obj is WlRegistry {
  172. var encoder = WaylandEncoder()
  173. encoder.writeUInt32(name)
  174. encoder.writeString(interface)
  175. encoder.writeUInt32(version)
  176. let event = encoder.buildMessage(objectID: id, opcode: WlRegistryEvent.global.rawValue)
  177. client.queueEvent(event)
  178. }
  179. }
  180. }
  181. }
  182. // MARK: - Client Management
  183. /// Creates a new client from an accepted connection.
  184. public func addClient(fd: Int32) {
  185. let client = ArkClient(fd: fd, server: self)
  186. // Pre-register wl_display as object ID 1
  187. let display = WlDisplay(server: self)
  188. _ = client.registerObject(id: 1, object: display)
  189. clients[fd] = client
  190. print("ARK-Graphics: Client connected (fd=\(fd))")
  191. }
  192. /// Removes and cleans up a disconnected client.
  193. public func removeClient(fd: Int32) {
  194. if let client = clients.removeValue(forKey: fd) {
  195. // Destroy all client objects
  196. for (_, obj) in client.objects {
  197. obj.destroy(client: client)
  198. }
  199. client.objects.removeAll()
  200. socket.removeFromEpoll(fd: fd)
  201. close(fd)
  202. print("ARK-Graphics: Client disconnected (fd=\(fd))")
  203. }
  204. }
  205. // MARK: - Event Loop
  206. /// Runs a single iteration of the event loop.
  207. /// Returns the events that were processed (for compositor integration).
  208. public func pollOnce(timeout: Int32 = 16) {
  209. let events = socket.wait(timeout: timeout)
  210. for (fd, eventMask) in events {
  211. if fd == socket.listenFD {
  212. // New client connection
  213. if let clientFD = socket.acceptClient() {
  214. addClient(fd: clientFD)
  215. }
  216. } else if let client = clients[fd] {
  217. if eventMask & (EPOLLERR | EPOLLHUP) != 0 {
  218. // Client error or hangup
  219. removeClient(fd: fd)
  220. } else if eventMask & EPOLLIN != 0 {
  221. // Incoming data from client
  222. if let (bytes, fds) = socket.recvMessage(from: fd) {
  223. client.recvBuffer.append(contentsOf: bytes)
  224. client.recvFDs.append(contentsOf: fds)
  225. client.dispatchMessages()
  226. } else {
  227. // recvMessage returned nil — client disconnected
  228. removeClient(fd: fd)
  229. }
  230. }
  231. }
  232. }
  233. // Flush all client event queues
  234. for (_, client) in clients {
  235. if client.alive {
  236. client.flush(socket: socket)
  237. } else {
  238. removeClient(fd: client.fd)
  239. }
  240. }
  241. }
  242. }