1
0

ArkGraphics.swift 11 KB

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