2HQD2V46JQIUYA5AZE2VXEUG3C4VTZXB5XUEZXVV5XROZHZ2PNYQC
{.deadCodeElim: on.}
when defined linux:
import ../platforms/unix/x11/[x, xlib, xcb, xkb, xkblib, xkbCommon, keysyms]
#, platforms/unix/x11/[xkblib, xlib, xkb, ]
from os import getEnv
type
Key* {.pure.} = enum
A = 0
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
Y
X
Z
K1
K2
K3
K4
K5
K6
K7
K8
K9
K0
Space
Tab
Up
Down
Left
Right
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
LSuper
RSuper
LCtrl
RCtrl
LShift
RShift
LAlt
RAlt
Minus
Equals
BackSpace
LBracket
RBracket
Enter
SemiColon
Apos
Tilde
BSlash
Comma
Period
FSlash
Caps
Esc
NPMult
NumLock
Scrl
NP7
NP8
NP9
NPMin
NP4
NP5
NP6
NPAdd
NP1
NP2
NP3
NP0
NPDeci
NPEnter
NPDiv
Prnt
Home
PgUp
End
PgDown
Ins
Del
Pause
Menu
NPEquals
KeyState* {.pure.} = enum Up Down Held ScrolledUp ScrolledDown
Key_Action* {.pure.} = enum Release Press
AKeyObj = object of RootObj
key*: Key
state*: KeyState
when defined linux:
keySym*: KeySym
#scanCode: int
#name: string
A_Key* = ref object of AKeyObj
var
keys*: array[105, A_Key] = [ A_Key( key: A, state: KeyState.Up)
, A_Key( key: B, state: KeyState.Up)
, A_Key( key: C, state: KeyState.Up)
, A_Key( key: D, state: KeyState.Up)
, A_Key( key: E, state: KeyState.Up)
, A_Key( key: Key.F, state: KeyState.Up)
, A_Key( key: G, state: KeyState.Up)
, A_Key( key: H, state: KeyState.Up)
, A_Key( key: I, state: KeyState.Up)
, A_Key( key: J, state: KeyState.Up)
, A_Key( key: K, state: KeyState.Up)
, A_Key( key: L, state: KeyState.Up)
, A_Key( key: M, state: KeyState.Up)
, A_Key( key: N, state: KeyState.Up)
, A_Key( key: O, state: KeyState.Up)
, A_Key( key: P, state: KeyState.Up)
, A_Key( key: Q, state: KeyState.Up)
, A_Key( key: R, state: KeyState.Up)
, A_Key( key: S, state: KeyState.Up)
, A_Key( key: T, state: KeyState.Up)
, A_Key( key: U, state: KeyState.Up)
, A_Key( key: V, state: KeyState.Up)
, A_Key( key: W, state: KeyState.Up)
, A_Key( key: Y, state: KeyState.Up)
, A_Key( key: X, state: KeyState.Up)
, A_Key( key: Z, state: KeyState.Up)
, A_Key( key: K1, state: KeyState.Up)
, A_Key( key: K2, state: KeyState.Up)
, A_Key( key: K3, state: KeyState.Up)
, A_Key( key: K4, state: KeyState.Up)
, A_Key( key: K5, state: KeyState.Up)
, A_Key( key: K6, state: KeyState.Up)
, A_Key( key: K7, state: KeyState.Up)
, A_Key( key: K8, state: KeyState.Up)
, A_Key( key: K9, state: KeyState.Up)
, A_Key( key: K0, state: KeyState.Up)
, A_Key( key: Space, state: KeyState.Up)
, A_Key( key: Tab, state: KeyState.Up)
, A_Key( key: Key.Up, state: KeyState.Up)
, A_Key( key: Key.Down, state: KeyState.Up)
, A_Key( key: Left, state: KeyState.Up)
, A_Key( key: Right, state: KeyState.Up)
, A_Key( key: F1, state: KeyState.Up)
, A_Key( key: F2, state: KeyState.Up)
, A_Key( key: F3, state: KeyState.Up)
, A_Key( key: F4, state: KeyState.Up)
, A_Key( key: F5, state: KeyState.Up)
, A_Key( key: F6, state: KeyState.Up)
, A_Key( key: F7, state: KeyState.Up)
, A_Key( key: F8, state: KeyState.Up)
, A_Key( key: F9, state: KeyState.Up)
, A_Key( key: F10, state: KeyState.Up)
, A_Key( key: F11, state: KeyState.Up)
, A_Key( key: F12, state: KeyState.Up)
, A_Key( key: LSuper, state: KeyState.Up)
, A_Key( key: RSuper, state: KeyState.Up)
, A_Key( key: LCtrl, state: KeyState.Up)
, A_Key( key: RCtrl, state: KeyState.Up)
, A_Key( key: LShift, state: KeyState.Up)
, A_Key( key: RShift, state: KeyState.Up)
, A_Key( key: LAlt, state: KeyState.Up)
, A_Key( key: RAlt, state: KeyState.Up)
, A_Key( key: Minus, state: KeyState.Up)
, A_Key( key: Equals, state: KeyState.Up)
, A_Key( key: BackSpace, state: KeyState.Up)
, A_Key( key: LBracket, state: KeyState.Up)
, A_Key( key: RBracket, state: KeyState.Up)
, A_Key( key: Enter, state: KeyState.Up)
, A_Key( key: SemiColon, state: KeyState.Up)
, A_Key( key: Apos, state: KeyState.Up)
, A_Key( key: Tilde, state: KeyState.Up)
, A_Key( key: BSlash, state: KeyState.Up)
, A_Key( key: Comma, state: KeyState.Up)
, A_Key( key: Period, state: KeyState.Up)
, A_Key( key: FSlash, state: KeyState.Up)
, A_Key( key: Caps, state: KeyState.Up)
, A_Key( key: Esc, state: KeyState.Up)
, A_Key( key: NPMult, state: KeyState.Up)
, A_Key( key: NumLock, state: KeyState.Up)
, A_Key( key: Scrl, state: KeyState.Up)
, A_Key( key: NP7, state: KeyState.Up)
, A_Key( key: NP8, state: KeyState.Up)
, A_Key( key: NP9, state: KeyState.Up)
, A_Key( key: NPMin, state: KeyState.Up)
, A_Key( key: NP4, state: KeyState.Up)
, A_Key( key: NP5, state: KeyState.Up)
, A_Key( key: NP6, state: KeyState.Up)
, A_Key( key: NPAdd, state: KeyState.Up)
, A_Key( key: NP1, state: KeyState.Up)
, A_Key( key: NP2, state: KeyState.Up)
, A_Key( key: NP3, state: KeyState.Up)
, A_Key( key: NP0, state: KeyState.Up)
, A_Key( key: NPDeci, state: KeyState.Up)
, A_Key( key: NPEnter, state: KeyState.Up)
, A_Key( key: NPDiv, state: KeyState.Up)
, A_Key( key: Prnt, state: KeyState.Up)
, A_Key( key: Home, state: KeyState.Up)
, A_Key( key: PgUp, state: KeyState.Up)
, A_Key( key: End, state: KeyState.Up)
, A_Key( key: PgDown, state: KeyState.Up)
, A_Key( key: Ins, state: KeyState.Up)
, A_Key( key: Del, state: KeyState.Up)
, A_Key( key: Pause, state: KeyState.Up)
, A_Key( key: Menu, state: KeyState.Up)
, A_Key( key: NPEquals, state: KeyState.Up)
]
proc state*(k: Key): KeyState = return keys[k.ord].state
when defined linux:
proc key_from_sym*(sym: KeySym): A_Key =
case sym
of XK_aa: return keys[0]
of XK_bb: return keys[1]
of XK_cc: return keys[2]
of XK_dd: return keys[3]
of XK_ee: return keys[4]
of XK_ff: return keys[5]
of XK_gg: return keys[6]
of XK_hh: return keys[7]
of XK_ii: return keys[8]
of XK_jj: return keys[9]
of XK_kk: return keys[10]
of XK_ll: return keys[11]
of XK_mm: return keys[12]
of XK_nn: return keys[13]
of XK_oo: return keys[14]
of XK_pp: return keys[15]
of XK_qq: return keys[16]
of XK_rr: return keys[17]
of XK_ss: return keys[18]
of XK_tt: return keys[19]
of XK_uu: return keys[20]
of XK_vv: return keys[21]
of XK_ww: return keys[22]
of XK_xx: return keys[23]
of XK_yy: return keys[24]
of XK_zz: return keys[25]
of XK_1: return keys[26]
of XK_2: return keys[27]
of XK_3: return keys[28]
of XK_4: return keys[29]
of XK_5: return keys[30]
of XK_6: return keys[31]
of XK_7: return keys[32]
of XK_8: return keys[33]
of XK_9: return keys[34]
of XK_0: return keys[35]
of XK_space: return keys[36]
of XK_Tab: return keys[37]
of XK_Up: return keys[38]
of XK_Down: return keys[39]
of XK_Left: return keys[40]
of XK_Right: return keys[41]
of XK_F1: return keys[42]
of XK_F2: return keys[43]
of XK_F3: return keys[44]
of XK_F4: return keys[45]
of XK_F5: return keys[46]
of XK_F6: return keys[47]
of XK_F7: return keys[48]
of XK_F8: return keys[49]
of XK_F9: return keys[50]
of XK_F10: return keys[51]
of XK_F11: return keys[52]
of XK_F12: return keys[53]
of XK_Super_L: return keys[54]
of XK_Super_R: return keys[55]
of XK_Control_L: return keys[56]
of XK_Control_R: return keys[57]
of XK_Shift_L: return keys[58]
of XK_Shift_R: return keys[59]
of XK_Alt_L: return keys[60]
of XK_Alt_R: return keys[61]
of XK_minus: return keys[62]
of XK_equal: return keys[63]
of XK_BackSpace: return keys[64]
of XK_bracketleft: return keys[65]
of XK_bracketright: return keys[66]
of XK_Return: return keys[67]
of XK_semicolon: return keys[68]
of XK_apostrophe: return keys[69]
of XK_grave: return keys[70]
of XK_backslash: return keys[71]
of XK_comma: return keys[72]
of XK_period: return keys[73]
of XK_slash: return keys[74]
of XK_Caps_Lock: return keys[75]
of XK_Escape: return keys[76]
#of XK_exclam:
#of XK_quotedbl:
#of XK_numbersign:
#of XK_dollar:
#of XK_percent:
#of XK_ampersand:
# of XK_apostrophe:
# of XK_quoteright:
# of XK_parenleft:
# of XK_parenright:
# of XK_asterisk:
# of XK_plus:
# of XK_comma:
# of XK_minus:
# of XK_period:
# of XK_slash:
# of XK_colon:
# of XK_semicolon:
# of XK_less:
# of XK_equal:
# of XK_greater:
# of XK_question:
# of XK_at:
# of XK_A:
# of XK_B:
# of XK_C:
# of XK_D:
# of XK_E:
# of XK_F:
# of XK_G:
# of XK_H:
# of XK_I:
# of XK_J:
# of XK_K:
# of XK_L:
# of XK_M:
# of XK_N:
# of XK_O:
# of XK_P:
# of XK_Q:
# of XK_R:
# of XK_S:
# of XK_T:
# of XK_U:
# of XK_V:
# of XK_W:
# of XK_X:
# of XK_Y:
# of XK_Z:
# of XK_bracketleft:
# of XK_backslash:
# of XK_bracketright:
# of XK_asciicircum:
# of XK_underscore:
# of XK_grave:
# of XK_quoteleft:
# of #:
# of #:
# of XK_braceleft:
# of XK_bar:
# of XK_braceright:
# of XK_asciitilde:
# of XK_nobreakspace:
else: discard
when defined linux:
proc theLayoutName*( group: xkb_layout_index_t
, xkbkm: ptr xkb_keymap
): cstring =
xkb_keymap_layout_get_name(xkbkm, xkb_layout_index_t group )
proc theLocale*: string =
when defined linux:
# TODO: Better (more robust) way to get locale?
if getEnv("LANG").len != 0:
#echo "valid: LANG: ", getEnv("LANG")
result = getEnv("LANG")
elif getEnv("LC_ALL").len != 0:
#echo "valid: LC_ALL"
result = getEnv("LC_ALL")
elif getEnv("LC_CTYPE").len != 0:
#echo "valid: LC_CTYPE"
result = getEnv("LC_CTYPE")
else:
result = "C"
echo "WARNING: Couldn't find locale, falling back to 'C'"
when defined linux:
var #TODO: do we need the global pragmas?
xkb_base_event* {.global.} : uint8
xkb_base_error* {.global.} : uint8
proc x11KeySymName*(ks: xcb_keysym_t): cstring =
discard xkb_keysym_get_name(ks, result, sizeof(cstring).csize_t)
result
proc loadComposeTable*( locale: string
, xkbCompTable: var ptr xkb_compose_table
, xkbContext: ptr xkb_context
, xkbCompState: var ptr xkb_compose_state
): bool =
#xkb_compose_table_unref addr p.xkbCompTable
#echo "Locale: ", locale
xkbCompTable = xkb_compose_table_new_from_locale(xkbContext, locale, xkb_compose_compile_flags 0)
assert not xkbCompTable.isNil
var newCompState: ptr xkb_compose_state = xkb_compose_state_new(xkbCompTable, xkb_compose_state_flags 0)
assert not newCompState.isNil
#xkb_compose_state_unref addr p.xkbCompState
xkbCompState = newCompState
return true
proc loadKeyMap*( conn: ptr xcb_connection_t
, xkbContext: var ptr xkb_context
, xkbkm: var ptr xkb_keymap
, xkbState: var ptr xkb_state
, xkbNewState: var ptr xkb_state
) =
xkbContext = xkb_context_new xkb_context_flags 0
assert not xkbContext.isNil
xkb_keymap_unref xkbkm
var deviceID: int32 = xkb_x11_get_core_keyboard_device_id conn
xkbkm = xkb_x11_keymap_new_from_device( xkbContext
, conn
, deviceID
, xkb_keymap_compile_flags 0
)
assert not xkbkm.isNil
xkbNewState = xkb_x11_state_new_from_device(xkbkm, conn, deviceID)
assert not xkbNewState.isNil
xkb_state_unref xkbState
xkbState = xkbNewState
assert not xkbNewState.isNil
#echo "DeviceID: ", deviceID
proc loadXKB*( dpy: PDisplay
, conn: ptr xcb_connection_t
# , group: var int8
, firstXkbEvent: var uint8
) =
# keep ?
# if XkbGetState(dpy, XkbUseCorekbd, addr state) == Success: group = state.group
# discard XkbGetNames(dpy, XkbKeyNamesMask, desc)
#discard XkbSetAutoRepeatRate(dpy, 1, 1, 1)
# desc = XkbGetMap(dpy, 0, XkbUseCoreKbd)
#kb: X11kb
var
supported: bool
xkbDesc: ptr XkbDescRec = XkbAllocKeyboard()
discard XAutoRepeatOn dpy
discard XkbSetDetectableAutoRepeat(dpy, true, addr supported)
assert supported
discard XkbSelectEventDetails(dpy, XkbUseCorekbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask)
assert xkb_x11_setup_xkb_extension( conn
, uint16 1 #XKB_X11_MIN_MAJOR_XKB_VERSION
, uint16 0 #XKB_X11_MIN_MINOR_XKB_VERSION
, xkb_x11_setup_xkb_extension_flags 0
, nil
, nil
, addr firstXkbEvent
, nil
) == 1
discard XkbGetControls(dpy, XkbAllControlsMask, xkbDesc)
discard XkbGetNames(dpy, XkbSymbolsNameMask, xkbDesc)
discard XkbGetNames(dpy, XkbGroupNamesMask, xkbDesc)
#echo xkbDesc.names.groups
const
required_map_parts: xcb_xkb_map_part_t =
xcb_xkb_map_part_t XCB_XKB_MAP_PART_KEY_TYPES.ord or
XCB_XKB_MAP_PART_KEY_SYMS.ord or
XCB_XKB_MAP_PART_MODIFIER_MAP.ord or
XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS.ord or
XCB_XKB_MAP_PART_KEY_ACTIONS.ord or
XCB_XKB_MAP_PART_KEY_BEHAVIORS.ord or
XCB_XKB_MAP_PART_VIRTUAL_MODS.ord or
XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP.ord
required_events: xcb_xkb_event_type_t =
xcb_xkb_event_type_t XCB_XKB_EVENT_TYPE_MAP_NOTIFY.ord or
XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY.ord or
XCB_XKB_EVENT_TYPE_STATE_NOTIFY.ord
discard xcb_xkb_select_events( conn
, xcb_xkb_device_spec_t xkb_x11_get_core_keyboard_device_id conn
, uint16 required_events
, 0
, uint16 required_events
, uint16 required_map_parts
, uint16 required_map_parts
, nil #cast[pointer](0)
)
var
#xkbs: ptr xkb_state
#xkb_base_event: uint8
xkberr: uint8
xcbXKBRep = xcb_get_extension_data(conn, addr xcb_xkb_id)
#echo xcbXKBRep.first_event
assert xkb_x11_setup_xkb_extension( conn
, 1
, 14
, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS
, nil
, nil
, addr firstXkbEvent
, addr xkberr
) == 1
assert not xcbXKBRep.isNil
proc is_down*(key: Key): bool = return bool keys[ord key].state == KeyState.Down
proc is_down_or_held*(key: Key): bool = return bool (keys[ord key].state == KeyState.Down or keys[ord key].state == KeyState.Held)
proc isUp*(key: Key): bool = return bool keys[ord key].state == KeyState.Up
# snippets to possibly use later
#[
var st = xkb_state_get_keymap(p.xkbNewState)
var group: xkb_layout_index_t = xkb_state_serialize_layout(p.xkbNewState, XKB_STATE_LAYOUT_EFFECTIVE)
echo group
echo xkb_keymap_num_layouts st
echo xkb_keymap_num_layouts st
echo xkb_keymap_num_layouts st
for grp in 0..xkb_keymap_num_layouts(st):
echo xkb_state_layout_index_is_active( p.xkbNewState
,xkb_layout_index_t group
, XKB_STATE_LAYOUT_EFFECTIVE
)
echo xkb_state_layout_index_is_active( p.xkbNewState
,xkb_layout_index_t group
, XKB_STATE_LAYOUT_DEPRESSED
)
echo xkb_state_layout_index_is_active( p.xkbNewState
,xkb_layout_index_t group
, XKB_STATE_LAYOUT_LATCHED
)
echo xkb_state_layout_index_is_active( p.xkbNewState
,xkb_layout_index_t group
, XKB_STATE_LAYOUT_LOCKED
)
]#
when defined linux:
{.passl: "-lXinerama".}
{.passl: "-lXrandr".}
# {.passl: "-lXi".}
# {.passl: "-lXcursor".}
# {.passl: "-lXxf86vm".}
import ../platforms/unix/x11/[x, xcb, xrandr,xlib]
import ../utils/etc
, re
type
VideoModeObj = object of RootObj
width*: int #! The width, in screen coordinates, of the video mode.
height*: int #! The height, in screen coordinates, of the video mode.
redBits*: int#! The bit depth of the red channel of the video mode.
greenBits*: int #! The bit depth of the green channel of the video mode.
blueBits*: int #! The bit depth of the blue channel of the video mode.
refreshRate*: float32 #! The refresh rate, in Hz, of the video mode.
bpp*:int
VideoMode* = ref object of VideoModeObj
GammaRampObj = object of RootObj
redBits*: cushort
greenBits*: cushort
blueBits*: cushort
size*: cuint
GammaRamp* = ref object of GammaRampObj
MonitorObj = object of RootObj
rotation*: int
widthMM*: int
heightMM*: int
currVideoMode*: VideoMode
videoModes*: seq[VideoMode]
gammaRamp: GammaRamp
connName*: string
name*: string
edid*: string
Monitor* = ref object of MonitorObj
#, xcb
#from utils import echor
#proc monitorCount*(p: Monitors): int = p.monitorCount
proc rgbFromDepth*( depth: int
, actualDepth: bool = false # for later ?
): array[3, int] =
var delta: int
#GLFW parity (???)
#if depth == 32: depth = 24
# R G B
result[0] = (depth div 3)
result[1] = (depth div 3)
result[2] = (depth div 3)
delta = depth - (result[0] * 3)
if delta >= 1: result[1] += 1
if delta == 2: result[0] += 1
when defined linux:
#TODO: handle when no monitor is marked a "primary"
proc thePrimaryMonitor*( conn: ptr xcb_connection_t
, root: ptr xcb_screen_t
, win: xcb_window_t
): Monitor =
result = Monitor(currVideoMode: VideoMode())
var
sr = xcb_randr_get_screen_resources_current(conn, win)
primaryCookie = xcb_randr_get_output_primary(conn, win)
reply = xcb_randr_get_screen_resources_current_reply( conn
, sr
, nil
)
ts = reply.timestamp
primary = xcb_randr_get_output_primary_reply(conn, primaryCookie, err0)
primaryOut: xcb_randr_get_output_info_cookie_t = xcb_randr_get_output_info(conn
, primary.output
, ts
)
primaryInfo = xcb_randr_get_output_info_reply( conn
, primaryOut
, nil
)
primaryCRTC = xcb_randr_get_crtc_info(conn, primaryInfo.crtc, ts)
actual = xcb_randr_get_crtc_info_reply(conn, primaryCRTC, err0)
geo = xcb_get_geometry(conn, win)
geoRep = xcb_get_geometry_reply(conn, geo, err0)
outs = xcb_randr_get_screen_resources_current_outputs reply
thing10 = xcb_randr_get_crtc_info_outputs actual
outProps = xcb_randr_list_output_properties(conn, primary.output)
outPropsRep = xcb_randr_list_output_properties_reply(conn, outProps, err0)
theAtoms = xcb_randr_list_output_properties_atoms(outpropsRep)
far = xcb_randr_get_output_info_modes(primaryInfo)
farLen = xcb_randr_get_output_info_modes_length(primaryInfo)
nthis = xcb_randr_get_screen_info(conn, win)
woo = xcb_randr_get_screen_info_reply(conn, nthis ,err0)
woop = xcb_randr_get_screen_info_rates_iterator(woo)
woopLen = xcb_randr_get_screen_info_rates_length(woo)
boop = xcb_randr_get_monitors(conn, win, 1)
boopRep = xcb_randr_get_monitors_reply(conn, boop, err0)
boopIter = xcb_randr_get_monitors_monitors_iterator(boopRep)
boopLen = xcb_randr_get_monitors_monitors_length(boopRep)
amo = xcb_randr_get_screen_resources_current_outputs_length(reply)
ops = xcb_randr_get_screen_resources_current_outputs(reply)
modesLen = xcb_randr_get_screen_resources_current_modes_length(reply)
modes = xcb_randr_get_screen_resources_current_modes(reply)
for x in 0 ..< modesLen:
if modes[x].id == actual.mode:
result.currVideoMode.refreshRate = (modes[x].dot_clock.float32 /
(modes[x].htotal.float32 *
modes[x].vtotal.float32
)
)
result.currVideoMode.bpp = int geoRep[].depth
result.currVideoMode.width = int geoRep[].width
result.currVideoMode.height = int geoRep[].height
# rgbFromDepth( result.currVideoMode.bpp
# , result.currVideoMode.redBits
# , result.currVideoMode.greenBits
# , result.currVideoMode.blueBits
# )
result.rotation = int (actual[].addr).rotation
result.widthMM = int primaryInfo.mm_width
result.heightMM = int primaryInfo.mm_height
result.connName = $cast[cstring](xcb_randr_get_output_info_name primaryInfo)
for x in 0 ..< theAtoms[]:
var
z = xcb_get_atom_name(conn, theAtoms[] + x)
zr = xcb_get_atom_name_reply(conn, z, err0)
#echo xcb_get_atom_name_name(zr) # print atom name
if xcb_get_atom_name_name(zr) == "EDID":
var
atom = xcb_intern_atom(conn, 0, 4, "EDID")
atomRep = xcb_intern_atom_reply(conn, atom, err0)
b1 = xcb_randr_get_output_property(conn
, primary.output
, atomRep.atom
, XCB_ATOM_ANY.ord
, 0
, 100
, 0
, 0
)
a1 = xcb_randr_get_output_property_reply(conn, b1, err0)
propData = xcb_randr_get_output_property_data(a1)
var str = newString(a1.num_items)
copyMem(str[0].addr, propData, a1.num_items)
result.edid = str
result.name = str.findAll(re"(\w+ \w+)")[0]
proc theMonitors*( conn: ptr xcb_connection_t
, root: ptr xcb_screen_t
, win: xcb_window_t
) =#: seq[Monitor] =
var
sr = xcb_randr_get_screen_resources_current(conn, win)
reply = xcb_randr_get_screen_resources_current_reply( conn
, sr
, nil
)
outs: ptr xcb_randr_output_t = xcb_randr_get_screen_resources_current_outputs reply
lenn = xcb_randr_get_screen_resources_current_outputs_length reply
for i in 0 ..< lenn:
var outt = xcb_randr_get_output_info_reply( conn
, xcb_randr_get_output_info(conn
, (outs + i)[]
, reply.timestamp
)
, nil
)
if not outt.isNil:
var
getcrtc = xcb_randr_get_crtc_info( conn
, outt.crtc
, reply.timestamp
)
crtc = xcb_randr_get_crtc_info_reply( conn
, getcrtc
, err0
)
if not crtc[].addr.isNil:
echo crtc[]
# printf("x = %d | y = %d | w = %d | h = %d\n",
# crtc.x, crtc.y, crtc.width, crtc.height)
# Relays* = ref object
# pos: WindowPosRelay
# size: WindowSizeRelay
# close: WindowCloseRelay
# refresh: WindowRefreshRelay
# focus: WindowFocusRelay
# iconify: WindowIconifyRelay
# maximize: WindowMaximizedRelay
# fbsize: WindowFBSizeRelay
# scale: WindowContentScaleRelay
# mouseButton: MouseButtonRelay
# cursorPos: MouseCursorPosRelay
# cursorEnter: MouseCursorEnterRelay
# scroll: MouseScrollRelay
# key: KeyboardRelay
# character: CharRelay
# charmods: CharModsRelay
# drop: WindowDropRelay
# MonitorX11* = ref object
# output*: RROutput
# crtc*: RRCrtc
# oldMode*: RRMode
# index: int # Index of corresponding Xinerama screen, for EWMH full screen window placement
# Monitor* = ref object
# name: cstring #array[128,char]
# userPointer: pointer
# widthMM, heightMM: int # Physical dimensions in millimeters.
# window: Window# The window whose video mode is current on this monitor
# modes: seq[Vidmode]
# modeCount: int
# currentMode: Vidmode
# originalRamp: Gammaramp
# currentRamp: Gammaramp
# platform: MonitorType #We're assuming x11 monitor until this is truly cross-plat
# x11*: MonitorX11
# const
# xineramaLib = "libXinerama.so"
# Rotate0* = 1
# Rotate90* = 2
# Rotate180* = 4
# Rotate270* = 8
# ReflectX* = 16
# ReflectY* = 32
# type
# PXineramaScreenInfo* = ptr XineramaScreenInfo
# XineramaScreenInfo*{.final.} = object
# screen_number*: cint
# x_org*: int16
# y_org*: int16
# width*: int16
# height*: int16
# proc XineramaQueryExtension*(dpy: PDisplay, event_base: Pcint, error_base: Pcint): bool{.cdecl, importc.}
# proc XineramaQueryVersion*(dpy: PDisplay, major: Pcint, minor: Pcint): int #[Status]# {.cdecl, importc.}
# proc XineramaIsActive*(dpy: PDisplay): bool{.cdecl, dynlib: xineramaLib, importc.}
# proc XineramaQueryScreens*(dpy: PDisplay, number: Pcint): PXineramaScreenInfo{.cdecl, importc.}
# proc XRRGetOutputPrimary*(dpy: ptr Display, window: Window): RROutput {.cdecl, importc.}
# proc thePrimary*( p: PDisplay
# , w: Window
# ) =
# var
# t = p.XRRGetScreenResourcesCurrent w
# primary = p.XRRGetOutputPrimary w
# echo repr primary
proc pollMonitors*() = discard
type
MouseObj = object of RootObj
posX*: int
posY*: int
Mouse* = ref object of MouseObj
MouseButton* {.size: int8.sizeof.} = enum
LMB
MMB
RMB
mb4
mb5
mb6
mb7
mb8
mb9
mb10
mb11
mb12
mb13
mb14
mb15
mb16
mb17
mb18
mb19
mb20
MouseBtnObj = object of RootObj
state*: MouseBtnState
MouseBtn* = ref object of MouseBtnObj
MouseBtnState* {.pure.} = enum Up Down Held ScrolledUp ScrolledDown PressedAndScrolledUp PressedAndScrolledDown
MouseBtnAction* {.pure.} = enum Release Press ScrollUp ScrollDown
var
mbs*: array[20, MouseBtn] = [ MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
, MouseBtn(state: MouseBtnState.Up)
]
proc state*(mb: MouseButton): MouseBtnState = mbs[ord mb].state
proc is_down*(mb: MouseButton): bool = return bool mbs[ord mb].state == MouseBtnState.Down
proc isUp*(mb: MouseButton): bool = return bool mbs[ord mb].state == MouseBtnState.Up
#[ THINGS TO FIX LATER:
]#
#{.experimental: "codeReordering".}
{.experimental: "notnil".}
when defined linux:
import ../platforms/unix/x11/[ x
, xlib
, xcb
, xkblib
, xkbCommon
, keysyms
, xfixes
, xcomposite
, xrandr
]
import keyboard
, mouse
, monitor
, portalObj
, ../utils/[etc]
import bitops
, re
, sequtils
, sugar
, tables
# , utils
# , ../vk/[vkTypes, swapchain, utils, vulkan]
# , monitor
# , ../scene/scene
# , ../drawable/[shapes]
# , ../scene/scene
# , ../camera
#, glm
when defined windows:
import winim
, winim/lean
, ../platforms/w64/[ events
, utils
, the_types
, the_functions
]
proc title_is*( the_portal: ptr Portal
, s: string
) =
when defined linux:
discard xcb_change_property( p.conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, p.window
, 39
, 31
, s.sizeof.uint8
, s.len.uint32
, addr s[0]
)
proc the_screen_info() = discard
# TODO:
# fix the bad(?) video modes for the monitors
# Gamma stuff
# figure out a solution to workarea
proc a_portal*( width: uint16 = 1920
, height: uint16 = 1080
, title: string = "Portal"
): Portal =
when defined linux:
var
title = title
xdisplay = XOpenDisplay nil
# ewmh: xcb_ewmh_connection_t
conn = XGetXCBConnection xdisplay
mask = XCB_GC_FOREGROUND.ord or XCB_GC_GRAPHICS_EXPOSURES.ord
setup: ptr xcb_setup_t
wmClassTitle = title & '\0' #& title & '\0'
root: Window
screenIter = xcb_setup_roots_iterator xcb_get_setup conn
#scr: cint = screenIter.rem
screen: ptr xcb_screen_t = screenIter.data
window = xcb_generate_id conn
# xwin = DefaultRootWindow xdisplay
# ewmhCookie = xcb_ewmh_init_atoms(conn, addr ewmh)
#ewmhRep = xcb_ewmh_init_atoms_replies(addr ewmh, ewmhCookie, err0)
valueList = [screen.black_pixel,0]
#ver = conn.xcb_randr_query_version_reply( xcb_randr_query_version(conn, 1, 6) , err0 ) # TODO: should it be 1.5 or 1.6???
sr = xcb_randr_get_screen_resources_current(conn, screen.root)
reply = xcb_randr_get_screen_resources_current_reply( conn
, sr
, nil
)
outs: ptr xcb_randr_output_t = xcb_randr_get_screen_resources_current_outputs reply
lenn = xcb_randr_get_screen_resources_current_outputs_length reply
monitorAmount: int
modesLen = xcb_randr_get_screen_resources_current_modes_length(reply)
modes = xcb_randr_get_screen_resources_current_modes(reply)
primaryCookie = xcb_randr_get_output_primary(conn, screen.root)
primary = xcb_randr_get_output_primary_reply(conn, primaryCookie, err0)
primaryOut: xcb_randr_get_output_info_cookie_t = xcb_randr_get_output_info(conn
, primary.output
, reply.timestamp
)
primaryInfo = xcb_randr_get_output_info_reply( conn
, primaryOut
, nil
)
primaryCRTC = xcb_randr_get_crtc_info(conn, primaryInfo.crtc, reply.timestamp)
actualPrimary = xcb_randr_get_crtc_info_reply(conn, primaryCRTC, err0)
# primaryModesInfo = xcb_randr_get_output_info_modes(primaryInfo)
# primaryModesInfoReply = xcb_randr_get_output_info_modes_length(primaryInfo)
#primaryOutputInfoModes = xcb_randr_get_output_info_modes_length()
monitors: seq[Monitor]
primaryMonitor: Monitor
theRandrModes: seq[xcb_randr_mode_info_t]
# TODO: stop being stupid and do it properly in the monitorAdd loop
for x in 0 ..< modesLen:
theRandrModes.add modes[x]
#theRandrModes.sort
mask = XCB_CW_BACK_PIXEL or XCB_CW_EVENT_MASK
valueList[1] = XCB_NONE or
XCB_EVENT_MASK_STRUCTURE_NOTIFY or
XCB_EVENT_MASK_ENTER_WINDOW or
XCB_EVENT_MASK_POINTER_MOTION_HINT or
XCB_EVENT_MASK_BUTTON_1_MOTION or
XCB_EVENT_MASK_BUTTON_2_MOTION or
XCB_EVENT_MASK_BUTTON_3_MOTION or
XCB_EVENT_MASK_BUTTON_4_MOTION or
XCB_EVENT_MASK_BUTTON_5_MOTION or
XCB_EVENT_MASK_EXPOSURE or
XCB_EVENT_MASK_BUTTON_PRESS or
XCB_EVENT_MASK_BUTTON_RELEASE or
XCB_EVENT_MASK_BUTTON_MOTION or
XCB_EVENT_MASK_POINTER_MOTION or
XCB_EVENT_MASK_LEAVE_WINDOW or
XCB_EVENT_MASK_KEY_PRESS or
XCB_EVENT_MASK_KEY_RELEASE or
XCB_MAP_NOTIFY or
XCB_MAPPING_NOTIFY or
XCB_MAP_REQUEST or
XCB_EVENT_MASK_PROPERTY_CHANGE or
XCB_EVENT_MASK_FOCUS_CHANGE or
XCB_EVENT_MASK_KEYMAP_STATE or
XCB_EVENT_MASK_VISIBILITY_CHANGE #or
# TODO: This complicates how screen.width|height is reported and complicates the viewport size
# as it combines 2+ monitors together
# not necessarily needed to handle resizing, but is it better™?
# XCB_EVENT_MASK_RESIZE_REDIRECT
if xcb_connection_has_error(conn).bool: quit "ERROR: xcb conn"
discard xcb_create_window( conn # connection
, 0 # depth (same as root)
, window # window ID
, screen.root # parent window
, 0 # X
, 0 # Y
, width
, height
, 0 #border width
, xcb_window_class_t.XCB_WINDOW_CLASS_INPUT_OUTPUT.uint16
, screen.root_visual
, mask.uint32
, valueList[0].addr
)
var
ev: xcb_generic_event_t
cookie = xcb_intern_atom(conn, 1, 12,"WM_PROTOCOLS")
cookie2 = xcb_intern_atom(conn, 0, 16, "WM_DELETE_WINDOW")
cookie3 = xcb_intern_atom(conn, 0, 15, "_MOTIF_WM_HINTS" )
# maxvertAtom = xcb_intern_atom(conn, 0, 13, "_NET_WORKAREA")
# maxHorzAtom = xcb_intern_atom(conn, 0, 28, "_NET_WM_STATE_MAXIMIZED_HORZ")
#something = xcb_get_property(conn, 0, window, x_NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW.ord, 0, 1024)
reply1 = xcb_intern_atom_reply(conn, cookie, err0)
reply2 = xcb_intern_atom_reply(conn, cookie2, err0)
reply3 = xcb_intern_atom_reply( conn, cookie3, err0)
# replyMaxV = xcb_intern_atom_reply( conn, maxvertAtom, err0)
# replyMaxH = xcb_intern_atom_reply( conn, maxhorzAtom, err0)
# replyMaxVProp = xcb_get_property( conn, 0, window, replyMaxV.atom, XCB_ATOM_CARDINAL.ord, 0, 1024)
# replyMaxHProp = xcb_get_property( conn, 0, window, replyMaxH.atom, XCB_ATOM_STRING.ord, 0, 0)
# vRep = xcb_get_property_reply(conn, replyMaxVProp, err0)
# hRep = xcb_get_property_reply(conn, replyMaxHProp, err0)
colors = rgbFromDepth screen.root_depth.int
atomEdid = xcb_intern_atom(conn, 0, 4, "EDID")
atomEdidRep = xcb_intern_atom_reply(conn, atomEdid, err0)
# atomWorkArea = xcb_intern_atom(conn, 0, 13, "_NET_WORKAREA")
# atomWARep = xcb_intern_atom_reply(conn, atomWorkArea, err0)
decoratedHint = MotifHints( flags: MWM_HINTS_FUNCTIONS or MWM_HINTS_DECORATIONS
, functions: MWM_FUNC_MOVE or MWM_FUNC_MINIMIZE or MWM_FUNC_CLOSE or MWM_FUNC_RESIZE
, decorations: MWM_DECOR_BORDER or MWM_DECOR_TITLE or MWM_DECOR_MINIMIZE or MWM_DECOR_MENU
, input_mode: 0
, status: 0
)
discard xcb_change_property( conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, window
, reply1.atom
, 4
, 32
, 1
, addr reply2.atom
)
# setting window title
discard xcb_change_property( conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, window
, 39
, 31
, title.sizeof.uint8
, title.len.uint32
, addr title[0]
)
# WM_CLASS property (?????)
discard xcb_change_property( conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, window
, 39
, 31
, wmClassTitle.sizeof.uint8
, wmClassTitle.len.uint32
, addr wmClassTitle[0]
)
discard xcb_change_property( conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, window
, reply3.atom
, reply3.atom
, 32
, 5
, addr decoratedHint
)
# this will get connected monitors
# but xcb_randr_get_output_info_reply will still report monitors even if they're disabled
# so we get the CRTC, which will only(?) get currently enabled monitors
# so we more accurately know what the user currently has
# NOTE:
# Currently, just using the depth, from the geoReply, which is from the overall "Screen" (combined render area from both monitors(?))
# Maybe in the future, properly get the depth, by individual monitor
for x in 0 ..< lenn:
var
outt = xcb_randr_get_output_info_reply(conn, xcb_randr_get_output_info(conn, outs[x], reply.timestamp), nil )
outCR = xcb_randr_get_crtc_info(conn, outt.crtc, reply.timestamp)
outActual = xcb_randr_get_crtc_info_reply(conn, outCR, err0)
outmodes = xcb_randr_get_output_info_modes outt
outmodeslen = xcb_randr_get_output_info_modes_length outt
# TODO: do we really need the connection check if we're getting a CRTC reply?
if outt.connection == RR_Connected and
not outActual.isNil:
for m in 0 ..< modesLen:
if modes[m].id == outActual.mode:
var
b1 = xcb_randr_get_output_property( conn
, outs[x]
, atomEdidRep.atom
, XCB_ATOM_ANY.ord
, 0
, 100
, 0
, 0
)
a1 = xcb_randr_get_output_property_reply(conn, b1, err0)
propData = xcb_randr_get_output_property_data(a1)
edidStr = newString(a1.num_items)
copyMem( edidStr[0].addr
, propData
, a1.num_items
)
var
actualvTotal: float32 = cdouble modes[m].vtotal
if bitand(modes[m].mode_flags, RR_DoubleScan).bool: actualvTotal *= 2
if bitand(modes[m].mode_flags, RR_Interlace).bool: actualvTotal /= 2
var
aMonitor = Monitor( currVideoMode: VideoMode( refreshRate: ( modes[m].dot_clock.float32 /
( modes[m].htotal.float32 *
actualvTotal.float32
)
)
, width: int outActual.width
, height: int outActual.height
, bpp: int screen.root_depth
, redbits: colors[0]
, greenbits: colors[1]
, bluebits: colors[2]
)
, rotation : int (outActual[].addr).rotation
, widthMM : int outt.mm_width
, heightMM : int outt.mm_height
, connName : $cast[cstring](xcb_randr_get_output_info_name outt)
, edid: edidStr
, name: (edidstr.findAll(re"(\w+ \w+)\n")[0])[0..^2] # chop the `\n` off at the end
)
#add all the display modes!
#TODO: investigate the weird video modes with high refreshrates
for mmm, theMode in theRandrModes:
for mmm2 in 0 ..< outmodesLen:
if theMode.id == outmodes[mmm2]:
var theActualvTotal: float32 = cdouble theMode.vtotal
if bitand(modes[m].mode_flags, RR_DoubleScan).bool: actualvTotal *= 2
if bitand(modes[m].mode_flags, RR_Interlace).bool: actualvTotal /= 2
var aVMode = VideoMode( refreshRate: (modes[m].dot_clock.float32 /
( modes[m].htotal.float32 *
theActualvTotal.float32
)
)
, width: int theMode.width
, height: int theMode.height
, bpp: int screen.root_depth
, redbits: colors[0]
, greenbits: colors[1]
, bluebits: colors[2]
)
aMonitor.videoModes.add aVMode
#echo aMonitor.name, " can: ", repr theMode
monitorAmount += 1
monitors.add aMonitor
#TODO: a better way to check(verify?) the primary monitor
if outActual.x == actualPrimary.x and
outActual.y == actualPrimary.y and
outActual.width == actualPrimary.width and
outActual.height == actualPrimary.height:
primaryMonitor = aMonitor
else: discard#echo repr modes[m]
discard xcb_map_window(conn, window)
#echo repr monitors
xcb_aux_sync conn
var
xcb_xfixes_id2 = xcb_extension_t( name: "XFIXES", global_id: 0)
p = Portal( width: width
, height: height
, title: title
, reply1: reply1
, reply2: reply2
, conn: conn
, window: window
, setup: setup
, xdisplay: xdisplay
, screen: screen
, currMonitor: primaryMonitor
, monitors: monitors
# , screenIter: iter
, locale: theLocale()
, mouse: Mouse()
#, xwindow: DefaultRootWindow xdiplay
, xcbReply: conn.xcb_get_extension_data( addr xcb_xfixes_id2)
#, xkbCompState: addr xkbcs
#, kb: kb
#, relays: Relays(key_relay: Key_Relay())
#, group: group
, root: root
# , iter: iter
)
p.xdisplay.loadXKB(p.conn, p.firstXkbEvent)
assert p.xdisplay.XCompositeQueryExtension(addr p.xcompEvent, addr p.xCompError) == true
assert p.xdisplay.XFixesQueryExtension(addr p.xfixes_event, addr p.xfixes_error) == true
p.xdisplay.XFixesSelectCursorInput( p.xwindow
, XFixesDisplayCursorNotifyMask
)
var xfixesMask: uint32 = XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE.ord or
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY.ord or
XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER.ord
discard xcb_xfixes_select_selection_input_checked(p.conn, p.window, xcb_atom_t XCB_ATOM_PRIMARY.ord, xfixesMask)
discard xcb_xfixes_select_selection_input_checked(p.conn, p.window, xcb_atom_t XCB_ATOM_SECONDARY.ord, xfixesMask)
p.wmState = xcb_intern_atom(conn, 0, 13, "_NET_WM_STATE" )
p.wmHidden = xcb_intern_atom(conn, 0, 20, "_NET_WM_STATE_HIDDEN" )
p.wmMaxY = xcb_intern_atom(conn, 0, 28, "_NET_WM_STATE_MAXIMIZED_VERT" )
p.wmMaxX = xcb_intern_atom(conn, 0, 28, "_NET_WM_STATE_MAXIMIZED_HORZ" )
#p.getDPI()
when defined windows:
var
h_instance = GetModuleHandle nil
appName = "portal!"
hwnd: lean.HWND
wndclass: WNDCLASS
p = Portal( width: width
, height: height
, title: title
)
GetModuleHandleExW( bitor( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
, GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT
)
, cast[ptr WCHAR] (addr appName)
, cast[ptr HMODULE] (addr h_instance)
)
wndclass.style = CS_HREDRAW or CS_VREDRAW
wndclass.lpfnWndProc = WindowProc
wndclass.cbClsExtra = cast[int32] (( Portal).sizeof)
wndclass.cbWndExtra = cast[int32] ((Portal).sizeof)
wndclass.hInstance = h_instance
wndclass.hIcon = LoadIcon(0, IDI_APPLICATION)
wndclass.hCursor = LoadCursor(0, IDC_ARROW)
wndclass.hbrBackground = GetStockObject(WHITE_BRUSH)
wndclass.lpszMenuName = nil
wndclass.lpszClassName = appName
RegisterClass(wndclass)
hwnd = CreateWindow( appName
, "p0"
, WS_VISIBLE or WS_OVERLAPPEDWINDOW
, CW_USEDEFAULT
, CW_USEDEFAULT
, CW_USEDEFAULT
, CW_USEDEFAULT
, 0
, 0
, h_instance
, addr (p)
)
discard ShowWindow(hwnd, SW_SHOW)
discard UpdateWindow(hwnd)
var lp_string: LPWSTR = "Windows did a bad"
if GetLastError() != 0: MessageBox( 0
, T $GetLastError().why
, lp_string # TODO ( T"some_string" ) macro gives an error
, 0
)
p.hwnd = hwnd
p.wndclass = wndclass
p.hInstance = h_instance
var
thing = 98
thing2: ptr int = addr thing
#[
if SetProp( GetActiveWindow()
, "thing2"
, cast[HANDLE] ((thing2))
) == 0: quit("SetProp thing2 failed")
var get_thing: pointer = cast[pointer] ( GetProp( GetActiveWindow() , "thing2") )
echo "GETPROP INSIDE PORTAL:"
echo repr (cast[ptr int](get_thing)[])
echo "GETPROP PORTAL END/ "
]#
ChangeWindowMessageFilterEx( p.hwnd
, WM_DROPFILES
, MSGFLT_ALLOW
, nil
)
ChangeWindowMessageFilterEx( p.hwnd
, WM_COPYDATA
, MSGFLT_ALLOW
, nil
)
#ChangeWindowMessageFilterEx(p.hwnd,WM_COPYGLOBALDATA, MSGFLT_ALLOW, nil);
var ctrl =
TINITCOMMONCONTROLSEX( dwSize: int32 sizeof(TINITCOMMONCONTROLSEX)
, dwICC: ICC_DATE_CLASSES or ICC_LISTVIEW_CLASSES or
ICC_INTERNET_CLASSES or
ICC_LINK_CLASS or ICC_BAR_CLASSES or ICC_COOL_CLASSES
)
InitCommonControlsEx(ctrl)
OleInitialize(nil)
p
# TODO: get this inside keyboard.nim without recursive import errors
when defined linux:
proc update_key_state*( p: var Portal
, key: KeySym
, action: Key_Action
) =
#var held: bool
#echo "[",key_from_sym(key).key, "]", " was: ", $key_from_sym(key).state
if key_from_sym(key).state == KeyState.Up and
action == Key_Action.Press: key_from_sym(key).state = KeyState.Down
elif ( key_from_sym(key).state == KeyState.Down or
key_from_sym(key).state == KeyState.Held) and
action == Key_Action.Release: key_from_sym(key).state = KeyState.Up
elif key_from_sym(key).state == KeyState.Down and
action == Key_Action.Press: key_from_sym(key).state = KeyState.Held
#echo "[",key_from_sym(key).key, "]", " now is: ", $key_from_sym(key).state
#echo ""
p.relays.key_relay( p
, key_from_sym(key).key
, action.ord
)
when defined windows:
proc update_key_state*( p: var Portal
, key: WORD
, action: Key_Action
) = discard
#var held: bool
#echo "[",key_from_sym(key).key, "]", " was: ", $key_from_sym(key).state
#[ if key_from_sym(key).state == KeyState.Up and
action == Key_Action.Press: key_from_sym(key).state = KeyState.Down
elif ( key_from_sym(key).state == KeyState.Down or
key_from_sym(key).state == KeyState.Held) and
action == Key_Action.Release: key_from_sym(key).state = KeyState.Up
elif key_from_sym(key).state == KeyState.Down and
action == Key_Action.Press: key_from_sym(key).state = KeyState.Held
#echo "[",key_from_sym(key).key, "]", " now is: ", $key_from_sym(key).state
#echo "" ]#
#[ p.relays.key_relay( p
, key_from_sym(key).key
, action.ord
) ]#
# TODO:
# 1. get this inside keyboard.nim without recursive import errors
# 2. Handle multiple scroll wheels
# 3. Do we actually care about MouseBtnState.PressedAndScrolledUp && MouseBtnState.PressedAndScrolledDown
when defined linux:
proc updateMouseKeyState*( the_portal: ptr Portal
, mb: xcb_button_t
, action: MouseBtnAction
) =
#[
MouseBtnState* {.pure.} = enum Up Down Held ScrolledUp ScrolledDown PressedAndScrolledUp PressedAndScrolledDown
MouseBtnAction* {.pure.} = enum Release Press ScrollUp ScrollDown
]#
case action
of MouseBtnAction.Press:
mbs[mb - 1].state = MouseBtnState.Down
of MouseBtnAction.Release:
mbs[mb - 1].state = MouseBtnState.Up
of MouseBtnAction.ScrollUp:
mbs[1].state = MouseBtnState.ScrolledUp
of MouseBtnAction.ScrollDown:
mbs[1].state = MouseBtnState.ScrolledDown
#else: discard
p.relays.mouse_key_relay( p
, MouseButton ord (mb - 1)
, action
)
# TODO:
# 1. get this inside keyboard.nim without recursive import errors
# 2. Handle multiple scroll wheels
# 3. Do we actually care about MouseBtnState.PressedAndScrolledUp && MouseBtnState.PressedAndScrolledDown
proc updateMouseKeyState*( the_portal: ptr Portal
, mb: xcb_button_t
, action: MouseBtnAction
) =
#[
MouseBtnState* {.pure.} = enum Up Down Held ScrolledUp ScrolledDown PressedAndScrolledUp PressedAndScrolledDown
MouseBtnAction* {.pure.} = enum Release Press ScrollUp ScrollDown
]#
case action
of MouseBtnAction.Press:
mbs[mb - 1].state = MouseBtnState.Down
of MouseBtnAction.Release:
mbs[mb - 1].state = MouseBtnState.Up
of MouseBtnAction.ScrollUp:
mbs[1].state = MouseBtnState.ScrolledUp
of MouseBtnAction.ScrollDown:
mbs[1].state = MouseBtnState.ScrolledDown
#else: discard
p.relays.mouse_key_relay( p
, MouseButton ord (mb - 1)
, action
)
proc events*( p: var Portal
) =
when defined linux:
discard xcb_flush p.conn
var
cm: xcb_client_message_event_t
ev: ptr xcb_generic_event_t
ev = xcb_poll_for_event p.conn
if not ev.isNil:
case ev.response_type and 0x7f
#of XCB_RESIZE_REQUEST: discard
of XCB_EXPOSE: discard nil
of XCB_MOTION_NOTIFY:
var me = cast[ptr xcb_motion_notify_event_t](ev)
p.mouse.posX = me.event_x
p.mouse.posY = me.event_y
of XCB_BUTTON_PRESS:
var bpe = cast[ptr xcb_button_press_event_t](ev)
case bpe.detail
of 4: p.updateMouseKeyState bpe.detail, MouseBtnAction.ScrollUp
of 5: p.updateMouseKeyState bpe.detail, MouseBtnAction.ScrollDown
else: p.updateMouseKeyState bpe.detail, MouseBtnAction.Press
p.mouseRep = cast[ptr xcb_query_pointer_reply_t](ev)
# need to subtract old mouse pos from new
of XCB_BUTTON_RELEASE:
var bpe = cast[ptr xcb_button_release_event_t](ev)
p.updateMouseKeyState bpe.detail, MouseBtnAction.Release
p.mouseRep = cast[ptr xcb_query_pointer_reply_t](ev)
of XCB_ENTER_NOTIFY: discard nil
of XCB_LEAVE_NOTIFY: discard nil
of XCB_KEY_PRESS:
var
kp: ptr xcb_key_press_event_t = cast[ ptr xcb_key_press_event_t](ev)
#kcs: int16 = if bitand(kp.state, ShiftMask).bool: 1 else: 0
#p.update_key_state(XkbKeycodeToKeysym(p.xdiplay, kp.detail.char, 0, kcs), Press)
# TODO: this can fail sometimes?
p.update_key_state(xkb_state_key_get_one_sym(p.xkbState, kp.detail), Key_Action.Press)
of XCB_KEY_RELEASE:
var
kr: ptr xcb_key_release_event_t = cast[ ptr xcb_key_release_event_t](ev)
#kcs: int16 = if bitand(kr.state, ShiftMask).bool: 1 else: 0
p.update_key_state(xkb_state_key_get_one_sym(p.xkbState, kr.detail), Key_Action.Release)
#of XCB_ALLOC_NAMED_COLOR: discard
of XCB_MAP_NOTIFY:
var
xkbe = cast[ptr xkb_event](ev)
#we = cast[ ptr xcb_map_notify_event_t](ev)
#geo = p.conn.xcb_get_geometry_reply(p.conn.xcb_get_geometry p.window, err0)
#echo geo[]
p.conn.loadKeyMap( p.xkbContext
, p.xkbkm
, p.xkbState
, p.xkbNewState
)
assert loadComposeTable( p.locale
, p.xkbCompTable
, p.xkbContext
, p.xkbCompState
) == true
p.currKeyboardGroup = xkbe[].state_notify.group
p.relays.window_mapped_relay( p
, 8
, 4
)
of XCB_XKB_MAP_NOTIFY: discard
of XCB_PROPERTY_NOTIFY:
var pe = cast[ptr xcb_property_notify_event_t](ev)
#if pe.atom == wmState: echo "?"
of XCB_CONFIGURE_NOTIFY:
var
ce = cast[ ptr xcb_configure_notify_event_t](ev)
if ce.width > 0 and ce.height > 0:
p.relays.window_resize_relay( p
, int ce.width
, int ce.height
)
of XCB_MAPPING_NOTIFY:
var km: ptr xcb_mapping_notify_event_t= cast[ ptr xcb_mapping_notify_event_t](ev)
#echo "mapping: MYA-NEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE!!!!~~~~"
# of XCB_MAP_REQUEST:
# var km: ptr xcb_map_request_event_t= cast[ ptr xcb_map_request_event_t](ev)
# echor km
# echo "map_request: MYA-NEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE!!!!~~~~"
# of XCB_XKB_STATE_NOTIFY: echo "wo"
#echo repr km
of XCB_CLIENT_MESSAGE:
# this is the "window close" response
if ev.response_type == 161: p.quit = true
#of XCB_INPUT_DEVICE_STATE_NOTIFY:
#of XCB_DESTROY_NOTIFY: echo "window destroyed"
of XCB_VISIBILITY_NOTIFY: discard
of XCB_FOCUS_IN: discard #echo "focused"
of XCB_FOCUS_OUT: discard #echo "un-focused"
else:
#echo repr ev
#echo ev.response_type
#echo ev.response_type, " <> ", p.firstXkbEvent
if ev.response_type == p.firstXkbEvent:
var xkbe = cast[ptr xkb_event](ev)
case xkbe.anyy.xkbType
of XCB_XKB_NEW_KEYBOARD_NOTIFY:
# check if things actually changed on the keyboard
# TODO: This also triggers for a first press on a 2nd mouse
echo "new keyboard!"
# echo repr p.xkbState.components
p.conn.loadKeyMap( p.xkbContext
, p.xkbkm
, p.xkbState
, p.xkbNewState
)
of XCB_XKB_MAP_NOTIFY:
echo "XCB_XKB_MAP_NOTIFY"
of XCB_XKB_STATE_NOTIFY:
#echo "XCB_XKB_STATE_NOTIFY"
#TODO: figure a better way
# Currently this event hits if any "mod" key is pressed like ctrl (4) or alt (8)
# so we check for this and filter out
# as we currently only care about getting a new keyboard layout/group
if xkbe[].state_notify.mods == 8 or
xkbe[].state_notify.mods == 4 or
xkbe[].state_notify.eventType == 5: discard # mouse button down (?)
else:
if xkbe[].state_notify.group != p.currKeyboardGroup:
p.conn.loadKeyMap( p.xkbContext
, p.xkbkm
, p.xkbState
, p.xkbNewState
)
p.group = xkb_state_serialize_layout(p.xkbNewState, XKB_STATE_LAYOUT_EFFECTIVE)
p.currKeyboardLayout = toString theLayoutName(p.group, p.xkbkm)
p.currKeyboardGroup = xkbe[].state_notify.group
of XCB_XKB_EVENT_TYPE_CONTROLS_NOTIFY.ord: discard
of XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY.ord: discard
of XCB_XKB_EVENT_TYPE_INDICATOR_MAP_NOTIFY.ord: discard
of XCB_XKB_EVENT_TYPE_NAMES_NOTIFY.ord: discard
of XCB_XKB_EVENT_TYPE_COMPAT_MAP_NOTIFY.ord: discard
#of uint8 XCB_XKB_EVENT_TYPE_BELL_NOTIFY.ord: discard
#of XCB_XKB_EVENT_TYPE_ACTION_MESSAGE.ord: discard
#of XCB_XKB_EVENT_TYPE_ACCESS_X_NOTIFY.ord: discard
#of XCB_XKB_EVENT_TYPE_EXTENSION_DEVICE_NOTIFY.ord: discard
else: discard
if ev.response_type == (uint p.xfixesEvent + XFixesCursorNotify): discard
# var ce = cast[ptr XFixesCursorNotifyEvent](ev)
# echo repr ce
# var e = cast[ptr XFixesCursorNotifyEvent](ev)
# echo repr e
when defined windows:
var
msg: MSG
while PeekMessageW( msg
, cast[HWND] (nil)
, 0
, 0
, PM_REMOVE
) != 0:
#for hook_proc in
TranslateMessage msg
DispatchMessage msg
{.experimental: "codeReordering".}
when defined linux:
import ../platforms/unix/x11/[x,xcb, xlib, xkbCommon]
when defined windows:
import winim/lean
import ../platforms/w64/[the_types]
import keyboard
, mouse
, monitor
import tables
type
NS = object
retina*: bool
frameName*: array[256, char]
X11 = object
className*: array[256, char]
instanceName*: array[256, char]
Win32_Key_Menu = object
keymenu*: bool
WL = object
appId*: array[256,char]
Portal_Config* = ref object
title*: ptr char
x_pos*: int
y_pos*: int
width*: int
height*: int
resizable*: bool
visible*: bool
decorated*: bool
focused*: bool
autoIconify*: bool
floating*: bool
maximized*: bool
centerCursor*: bool
focusOnShow*: bool
mousePassthrough*: bool
scaleToMonitor*: bool
ns*: NS
x11*: X11
win32*: Win32_Key_Menu
wl*: WL
PortalObj* = object of RootObj
relays*: Relays
quit*: bool
title*: string
monitorCount: int
currMonitor*: Monitor
monitors*: seq[Monitor]
width*: uint16
height*: uint16
config*: Portal_Config
when defined linux:
conn*: ptr xcb_connection_t
wid*: uint32
reply1*,reply2*: ptr xcb_intern_atom_reply_t
window*: xcb.xcb_window_t # is this root???
root*: Window
iter*: xcb_screen_iterator_t
setup*: ptr xcb_setup_t
userPtr*: pointer
group*: xkb_layout_index_t
xdisplay*: xlib.PDisplay
#kb*: X11Kb
xkbContext*: ptr xkb_context
xkbkm*: ptr xkb_keymap
xkbState* : ptr xkb_state
xkbNewState* : ptr xkb_state
xkbCompState*: ptr xkb_compose_state
xkbCompTable*: ptr xkb_compose_table
locale*: string
screen*: ptr xcb_screen_t
screenIter*: xcb_screen_iterator_t
firstXkbEvent*: uint8
# TODO: find the """proper""" xcb/xkb function for getting group change
# until then, we store the group, and compare it with the event's group
# when the proper event fires, in order to check if we need to update layouts for an actually new group
currKeyboardGroup*: uint8
# TODO: break this out into a proper `Keyboard` struct later
currKeyboardLayout*: string
xwindow*: Window
xfixesEvent*: int
xfixesError*: int
xcompEvent*: int
xcompError*: int
xcbReply*: ptr xcb_query_extension_reply_t
qp*: xcb_query_pointer_cookie_t
mouseRep*: ptr xcb_query_pointer_reply_t
mouse*: Mouse
wmState*: xcb_intern_atom_cookie_t
wmHidden*: xcb_intern_atom_cookie_t
wmMaxX*: xcb_intern_atom_cookie_t
wmMaxY*: xcb_intern_atom_cookie_t
winGeo*: WindowArea
#clientGeo*: WindowArea
#frameGeo*: WindowArea
when defined windows:
hwnd*: lean.HWND
msg*: MSG
wParam*: WPARAM
lParam*: LPARAM
wndclass*: WNDCLASS
h_Instance*: HMODULE
main_window_class*: ATOM
win32_message_loop_hook_procs*: seq[Win32_Message_Loop_Hook_Proc]
class_atom_table*: Table[string, ATOM]
accelorator_exists*: bool
w32_window_table*: Table[HWND, Win32_Window]
win32_Instance*: HANDLE
Portal* = ref object of PortalObj
# # relays.nim
type
Key_Relay* = proc ( the_portal: ptr Portal
, key: Key
, action: int
) {.closure.}
Mouse_Key_Relay* = proc ( the_portal: ptr Portal
, mb: MouseButton
, action: MouseBtnAction
) {.closure.}
Window_Resize_Relay* = proc ( the_portal: ptr Portal
, width: int
, height: int
) {.closure.}
Window_Mapped_Relay* = proc ( the_portal: ptr Portal
, width: int
, height: int
) {.closure.}
Window_Closed_Relay* = proc ( the_portal: ptr Portal
) {.closure.}
Window_Input_Language_Change_Relay* = proc ( the_portal: ptr Portal
) {.closure.}
Relays* = object
window_closed_relay*: Window_Closed_Relay
#key_relay*: Key_Relay
#mouse_key_relay*: Mouse_Key_Relay
#window_resize_relay*: Window_Resize_Relay
#window_mapped_relay*: Window_Mapped_Relay
#window_input_language_change_relay*: Window_Input_Language_Change_Relay
#[ proc set_key_relay*( the_portal: ptr Portal
, key_relay: Key_Relay
) =
#TODO: actual pointers like GLFW
the_portal.relays.key_relay = key_relay
proc set_mouse_relay*( the_portal: ptr Portal
, mouse_key_relay: Mouse_Key_Relay
) = the_portal.relays.mouse_key_relay = mouse_key_relay
proc set_window_resize_relay*( the_portal: ptr Portal
, window_resize_relay: Window_Resize_Relay
) = the_portal.relays.window_resize_relay = window_resize_relay
proc set_window_mapped_relay*( the_portal: ptr Portal
, window_mapped_relay: Window_Mapped_Relay
) = the_portal.relays.window_mapped_relay = window_mapped_relay ]#
proc set_window_closed_relay*( the_portal: ptr Portal
, window_closed_relay: Window_Closed_Relay
) = the_portal.relays.window_closed_relay = window_closed_relay
#[ proc set_window_input_language_change_relay*( the_portal: ptr Portal
, window_input_language_change_relay: Window_Input_Language_Change_Relay
) = the_portal.relays.window_input_language_change_relay = window_input_language_change_relay ]#
#[
void _glfwInputWindowCloseRequest(_GLFWwindow* window)
{
assert(window != NULL);
window->shouldClose = GLFW_TRUE;
if (window->callbacks.close)
window->callbacks.close((GLFWwindow*) window);
}
]#
proc close_window*(p: var Portal) =
when defined linux:
xcb_disconnect p.conn
discard xcb_destroy_window( p.conn
, p.window
)
when defined windows:
p.quit = true
import portalObj
, ../platforms/unix/x11/xcb
#, ../scene/tys
proc lock_window*( the_portal: ptr Portal
, width: int
, height: int
) =
when defined linux:
var theHints = WMSizeHints( flags: WM_SIZE_HINT_P_MIN_SIZE.ord
, minWidth: int32 width
, minHeight: int32 height
, maxWidth: int32 width
, maxHeight: int32 height
)
discard xcb_change_property( p.conn
, ord XCB_PROP_MODE_REPLACE
, p.window
, xcb_atom_t XCB_ATOM_WM_NORMAL_HINTS
, xcb_atom_t XCB_ATOM_WM_SIZE_HINTS
, 32
, uint32 WMSizeHints.sizeof shr 2
, addr theHints
)
proc toggle_full_screen*(the_portal: ptr Portal) =
when defined linux:
var
cookie3 = xcb_intern_atom(p.conn, 0, 15, "_MOTIF_WM_HINTS" )
reply3 = xcb_intern_atom_reply( p.conn, cookie3, err0)
fullscreenHint = MotifHints( flags: 2
, functions: 0
, decorations: 0
, input_mode: 0
, status: 0
)
decoratedHint = MotifHints( flags: MWM_HINTS_FUNCTIONS or MWM_HINTS_DECORATIONS
, functions: MWM_FUNC_MOVE or MWM_FUNC_MINIMIZE or MWM_FUNC_CLOSE
, decorations: MWM_DECOR_BORDER or MWM_DECOR_TITLE or MWM_DECOR_MINIMIZE or MWM_DECOR_MENU
, input_mode: 0
, status: 0
)
if not p.isFullScreen:
discard xcb_change_property( p.conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, p.window
, reply3.atom
, reply3.atom
, 32
, 5
, addr fullscreenHint
)
p.isFullScreen = true
else:
discard xcb_change_property( p.conn
, xcb_prop_mode_t.XCB_PROP_MODE_REPLACE.ord
, p.window
, reply3.atom
, reply3.atom #THIS is essential
, 32
, 5
, addr decoratedHint
)
p.isFullScreen = false
#[ proc top*(a: WindowArea): int16 = a.y
proc left*(a: WindowArea): int16 = a.x
proc right*(a: WindowArea): int16 = a.x + int16 a.width
proc bottom*(a: WindowArea): int16 = a.y + int16 a.height
proc `==`*(a, b: WindowArea): bool =
a.x == b.x and
a.y == b.y and
a.width == b.width and
a.height == b.height
]#
surfaceCreateInfo.connection = cast[ptr nvk.xcb_connection_t](portal.conn)
surfaceCreateInfo.window = cast[nvk.xcb_window_t](portal.window)
surfaceCreateInfo.connection = cast[ptr nvk.xcb_connection_t](wain.conn)
surfaceCreateInfo.window = cast[nvk.xcb_window_t](wain.window)
surfaceCreateInfo.hinstance = cast[nvk.HINSTANCE] ( addr portal.h_instance )
surfaceCreateInfo.hwnd = cast[nvk.HWND] (portal.hwnd)
surfaceCreateInfo.hinstance = cast[nvk.HINSTANCE] ( addr wain.h_instance )
surfaceCreateInfo.hwnd = cast[nvk.HWND] (wain.hwnd)
#set_key_relay (addr the_portal), key_relay
#set_mouse_relay(the_portal.addr, mouse_key_relay)
#set_window_resize_relay (addr the_portal), window_resize_relay
#set_window_mapped_relay (addr the_portal), window_mapped_relay
set_window_closed_relay (addr the_portal), window_closed_relay
echo repr the_portal.relays
set_key_relay( the_waindow[]
, key_relay
)
set_mouse_relay( the_waindow[]
, mouse_key_relay
)
echo "created window!"
echo repr lParam
SetWindowLongPtr( hwnd, GWLP_USERDATA, cast[LONG_PTR]( lParam) )
echo "WM_CREATE"
a_waindow = create(Waindow)
a_waindow = cast[ptr Waindow]( the_struct.lpCreateParams)
SetWindowLongPtr( hwnd, GWLP_USERDATA, cast[LONG_PTR]( a_waindow) )
of WM_KEYUP , WM_KEYDOWN, WM_SYSKEYDOWN, WM_SYSKEYUP:
of WM_KEYUP ,WM_SYSKEYUP:
var
virtual_key_code: WORD = LOWORD(wParam)
the_wain_key = wain_keyboard_key_from_windows_virtual_key_code(virtual_key_code)
echo "up: ", virtual_key_code
update_keyboard( the_wain_key
, ord Release
)
a_waindow[].relays.key_relay(a_waindow[]
, the_wain_key.key
, ord Release
)
of WM_KEYDOWN, WM_SYSKEYDOWN:
echo virtual_key_code, " <> ", scan_code
echo "down: ", virtual_key_code
var the_wain_key = wain_keyboard_key_from_windows_virtual_key_code(virtual_key_code)
update_keyboard( the_wain_key
, ord Press
)
a_waindow[].relays.key_relay( a_waindow[]
, the_wain_key.key
, Press.ord
)