Current section

Files

Jump to
etch src etch event.gleam
Raw

src/etch/event.gleam

//// This module provides functionality to capture different events like
//// pressing keys, mouse movement etc.
//// In order to be able to read events, [`init_event_server`](event.html#init_event_server)
//// must be called at the start of the application. See `events` example.
//// ```gleam
//// pub fn main()
//// stdout.execute([
//// command.EnterRaw,
//// ])
//// init_event_server()
//// loop()
////
//// fn loop() {
//// handle_input()
//// loop()
//// }
////
//// fn handle_input() {
//// case event.read() {
//// Some(Ok(Mouse(m))) -> {
//// stdout.execute([
//// command.Println("Got mouse event"),
//// ])
//// }
//// Some(Ok(Key(s))) -> {
//// stdout.execute([
//// command.Println(
//// "Got key event: \"" <> event.to_string(s.code) <> "\"",
//// ),
//// ])
//// }
//// }
//// }
//// Some(Error(_)) -> Nil
//// None -> Nil
//// }
//// }
//// ```
import etch/internal/consts.{csi}
import gleam/erlang/process
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result.{try}
import gleam/string
/// Event error.
pub type EventError {
FailedToParseEvent(String)
}
/// Keyboard enhancement flag.
/// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
pub type KeyboardEnhancementFlag {
DisambiguateEscapeCode
ReportEventTypes
ReportAlternateKeys
ReportAllKeysAsEscapeCode
ReportAssociatedText
}
/// Modifiers.
pub type Modifiers {
/// Modifiers.
Modifiers(
shift: Bool,
alt: Bool,
control: Bool,
super: Bool,
hyper: Bool,
meta: Bool,
)
}
/// Mouse button.
pub type MouseButton {
Left
Right
Middle
}
/// Mouse event kind.
pub type MouseEventKind {
/// Down (pressing).
Down(MouseButton)
/// Up (releasing).
Up(MouseButton)
/// Drag (moving cursor while holding a button).
Drag(MouseButton)
/// Moved cursor.
Moved
/// Scroll down.
ScrollDown
/// Scroll up.
ScrollUp
/// Scroll left.
ScrollLeft
/// Scroll right.
ScrollRight
}
/// Mouse event.
pub type MouseEvent {
/// Mouse event.
MouseEvent(kind: MouseEventKind, column: Int, row: Int, modifiers: Modifiers)
}
/// Media key code.
pub type MediaKeyCode {
Play
Pause
PlayPause
Reverse
Stop
FastForward
Rewind
TrackNext
TrackPrevious
Record
LowerVolume
RaiseVolume
MuteVolume
}
/// Modifier key code.
pub type ModifierKeyCode {
LeftShift
LeftControl
LeftAlt
LeftSuper
LeftHyper
LeftMeta
RightShift
RightControl
RightAlt
RightSuper
RightHyper
RightMeta
IsoLevel3Shift
IsoLevel5Shift
}
/// Key code (key pressed).
pub type KeyCode {
Char(String)
UpArrow
LeftArrow
DownArrow
RightArrow
Home
End
PageDown
Insert
Delete
PageUp
KeypadBegin
Enter
Backspace
Esc
Media(MediaKeyCode)
Modifier(ModifierKeyCode)
F(n: Int)
CapsLock
ScrollLock
NumLock
PrintScreen
PauseKeyCode
Tab
Backtab
Menu
}
/// Key event kind.
pub type KeyEventKind {
Press
Repeat
Release
}
/// Key event state.
pub type KeyEventState {
KeyEventState(keypad: Bool, capslock: Bool, numlock: Bool)
}
/// Key event.
pub type KeyEvent {
KeyEvent(
code: KeyCode,
modifiers: Modifiers,
kind: KeyEventKind,
state: KeyEventState,
text: String,
)
}
/// Event
pub type Event {
FocusGained
FocusLost
Key(KeyEvent)
Mouse(MouseEvent)
Resize(Int, Int)
}
@external(erlang, "io", "get_chars")
fn get_chars(chars: String, n: Int) -> String
@external(erlang, "event_ffi", "start_link")
fn start_link() -> Nil
@external(erlang, "event_ffi", "push")
fn push(event: Result(Event, EventError)) -> Nil
/// Checks if there is an [`Event`](event.html#Event) available.
/// Returns None if no events were received within the timeout.
/// See also [`read`](event.html#read).
@external(erlang, "event_ffi", "poll")
pub fn poll(timeout: Int) -> Option(Result(Event, EventError))
/// Checks if there is an [`Event`](event.html#Event) available.
/// Waits forever for an available event.
/// See also [`poll`](event.html#poll).
@external(erlang, "event_ffi", "read")
pub fn read() -> Option(Result(Event, EventError))
/// Initializes the event server responsible for listening for events.
pub fn init_event_server() {
start_link()
process.spawn(fn() { input_loop() })
}
fn input_loop() {
let str = get_chars("", 1024)
let str = string.to_graphemes(str)
let events = parse_events(str, "", [], False) |> list.reverse
push_events(events)
input_loop()
}
fn push_events(events: List(Result(Event, EventError))) {
case events {
[] -> Nil
[e, ..rest] -> {
push(e)
push_events(rest)
}
}
}
@internal
pub fn parse_events(
str: List(String),
esc_acc: String,
list_acc: List(Result(Event, EventError)),
in_escape_sequence: Bool,
) -> List(Result(Event, EventError)) {
case str, in_escape_sequence {
["\u{001b}", "[", ..rest], False -> {
parse_events(rest, "", list_acc, True)
}
["\u{001b}", "[", ..rest], True -> {
let list_acc = [
Error(FailedToParseEvent("Unterminated escape sequence")),
..list_acc
]
parse_events(rest, "", list_acc, True)
}
[c, ..rest], True -> {
let assert Ok(x) = string.to_utf_codepoints(c) |> list.first()
let x = string.utf_codepoint_to_int(x)
case x {
x if x >= 64 && x <= 126 -> {
let event = handle_escape_code(esc_acc <> c)
let list_acc = [event, ..list_acc]
parse_events(rest, "", list_acc, False)
}
_ -> {
parse_events(rest, esc_acc <> c, list_acc, True)
}
}
}
[s, ..rest], False -> {
let list_acc = [Ok(Key(default_key_event(Char(s)))), ..list_acc]
parse_events(rest, esc_acc <> s, list_acc, False)
}
[], _ -> {
list.reverse(list_acc)
}
}
}
fn default_key_event(key_code: KeyCode) -> KeyEvent {
KeyEvent(
code: key_code,
kind: Press,
modifiers: Modifiers(False, False, False, False, False, False),
state: KeyEventState(False, False, False),
text: "",
)
}
/// Convert [`KeyCode`](event.html#KeyCode) to a string.
pub fn to_string(key_code: KeyCode) -> String {
case key_code {
Char(s) -> s
DownArrow -> "down arrow"
End -> "end"
Home -> "home"
LeftArrow -> "left arrow"
RightArrow -> "right arrow"
UpArrow -> "up arrow"
F(n) -> "F" <> int.to_string(n)
PageDown -> "page down"
PageUp -> "page up"
Insert -> "insert"
Delete -> "delete"
CapsLock -> "capslock"
Enter -> "enter"
KeypadBegin -> "keypad begin"
Media(m) ->
case m {
FastForward -> "fast forward"
LowerVolume -> "lower volume"
MuteVolume -> "mute volume"
Pause -> "pause"
Play -> "play"
PlayPause -> "play-pause"
RaiseVolume -> "raise volume"
Record -> "record"
Reverse -> "reverse"
Rewind -> "rewind"
Stop -> "stop"
TrackNext -> "track next"
TrackPrevious -> "track previous"
}
Menu -> "menu"
Modifier(m) ->
case m {
IsoLevel3Shift -> "iso level-3 shift"
IsoLevel5Shift -> "iso level-5 shift"
LeftAlt -> "left alt"
LeftControl -> "left control"
LeftHyper -> "left hyper"
LeftMeta -> "left meta"
LeftShift -> "left shift"
LeftSuper -> "left super"
RightAlt -> "right alt"
RightControl -> "right control"
RightHyper -> "right hyper"
RightMeta -> "right meta"
RightShift -> "right shift"
RightSuper -> "right super"
}
NumLock -> "num lock"
PauseKeyCode -> "pause"
PrintScreen -> "print screen"
ScrollLock -> "scroll lock"
Esc -> "escape"
Backspace -> "backspace"
Backtab -> "backtab"
Tab -> "tab"
}
}
@internal
pub fn handle_escape_code(s: String) -> Result(Event, EventError) {
case s {
"A" -> Ok(Key(default_key_event(UpArrow)))
"B" -> Ok(Key(default_key_event(DownArrow)))
"C" -> Ok(Key(default_key_event(RightArrow)))
"D" -> Ok(Key(default_key_event(LeftArrow)))
"H" -> Ok(Key(default_key_event(Home)))
"F" -> Ok(Key(default_key_event(End)))
"O" -> Ok(FocusLost)
"I" -> Ok(FocusGained)
"M" <> code -> parse_normal_mouse(code)
"<" <> code -> parse_sgr_mouse(code)
";" <> code -> parse_modifier_key_code(code)
"P" -> Ok(Key(default_key_event(F(1))))
"Q" -> Ok(Key(default_key_event(F(2))))
"S" -> Ok(Key(default_key_event(F(4))))
"?" <> _s -> {
// case string.last(s) {
// // Ok("u") -> parse_keyboard_enhancement_flags(s)
// // Ok("s") -> parse_primary_device_attributes(s)
// _ -> Error(FailedToParseEvent("Failed to parse escape code"))
// }
Error(FailedToParseEvent("Failed to parse escape code"))
}
s ->
case starts_with_number(s) {
True -> {
case string.last(s) {
Ok("M") -> parse_rxvt_mouse(s)
Ok("~") -> parse_special_key_code(s)
Ok("u") -> parse_u_encoded_key_code(s)
Ok(l) if l != "R" -> parse_modifier_key_code(s)
_ -> Error(FailedToParseEvent("Unsupported numbered escape code"))
}
}
False -> {
Error(FailedToParseEvent("Failed to parse escape code"))
}
}
}
}
/// Returns cursor position.
/// Be careful when calling this function inside a loop where you are also listening for events.
/// Some events may be lost.
pub fn get_cursor_position() -> Result(#(Int, Int), EventError) {
io.print(csi <> "6n")
let pos = get_chars("", 32)
case pos {
"\u{001b}[" <> s -> {
case string.last(s) {
Ok("R") -> parse_cursor_position(s)
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}
@internal
pub fn parse_cursor_position(s: String) -> Result(#(Int, Int), EventError) {
let code = string.drop_end(s, 1)
let split = string.split(code, ";")
let res = case split {
[x, y] -> Ok(#(x, y))
_ -> Error(FailedToParseEvent("Failed to parse cursor position"))
}
use #(x, y) <- try(res)
let x = int.parse(x) |> result.unwrap(0)
let y = int.parse(y) |> result.unwrap(0)
Ok(#(x, y))
}
@internal
pub fn parse_u_encoded_key_code(code: String) -> Result(Event, EventError) {
let code = string.drop_end(code, 1)
let split = string.split(code, ";")
let res = case split {
[code, modifiers, text] -> Ok(#(code, modifiers, Some(text)))
[code, modifiers] -> Ok(#(code, modifiers, None))
[code] -> Ok(#(code, "0", None))
_ ->
Error(FailedToParseEvent("Failed to parse u encoded code (CSI <..> u)"))
}
use #(code, modifiers, text) <- try(res)
let text = case text {
Some(text) -> {
let assert Ok(text) =
int.parse(text)
|> result.unwrap(0)
|> string.utf_codepoint()
string.from_utf_codepoints([text])
}
None -> ""
}
let code_parts = string.split(code, ":")
let res = case code_parts {
[unicode, alternate_code] -> Ok(#(unicode, Some(alternate_code)))
[unicode] -> Ok(#(unicode, None))
_ -> Error(FailedToParseEvent("Failed to parse u encoded code"))
}
use #(code, alternate_code) <- try(res)
use #(modifier_mask, kind_mask) <- try(parse_modifier_and_kind(modifiers))
let #(modifiers, kind, state_from_modifier) = #(
parse_modifiers(modifier_mask),
parse_kind(kind_mask),
parse_modifier_to_state(modifier_mask),
)
let #(keycode, state_from_keycode) = case
translate_functional_key_code(code)
{
Some(#(keycode, state)) -> #(keycode, state)
None -> {
let assert Ok(c) =
int.parse(code)
|> result.unwrap(0)
|> string.utf_codepoint()
let c = string.from_utf_codepoints([c])
let keycode = case c {
"\r" -> Enter
"\n" -> Enter
"\u{001b}" -> Esc
"\u{0007}" -> Backspace
"\t" if modifiers.shift -> Backtab
"\t" -> Tab
c -> Char(c)
}
#(keycode, KeyEventState(False, False, False))
}
}
let modifiers = case keycode {
Modifier(x) if x == LeftAlt || x == RightAlt ->
Modifiers(..modifiers, alt: True)
Modifier(x) if x == LeftShift || x == RightShift ->
Modifiers(..modifiers, shift: True)
Modifier(x) if x == LeftControl || x == RightControl ->
Modifiers(..modifiers, control: True)
Modifier(x) if x == LeftSuper || x == RightSuper ->
Modifiers(..modifiers, super: True)
Modifier(x) if x == LeftHyper || x == RightHyper ->
Modifiers(..modifiers, hyper: True)
Modifier(x) if x == LeftMeta || x == RightMeta ->
Modifiers(..modifiers, meta: True)
_ -> modifiers
}
let keycode = case modifiers.shift {
False -> keycode
True ->
case alternate_code {
Some(c) -> {
let assert Ok(c) =
int.parse(c)
|> result.unwrap(0)
|> string.utf_codepoint()
let c = string.from_utf_codepoints([c])
Char(c)
}
None -> keycode
}
}
let state =
KeyEventState(
capslock: state_from_modifier.capslock || state_from_keycode.capslock,
keypad: state_from_modifier.keypad || state_from_keycode.keypad,
numlock: state_from_modifier.numlock || state_from_keycode.numlock,
)
Ok(Key(KeyEvent(keycode, modifiers, kind, state, text)))
}
@internal
pub fn translate_functional_key_code(
code: String,
) -> Option(#(KeyCode, KeyEventState)) {
let keycode = case code {
"57399" -> Some(Char("0"))
"57400" -> Some(Char("1"))
"57401" -> Some(Char("2"))
"57402" -> Some(Char("3"))
"57403" -> Some(Char("4"))
"57404" -> Some(Char("5"))
"57405" -> Some(Char("6"))
"57406" -> Some(Char("7"))
"57407" -> Some(Char("8"))
"57408" -> Some(Char("9"))
"57409" -> Some(Char("."))
"57410" -> Some(Char("/"))
"57411" -> Some(Char("*"))
"57412" -> Some(Char("-"))
"57413" -> Some(Char("+"))
"57414" -> Some(Enter)
"57415" -> Some(Char("="))
"57416" -> Some(Char(","))
"57417" -> Some(LeftArrow)
"57418" -> Some(RightArrow)
"57419" -> Some(UpArrow)
"57420" -> Some(DownArrow)
"57421" -> Some(PageUp)
"57422" -> Some(PageDown)
"57423" -> Some(Home)
"57424" -> Some(End)
"57425" -> Some(Insert)
"57426" -> Some(Delete)
"57427" -> Some(KeypadBegin)
_ -> None
}
case keycode {
Some(c) -> Some(#(c, KeyEventState(False, False, True)))
None -> {
let keycode = case code {
"57358" -> Some(CapsLock)
"57359" -> Some(ScrollLock)
"57360" -> Some(NumLock)
"57361" -> Some(PrintScreen)
"57362" -> Some(PauseKeyCode)
"57363" -> Some(Menu)
"57376" -> Some(F(13))
"57377" -> Some(F(14))
"57378" -> Some(F(15))
"57379" -> Some(F(16))
"57380" -> Some(F(17))
"57381" -> Some(F(18))
"57382" -> Some(F(19))
"57383" -> Some(F(20))
"57384" -> Some(F(21))
"57385" -> Some(F(22))
"57386" -> Some(F(23))
"57387" -> Some(F(24))
"57388" -> Some(F(25))
"57389" -> Some(F(26))
"57390" -> Some(F(27))
"57391" -> Some(F(28))
"57392" -> Some(F(29))
"57393" -> Some(F(30))
"57394" -> Some(F(31))
"57395" -> Some(F(32))
"57396" -> Some(F(33))
"57397" -> Some(F(34))
"57398" -> Some(F(35))
"57428" -> Some(Media(Play))
"57429" -> Some(Media(Pause))
"57430" -> Some(Media(PlayPause))
"57431" -> Some(Media(Reverse))
"57432" -> Some(Media(Stop))
"57433" -> Some(Media(FastForward))
"57434" -> Some(Media(Rewind))
"57435" -> Some(Media(TrackNext))
"57436" -> Some(Media(TrackPrevious))
"57437" -> Some(Media(Record))
"57438" -> Some(Media(LowerVolume))
"57439" -> Some(Media(RaiseVolume))
"57440" -> Some(Media(MuteVolume))
"57441" -> Some(Modifier(LeftShift))
"57442" -> Some(Modifier(LeftControl))
"57443" -> Some(Modifier(LeftAlt))
"57444" -> Some(Modifier(LeftSuper))
"57445" -> Some(Modifier(LeftHyper))
"57446" -> Some(Modifier(LeftMeta))
"57447" -> Some(Modifier(RightShift))
"57448" -> Some(Modifier(RightControl))
"57449" -> Some(Modifier(RightAlt))
"57450" -> Some(Modifier(RightSuper))
"57451" -> Some(Modifier(RightHyper))
"57452" -> Some(Modifier(RightMeta))
"57453" -> Some(Modifier(IsoLevel3Shift))
"57454" -> Some(Modifier(IsoLevel5Shift))
_ -> None
}
case keycode {
None -> None
Some(c) -> Some(#(c, KeyEventState(False, False, False)))
}
}
}
}
@internal
pub fn parse_special_key_code(code: String) -> Result(Event, EventError) {
let code = string.drop_end(code, 1)
let split = string.split(code, ";")
let res = case split {
[key, modifier] -> Ok(#(key, modifier))
[key] -> Ok(#(key, "0"))
_ ->
Error(FailedToParseEvent("Failed to parse special key code (CSI <..> ~)"))
}
use #(key, modifier) <- try(res)
use #(modifier_mask, kind_mask) <- try(parse_modifier_and_kind(modifier))
let #(modifiers, kind, state) = #(
parse_modifiers(modifier_mask),
parse_kind(kind_mask),
parse_modifier_to_state(modifier_mask),
)
let key = int.parse(key) |> result.unwrap(1)
let key = case key {
1 | 7 -> Ok(Home)
2 -> Ok(Insert)
3 -> Ok(Delete)
4 | 8 -> Ok(End)
5 -> Ok(PageUp)
6 -> Ok(PageDown)
k -> {
case k {
k if k >= 11 && k <= 15 -> Ok(F(k - 10))
k if k >= 17 && k <= 21 -> Ok(F(k - 11))
k if k >= 23 && k <= 26 -> Ok(F(k - 12))
28 | 29 -> Ok(F(k - 15))
k if k >= 31 && k <= 34 -> Ok(F(k - 17))
_ ->
Error(FailedToParseEvent(
"Failed to parse special key code (CSI <..> ~)",
))
}
}
}
use key <- try(key)
Ok(
Key(KeyEvent(
code: key,
kind: kind,
modifiers: modifiers,
state: state,
text: "",
)),
)
}
@internal
pub fn parse_modifier_to_state(modifier_mask: Int) -> KeyEventState {
let state = KeyEventState(keypad: False, numlock: False, capslock: False)
let mask = case modifier_mask - 1 {
n if n < 0 -> 0
n -> n
}
let state = case int.bitwise_and(mask, 64) != 0 {
True -> KeyEventState(..state, capslock: True)
False -> state
}
let state = case int.bitwise_and(mask, 128) != 0 {
True -> KeyEventState(..state, numlock: True)
False -> state
}
state
}
@internal
pub fn parse_rxvt_mouse(s: String) -> Result(Event, EventError) {
let s = string.drop_end(s, 1)
let split = string.split(s, ";")
let res = case split {
[code, column, row] -> Ok(#(code, column, row))
_ -> Error(FailedToParseEvent("Failed to parse sgr mouse code"))
}
use #(code, column, row) <- try(res)
let column = int.parse(column) |> result.unwrap(0)
let row = int.parse(row) |> result.unwrap(0)
let column = column - 1
let row = row - 1
use #(modifiers, kind) <- try(parse_cb(code))
Ok(
Mouse(MouseEvent(kind: kind, modifiers: modifiers, column: column, row: row)),
)
}
@internal
pub fn starts_with_number(s: String) -> Bool {
string.first(s) |> result.unwrap("") |> int.parse() |> result.is_ok()
}
/// Get keyboard enhancement flags. See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
/// Be careful when calling this function inside a loop where you are also listening for events.
/// Some events may be lost.
pub fn get_keyboard_enhancement_flags() -> Result(
List(KeyboardEnhancementFlag),
EventError,
) {
io.print(csi <> "?u")
let flags = get_chars("", 32)
case flags {
"\u{001b}[?" <> s -> {
case string.last(s) {
Ok("u") -> Ok(parse_keyboard_enhancement_flags(s))
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}
@internal
pub fn parse_keyboard_enhancement_flags(
code: String,
) -> List(KeyboardEnhancementFlag) {
let code = string.drop_end(code, 1) |> int.parse() |> result.unwrap(0)
let list = []
let list = case int.bitwise_and(code, 1) != 0 {
True -> [DisambiguateEscapeCode, ..list]
False -> list
}
let list = case int.bitwise_and(code, 2) != 0 {
True -> [ReportEventTypes, ..list]
False -> list
}
let list = case int.bitwise_and(code, 4) != 0 {
True -> [ReportAlternateKeys, ..list]
False -> list
}
let list = case int.bitwise_and(code, 8) != 0 {
True -> [ReportAllKeysAsEscapeCode, ..list]
False -> list
}
let list = case int.bitwise_and(code, 16) != 0 {
True -> [ReportAssociatedText, ..list]
False -> list
}
list
}
@internal
pub fn parse_modifier_key_code(code: String) -> Result(Event, EventError) {
let key = string.last(code) |> result.unwrap("fallback")
let code = string.drop_end(code, 1)
let split = string.split(code, ";")
let res = case split {
[_, modifiers] -> Ok(modifiers)
_ ->
Error(FailedToParseEvent("Failed to parse special key code (CSI <..> ~)"))
}
use modifiers <- try(res)
use #(modifier_mask, kind_mask) <- try(parse_modifier_and_kind(modifiers))
let #(modifiers, kind) = #(
parse_modifiers(modifier_mask),
parse_kind(kind_mask),
)
let res = case key {
"A" -> Ok(UpArrow)
"B" -> Ok(DownArrow)
"C" -> Ok(RightArrow)
"D" -> Ok(LeftArrow)
"F" -> Ok(End)
"H" -> Ok(Home)
"P" -> Ok(F(1))
"Q" -> Ok(F(2))
"R" -> Ok(F(3))
"S" -> Ok(F(4))
_ -> Error(FailedToParseEvent("Failed to parse modifier key code"))
}
use key <- try(res)
Ok(
Key(KeyEvent(
code: key,
kind: kind,
modifiers: modifiers,
state: KeyEventState(False, False, False),
text: "",
)),
)
}
@internal
pub fn parse_kind(kind: Int) -> KeyEventKind {
case kind {
1 -> Press
2 -> Repeat
3 -> Release
_ -> Press
}
}
@internal
pub fn parse_modifier_and_kind(code: String) -> Result(#(Int, Int), EventError) {
let split = string.split(code, ":")
case split {
[modifier_mask, kind_mask] -> {
let modifier_mask = int.parse(modifier_mask) |> result.unwrap(0)
let kind_mask = int.parse(kind_mask) |> result.unwrap(0)
Ok(#(modifier_mask, kind_mask))
}
[modifier_mask] -> {
let modifier_mask = int.parse(modifier_mask) |> result.unwrap(0)
Ok(#(modifier_mask, 0))
}
_ -> Error(FailedToParseEvent("Failed to parse modifiers"))
}
}
/// Enables mouse capture.
/// It is prefered not to use this directly. See [`EnableMouseCapture`](command.html#EnableMouseCapture).
pub fn enable_mouse_capture() {
csi
<> "?1000h"
<> csi
<> "?1002h"
<> csi
<> "?1003h"
<> csi
<> "?1015h"
<> csi
<> "?1006h"
}
/// Disables mouse capture.
/// It is prefered not to use this directly. See [`DisableMouseCapture`](command.html#DisableMouseCapture).
pub fn disable_mouse_capture() {
csi
<> "?1000l"
<> csi
<> "?1002l"
<> csi
<> "?1003l"
<> csi
<> "?1015l"
<> csi
<> "?1006l"
}
@internal
pub fn parse_normal_mouse(code: String) -> Result(Event, EventError) {
let res = case string.to_graphemes(code) {
[cb, cx, cy] -> Ok(#(cb, cx, cy))
_ -> Error(FailedToParseEvent("Failed to parse normal mouse code"))
}
use #(cb, cx, cy) <- try(res)
let column = int.parse(cx) |> result.unwrap(0)
let row = int.parse(cy) |> result.unwrap(0)
use #(modifiers, kind) <- try(parse_cb(cb))
Ok(
Mouse(MouseEvent(kind: kind, modifiers: modifiers, column: column, row: row)),
)
}
@internal
pub fn parse_sgr_mouse(s: String) -> Result(Event, EventError) {
let split = string.split(s, ";")
let res = case split {
[code, column, row] -> Ok(#(code, column, row))
_ -> Error(FailedToParseEvent("Failed to parse sgr mouse code"))
}
use #(code, column, row) <- try(res)
let column = int.parse(column) |> result.unwrap(0)
let is_release = string.ends_with(row, "m")
let row = string.drop_end(row, 1) |> int.parse() |> result.unwrap(0)
use #(modifiers, kind) <- try(parse_cb(code))
let kind = case is_release {
True ->
case kind {
Down(b) -> Up(b)
other -> other
}
False -> {
kind
}
}
Ok(
Mouse(MouseEvent(kind: kind, modifiers: modifiers, column: column, row: row)),
)
}
@internal
pub fn parse_cb(
code: String,
) -> Result(#(Modifiers, MouseEventKind), EventError) {
let code = int.parse(code) |> result.unwrap(0)
let button_number =
int.bitwise_and(code, 0b0000_0011)
|> int.bitwise_or(
int.bitwise_and(code, 0b1100_0000) |> int.bitwise_shift_right(4),
)
let dragging = int.bitwise_and(code, 0b0010_0000) == 0b0010_0000
let modifiers =
Modifiers(
shift: int.bitwise_and(code, 4) != 0,
alt: int.bitwise_and(code, 8) != 0,
control: int.bitwise_and(code, 16) != 0,
super: int.bitwise_and(code, 32) != 0,
hyper: int.bitwise_and(code, 64) != 0,
meta: int.bitwise_and(code, 128) != 0,
)
let button = case button_number {
0 -> Left
1 -> Middle
_ -> Right
}
let res = case button_number, dragging {
0, False -> Ok(Down(Left))
1, False -> Ok(Down(Middle))
2, False -> Ok(Down(Right))
0, True -> Ok(Drag(Left))
1, True -> Ok(Drag(Middle))
2, True -> Ok(Drag(Right))
3, False -> Ok(Up(Left))
4, False -> Ok(ScrollUp)
5, False -> Ok(ScrollDown)
6, False -> Ok(ScrollLeft)
7, False -> Ok(ScrollRight)
c, True if c == 3 || c == 4 || c == 5 -> Ok(Moved)
_, True -> Ok(Drag(button))
_, _ -> Error(FailedToParseEvent("Failed to parse sgr mouse code"))
}
use kind <- try(res)
Ok(#(modifiers, kind))
}
@internal
pub fn parse_modifiers(code: Int) -> Modifiers {
let mask = case code - 1 {
x if x < 0 -> 0
x -> x
}
Modifiers(
shift: int.bitwise_and(mask, 1) != 0,
alt: int.bitwise_and(mask, 2) != 0,
control: int.bitwise_and(mask, 4) != 0,
super: int.bitwise_and(mask, 8) != 0,
hyper: int.bitwise_and(mask, 16) != 0,
meta: int.bitwise_and(mask, 32) != 0,
)
}
/// Enables focus change.
/// It is prefered not to use this directly. See [`EnableFocusChange`](command.html#EnableFocusChange).
pub fn enable_focus_change() {
csi <> "?1004h"
}
/// Disables focus change.
/// It is prefered not to use this directly. See [`DisableFocusChange`](command.html#DisableFocusChange).
pub fn disable_focus_change() {
csi <> "?1004l"
}
/// Pushes keyboard enhancement flags.
/// It is prefered not to use this directly. See [`PushKeyboardEnhancementFlags`](command.html#PushKeyboardEnhancementFlags).
pub fn push_keyboard_enhancement_flags(flags: List(KeyboardEnhancementFlag)) {
push_keyboard_enhancement_flags_inner(flags, 0)
}
fn push_keyboard_enhancement_flags_inner(
flags: List(KeyboardEnhancementFlag),
acc: Int,
) {
case flags {
[DisambiguateEscapeCode, ..rest] ->
push_keyboard_enhancement_flags_inner(rest, acc + 1)
[ReportEventTypes, ..rest] ->
push_keyboard_enhancement_flags_inner(rest, acc + 2)
[ReportAlternateKeys, ..rest] ->
push_keyboard_enhancement_flags_inner(rest, acc + 4)
[ReportAllKeysAsEscapeCode, ..rest] ->
push_keyboard_enhancement_flags_inner(rest, acc + 8)
[ReportAssociatedText, ..rest] ->
push_keyboard_enhancement_flags_inner(rest, acc + 16)
[] -> {
csi <> ">" <> int.to_string(acc) <> "u"
}
}
}
/// Pops keyboard enhancement flags.
/// It is prefered not to use this directly. See [`PopKeyboardEnhancementFlags`](command.html#PopKeyboardEnhancementFlags).
pub fn pop_keyboard_enhancement_flags() {
csi <> "<1u"
}