tl

Owner: IIIlllIIIllI URL: git@github.com:nyangkosense/tl.git

tl.nim

# tl - tinylauncher
#
# see LICENSE for copyright and license details.
# 
# (c) 2025 sebastian michalk <sebastian.michalk@pm.me>

import posix
import x11/x
import x11/xlib
import x11/xutil
import x11/keysym
import x11/xft
import x11/xrender

# compile-time conf
const
  barH: cint = 56
  fontName = "monospace:size=18"
  prompt = "launch: "
  maxLen = 256

  colBg  = 0xFFFFFF'u32    # white
  colFg  = 0x000000'u32    # black
  colSel = 0xDD6666'u32    # highlight

type
  App = object
    d: PDisplay
    win: Window
    gc: GC
    xftd: PXftDraw
    xftFont: PXftFont
    colFgXft: XftColor
    colSelXft: XftColor
    buf: array[maxLen, char]
    len: int

proc die(msg: string) {.noreturn.} =
  stderr.writeLine msg
  quit(1)

proc draw(app: var App) =
  discard XClearWindow(app.d, app.win)
  discard XSetForeground(app.d, app.gc, colBg)
  let screen = DefaultScreen(app.d)
  discard XFillRectangle(app.d, app.win, app.gc, 0, 0, cuint(DisplayWidth(app.d, screen)), cuint(barH))
  let baseline = barH div 2 + app.xftFont.ascent div 2

  var pExt, iExt: XGlyphInfo
  XftTextExtentsUtf8(app.d, app.xftFont, cast[PFcChar8](prompt.cstring), prompt.len.cint, addr pExt)
  XftTextExtentsUtf8(app.d, app.xftFont, cast[PFcChar8](addr app.buf[0]), app.len.cint, addr iExt)

  XftDrawStringUtf8(app.xftd, addr app.colSelXft, app.xftFont, cint(8), cint(baseline), cast[PFcChar8](prompt.cstring), prompt.len.cint)
  XftDrawStringUtf8(app.xftd, addr app.colFgXft, app.xftFont, cint(8 + pExt.xOff), cint(baseline), cast[PFcChar8](addr app.buf[0]), app.len.cint)
  discard XFlush(app.d)

proc spawn(cmd: cstring) =
  let pid = fork()
  if pid < 0: die("fork failed")
  if pid == 0:
    discard setsid()
    let args = allocCStringArray(@[$cmd])
    discard execvp(cmd, args)
    quit(1)

proc handleKey(app: var App, e: var XKeyEvent) =
  var keybuf: array[32, char]
  var keysym: KeySym = 0
  let n = XLookupString(addr e, cast[cstring](addr keybuf[0]), keybuf.len.cint, addr keysym, nil)

  case keysym
  of XK_Return:
    if app.len > 0:
      app.buf[app.len] = '\0'
      spawn(cast[cstring](addr app.buf[0]))
      quit(0)
  of XK_Escape:
    quit(0)
  of XK_BackSpace:
    if app.len > 0: app.len.dec
  else:
    if n > 0 and app.len + n < maxLen:
      for i in 0 ..< n:
        app.buf[app.len + i] = keybuf[i]
      app.len += n

proc setFloatingHints(app: var App) =
  let atomWindowType = XInternAtom(app.d, "_NET_WM_WINDOW_TYPE".cstring, XBool(0))
  let atomDialog = XInternAtom(app.d, "_NET_WM_WINDOW_TYPE_DIALOG".cstring, XBool(0))
  let atomAtom = XInternAtom(app.d, "ATOM".cstring, XBool(0))
  if atomWindowType != 0 and atomDialog != 0 and atomAtom != 0:
    var types = [atomDialog]
    discard XChangeProperty(app.d, app.win, atomWindowType, atomAtom, 32, PropModeReplace, cast[cstring](addr types[0]), types.len.cint)

  let atomState = XInternAtom(app.d, "_NET_WM_STATE".cstring, XBool(0))
  let atomAbove = XInternAtom(app.d, "_NET_WM_STATE_ABOVE".cstring, XBool(0))
  let atomSticky = XInternAtom(app.d, "_NET_WM_STATE_STICKY".cstring, XBool(0))
  var states: array[2, Atom]
  var count = 0
  if atomAbove != 0:
    states[count] = atomAbove
    inc count
  if atomSticky != 0:
    states[count] = atomSticky
    inc count
  if atomState != 0 and atomAtom != 0 and count > 0:
    discard XChangeProperty(app.d, app.win, atomState, atomAtom, 32, PropModeReplace, cast[cstring](addr states[0]), count.cint)

proc main() =
  var app: App
  app.d = XOpenDisplay(nil)
  if app.d.isNil: die("cannot open display")

  let screen = DefaultScreen(app.d)
  let sw = DisplayWidth(app.d, screen)
  let sh = DisplayHeight(app.d, screen)

  let w: cuint = cuint(sw div 4)
  let h: cuint = cuint(barH)
  let x: cint = cint((sw - cint(w)) div 2)
  let y: cint = cint((sh - cint(h)) div 2)

  app.win = XCreateSimpleWindow(app.d, RootWindow(app.d, screen), x, y, w, h, 0, colFg, colBg)
  discard XSelectInput(app.d, app.win, ExposureMask or KeyPressMask)
  discard XStoreName(app.d, app.win, "zmen-nim")
  setFloatingHints(app)
  discard XMapWindow(app.d, app.win)

  app.gc = XCreateGC(app.d, app.win, 0, nil)
  let cmap = DefaultColormap(app.d, screen)
  app.xftFont = XftFontOpenName(app.d, screen, fontName.cstring)
  if app.xftFont.isNil: die("failed to load xft font")
  if XftColorAllocName(app.d, DefaultVisual(app.d, screen), cmap, "#000000".cstring, addr app.colFgXft) == 0:
    die("failed to alloc fg color")
  if XftColorAllocName(app.d, DefaultVisual(app.d, screen), cmap, "#dd6666".cstring, addr app.colSelXft) == 0:
    die("failed to alloc sel color")
  app.xftd = XftDrawCreate(app.d, app.win, DefaultVisual(app.d, screen), cmap)
  if app.xftd.isNil: die("failed to create xft draw")

  while true:
    var ev: XEvent
    discard XNextEvent(app.d, addr ev)
    case ev.theType
    of Expose:
      draw(app)
    of KeyPress:
      var ke = cast[PXKeyEvent](addr ev)
      handleKey(app, ke[])
      draw(app)
    else:
      discard

  discard XCloseDisplay(app.d)

when isMainModule:
  main()