ArkGraphics.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import Foundation
  2. #if canImport(Glibc)
  3. import Glibc
  4. #endif
  5. // -
  6. // ArkGraphics — Display Client Library
  7. // -
  8. // Client-side library that connects to the ArkOS Display Daemon
  9. // via /dev/display.sock and sends rendering commands.
  10. //
  11. // This is the rendering API used by all ArkOS UI applications.
  12. // Commands are sent as packed binary structs over a Unix domain
  13. // socket for minimal overhead.
  14. //
  15. // Usage:
  16. // let gfx = ArkGraphics()
  17. // gfx.clear(r: 30, g: 30, b: 35)
  18. // gfx.fillRect(x: 100, y: 100, w: 200, h: 50, r: 0, g: 120, b: 215)
  19. // gfx.drawLine(x1: 10, y1: 10, x2: 200, y2: 200, r: 255, g: 255, b: 255)
  20. // gfx.pan() // Present to screen
  21. //
  22. // -
  23. // - Wire Protocol -
  24. /// Packed command structure sent to the display daemon.
  25. /// Total size: 21 bytes (1 + 4*4 + 4*1).
  26. private struct DisplayCommand {
  27. var cmd: UInt8 // Command ID (1=fillRect, 2=pan, 3=clear)
  28. var x: Int32 // X coordinate (or 0 for non-spatial commands)
  29. var y: Int32 // Y coordinate
  30. var w: Int32 // Width
  31. var h: Int32 // Height
  32. var r: UInt8 // Red (0–255)
  33. var g: UInt8 // Green (0–255)
  34. var b: UInt8 // Blue (0–255)
  35. var a: UInt8 // Alpha (0–255, unused by framebuffer)
  36. }
  37. // - Graphics Client -
  38. /// Client-side handle to the display daemon. Manages the socket
  39. /// connection and provides high-level drawing primitives.
  40. // C bridge to bypass varargs issues in Swift's ioctl binding
  41. @_silgen_name("ioctl")
  42. func ioctl_fb(_ fd: Int32, _ request: UInt, _ info: UnsafeMutableRawPointer) -> Int32
  43. public class ArkGraphics {
  44. /// File descriptor for the Unix domain socket connection.
  45. private var fd: Int32 = -1
  46. /// Path to the display daemon socket.
  47. private let socketPath = "/dev/display.sock"
  48. /// Maximum connection retry attempts on initialization.
  49. private let maxRetries = 10
  50. private let retryDelay: useconds_t = 500_000 // 500ms
  51. public var screenWidth: Int = 1024
  52. public var screenHeight: Int = 768
  53. /// Creates a new graphics client and connects to the display daemon.
  54. /// Retries up to 10 times with 500ms delay (total 5s) to allow the
  55. /// daemon time to start during boot.
  56. public init() {
  57. // Query real resolution from fb0
  58. let fb_fd = open("/dev/fb0", O_RDONLY)
  59. if fb_fd >= 0 {
  60. // Memory layout of fb_var_screeninfo (ABI compatible with Linux)
  61. struct fb_var_screeninfo {
  62. var xres: UInt32 = 0
  63. var yres: UInt32 = 0
  64. var xres_virtual: UInt32 = 0
  65. var yres_virtual: UInt32 = 0
  66. var xoffset: UInt32 = 0
  67. var yoffset: UInt32 = 0
  68. var bits_per_pixel: UInt32 = 0
  69. var grayscale: UInt32 = 0
  70. var red: (UInt32, UInt32, UInt32) = (0,0,0)
  71. var green: (UInt32, UInt32, UInt32) = (0,0,0)
  72. var blue: (UInt32, UInt32, UInt32) = (0,0,0)
  73. var transp: (UInt32, UInt32, UInt32) = (0,0,0)
  74. var nonstd: UInt32 = 0
  75. var activate: UInt32 = 0
  76. var height: UInt32 = 0
  77. var width: UInt32 = 0
  78. var accel_flags: UInt32 = 0
  79. var pixclock: UInt32 = 0
  80. var left_margin: UInt32 = 0
  81. var right_margin: UInt32 = 0
  82. var upper_margin: UInt32 = 0
  83. var lower_margin: UInt32 = 0
  84. var hsync_len: UInt32 = 0
  85. var vsync_len: UInt32 = 0
  86. var sync: UInt32 = 0
  87. var vmode: UInt32 = 0
  88. var reserved: (UInt32, UInt32, UInt32, UInt32, UInt32, UInt32) = (0, 0, 0, 0, 0, 0)
  89. }
  90. var vinfo = fb_var_screeninfo()
  91. let FBIOGET_VSCREENINFO: UInt = 0x4600
  92. if ioctl_fb(fb_fd, FBIOGET_VSCREENINFO, &vinfo) >= 0 {
  93. self.screenWidth = Int(vinfo.xres)
  94. self.screenHeight = Int(vinfo.yres)
  95. }
  96. close(fb_fd)
  97. }
  98. for attempt in 1...maxRetries {
  99. if tryConnect() {
  100. return
  101. }
  102. if attempt < maxRetries {
  103. usleep(retryDelay)
  104. }
  105. }
  106. print("ArkGraphics: WARNING — Failed to connect to display daemon after \(maxRetries) attempts")
  107. }
  108. deinit {
  109. if fd >= 0 { close(fd) }
  110. }
  111. // - Connection Management -
  112. /// Attempts to connect to the display daemon socket.
  113. /// Returns true on success, false on failure.
  114. private func tryConnect() -> Bool {
  115. let sock = socket(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0)
  116. if sock < 0 { return false }
  117. var addr = sockaddr_un()
  118. addr.sun_family = sa_family_t(AF_UNIX)
  119. let pathBytes = socketPath.utf8CString
  120. withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in
  121. let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self)
  122. for i in 0..<min(pathBytes.count, 108) {
  123. rawPtr[i] = pathBytes[i]
  124. }
  125. }
  126. let addrSize = MemoryLayout<sockaddr_un>.size
  127. let connectResult = withUnsafePointer(to: &addr) { addrPtr in
  128. addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in
  129. connect(sock, saPtr, socklen_t(addrSize))
  130. }
  131. }
  132. if connectResult < 0 {
  133. close(sock)
  134. return false
  135. }
  136. self.fd = sock
  137. return true
  138. }
  139. /// Returns true if connected to the display daemon.
  140. public var isConnected: Bool {
  141. return fd >= 0
  142. }
  143. // - Rendering Commands -
  144. /// Fills a rectangle with a solid RGB color.
  145. ///
  146. /// - Parameters:
  147. /// - x: Left edge in pixels
  148. /// - y: Top edge in pixels
  149. /// - w: Width in pixels
  150. /// - h: Height in pixels
  151. /// - r: Red component (0–255)
  152. /// - g: Green component (0–255)
  153. /// - b: Blue component (0–255)
  154. public func fillRectRGB(x: Int, y: Int, w: Int, h: Int,
  155. r: UInt8, g: UInt8, b: UInt8) {
  156. sendCommand(DisplayCommand(
  157. cmd: 1, x: Int32(x), y: Int32(y), w: Int32(w), h: Int32(h),
  158. r: r, g: g, b: b, a: 255
  159. ))
  160. }
  161. /// Clears the entire screen to a solid RGB color.
  162. public func clear(r: UInt8, g: UInt8, b: UInt8) {
  163. sendCommand(DisplayCommand(
  164. cmd: 3, x: 0, y: 0, w: 0, h: 0,
  165. r: r, g: g, b: b, a: 255
  166. ))
  167. }
  168. /// Presents the current back buffer to the screen (page flip).
  169. /// Must be called after all drawing commands to make them visible.
  170. public func pan() {
  171. sendCommand(DisplayCommand(
  172. cmd: 2, x: 0, y: 0, w: 0, h: 0,
  173. r: 0, g: 0, b: 0, a: 0
  174. ))
  175. }
  176. /// Draws a horizontal line of 1 pixel height.
  177. public func drawHLine(x: Int, y: Int, w: Int,
  178. r: UInt8, g: UInt8, b: UInt8) {
  179. fillRectRGB(x: x, y: y, w: w, h: 1, r: r, g: g, b: b)
  180. }
  181. /// Draws a vertical line of 1 pixel width.
  182. public func drawVLine(x: Int, y: Int, h: Int,
  183. r: UInt8, g: UInt8, b: UInt8) {
  184. fillRectRGB(x: x, y: y, w: 1, h: h, r: r, g: g, b: b)
  185. }
  186. /// Draws a rectangle outline (border only, no fill).
  187. ///
  188. /// - Parameters:
  189. /// - thickness: Border width in pixels (default 1)
  190. public func drawRectOutline(x: Int, y: Int, w: Int, h: Int,
  191. r: UInt8, g: UInt8, b: UInt8,
  192. thickness: Int = 1) {
  193. // Top edge
  194. fillRectRGB(x: x, y: y, w: w, h: thickness, r: r, g: g, b: b)
  195. // Bottom edge
  196. fillRectRGB(x: x, y: y + h - thickness, w: w, h: thickness, r: r, g: g, b: b)
  197. // Left edge
  198. fillRectRGB(x: x, y: y, w: thickness, h: h, r: r, g: g, b: b)
  199. // Right edge
  200. fillRectRGB(x: x + w - thickness, y: y, w: thickness, h: h, r: r, g: g, b: b)
  201. }
  202. // - Internal Transport -
  203. /// Sends a packed command struct to the display daemon.
  204. private func sendCommand(_ cmd: DisplayCommand) {
  205. guard fd >= 0 else { return }
  206. var buffer = [UInt8](repeating: 0, count: 21)
  207. buffer[0] = cmd.cmd
  208. var x = cmd.x
  209. var y = cmd.y
  210. var w = cmd.w
  211. var h = cmd.h
  212. buffer.withUnsafeMutableBufferPointer { ptr in
  213. guard let baseAddress = ptr.baseAddress else { return }
  214. memcpy(baseAddress + 1, &x, 4)
  215. memcpy(baseAddress + 5, &y, 4)
  216. memcpy(baseAddress + 9, &w, 4)
  217. memcpy(baseAddress + 13, &h, 4)
  218. }
  219. buffer[17] = cmd.r
  220. buffer[18] = cmd.g
  221. buffer[19] = cmd.b
  222. buffer[20] = cmd.a
  223. buffer.withUnsafeBufferPointer { ptr in
  224. var totalWritten = 0
  225. while totalWritten < 21 {
  226. let written = write(fd, ptr.baseAddress! + totalWritten, 21 - totalWritten)
  227. if written <= 0 { break }
  228. totalWritten += written
  229. }
  230. }
  231. }
  232. }