| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- //
- // Copyright 2026 Aarav Ravindra Kharade
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- import Foundation
- #if canImport(Glibc)
- import Glibc
- #endif
- // -
- // ArkGraphics — Display Client Library
- // -
- // Client-side library that connects to the ArkOS Display Daemon
- // via /dev/display.sock and sends rendering commands.
- //
- // This is the rendering API used by all ArkOS UI applications.
- // Commands are sent as packed binary structs over a Unix domain
- // socket for minimal overhead.
- //
- // Usage:
- // let gfx = ArkGraphics()
- // gfx.clear(r: 30, g: 30, b: 35)
- // gfx.fillRect(x: 100, y: 100, w: 200, h: 50, r: 0, g: 120, b: 215)
- // gfx.drawLine(x1: 10, y1: 10, x2: 200, y2: 200, r: 255, g: 255, b: 255)
- // gfx.pan() // Present to screen
- //
- // -
- // - Wire Protocol -
- /// Packed command structure sent to the display daemon.
- /// Total size: 25 bytes (1 + 5*4 + 4*1).
- private struct DisplayCommand {
- var cmd: UInt8 // Command ID (1=fillRect, 2=pan, 3=clear, 4=fillRoundedRect)
- var x: Int32 // X coordinate (or 0 for non-spatial commands)
- var y: Int32 // Y coordinate
- var w: Int32 // Width
- var h: Int32 // Height
- var r: UInt8 // Red (0–255)
- var g: UInt8 // Green (0–255)
- var b: UInt8 // Blue (0–255)
- var a: UInt8 // Alpha (0–255)
- var radius: Int32 // Border radius
- }
- // - Graphics Client -
- /// Client-side handle to the display daemon. Manages the socket
- /// connection and provides high-level drawing primitives.
- // C bridge to bypass varargs issues in Swift's ioctl binding
- @_silgen_name("ioctl")
- func ioctl_fb(_ fd: Int32, _ request: UInt, _ info: UnsafeMutableRawPointer) -> Int32
- public class ArkGraphics {
- /// File descriptor for the Unix domain socket connection.
- private var fd: Int32 = -1
- /// Path to the display daemon socket.
- private let socketPath = "/dev/display.sock"
- /// Maximum connection retry attempts on initialization.
- private let maxRetries = 10
- private let retryDelay: useconds_t = 500_000 // 500ms
- public var screenWidth: Int = 1024
- public var screenHeight: Int = 768
- /// Creates a new graphics client and connects to the display daemon.
- /// Retries up to 10 times with 500ms delay (total 5s) to allow the
- /// daemon time to start during boot.
- public init() {
- // Query real resolution from fb0
- let fb_fd = open("/dev/fb0", O_RDONLY)
- if fb_fd >= 0 {
- // Memory layout of fb_var_screeninfo (ABI compatible with Linux)
- struct fb_var_screeninfo {
- var xres: UInt32 = 0
- var yres: UInt32 = 0
- var xres_virtual: UInt32 = 0
- var yres_virtual: UInt32 = 0
- var xoffset: UInt32 = 0
- var yoffset: UInt32 = 0
- var bits_per_pixel: UInt32 = 0
- var grayscale: UInt32 = 0
- var red: (UInt32, UInt32, UInt32) = (0,0,0)
- var green: (UInt32, UInt32, UInt32) = (0,0,0)
- var blue: (UInt32, UInt32, UInt32) = (0,0,0)
- var transp: (UInt32, UInt32, UInt32) = (0,0,0)
- var nonstd: UInt32 = 0
- var activate: UInt32 = 0
- var height: UInt32 = 0
- var width: UInt32 = 0
- var accel_flags: UInt32 = 0
- var pixclock: UInt32 = 0
- var left_margin: UInt32 = 0
- var right_margin: UInt32 = 0
- var upper_margin: UInt32 = 0
- var lower_margin: UInt32 = 0
- var hsync_len: UInt32 = 0
- var vsync_len: UInt32 = 0
- var sync: UInt32 = 0
- var vmode: UInt32 = 0
- var reserved: (UInt32, UInt32, UInt32, UInt32, UInt32, UInt32) = (0, 0, 0, 0, 0, 0)
- }
- var vinfo = fb_var_screeninfo()
- let FBIOGET_VSCREENINFO: UInt = 0x4600
- if ioctl_fb(fb_fd, FBIOGET_VSCREENINFO, &vinfo) >= 0 {
- self.screenWidth = Int(vinfo.xres)
- self.screenHeight = Int(vinfo.yres)
- }
- close(fb_fd)
- }
- for attempt in 1...maxRetries {
- if tryConnect() {
- return
- }
- if attempt < maxRetries {
- usleep(retryDelay)
- }
- }
- print("ArkGraphics: WARNING — Failed to connect to display daemon after \(maxRetries) attempts")
- }
- deinit {
- if fd >= 0 { close(fd) }
- }
- // - Connection Management -
- /// Attempts to connect to the display daemon socket.
- /// Returns true on success, false on failure.
- private func tryConnect() -> Bool {
- let sock = socket(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0)
- if sock < 0 { return false }
- var addr = sockaddr_un()
- addr.sun_family = sa_family_t(AF_UNIX)
- let pathBytes = socketPath.utf8CString
- withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in
- let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self)
- for i in 0..<min(pathBytes.count, 108) {
- rawPtr[i] = pathBytes[i]
- }
- }
- let addrSize = MemoryLayout<sockaddr_un>.size
- let connectResult = withUnsafePointer(to: &addr) { addrPtr in
- addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in
- connect(sock, saPtr, socklen_t(addrSize))
- }
- }
- if connectResult < 0 {
- close(sock)
- return false
- }
- self.fd = sock
- return true
- }
- /// Returns true if connected to the display daemon.
- public var isConnected: Bool {
- return fd >= 0
- }
- // - Rendering Commands -
- /// Fills a rectangle with a solid RGB color.
- ///
- /// - Parameters:
- /// - x: Left edge in pixels
- /// - y: Top edge in pixels
- /// - w: Width in pixels
- /// - h: Height in pixels
- /// - r: Red component (0–255)
- /// - g: Green component (0–255)
- /// - b: Blue component (0–255)
- public func fillRectRGB(x: Int, y: Int, w: Int, h: Int,
- r: UInt8, g: UInt8, b: UInt8) {
- sendCommand(DisplayCommand(
- cmd: 1, x: Int32(x), y: Int32(y), w: Int32(w), h: Int32(h),
- r: r, g: g, b: b, a: 255, radius: 0
- ))
- }
- /// Fills a rectangle with an RGBA color, blending it with the background.
- public func fillRectRGBA(x: Int, y: Int, w: Int, h: Int,
- r: UInt8, g: UInt8, b: UInt8, a: UInt8) {
- sendCommand(DisplayCommand(
- cmd: 1, x: Int32(x), y: Int32(y), w: Int32(w), h: Int32(h),
- r: r, g: g, b: b, a: a, radius: 0
- ))
- }
-
- /// Fills a rounded rectangle with a solid RGB color.
- public func fillRoundedRectRGB(x: Int, y: Int, w: Int, h: Int,
- r: UInt8, g: UInt8, b: UInt8, radius: Int) {
- sendCommand(DisplayCommand(
- cmd: 4, x: Int32(x), y: Int32(y), w: Int32(w), h: Int32(h),
- r: r, g: g, b: b, a: 255, radius: Int32(radius)
- ))
- }
- /// Fills a rounded rectangle with an RGBA color.
- public func fillRoundedRectRGBA(x: Int, y: Int, w: Int, h: Int,
- r: UInt8, g: UInt8, b: UInt8, a: UInt8, radius: Int) {
- sendCommand(DisplayCommand(
- cmd: 4, x: Int32(x), y: Int32(y), w: Int32(w), h: Int32(h),
- r: r, g: g, b: b, a: a, radius: Int32(radius)
- ))
- }
- /// Clears the entire screen to a solid RGB color.
- public func clear(r: UInt8, g: UInt8, b: UInt8) {
- sendCommand(DisplayCommand(
- cmd: 3, x: 0, y: 0, w: 0, h: 0,
- r: r, g: g, b: b, a: 255, radius: 0
- ))
- }
- /// Presents the current back buffer to the screen (page flip).
- /// Must be called after all drawing commands to make them visible.
- public func pan() {
- sendCommand(DisplayCommand(
- cmd: 2, x: 0, y: 0, w: 0, h: 0,
- r: 0, g: 0, b: 0, a: 0, radius: 0
- ))
- }
- /// Draws a horizontal line of 1 pixel height.
- public func drawHLine(x: Int, y: Int, w: Int,
- r: UInt8, g: UInt8, b: UInt8) {
- fillRectRGB(x: x, y: y, w: w, h: 1, r: r, g: g, b: b)
- }
- /// Draws a vertical line of 1 pixel width.
- public func drawVLine(x: Int, y: Int, h: Int,
- r: UInt8, g: UInt8, b: UInt8) {
- fillRectRGB(x: x, y: y, w: 1, h: h, r: r, g: g, b: b)
- }
- /// Draws a rectangle outline (border only, no fill).
- ///
- /// - Parameters:
- /// - thickness: Border width in pixels (default 1)
- public func drawRectOutline(x: Int, y: Int, w: Int, h: Int,
- r: UInt8, g: UInt8, b: UInt8,
- thickness: Int = 1) {
- // Top edge
- fillRectRGB(x: x, y: y, w: w, h: thickness, r: r, g: g, b: b)
- // Bottom edge
- fillRectRGB(x: x, y: y + h - thickness, w: w, h: thickness, r: r, g: g, b: b)
- // Left edge
- fillRectRGB(x: x, y: y, w: thickness, h: h, r: r, g: g, b: b)
- // Right edge
- fillRectRGB(x: x + w - thickness, y: y, w: thickness, h: h, r: r, g: g, b: b)
- }
- // - Internal Transport -
- /// Sends a packed command struct to the display daemon.
- private func sendCommand(_ cmd: DisplayCommand) {
- guard fd >= 0 else { return }
- var buffer = [UInt8](repeating: 0, count: 25)
- buffer[0] = cmd.cmd
-
- var x = cmd.x
- var y = cmd.y
- var w = cmd.w
- var h = cmd.h
- var radius = cmd.radius
-
- buffer.withUnsafeMutableBufferPointer { ptr in
- guard let baseAddress = ptr.baseAddress else { return }
- memcpy(baseAddress + 1, &x, 4)
- memcpy(baseAddress + 5, &y, 4)
- memcpy(baseAddress + 9, &w, 4)
- memcpy(baseAddress + 13, &h, 4)
- memcpy(baseAddress + 21, &radius, 4)
- }
-
- buffer[17] = cmd.r
- buffer[18] = cmd.g
- buffer[19] = cmd.b
- buffer[20] = cmd.a
- buffer.withUnsafeBufferPointer { ptr in
- var totalWritten = 0
- while totalWritten < 25 {
- let written = write(fd, ptr.baseAddress! + totalWritten, 25 - totalWritten)
- if written <= 0 { break }
- totalWritten += written
- }
- }
- }
- }
|