├── docs ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-70x70.png ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── browserconfig.xml ├── site.webmanifest ├── safari-pinned-tab.svg ├── normalize.css ├── doc.css └── auto.html ├── v.mod ├── .gitattributes ├── src ├── c │ ├── mouse.h │ ├── mouse_linux.h │ └── mouse_linux.c ├── types.c.v ├── screen.c.v ├── mouse.c.v └── keyboard.c.v ├── .gitignore ├── README.md └── LICENSE /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/mstile-70x70.png -------------------------------------------------------------------------------- /docs/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/mstile-144x144.png -------------------------------------------------------------------------------- /docs/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/mstile-150x150.png -------------------------------------------------------------------------------- /docs/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/mstile-310x150.png -------------------------------------------------------------------------------- /docs/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/mstile-310x310.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/islonely/auto/HEAD/docs/android-chrome-512x512.png -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'auto' 3 | description: 'Set and get mouse position.' 4 | version: '0.1' 5 | license: 'MIT' 6 | dependencies: [] 7 | } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.bat eol=crlf 3 | 4 | **/*.v linguist-language=V 5 | **/*.vv linguist-language=V 6 | **/*.vsh linguist-language=V 7 | **/v.mod linguist-language=V 8 | -------------------------------------------------------------------------------- /src/c/mouse.h: -------------------------------------------------------------------------------- 1 | // Position is a point on an X, Y axis used to 2 | // represent the location of the mouse pointer. 3 | struct Position { 4 | int x, y; 5 | }; 6 | 7 | // Size is the width and height of something. 8 | struct Size { 9 | int width, height; 10 | }; -------------------------------------------------------------------------------- /docs/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | main 3 | mouse 4 | *.exe 5 | *.exe~ 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Ignore binary output folders 11 | bin/ 12 | 13 | # Ignore common editor/system specific metadata 14 | .DS_Store 15 | .idea/ 16 | .vscode/ 17 | *.iml 18 | 19 | # ENV 20 | .env 21 | 22 | # vweb and database 23 | *.db 24 | *.js 25 | 26 | test.v 27 | .editorconfig -------------------------------------------------------------------------------- /src/c/mouse_linux.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mouse.h" 5 | 6 | // get_mouse_pos gets the mouse position and returns it. 7 | struct Position get_mouse_pos(); 8 | // set_mouse_pos moves the position of the mouse to X, Y. 9 | void set_mouse_pos(int x, int y); 10 | // screen_size returns the width and height of the screen. 11 | struct Size screen_size(); -------------------------------------------------------------------------------- /docs/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Cross-Platform mouse and keyboard manipulation. 2 | Cross-platform tool to manipulate the mouse and keyboard to automate tasks. 3 | ```v 4 | import auto 5 | import time 6 | 7 | fn main() { 8 | // get current mouse position 9 | x, y := auto.Mouse.get_pos() 10 | println('Mouse is at: X: ${x}, Y: ${y}') 11 | 12 | // get the dimensions of the primary display 13 | sz := auto.Screen.size() 14 | 15 | // center the mouse in the middle of the screen 16 | auto.Mouse.set_pos(sz.width / 2, sz.height / 2) 17 | 18 | // left and right click the mouse 19 | auto.Mouse.click(.left) 20 | auto.Mouse.click(.right) 21 | 22 | // double left click 23 | auto.Mouse.double_click() 24 | 25 | // click and drag mouse to 100, 150 26 | auto.Mouse.drag_to(100, 150, button: .left, duration: time.second) 27 | 28 | // emulate keyboard typing 29 | auto.Keyboard.press(.s) 30 | auto.Keyboard.write('some message to write', speed: 100 * time.millisecond) 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /src/c/mouse_linux.c: -------------------------------------------------------------------------------- 1 | // See .h for comments. 2 | #include "mouse_linux.h" 3 | 4 | struct Position get_mouse_pos() { 5 | struct Position pos = {-1, -1}; 6 | Display *dpy = XOpenDisplay(0); 7 | Window root_window = XRootWindow(dpy, 0); 8 | Window root_id = 0, child_id = 0; 9 | int win_x = 0, 10 | win_y = 0, 11 | mask = 0; 12 | XQueryPointer(dpy, root_window, &root_id, &child_id, &pos.x, &pos.y, &win_x, &win_y, &mask); 13 | XCloseDisplay(dpy); 14 | return pos; 15 | } 16 | 17 | void set_mouse_pos(int x, int y) { 18 | Display *dpy = XOpenDisplay(0); 19 | Window root_window = XRootWindow(dpy, 0); 20 | XSelectInput(dpy, root_window, KeyReleaseMask); 21 | XWarpPointer(dpy, None, root_window, 0, 0, 0, 0, x, y); 22 | XFlush(dpy); 23 | XCloseDisplay(dpy); 24 | } 25 | 26 | struct Size screen_size() { 27 | Display *dpy = XOpenDisplay(0); 28 | int screen = DefaultScreen(dpy); 29 | struct Size sz = { 30 | DisplayWidth(dpy, screen), 31 | DisplayHeight(dpy, screen) 32 | }; 33 | XCloseDisplay(dpy); 34 | return sz; 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Adam Oates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | html { 3 | line-height: 1.15; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | 7 | main { 8 | display: block; 9 | } 10 | 11 | h1 { 12 | font-size: 2em; 13 | margin: 0.67em 0; 14 | } 15 | 16 | pre { 17 | font-family: monospace, monospace; 18 | font-size: 1em; 19 | } 20 | 21 | a { 22 | background-color: transparent; 23 | } 24 | 25 | abbr[title] { 26 | border-bottom: none; 27 | text-decoration: underline; 28 | text-decoration: underline dotted; 29 | } 30 | 31 | b, 32 | strong { 33 | font-weight: bolder; 34 | } 35 | 36 | kbd, 37 | samp { 38 | font-family: monospace, monospace; 39 | font-size: 1em; 40 | } 41 | 42 | small { 43 | font-size: 80%; 44 | } 45 | 46 | sub, 47 | sup { 48 | font-size: 75%; 49 | line-height: 0; 50 | position: relative; 51 | vertical-align: baseline; 52 | } 53 | 54 | sub { 55 | bottom: -0.25em; 56 | } 57 | 58 | sup { 59 | top: -0.5em; 60 | } 61 | 62 | img { 63 | border-style: none; 64 | } 65 | 66 | button, 67 | input, 68 | optgroup, 69 | select, 70 | textarea { 71 | font-family: inherit; 72 | font-size: 100%; 73 | line-height: 1.15; 74 | margin: 0; 75 | } 76 | 77 | button, 78 | input { 79 | overflow: visible; 80 | } 81 | 82 | button, 83 | select { 84 | text-transform: none; 85 | } 86 | 87 | button, 88 | [type='button'], 89 | [type='reset'], 90 | [type='submit'] { 91 | -webkit-appearance: button; 92 | } 93 | 94 | button::-moz-focus-inner, 95 | [type='button']::-moz-focus-inner, 96 | [type='reset']::-moz-focus-inner, 97 | [type='submit']::-moz-focus-inner { 98 | border-style: none; 99 | padding: 0; 100 | } 101 | 102 | button:-moz-focusring, 103 | [type='button']:-moz-focusring, 104 | [type='reset']:-moz-focusring, 105 | [type='submit']:-moz-focusring { 106 | outline: 1px dotted ButtonText; 107 | } 108 | 109 | fieldset { 110 | padding: 0.35em 0.75em 0.625em; 111 | } 112 | 113 | legend { 114 | box-sizing: border-box; 115 | color: inherit; 116 | display: table; 117 | max-width: 100%; 118 | padding: 0; 119 | white-space: normal; 120 | } 121 | 122 | progress { 123 | vertical-align: baseline; 124 | } 125 | 126 | textarea { 127 | overflow: auto; 128 | } 129 | 130 | [type='checkbox'], 131 | [type='radio'] { 132 | box-sizing: border-box; 133 | padding: 0; 134 | } 135 | 136 | [type='number']::-webkit-inner-spin-button, 137 | [type='number']::-webkit-outer-spin-button { 138 | height: auto; 139 | } 140 | 141 | [type='search'] { 142 | -webkit-appearance: textfield; 143 | outline-offset: -2px; 144 | } 145 | 146 | [type='search']::-webkit-search-decoration { 147 | -webkit-appearance: none; 148 | } 149 | 150 | ::-webkit-file-upload-button { 151 | -webkit-appearance: button; 152 | font: inherit; 153 | } 154 | 155 | summary { 156 | display: list-item; 157 | } 158 | 159 | template { 160 | display: none; 161 | } 162 | 163 | [hidden] { 164 | display: none; 165 | } 166 | -------------------------------------------------------------------------------- /src/types.c.v: -------------------------------------------------------------------------------- 1 | module auto 2 | 3 | $if linux { 4 | #flag -lX11 5 | #flag -lXrandr 6 | #include 7 | #include 8 | #include 9 | #include 10 | } $else $if windows { 11 | #flag -mwindows 12 | #include 13 | } $else $if macos { 14 | #flag -framework ApplicationServices 15 | #include 16 | } $else { 17 | $compile_error('Unsupported OS') 18 | } 19 | 20 | // Size is the width and height of a screen. 21 | pub struct Size { 22 | __global: 23 | width int 24 | height int 25 | } 26 | 27 | // Pos is the X and Y coordinates on a screen. 28 | pub struct Pos { 29 | __global: 30 | x int 31 | y int 32 | } 33 | 34 | // Linux X11 35 | @[typedef] 36 | struct C.XRRScreenResources { 37 | noutput int 38 | outputs &int 39 | } 40 | 41 | @[typedef] 42 | struct C.XRROutputInfo { 43 | crtc u64 44 | } 45 | 46 | @[typedef] 47 | struct C.XRRCrtcInfo { 48 | width u32 49 | height u32 50 | x int 51 | y int 52 | } 53 | 54 | fn C.XOpenDisplay(int) voidptr 55 | fn C.XCloseDisplay(voidptr) int 56 | fn C.XQueryPointer(voidptr, voidptr, &u32, &u32, &int, &int, &u32, &u32, &u32) 57 | fn C.XRootWindow(voidptr, int) voidptr 58 | fn C.XSelectInput(voidptr, voidptr, int) 59 | fn C.XWarpPointer(voidptr, int, voidptr, int, int, int, int, int, int) 60 | fn C.XFlush(voidptr) 61 | fn C.DefaultScreen(voidptr) int 62 | fn C.DefaultRootWindow(voidptr) u64 63 | fn C.XRRGetScreenResources(voidptr, u64) &C.XRRScreenResources 64 | fn C.XRRGetOutputPrimary(voidptr, u64) u64 65 | fn C.XRRFreeScreenResources(&C.XRRScreenResources) 66 | fn C.XRRGetOutputInfo(voidptr, &C.XRRScreenResources, u64) &C.XRROutputInfo 67 | fn C.XRRFreeOutputInfo(&C.XRROutputInfo) 68 | fn C.XRRGetCrtcInfo(voidptr, &C.XRRScreenResources, u64) &C.XRRCrtcInfo 69 | fn C.XRRFreeCrtcInfo(&C.XRRCrtcInfo) 70 | 71 | // MacOS declarations 72 | @[typedef] 73 | struct C.CGPoint { 74 | __global: 75 | x f64 76 | y f64 77 | } 78 | 79 | @[typedef] 80 | struct C.CGSize { 81 | __global: 82 | width f64 83 | height f64 84 | } 85 | 86 | @[typedef] 87 | struct C.CGRect { 88 | __global: 89 | origin C.CGPoint 90 | size C.CGSize 91 | } 92 | 93 | fn C.CGDisplayBounds(u32) C.CGRect 94 | fn C.CGMainDisplayID() u32 95 | fn C.CGWarpMouseCursorPosition(C.CGPoint) 96 | fn C.CGEventCreate(voidptr) voidptr 97 | fn C.CGEventGetLocation(voidptr) C.CGPoint 98 | fn C.CGEventCreateMouseEvent(voidptr, int, C.CGPoint, int) voidptr 99 | fn C.CFRelease(voidptr) 100 | fn C.CGEventPost(int, voidptr) 101 | fn C.CGDisplayCopyDisplayMode(u32) voidptr 102 | fn C.CGDisplayModeGetRefreshRate(voidptr) f64 103 | fn C.CGEventCreateKeyboardEvent(voidptr, int, bool) voidptr 104 | 105 | // Windows 106 | @[typedef] 107 | struct C.POINT { 108 | x int 109 | y int 110 | } 111 | 112 | @[typedef] 113 | struct C.DEVMODE { 114 | dmDisplayFrequency u32 115 | dmSize u16 116 | } 117 | 118 | fn C.GetCursorPos(&C.POINT) bool 119 | fn C.GetSystemMetrics(int) int 120 | fn C.mouse_event(u32, u32, u32, u32, u64) 121 | fn C.EnumDisplaySettings(&u16, u32, &C.DEVMODE) bool 122 | fn C.keybd_event(u8, u8, u32, u64) 123 | -------------------------------------------------------------------------------- /src/screen.c.v: -------------------------------------------------------------------------------- 1 | module auto 2 | 3 | import os 4 | 5 | // The compositor used by the user. 6 | const compositor = Screen.compositor() 7 | 8 | // Compositor is the window compositor being used by the system. 9 | pub enum Compositor { 10 | unknown 11 | wayland 12 | x11 13 | quartz 14 | windows 15 | } 16 | 17 | // Screen acts as a namespace for screen related functions. 18 | @[noinit] 19 | pub struct Screen {} 20 | 21 | // Screen.primary_info gets the width, height, x, and y of the primary display on X11. 22 | fn Screen.primary_info() ?C.XRRCrtcInfo { 23 | display := C.XOpenDisplay(unsafe { nil }) 24 | if display == unsafe { nil } { 25 | return none 26 | } 27 | defer { C.XCloseDisplay(display) } 28 | root := C.DefaultRootWindow(display) 29 | resources := C.XRRGetScreenResources(display, root) 30 | if resources == unsafe { nil } { 31 | return none 32 | } 33 | defer { C.XRRFreeScreenResources(resources) } 34 | primary_output := C.XRRGetOutputPrimary(display, root) 35 | if primary_output == 0 { 36 | return none 37 | } 38 | for i := 0; i < resources.noutput; i++ { 39 | if unsafe { u64(resources.outputs[i]) } == primary_output { 40 | output_info := C.XRRGetOutputInfo(display, resources, unsafe { resources.outputs[i] }) 41 | if output_info == unsafe { nil } { 42 | return none 43 | } 44 | defer { C.XRRFreeOutputInfo(output_info) } 45 | crtc_info := C.XRRGetCrtcInfo(display, resources, output_info.crtc) 46 | if crtc_info == unsafe { nil } { 47 | return none 48 | } 49 | val := *crtc_info 50 | defer { C.XRRFreeCrtcInfo(crtc_info) } 51 | return val 52 | } 53 | } 54 | return none 55 | } 56 | 57 | // Screen.get_compositor gets whether or not a user has X11 or Wayland as 58 | // their window compositor. 59 | pub fn Screen.compositor() Compositor { 60 | $if macos { 61 | return .quartz 62 | } 63 | $if windows { 64 | return .windows 65 | } 66 | xdg_session_type := os.getenv_opt('XDG_SESSION_TYPE') or { 67 | // assume X11 68 | return .x11 69 | } 70 | return if xdg_session_type == 'wayland' { 71 | .wayland 72 | } else { 73 | // assume X11 74 | .x11 75 | } 76 | } 77 | 78 | // Screen.size returns the size of the primary display. 79 | pub fn Screen.size() Size { 80 | $if macos { 81 | primary_screen := C.CGDisplayBounds(C.CGMainDisplayID()) 82 | return Size{ 83 | width: int(primary_screen.size.width) 84 | height: int(primary_screen.size.height) 85 | } 86 | } $else $if linux { 87 | crtc_info := Screen.primary_info() or { return Size{} } 88 | return Size{ 89 | width: int(crtc_info.width) 90 | height: int(crtc_info.height) 91 | } 92 | } $else $if windows { 93 | return Size{ 94 | width: C.GetSystemMetrics(C.SM_CXSCREEN) 95 | height: C.GetSystemMetrics(C.SM_CYSCREEN) 96 | } 97 | } 98 | return Size{} 99 | } 100 | 101 | // Screen.refresh_rate returns the screen refresh rate of the primary display. 102 | pub fn Screen.refresh_rate() ?int { 103 | $if macos { 104 | primary_display := C.CGMainDisplayID() 105 | display_mode := C.CGDisplayCopyDisplayMode(primary_display) 106 | if display_mode == unsafe { nil } { 107 | return none 108 | } 109 | return int(C.CGDisplayModeGetRefreshRate(display_mode)) 110 | } $else $if windows { 111 | devmode := C.DEVMODE{} 112 | if C.EnumDisplaySettings(unsafe { nil }, C.ENUM_CURRENT_SETTINGS, &devmode) { 113 | return int(devmode.dmDisplayFrequency) 114 | } 115 | } 116 | return none 117 | } 118 | -------------------------------------------------------------------------------- /src/mouse.c.v: -------------------------------------------------------------------------------- 1 | module auto 2 | 3 | import time 4 | 5 | // Button is the buttons on a mouse or touchpad. 6 | pub enum Button { 7 | left 8 | right 9 | middle 10 | } 11 | 12 | // EventType is an event used by C.CGEventCreateMouseEvent. 13 | enum EventType { 14 | tap_disabled_by_timeout = -2 15 | tap_disabled_by_user_input = -1 16 | null 17 | left_mouse_down 18 | left_mouse_up 19 | right_mouse_down 20 | right_mouse_up 21 | mouse_moved 22 | left_mouse_dragged 23 | right_mouse_dragged 24 | key_down = 10 25 | key_up 26 | flags_changed 27 | scroll_wheel = 22 28 | tablet_pointer 29 | tablet_proximity 30 | other_mouse_down 31 | other_mouse_up 32 | other_mouse_dragged 33 | } 34 | 35 | // Mouse acts as a namespace for mouse related functions. 36 | @[noinit] 37 | pub struct Mouse {} 38 | 39 | // Mouse.get_pos returns the global X and Y coordinates of the mouse cursor. 40 | // Returns -1, -1 if there is an error getting the mosue position. 41 | pub fn Mouse.get_pos() (int, int) { 42 | $if macos { 43 | event := C.CGEventCreate(unsafe { nil }) 44 | point := C.CGEventGetLocation(event) 45 | C.CFRelease(event) 46 | return int(point.x), int(point.y) 47 | } $else $if windows { 48 | mut point := &C.POINT{} 49 | if C.GetCursorPos(point) { 50 | return point.x, point.y 51 | } 52 | return -1, -1 53 | } $else { 54 | if compositor == .x11 { 55 | display := C.XOpenDisplay(unsafe { nil }) 56 | if display == unsafe { nil } { 57 | return -1, -1 58 | } 59 | defer { C.XCloseDisplay(display) } 60 | root := C.DefaultRootWindow(display) 61 | crtc_info := Screen.primary_info() or { return -1, -1 } 62 | root_id, child_id := u32(0), u32(0) 63 | win_x, win_y, mask := u32(0), u32(0), u32(0) 64 | mut pos_x, mut pos_y := 0, 0 65 | C.XQueryPointer(display, root, &root_id, &child_id, &pos_x, &pos_y, &win_x, 66 | &win_y, &mask) 67 | 68 | return pos_x - int(crtc_info.x), pos_y - int(crtc_info.y) 69 | } 70 | println(compositor) 71 | } 72 | return -1, -1 73 | } 74 | 75 | // Mouse.get_pos_opt returns the global X and Y coordinates of the mouse cursor. 76 | @[inline] 77 | pub fn Mouse.get_pos_opt() ?(int, int) { 78 | x, y := Mouse.get_pos() 79 | if -1 in [x, y] { 80 | return none 81 | } 82 | return x, y 83 | } 84 | 85 | // Mouse.set_pos immediately moves the mouse cursor to the `x`, `y`. 86 | pub fn Mouse.set_pos(x int, y int) { 87 | $if macos { 88 | C.CGWarpMouseCursorPosition(C.CGPoint{x, y}) 89 | return 90 | } $else $if windows { 91 | target_x := x * 65535 / C.GetSystemMetrics(C.SM_CXSCREEN) 92 | target_y := y * 65535 / C.GetSystemMetrics(C.SM_CYSCREEN) 93 | C.mouse_event(C.MOUSEEVENTF_ABSOLUTE | C.MOUSEEVENTF_MOVE, target_x, target_y, 94 | 0, 0) 95 | } $else { 96 | if compositor == .x11 { 97 | display := C.XOpenDisplay(unsafe { nil }) 98 | if display == unsafe { nil } { 99 | return 100 | } 101 | root := C.DefaultRootWindow(display) 102 | crtc_info := Screen.primary_info() or { return } 103 | C.XWarpPointer(display, C.None, root, 0, 0, 0, 0, int(crtc_info.x) + x, 104 | int(crtc_info.y) + y) 105 | C.XFlush(display) 106 | } 107 | } 108 | } 109 | 110 | // Mouse.click triggers a mouse click event at the current mouse cursor position. 111 | pub fn Mouse.click(button Button) { 112 | $if macos { 113 | mouse_x, mouse_y := get_pos() 114 | point := C.CGPoint{mouse_x, mouse_y} 115 | mut mouse_down_evt := C.CGEventCreateMouseEvent(0, int(EventType.left_mouse_down), 116 | point, int(button)) 117 | mut mouse_up_evt := C.CGEventCreateMouseEvent(0, int(EventType.left_mouse_up), 118 | point, int(button)) 119 | C.CGEventPost(0, mouse_down_evt) 120 | C.CGEventPost(0, mouse_up_evt) 121 | C.CFRelease(mouse_down_evt) 122 | C.CFRelease(mouse_up_evt) 123 | return 124 | } $else $if windows { 125 | button_down, button_up := match button { 126 | .left { C.MOUSEEVENTF_LEFTDOWN, C.MOUSEEVENTF_LEFTUP } 127 | .right { C.MOUSEEVENTF_RIGHTDOWN, C.MOUSEEVENTF_RIGHTUP } 128 | .middle { C.MOUSEEVENTF_MIDDLEDOWN, C.MOUSEEVENTF_MIDDLEUP } 129 | } 130 | C.mouse_event(button_down, 0, 0, 0, 0) 131 | C.mouse_event(button_up, 0, 0, 0, 0) 132 | } 133 | } 134 | 135 | // Mouse.double_click triggers a double click event at the current mouse cursor position. 136 | @[inline] 137 | pub fn Mouse.double_click(button Button) { 138 | Mouse.click(button) 139 | Mouse.click(button) 140 | } 141 | 142 | // DragParams are the options for determining how the mouse is dragged 143 | // across the screen. 144 | @[params] 145 | pub struct DragParams { 146 | __global: 147 | duration time.Duration = time.millisecond * 750 148 | button Button = .left 149 | } 150 | 151 | // Mouse.drag_rel drags the mouse cursor relative to the current location of 152 | // the mouse. 153 | @[inline] 154 | pub fn Mouse.drag_rel(rel_x int, rel_y int, params DragParams) { 155 | cur_x, cur_y := Mouse.get_pos() 156 | target_x := cur_x + rel_x 157 | target_y := cur_y + rel_y 158 | Mouse.drag_to(target_x, target_y, params) 159 | } 160 | 161 | // Mouse.drag_to moves the the mouse cursor while holding down a mouse button. 162 | pub fn Mouse.drag_to(target_x int, target_y int, params DragParams) { 163 | start_x, start_y := Mouse.get_pos() 164 | mut refresh_rate := Screen.refresh_rate() or { 60 } 165 | mut duration := params.duration 166 | if params.duration == 0 { 167 | refresh_rate = 60 168 | duration = 1_000_000_000 169 | } 170 | duration_in_seconds := f64(duration) / 1000000000.0 171 | steps := refresh_rate * duration_in_seconds 172 | delta_x := (target_x - start_x) / steps 173 | delta_y := (target_y - start_y) / steps 174 | sleep_for := duration / steps 175 | 176 | $if macos { 177 | target_point := C.CGPoint{target_x, target_y} 178 | start_point := C.CGPoint{start_x, start_y} 179 | button := match params.button { 180 | .left { C.kCGMouseButtonLeft } 181 | .right { C.kCGMouseButtonRight } 182 | .middle { C.kCGMouseButtonCenter } 183 | } 184 | mouse_down_event := C.CGEventCreateMouseEvent(unsafe { nil }, C.kCGEventLeftMouseDown, 185 | start_point, button) 186 | C.CGEventPost(C.kCGHIDEventTap, mouse_down_event) 187 | C.CFRelease(mouse_down_event) 188 | for i := 0; i < steps; i++ { 189 | current_point := C.CGPoint{ 190 | x: start_x + delta_x * i 191 | y: start_y + delta_y * i 192 | } 193 | mouse_drag_event := C.CGEventCreateMouseEvent(unsafe { nil }, C.kCGEventLeftMouseDragged, 194 | current_point, button) 195 | C.CGEventPost(C.kCGHIDEventTap, mouse_drag_event) 196 | C.CFRelease(mouse_drag_event) 197 | time.sleep(sleep_for) 198 | } 199 | mouse_up_event := C.CGEventCreateMouseEvent(unsafe { nil }, C.kCGEventLeftMouseUp, 200 | target_point, button) 201 | C.CGEventPost(C.kCGHIDEventTap, mouse_up_event) 202 | C.CFRelease(mouse_up_event) 203 | } $else $if windows { 204 | button_down, button_up := match params.button { 205 | .left { C.MOUSEEVENTF_LEFTDOWN, C.MOUSEEVENTF_LEFTUP } 206 | .right { C.MOUSEEVENTF_RIGHTDOWN, C.MOUSEEVENTF_RIGHTUP } 207 | .middle { C.MOUSEEVENTF_MIDDLEDOWN, C.MOUSEEVENTF_MIDDLEUP } 208 | } 209 | C.mouse_event(button_down, 0, 0, 0, 0) 210 | for i := 0; i < steps; i++ { 211 | current_x := start_x + delta_x * i 212 | current_y := start_y + delta_y * i 213 | Mouse.set_pos(int(current_x), int(current_y)) 214 | time.sleep(sleep_for) 215 | } 216 | C.mouse_event(button_up, 0, 0, 0, 0) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/keyboard.c.v: -------------------------------------------------------------------------------- 1 | module auto 2 | 3 | import time 4 | 5 | // vfmt off 6 | const keycode_a = $if macos { 0 } $else { 0x41 } 7 | const keycode_b = $if macos { 11 } $else { 0x42 } 8 | const keycode_c = $if macos { 8 } $else { 0x43 } 9 | const keycode_d = $if macos { 2 } $else { 0x44 } 10 | const keycode_e = $if macos { 14 } $else { 0x45 } 11 | const keycode_f = $if macos { 3 } $else { 0x46 } 12 | const keycode_g = $if macos { 5 } $else { 0x47 } 13 | const keycode_h = $if macos { 4 } $else { 0x48 } 14 | const keycode_i = $if macos { 34 } $else { 0x49 } 15 | const keycode_j = $if macos { 38 } $else { 0x4A } 16 | const keycode_k = $if macos { 40 } $else { 0x4B } 17 | const keycode_l = $if macos { 37 } $else { 0x4C } 18 | const keycode_m = $if macos { 46 } $else { 0x4D } 19 | const keycode_n = $if macos { 45 } $else { 0x4E } 20 | const keycode_o = $if macos { 31 } $else { 0x4F } 21 | const keycode_p = $if macos { 35 } $else { 0x50 } 22 | const keycode_q = $if macos { 12 } $else { 0x51 } 23 | const keycode_r = $if macos { 15 } $else { 0x52 } 24 | const keycode_s = $if macos { 1 } $else { 0x53 } 25 | const keycode_t = $if macos { 17 } $else { 0x54 } 26 | const keycode_u = $if macos { 32 } $else { 0x55 } 27 | const keycode_v = $if macos { 9 } $else { 0x56 } 28 | const keycode_w = $if macos { 13 } $else { 0x57 } 29 | const keycode_x = $if macos { 7 } $else { 0x58 } 30 | const keycode_y = $if macos { 16 } $else { 0x59 } 31 | const keycode_z = $if macos { 6 } $else { 0x5A } 32 | const keycode_0 = $if macos { 29 } $else { 0x30 } 33 | const keycode_1 = $if macos { 18 } $else { 0x31 } 34 | const keycode_2 = $if macos { 19 } $else { 0x32 } 35 | const keycode_3 = $if macos { 20 } $else { 0x33 } 36 | const keycode_4 = $if macos { 21 } $else { 0x34 } 37 | const keycode_5 = $if macos { 23 } $else { 0x35 } 38 | const keycode_6 = $if macos { 22 } $else { 0x36 } 39 | const keycode_7 = $if macos { 26 } $else { 0x37 } 40 | const keycode_8 = $if macos { 28 } $else { 0x38 } 41 | const keycode_9 = $if macos { 25 } $else { 0x39 } 42 | const keycode_space = $if macos { 49 } $else { 0x20 } 43 | const keycode_semicolon = $if macos { 41 } $else $if windows { 0xBA } $else { 0x003B } 44 | const keycode_comma = $if macos { 43 } $else $if windows { 0xBC } $else { 0x002C } 45 | const keycode_period = $if macos { 47 } $else $if windows { 0xBE } $else { 0x002E } 46 | const keycode_slash = $if macos { 44 } $else $if windows { 0xBF } $else { 0x002F } 47 | const keycode_backtick = $if macos { 50 } $else $if windows { 0xC0 } $else { 0x0060 } 48 | const keycode_left_bracket = $if macos { 33 } $else $if windows { 0xDB } $else { 0x005B } 49 | const keycode_right_bracket = $if macos { 30 } $else $if windows { 0xDD } $else { 0x005D } 50 | const keycode_backslash = $if macos { 42 } $else $if windows { 0xDC } $else { 0x005C } 51 | const keycode_quote = $if macos { 39 } $else $if windows { 0xDE } $else { 0x0027 } 52 | const keycode_hyphen = $if macos { 27 } $else $if windows { 0xBD } $else { 0x002D } 53 | const keycode_equals = $if macos { 24 } $else $if windows { 0xBB } $else { 0x003D } 54 | const keycode_return = $if macos { 36 } $else $if windows { 0x0D } $else { 0xFF0D } 55 | const keycode_backspace = $if macos { 51 } $else $if windows { 0x08 } $else { 0xFF08 } 56 | const keycode_tab = $if macos { 48 } $else $if windows { 0x09 } $else { 0xFF09 } 57 | const keycode_left_shift = $if macos { 56 } $else $if windows { 0x10 } $else { 0xFFE1 } 58 | const keycode_right_shift = $if macos { 60 } $else $if windows { 0xA1 } $else { 0xFFE2 } 59 | const keycode_left_ctrl = $if macos { 59 } $else $if windows { 0x11 } $else { 0xFFE3 } 60 | const keycode_right_ctrl = $if macos { 62 } $else $if windows { 0xA3 } $else { 0xFFE4 } 61 | const keycode_left_alt = $if macos { 58 } $else $if windows { 0x12 } $else { 0xFFE9 } 62 | const keycode_right_alt = $if macos { 61 } $else $if windows { 0xA5 } $else { 0xFFEA } 63 | const keycode_escape = $if macos { 53 } $else $if windows { 0x1B } $else { 0xFF1B } 64 | const keycode_f1 = $if macos { 122 } $else $if windows { 0x70 } $else { 0xFFBE } 65 | const keycode_f2 = $if macos { 120 } $else $if windows { 0x71 } $else { 0xFFBF } 66 | const keycode_f3 = $if macos { 99 } $else $if windows { 0x72 } $else { 0xFFC0 } 67 | const keycode_f4 = $if macos { 118 } $else $if windows { 0x73 } $else { 0xFFC1 } 68 | const keycode_f5 = $if macos { 96 } $else $if windows { 0x74 } $else { 0xFFC2 } 69 | const keycode_f6 = $if macos { 97 } $else $if windows { 0x75 } $else { 0xFFC3 } 70 | const keycode_f7 = $if macos { 98 } $else $if windows { 0x76 } $else { 0xFFC4 } 71 | const keycode_f8 = $if macos { 100 } $else $if windows { 0x77 } $else { 0xFFC5 } 72 | const keycode_f9 = $if macos { 101 } $else $if windows { 0x78 } $else { 0xFFC6 } 73 | const keycode_f10 = $if macos { 109 } $else $if windows { 0x79 } $else { 0xFFC7 } 74 | const keycode_f11 = $if macos { 103 } $else $if windows { 0x7A } $else { 0xFFC8 } 75 | const keycode_f12 = $if macos { 111 } $else $if windows { 0x7B } $else { 0xFFC9 } 76 | const keycode_left_arrow = $if macos { 123 } $else $if windows { 0x25 } $else { 0xFF51 } 77 | const keycode_right_arrow = $if macos { 124 } $else $if windows { 0x27 } $else { 0xFF53 } 78 | const keycode_up_arrow = $if macos { 126 } $else $if windows { 0x26 } $else { 0xFF52 } 79 | const keycode_down_arrow = $if macos { 125 } $else $if windows { 0x28 } $else { 0xFF54 } 80 | const keycode_delete = $if macos { 51 } $else $if windows { 0x2E } $else { 0xFFFF } 81 | // vfmt on 82 | 83 | // KeyModifier is a key which can be pressed in combination with another key. 84 | @[flag] 85 | pub enum KeyModifier { 86 | shift 87 | ctrl 88 | alt 89 | } 90 | 91 | // KeyCode is a key on a keyboard. 92 | @[_allow_multiple_values] 93 | pub enum KeyCode { 94 | a = keycode_a 95 | b = keycode_b 96 | c = keycode_c 97 | d = keycode_d 98 | e = keycode_e 99 | f = keycode_f 100 | g = keycode_g 101 | h = keycode_h 102 | i = keycode_i 103 | j = keycode_j 104 | k = keycode_k 105 | l = keycode_l 106 | m = keycode_m 107 | n = keycode_n 108 | o = keycode_o 109 | p = keycode_p 110 | q = keycode_q 111 | r = keycode_r 112 | s = keycode_s 113 | t = keycode_t 114 | u = keycode_u 115 | v = keycode_v 116 | w = keycode_w 117 | x = keycode_x 118 | y = keycode_y 119 | z = keycode_z 120 | _0 = keycode_0 121 | _1 = keycode_1 122 | _2 = keycode_2 123 | _3 = keycode_3 124 | _4 = keycode_4 125 | _5 = keycode_5 126 | _6 = keycode_6 127 | _7 = keycode_7 128 | _8 = keycode_8 129 | _9 = keycode_9 130 | space = keycode_space 131 | semicolon = keycode_semicolon 132 | comma = keycode_comma 133 | period = keycode_period 134 | slash = keycode_slash 135 | backtick = keycode_backtick 136 | left_bracket = keycode_left_bracket 137 | right_bracket = keycode_right_bracket 138 | backslash = keycode_backslash 139 | quote = keycode_quote 140 | hyphen = keycode_hyphen 141 | equals = keycode_equals 142 | return = keycode_return 143 | enter = keycode_return 144 | backspace = keycode_backspace 145 | tab = keycode_tab 146 | left_shift = keycode_left_shift 147 | right_shift = keycode_right_shift 148 | left_ctrl = keycode_left_ctrl 149 | right_ctrl = keycode_right_ctrl 150 | left_alt = keycode_left_alt 151 | right_alt = keycode_right_alt 152 | escape = keycode_escape 153 | f1 = keycode_f1 154 | f2 = keycode_f2 155 | f3 = keycode_f3 156 | f4 = keycode_f4 157 | f5 = keycode_f5 158 | f6 = keycode_f6 159 | f7 = keycode_f7 160 | f8 = keycode_f8 161 | f9 = keycode_f9 162 | f10 = keycode_f10 163 | f11 = keycode_f11 164 | f12 = keycode_f12 165 | left_arrow = keycode_left_arrow 166 | right_arrow = keycode_right_arrow 167 | up_arrow = keycode_up_arrow 168 | down_arrow = keycode_down_arrow 169 | delete = keycode_delete 170 | } 171 | 172 | // KeyCode.from_byte returns the KeyCode and KeyModifier for an ascii character. 173 | @[inline] 174 | pub fn KeyCode.from_byte(c u8) ?(KeyCode, KeyModifier) { 175 | return match c { 176 | `a` { KeyCode.a, KeyModifier.zero() } 177 | `A` { KeyCode.a, KeyModifier.shift } 178 | `b` { KeyCode.b, KeyModifier.zero() } 179 | `B` { KeyCode.b, KeyModifier.shift } 180 | `c` { KeyCode.c, KeyModifier.zero() } 181 | `C` { KeyCode.c, KeyModifier.shift } 182 | `d` { KeyCode.d, KeyModifier.zero() } 183 | `D` { KeyCode.d, KeyModifier.shift } 184 | `e` { KeyCode.e, KeyModifier.zero() } 185 | `E` { KeyCode.e, KeyModifier.shift } 186 | `f` { KeyCode.f, KeyModifier.zero() } 187 | `F` { KeyCode.f, KeyModifier.shift } 188 | `g` { KeyCode.g, KeyModifier.zero() } 189 | `G` { KeyCode.g, KeyModifier.shift } 190 | `h` { KeyCode.h, KeyModifier.zero() } 191 | `H` { KeyCode.h, KeyModifier.shift } 192 | `i` { KeyCode.i, KeyModifier.zero() } 193 | `I` { KeyCode.i, KeyModifier.shift } 194 | `j` { KeyCode.j, KeyModifier.zero() } 195 | `J` { KeyCode.j, KeyModifier.shift } 196 | `k` { KeyCode.k, KeyModifier.zero() } 197 | `K` { KeyCode.k, KeyModifier.shift } 198 | `l` { KeyCode.l, KeyModifier.zero() } 199 | `L` { KeyCode.l, KeyModifier.shift } 200 | `m` { KeyCode.m, KeyModifier.zero() } 201 | `M` { KeyCode.m, KeyModifier.shift } 202 | `n` { KeyCode.n, KeyModifier.zero() } 203 | `N` { KeyCode.n, KeyModifier.shift } 204 | `o` { KeyCode.o, KeyModifier.zero() } 205 | `O` { KeyCode.o, KeyModifier.shift } 206 | `p` { KeyCode.p, KeyModifier.zero() } 207 | `P` { KeyCode.p, KeyModifier.shift } 208 | `q` { KeyCode.q, KeyModifier.zero() } 209 | `Q` { KeyCode.q, KeyModifier.shift } 210 | `r` { KeyCode.r, KeyModifier.zero() } 211 | `R` { KeyCode.r, KeyModifier.shift } 212 | `s` { KeyCode.s, KeyModifier.zero() } 213 | `S` { KeyCode.s, KeyModifier.shift } 214 | `t` { KeyCode.t, KeyModifier.zero() } 215 | `T` { KeyCode.t, KeyModifier.shift } 216 | `u` { KeyCode.u, KeyModifier.zero() } 217 | `U` { KeyCode.u, KeyModifier.shift } 218 | `v` { KeyCode.v, KeyModifier.zero() } 219 | `V` { KeyCode.v, KeyModifier.shift } 220 | `w` { KeyCode.w, KeyModifier.zero() } 221 | `W` { KeyCode.w, KeyModifier.shift } 222 | `x` { KeyCode.x, KeyModifier.zero() } 223 | `X` { KeyCode.x, KeyModifier.shift } 224 | `y` { KeyCode.y, KeyModifier.zero() } 225 | `Y` { KeyCode.y, KeyModifier.shift } 226 | `z` { KeyCode.z, KeyModifier.zero() } 227 | `Z` { KeyCode.z, KeyModifier.shift } 228 | `0` { KeyCode._0, KeyModifier.zero() } 229 | `1` { KeyCode._1, KeyModifier.zero() } 230 | `2` { KeyCode._2, KeyModifier.zero() } 231 | `3` { KeyCode._3, KeyModifier.zero() } 232 | `4` { KeyCode._4, KeyModifier.zero() } 233 | `5` { KeyCode._5, KeyModifier.zero() } 234 | `6` { KeyCode._6, KeyModifier.zero() } 235 | `7` { KeyCode._7, KeyModifier.zero() } 236 | `8` { KeyCode._8, KeyModifier.zero() } 237 | `9` { KeyCode._9, KeyModifier.zero() } 238 | ` ` { KeyCode.space, KeyModifier.zero() } 239 | `;` { KeyCode.semicolon, KeyModifier.zero() } 240 | `,` { KeyCode.comma, KeyModifier.zero() } 241 | `.` { KeyCode.period, KeyModifier.zero() } 242 | `/` { KeyCode.slash, KeyModifier.zero() } 243 | `\\` { KeyCode.backslash, KeyModifier.zero() } 244 | `'` { KeyCode.quote, KeyModifier.zero() } 245 | `-` { KeyCode.hyphen, KeyModifier.zero() } 246 | `=` { KeyCode.equals, KeyModifier.zero() } 247 | `\n` { KeyCode.return, KeyModifier.zero() } 248 | `\t` { KeyCode.tab, KeyModifier.zero() } 249 | `~` { KeyCode.backtick, KeyModifier.shift } 250 | `!` { KeyCode._1, KeyModifier.shift } 251 | `@` { KeyCode._2, KeyModifier.shift } 252 | `#` { KeyCode._3, KeyModifier.shift } 253 | `$` { KeyCode._4, KeyModifier.shift } 254 | `%` { KeyCode._5, KeyModifier.shift } 255 | `^` { KeyCode._6, KeyModifier.shift } 256 | `&` { KeyCode._7, KeyModifier.shift } 257 | `*` { KeyCode._8, KeyModifier.shift } 258 | `(` { KeyCode._9, KeyModifier.shift } 259 | `)` { KeyCode._0, KeyModifier.shift } 260 | `_` { KeyCode.hyphen, KeyModifier.shift } 261 | `+` { KeyCode.equals, KeyModifier.shift } 262 | `{` { KeyCode.left_bracket, KeyModifier.shift } 263 | `}` { KeyCode.right_bracket, KeyModifier.shift } 264 | `|` { KeyCode.backslash, KeyModifier.shift } 265 | `:` { KeyCode.semicolon, KeyModifier.shift } 266 | `"` { KeyCode.quote, KeyModifier.shift } 267 | `<` { KeyCode.comma, KeyModifier.shift } 268 | `>` { KeyCode.period, KeyModifier.shift } 269 | `?` { KeyCode.slash, KeyModifier.shift } 270 | else { return none } 271 | } 272 | } 273 | 274 | // KeyboardWriteParams is the parameters for determining how Keyboard.write behaves. 275 | @[params] 276 | pub struct KeyboardWriteParams { 277 | __global: 278 | speed time.Duration = 50 * time.millisecond 279 | } 280 | 281 | // Keyboard acts as a namespace for keyboard related functions. 282 | @[noinit] 283 | pub struct Keyboard {} 284 | 285 | // Keyboard.press triggers a key press. 286 | @[inline] 287 | pub fn Keyboard.press(key_code KeyCode, mod KeyModifier) { 288 | keyboard_event(key_code, mod, true) 289 | time.sleep(50 * time.millisecond) 290 | keyboard_event(key_code, mod, false) 291 | } 292 | 293 | // Keyboard.write types out a string. 294 | @[inline] 295 | pub fn Keyboard.write(str string, params KeyboardWriteParams) { 296 | for c in str { 297 | if code, mod := KeyCode.from_byte(c) { 298 | keyboard_event(code, mod, true) 299 | time.sleep(params.speed) 300 | keyboard_event(code, mod, false) 301 | } 302 | } 303 | } 304 | 305 | // keyboard_event creates an event from the given key_code and mod. 306 | fn keyboard_event(key_code KeyCode, mod KeyModifier, is_key_down_event bool) { 307 | $if macos { 308 | event := C.CGEventCreateKeyboardEvent(unsafe { nil }, int(key_code), is_key_down_event) 309 | C.CGEventPost(C.kCGHIDEventTap, event) 310 | C.CFRelease(event) 311 | } $else $if windows { 312 | mut keys := []int{cap: 3} 313 | if mod.has(.shift) { 314 | keys << C.VK_SHIFT 315 | } 316 | if mod.has(.ctrl) { 317 | keys << C.VK_CONTROL 318 | } 319 | if mod.has(.alt) { 320 | keys << C.VK_MENU 321 | } 322 | keys << int(key_code) 323 | for key in keys { 324 | C.keybd_event(key, 0, if is_key_down_event { 0 } else { C.KEYEVENTF_KEYUP }, 325 | 0) 326 | } 327 | } $else { 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /docs/doc.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: #fff; 3 | --link-color: #2779bd; 4 | --text-color: #000; 5 | --ref-symbol-color: #dae1e7; 6 | --ref-symbol-hover-color: #b8c2cc; 7 | --title-bottom-line-color: #f1f5f8; 8 | --footer-top-line-color: #f1f5f8; 9 | --footer-text-color: #616161; 10 | --code-signature-border-color: #a0aec0; 11 | --menu-background-color: #4b6c88; 12 | --menu-text-color: #fff; 13 | --menu-indent-line-color: #3b3b3b66; 14 | --menu-indent-line-active-color: #00000066; 15 | --menu-scrollbar-color: #a0aec0; 16 | --menu-toggle-icon-color: #fff; 17 | --menu-toggle-icon-hover-color: #00000044; 18 | --menu-search-background-color: #00000044; 19 | --menu-search-font-color: #fff; 20 | --menu-search-result-background-hover-color: #00000021; 21 | --menu-search-separator-color: #00000044; 22 | --menu-search-title-text-color: #d5efff; 23 | --menu-search-badge-background-color: #00000044; 24 | --menu-search-badge-background-hover-color: #0000004d; 25 | --toc-text-color: #2779bd; 26 | --toc-indicator-color: #4299e1; 27 | --code-default-text-color: #2c3e64; 28 | --code-background-color: #edf2f7; 29 | --code-keyword-text-color: #2b6cb0; 30 | --code-builtin-text-color: #219321; 31 | --code-function-text-color: #288341; 32 | --code-comment-text-color: #93a1a1; 33 | --code-punctuation-text-color: #696969; 34 | --code-symbol-text-color: #702459; 35 | --code-operator-text-color: #864f29; 36 | --attribute-deprecated-background-color: #f59f0b48; 37 | --attribute-deprecated-text-color: #92400e; 38 | --attribute-text-color: #000000cf; 39 | --table-header-line-color: #2c3e64; 40 | --table-background-color: #edf2f7; 41 | } 42 | html.dark { 43 | --background-color: #1a202c; 44 | --text-color: #fff; 45 | --link-color: #90cdf4; 46 | --ref-symbol-color: #2d3748; 47 | --ref-symbol-hover-color: #4a5568; 48 | --title-bottom-line-color: #2d3748; 49 | --footer-top-line-color: #2d3748; 50 | --footer-text-color: #bbd3e1; 51 | --code-signature-border-color: #4a5568; 52 | --menu-background-color: #2d3748; 53 | --menu-text-color: #fff; 54 | --menu-indent-line-color: #4a5568; 55 | --menu-indent-line-active-color: #90cdf4; /*#4a5568*/ 56 | --menu-scrollbar-color: #4a5568; 57 | --menu-toggle-icon-color: #fff; 58 | --menu-search-background-color: #4a5568; 59 | --menu-search-font-color: #fff; 60 | --menu-search-separator-color: #4a5568; 61 | --menu-search-title-text-color: #90cdf4; 62 | --menu-search-badge-background-color: #4a5568; 63 | --menu-search-badge-background-hover-color: #4a5568; 64 | --toc-text-color: #90cdf4; 65 | --toc-indicator-color: #4299e1; 66 | --code-default-text-color: #cbd5e0; 67 | --code-background-color: #2d3748; 68 | --code-builtin-text-color: #68d391; 69 | --code-keyword-text-color: #63b3ed; 70 | --code-function-text-color: #4fd1c5; 71 | --code-comment-text-color: #a0aec0; 72 | --code-punctuation-text-color: #a0aec0; 73 | --code-symbol-text-color: #ed64a6; 74 | --code-operator-text-color: #a67f59; 75 | --attribute-background-color: #ffffff20; 76 | --attribute-text-color: #ffffffaf; 77 | --attribute-deprecated-text-color: #fef3c7; 78 | --table-header-line-color: #cbd5e0; 79 | --table-background-color: #2d3748; 80 | } 81 | html.dark .dark-icon { 82 | display: none; 83 | } 84 | html:not(.dark) .light-icon { 85 | display: none; 86 | } 87 | html { 88 | height: 100%; 89 | } 90 | body { 91 | margin: 0; 92 | font-family: 93 | Jost, 94 | -apple-system, 95 | BlinkMacSystemFont, 96 | 'Segoe UI', 97 | Helvetica, 98 | Arial, 99 | sans-serif, 100 | 'Apple Color Emoji', 101 | 'Segoe UI Emoji', 102 | 'Segoe UI Symbol'; 103 | background-color: #fff; 104 | background-color: var(--background-color); 105 | color: #000; 106 | color: var(--text-color); 107 | height: 100%; 108 | } 109 | hr { 110 | height: 0.25rem; 111 | background-color: var(--title-bottom-line-color); 112 | border: 0; 113 | padding: 0; 114 | margin: 1.2rem 0; 115 | } 116 | #page { 117 | height: 100%; 118 | padding-top: 56px; 119 | box-sizing: border-box; 120 | } 121 | 122 | /** Reset for menus */ 123 | .doc-nav ul, 124 | .doc-toc ul { 125 | list-style: none; 126 | padding: 0; 127 | margin: 0; 128 | } 129 | 130 | /* Left nav */ 131 | .doc-nav { 132 | position: fixed; 133 | width: 100%; 134 | left: 0; 135 | right: 0; 136 | top: 0; 137 | display: flex; 138 | background-color: #4b6c88; 139 | background-color: var(--menu-background-color); 140 | color: #fff; 141 | color: var(--menu-text-color); 142 | flex-direction: column; 143 | overflow-y: auto; 144 | height: 100vh; 145 | z-index: 10; 146 | scrollbar-width: thin; 147 | scrollbar-color: #a0aec0 transparent; 148 | scrollbar-color: var(--menu-scrollbar-color) transparent; 149 | font-family: Jost, sans-serif; 150 | } 151 | *::-webkit-scrollbar { 152 | width: 4px; 153 | height: 4px; 154 | } 155 | *::-webkit-scrollbar-track { 156 | background: transparent; 157 | } 158 | *::-webkit-scrollbar-thumb { 159 | background-color: #a0aec0; 160 | background-color: var(--menu-scrollbar-color); 161 | border: 3px solid transparent; 162 | } 163 | .doc-nav .content li { 164 | line-height: 1.8; 165 | } 166 | .doc-nav .content.show { 167 | display: flex; 168 | } 169 | .doc-nav .content.hidden { 170 | display: none; 171 | } 172 | .doc-nav #toggle-menu { 173 | cursor: pointer; 174 | padding: 0.3rem; 175 | fill: #fff; 176 | fill: var(--menu-toggle-icon-color); 177 | } 178 | .doc-nav > .heading-container { 179 | position: sticky; 180 | position: -webkit-sticky; 181 | top: 0; 182 | background-color: #4b6c88; 183 | background-color: var(--menu-background-color); 184 | z-index: 10; 185 | } 186 | .doc-nav > .heading-container > .heading { 187 | display: flex; 188 | flex-direction: column; 189 | } 190 | .doc-nav > .heading-container > .heading > .info { 191 | display: flex; 192 | padding: 0 1rem; 193 | height: 56px; 194 | } 195 | .doc-nav > .heading-container > .heading > .info > .module { 196 | font-size: 1.6rem; 197 | font-weight: 500; 198 | margin: 0; 199 | } 200 | .doc-nav > .heading-container > .heading > .info > .toggle-version-container { 201 | display: flex; 202 | align-items: center; 203 | } 204 | .doc-nav > .heading-container > .heading > .info > .toggle-version-container > #dark-mode-toggle { 205 | cursor: pointer; 206 | fill: #fff; 207 | display: flex; 208 | } 209 | .doc-nav 210 | > .heading-container 211 | > .heading 212 | > .info 213 | > .toggle-version-container 214 | > #dark-mode-toggle 215 | > svg { 216 | width: 1.2rem; 217 | height: 1.2rem; 218 | } 219 | .doc-nav #search { 220 | position: relative; 221 | margin: 0.6rem 1.2rem 1rem 1.2rem; 222 | display: flex; 223 | } 224 | .doc-nav #search input { 225 | border: none; 226 | width: 100%; 227 | border-radius: 0.2rem; 228 | padding: 0.5rem 1rem; 229 | outline: none; 230 | background-color: #00000044; 231 | background-color: var(--menu-search-background-color); 232 | color: #fff; 233 | color: var(--menu-search-text-color); 234 | } 235 | .doc-nav #search input::placeholder { 236 | color: #edf2f7; 237 | text-transform: uppercase; 238 | font-size: 12px; 239 | font-weight: 600; 240 | } 241 | .doc-nav #search-keys { 242 | position: absolute; 243 | height: 100%; 244 | align-items: center; 245 | display: flex; 246 | top: 0; 247 | right: 0.75rem; 248 | opacity: 0.33; 249 | transition: opacity 0.1s; 250 | } 251 | .doc-nav #search-keys.hide { 252 | opacity: 0; 253 | } 254 | .doc-nav #search-keys kbd { 255 | padding: 2.5px 4px; 256 | margin-left: 1px; 257 | font-size: 11px; 258 | background-color: var(--menu-background-color); 259 | border: 1px solid #ffffff44; 260 | border-radius: 3px; 261 | } 262 | .doc-nav > .content { 263 | padding: 0 2rem 2rem 2rem; 264 | display: flex; 265 | flex-direction: column; 266 | } 267 | .doc-nav > .content > ul > li.active { 268 | font-weight: 600; 269 | } 270 | .doc-nav > .content > ul > li.open ul { 271 | display: initial; 272 | } 273 | .doc-nav > .content > ul > li.open > .menu-row > .dropdown-arrow { 274 | transform: initial; 275 | } 276 | .doc-nav > .content > ul > li > .menu-row { 277 | display: flex; 278 | align-items: center; 279 | } 280 | .doc-nav > .content > ul > li > .menu-row > .dropdown-arrow { 281 | transform: rotate(-90deg); 282 | height: 18px; 283 | width: 18px; 284 | margin-left: calc(-18px - 0.3rem); 285 | margin-right: 0.3rem; 286 | cursor: pointer; 287 | fill: #fff; 288 | pointer-events: all; 289 | } 290 | .doc-nav > .content > ul > li > ul { 291 | margin: 0.4rem 0; 292 | display: none; 293 | } 294 | .doc-nav > .content > ul > li > ul > li { 295 | border-color: #ffffff66; 296 | border-color: var(--menu-indent-line-color); 297 | border-left-width: 1.7px; 298 | border-left-style: solid; 299 | padding-left: 0.7rem; 300 | } 301 | .doc-nav > .content > ul > li > ul > li.active { 302 | border-color: #00000066; 303 | border-color: var(--menu-indent-line-active-color); 304 | } 305 | .doc-nav > .content a { 306 | color: #fff; 307 | color: var(--menu-text-color); 308 | text-decoration: none; 309 | user-select: none; 310 | } 311 | .doc-nav > .content a:hover { 312 | text-decoration: underline; 313 | } 314 | .doc-nav .search { 315 | overflow-y: auto; 316 | } 317 | .doc-nav .search.hidden { 318 | display: none; 319 | } 320 | .doc-nav .search li { 321 | line-height: 1.5; 322 | } 323 | .doc-nav > .search .result:hover, 324 | .doc-nav > .search .result.selected { 325 | background-color: #00000021; 326 | background-color: var(--menu-search-result-background-hover-color); 327 | } 328 | .doc-nav > .search .result:hover > .link > .definition > .badge { 329 | background-color: #0000004d; 330 | background-color: var(--menu-search-badge-background-hover-color); 331 | } 332 | .doc-nav > .search .result > .link { 333 | padding: 0.5rem 1.4rem; 334 | text-decoration: none; 335 | color: #fff; 336 | color: var(--menu-text-color); 337 | display: block; 338 | } 339 | .doc-nav > .search .result > .link > .definition { 340 | display: flex; 341 | } 342 | .doc-nav > .search .result > .link > .definition > .title { 343 | color: #90cdf4; 344 | color: var(--menu-search-title-text-color); 345 | font-size: 0.875rem; 346 | font-weight: 500; 347 | overflow: hidden; 348 | white-space: nowrap; 349 | text-overflow: ellipsis; 350 | } 351 | .doc-nav > .search .result > .link > .definition > .badge { 352 | font-size: 0.75rem; 353 | display: inline-flex; 354 | padding: 0 0.5rem; 355 | background-color: #00000044; 356 | background-color: var(--menu-search-badge-background-color); 357 | margin-left: auto; 358 | align-items: center; 359 | border-radius: 9999px; 360 | font-weight: 500; 361 | } 362 | .doc-nav > .search .result > .link > .description { 363 | font-family: 364 | Jost, 365 | -apple-system, 366 | BlinkMacSystemFont, 367 | 'Segoe UI', 368 | Helvetica, 369 | Arial, 370 | sans-serif, 371 | 'Apple Color Emoji', 372 | 'Segoe UI Emoji', 373 | 'Segoe UI Symbol'; 374 | font-size: 0.75rem; 375 | overflow: hidden; 376 | white-space: nowrap; 377 | text-overflow: ellipsis; 378 | margin-top: 0.25rem; 379 | } 380 | .doc-nav > .search > hr.separator { 381 | margin: 0.5rem 0; 382 | border-color: #00000044; 383 | border-color: var(--menu-search-separator-color); 384 | box-sizing: content-box; 385 | height: 0; 386 | border-width: 0; 387 | border-top-width: 1px; 388 | border-style: solid; 389 | overflow: visible; 390 | } 391 | 392 | /* Main content */ 393 | #main-content { 394 | outline: none; 395 | } 396 | .doc-scrollview { 397 | height: 100%; 398 | overflow-y: scroll; 399 | } 400 | .doc-container { 401 | display: flex; 402 | flex-direction: column-reverse; 403 | } 404 | .doc-content { 405 | display: flex; 406 | flex-direction: column; 407 | padding: 1rem; 408 | overflow: hidden; 409 | } 410 | .doc-content img { 411 | width: auto; 412 | max-width: 100%; 413 | } 414 | .doc-content p { 415 | font-size: 1rem; 416 | line-height: 1.6; 417 | letter-spacing: 0.025em; 418 | max-width: 100ch; 419 | word-wrap: break-word; 420 | } 421 | .doc-content a { 422 | color: #2779bd; 423 | color: var(--link-color); 424 | } 425 | .doc-content p code { 426 | font-size: 0.85rem; 427 | } 428 | .doc-content > .doc-node { 429 | padding: 5rem 0 2rem 0; 430 | margin-top: -4rem; 431 | overflow: hidden; 432 | word-break: break-word; 433 | } 434 | .doc-content > .doc-node.const:nth-child(2) { 435 | padding-bottom: 0 !important; 436 | } 437 | .doc-content > .doc-node.const:not(:first-child) { 438 | padding-top: 4rem; 439 | } 440 | .doc-content > .doc-node.const:not(:last-child) { 441 | padding-bottom: 2rem; 442 | } 443 | .doc-content > .timestamp { 444 | font-size: 0.8rem; 445 | color: #b8c2cc; 446 | color: var(--timestamp-color); 447 | } 448 | .doc-content > .doc-node > .title { 449 | display: flex; 450 | font-family: Jost, sans-serif; 451 | font-weight: 500; 452 | padding: 0.3rem; 453 | align-items: center; 454 | margin-bottom: 1rem; 455 | border-bottom: 1px solid #f1f5f8; 456 | border-bottom: 1px solid var(--title-bottom-line-color); 457 | } 458 | .doc-content > .doc-node > .attributes { 459 | margin-bottom: 0.6rem; 460 | } 461 | .doc-content > .doc-node > .attributes > .attribute { 462 | display: inline-block; 463 | border-radius: 100px; 464 | padding: 0.3rem 0.6rem; 465 | background-color: var(--code-background-color); 466 | color: var(--attribute-text-color); 467 | margin-right: 0.8rem; 468 | font-family: 'Jetbrains Mono', monospace; 469 | font-size: 0.9rem; 470 | } 471 | .doc-content > .doc-node > .attributes > .attribute-deprecated { 472 | background-color: var(--attribute-deprecated-background-color); 473 | color: var(--attribute-deprecated-text-color); 474 | } 475 | .doc-content > .doc-node > .title > .link { 476 | display: flex; 477 | margin-left: auto; 478 | fill: #dae1e7; 479 | fill: var(--ref-symbol-color); 480 | } 481 | .doc-content > .doc-node > .title > .link:hover { 482 | fill: #b8c2cc; 483 | fill: var(--ref-symbol-hover-color); 484 | } 485 | .doc-content > .doc-node h1 { 486 | font-size: 2rem; 487 | } 488 | .doc-content > .doc-node h2 { 489 | font-size: 1.3rem; 490 | } 491 | .doc-content > .doc-node .signature { 492 | border-color: #a0aec0; 493 | border-color: var(--code-signature-border-color); 494 | border-left-width: 3px; 495 | border-left-style: solid; 496 | } 497 | .doc-content > .doc-node > ul > li .task-list-item-checkbox { 498 | margin-right: 0.5rem; 499 | } 500 | .doc-content > .doc-node > .title h1, 501 | .doc-content > .doc-node > .title h2, 502 | .doc-content > .doc-node > .title h3, 503 | .doc-content > .doc-node > .title h4, 504 | .doc-content > .doc-node > .title h5, 505 | .doc-content > .doc-node > .title h6 { 506 | font-weight: 500; 507 | margin: 0; 508 | } 509 | .doc-content > .doc-node > .title h1 a, 510 | .doc-content > .doc-node > .title h2 a, 511 | .doc-content > .doc-node > .title h3 a, 512 | .doc-content > .doc-node > .title h4 a, 513 | .doc-content > .doc-node > .title h5 a, 514 | .doc-content > .doc-node > .title h6 a { 515 | text-decoration: none; 516 | color: #dae1e7; 517 | color: var(--ref-symbol-color); 518 | } 519 | .doc-content > .doc-node > .title h1 a:hover, 520 | .doc-content > .doc-node > .title h2 a:hover, 521 | .doc-content > .doc-node > .title h3 a:hover, 522 | .doc-content > .doc-node > .title h4 a:hover, 523 | .doc-content > .doc-node > .title h5 a:hover, 524 | .doc-content > .doc-node > .title h6 a:hover { 525 | color: #b8c2cc; 526 | color: var(--ref-symbol-hover-color); 527 | } 528 | .doc-content > .footer { 529 | padding-top: 1rem; 530 | margin-top: auto; 531 | bottom: 1rem; 532 | color: 616161; 533 | color: var(--footer-text-color); 534 | border-color: #f1f5f8; 535 | border-color: var(--footer-top-line-color); 536 | border-top-style: solid; 537 | border-top-width: 1px; 538 | font-size: 0.8rem; 539 | font-weight: 500; 540 | } 541 | 542 | /* Right menu */ 543 | .doc-toc { 544 | right: 0; 545 | top: 0; 546 | height: 100%; 547 | overflow-y: auto; 548 | padding: 1rem 1rem 0 1rem; 549 | width: 100%; 550 | box-sizing: border-box; 551 | -ms-overflow-style: none; 552 | scrollbar-width: none; 553 | font-family: Jost, sans-serif; 554 | } 555 | .doc-toc::-webkit-scrollbar { 556 | display: none; 557 | } 558 | .doc-toc li { 559 | line-height: 1.5; 560 | } 561 | .doc-toc a { 562 | color: #2779bd; 563 | color: var(--toc-text-color); 564 | font-size: 0.9rem; 565 | font-weight: 600; 566 | overflow: hidden; 567 | text-overflow: ellipsis; 568 | display: block; 569 | text-decoration: none; 570 | border-left-width: 2px; 571 | border-left-style: solid; 572 | border-color: transparent; 573 | padding-left: 0.4rem; 574 | } 575 | .doc-toc a:hover { 576 | text-decoration: underline; 577 | } 578 | .doc-toc a.active { 579 | border-color: #4299e1; 580 | border-color: var(--toc-indicator-color); 581 | } 582 | .doc-toc li ul { 583 | margin: 0.2rem 0 0.2rem; 584 | font-size: 0.7rem; 585 | list-style: none; 586 | } 587 | .doc-toc li ul a { 588 | font-weight: 400; 589 | padding-left: 0.8rem; 590 | } 591 | 592 | /* Code highlight */ 593 | pre, 594 | code, 595 | pre code { 596 | color: #5c6e74; 597 | color: var(--code-default-text-color); 598 | font-size: 0.948em; 599 | text-shadow: none; 600 | font-family: 'Jetbrains Mono', monospace; 601 | background-color: #edf2f7; 602 | background-color: var(--code-background-color); 603 | border-radius: 0.25rem; 604 | } 605 | pre code { 606 | direction: ltr; 607 | text-align: left; 608 | white-space: pre; 609 | word-spacing: normal; 610 | word-break: normal; 611 | line-height: 1.5; 612 | -moz-tab-size: 4; 613 | -o-tab-size: 4; 614 | tab-size: 4; 615 | -webkit-hyphens: none; 616 | -moz-hyphens: none; 617 | -ms-hyphens: none; 618 | hyphens: none; 619 | display: block; 620 | overflow-x: auto; 621 | padding: 1rem; 622 | } 623 | code { 624 | padding: 0 0.2rem; 625 | } 626 | pre { 627 | overflow: auto; 628 | margin: 0; 629 | position: relative; 630 | } 631 | .namespace { 632 | opacity: 0.7; 633 | } 634 | .token.comment { 635 | color: #93a1a1; 636 | color: var(--code-comment-text-color); 637 | } 638 | .token.punctuation { 639 | color: #999999; 640 | color: var(--code-punctuation-text-color); 641 | } 642 | .token.number, 643 | .token.symbol { 644 | color: #702459; 645 | color: var(--code-symbol-text-color); 646 | } 647 | .token.string, 648 | .token.char, 649 | .token.builtin { 650 | color: #38a169; 651 | color: var(--code-builtin-text-color); 652 | } 653 | .token.operator, 654 | .token.entity, 655 | .token.url, 656 | .language-css .token.string, 657 | .style .token.string { 658 | color: #a67f59; 659 | color: var(--code-operator-text-color); 660 | background: transparent; 661 | } 662 | .token.boolean, 663 | .token.keyword { 664 | color: #2b6cb0; 665 | color: var(--code-keyword-text-color); 666 | font-weight: bold; 667 | } 668 | .token.function { 669 | color: #319795; 670 | color: var(--code-function-text-color); 671 | } 672 | .examples > h4 { 673 | margin: 0 0 0.4rem 0; 674 | } 675 | 676 | table { 677 | border: 1px solid var(--table-background-color); 678 | border-collapse: collapse; 679 | } 680 | table tr td, 681 | table tr th { 682 | padding: 4px 8px; 683 | } 684 | table tr th { 685 | background-color: var(--table-background-color); 686 | } 687 | tr:nth-child(even) { 688 | background-color: var(--table-background-color); 689 | } 690 | 691 | button.copy { 692 | border: none; 693 | background-color: transparent; 694 | position: absolute; 695 | font-size: 12px; 696 | top: 5px; 697 | right: 5px; 698 | color: var(--ref-symbol-hover-color); 699 | } 700 | 701 | /* Medium screen and up */ 702 | @media (min-width: 768px) { 703 | *::-webkit-scrollbar { 704 | width: 8px; 705 | height: 8px; 706 | } 707 | *::-webkit-scrollbar-thumb { 708 | border: 3px solid transparent; 709 | } 710 | .doc-container { 711 | flex-direction: row; 712 | } 713 | .doc-content { 714 | font-size: 0.95rem; 715 | flex: 1; 716 | padding: 0rem 2rem 1rem 2rem; 717 | } 718 | .doc-toc { 719 | position: sticky; 720 | height: 100vh; 721 | position: -webkit-sticky; 722 | align-self: flex-start; 723 | min-width: 200px; 724 | width: auto; 725 | max-width: 300px; 726 | } 727 | .doc-toc > ul { 728 | padding-bottom: 1rem; 729 | } 730 | } 731 | 732 | @media (max-width: 1023px) { 733 | .doc-nav.hidden { 734 | height: auto; 735 | } 736 | .doc-nav.hidden #search { 737 | display: none; 738 | } 739 | .doc-nav .search.mobile-hidden { 740 | display: none; 741 | } 742 | .doc-nav > .heading-container > .heading > .info { 743 | align-items: center; 744 | } 745 | .doc-nav > .heading-container > .heading > .info > .toggle-version-container { 746 | flex-grow: 1; 747 | padding: 0 1rem; 748 | justify-content: space-between; 749 | } 750 | } 751 | 752 | @media (min-width: 1024px) { 753 | #page { 754 | padding-top: 0; 755 | } 756 | .doc-nav { 757 | width: 300px; 758 | } 759 | .doc-nav #toggle-menu { 760 | display: none; 761 | } 762 | .doc-nav > .heading-container > .heading > .info { 763 | height: auto; 764 | padding: 1rem 2rem 0 2rem; 765 | flex-direction: column-reverse; 766 | justify-content: center; 767 | } 768 | .doc-nav > .heading-container > .heading > .info > .toggle-version-container { 769 | align-items: center; 770 | margin-bottom: 0.2rem; 771 | display: flex; 772 | flex-direction: row-reverse; 773 | } 774 | .doc-nav > .heading-container > .heading > .info > .toggle-version-container > #dark-mode-toggle { 775 | margin-right: auto; 776 | } 777 | .doc-nav .content.show, 778 | .doc-nav .content.hidden { 779 | display: flex; 780 | } 781 | .doc-content > .doc-node.const:nth-child(2) { 782 | padding-bottom: 0 !important; 783 | } 784 | .doc-content > .doc-node.const:not(:first-child) { 785 | padding-top: 0; 786 | } 787 | .doc-content > .doc-node.const:not(:last-child) { 788 | padding-bottom: 1rem; 789 | } 790 | .doc-container { 791 | margin-left: 300px; 792 | } 793 | .doc-node { 794 | padding-top: 1rem !important; 795 | margin-top: 0 !important; 796 | } 797 | } 798 | 799 | #skip-to-content-link { 800 | height: 30px; 801 | left: 50%; 802 | padding: 8px; 803 | position: absolute; 804 | transform: translateY(-100%); 805 | transition: transform 0.3s; 806 | background: var(--links); 807 | color: var(--warn-text); 808 | border-radius: 1px; 809 | } 810 | #skip-to-content-link:focus { 811 | transform: translateY(0%); 812 | z-index: 1000; 813 | } 814 | -------------------------------------------------------------------------------- /docs/auto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | auto | vdoc 8 | 9 | 10 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 69 |
70 |
71 |
72 |
73 |

auto #

74 |

Cross-Platform mouse and keyboard manipulation.

Cross-platform tool to manipulate the mouse and keyboard to automate tasks.

import auto
 75 | import time
 76 | 
 77 | fn main() {
 78 |     // get current mouse position
 79 |     x, y := auto.Mouse.get_pos()
 80 |     println('Mouse is at: X: ${x}, Y: ${y}')
 81 | 
 82 |     // get the dimensions of the primary display
 83 |     sz := auto.Screen.size()
 84 | 
 85 |     // center the mouse in the middle of the screen
 86 |     auto.Mouse.set_pos(sz.width / 2, sz.height / 2)
 87 | 
 88 |     // left and right click the mouse
 89 |     auto.Mouse.click(.left)
 90 |     auto.Mouse.click(.right)
 91 | 
 92 |     // double left click
 93 |     auto.Mouse.double_click()
 94 | 
 95 |     // click and drag mouse to 100, 150
 96 |     auto.Mouse.drag_to(100, 150, button: .left, duration: time.second)
 97 | 
 98 |     // emulate keyboard typing
 99 |     auto.Keyboard.press(.s)
100 |     auto.Keyboard.write('some message to write', speed: 100 * time.millisecond)
101 | }
102 | 103 |
104 | 105 |
106 |

fn KeyCode.from_byte #

107 |
108 | fn KeyCode.from_byte(c u8) ?(KeyCode, KeyModifier)
109 |

KeyCode.from_byte returns the KeyCode and KeyModifier for an ascii character.

110 | 111 |
112 | 113 |
114 |

fn Keyboard.press #

115 |
116 | fn Keyboard.press(key_code KeyCode, mod KeyModifier)
117 |

Keyboard.press triggers a key press.

118 | 119 |
120 | 121 |
122 |

fn Keyboard.write #

123 |
124 | fn Keyboard.write(str string, params KeyboardWriteParams)
125 |

Keyboard.write types out a string.

126 | 127 |
128 | 129 |
130 |

fn Mouse.click #

131 |
132 | fn Mouse.click(button Button)
133 |

Mouse.click triggers a mouse click event at the current mouse cursor position.

134 | 135 |
136 | 137 |
138 |

fn Mouse.double_click #

139 |
140 | fn Mouse.double_click(button Button)
141 |

Mouse.double_click triggers a double click event at the current mouse cursor position.

142 | 143 |
144 | 145 |
146 |

fn Mouse.drag_rel #

147 |
148 | fn Mouse.drag_rel(rel_x int, rel_y int, params DragParams)
149 |

Mouse.drag_rel drags the mouse cursor relative to the current location of the mouse.

150 | 151 |
152 | 153 |
154 |

fn Mouse.drag_to #

155 |
156 | fn Mouse.drag_to(target_x int, target_y int, params DragParams)
157 |

Mouse.drag_to moves the the mouse cursor while holding down a mouse button.

158 | 159 |
160 | 161 |
162 |

fn Mouse.get_pos #

163 |
164 | fn Mouse.get_pos() (int, int)
165 |

Mouse.get_pos returns the global X and Y coordinates of the mouse cursor. Returns -1, -1 if there is an error getting the mosue position.

166 | 167 |
168 | 169 |
170 |

fn Mouse.get_pos_opt #

171 |
172 | fn Mouse.get_pos_opt() ?(int, int)
173 |

Mouse.get_pos_opt returns the global X and Y coordinates of the mouse cursor.

174 | 175 |
176 | 177 |
178 |

fn Mouse.set_pos #

179 |
180 | fn Mouse.set_pos(x int, y int)
181 |

Mouse.set_pos immediately moves the mouse cursor to the x, y.

182 | 183 |
184 | 185 |
186 |

fn Screen.compositor #

187 |
188 | fn Screen.compositor() Compositor
189 |

Screen.get_compositor gets whether or not a user has X11 or Wayland as their window compositor.

190 | 191 |
192 | 193 |
194 |

fn Screen.refresh_rate #

195 |
196 | fn Screen.refresh_rate() ?int
197 |

Screen.refresh_rate returns the screen refresh rate of the primary display.

198 | 199 |
200 | 201 |
202 |

fn Screen.size #

203 |
204 | fn Screen.size() Size
205 |

Screen.size returns the size of the primary display.

206 | 207 |
208 | 209 |
210 |

enum Button #

211 |
212 | enum Button {
213 | 	left
214 | 	right
215 | 	middle
216 | }
217 |

Button is the buttons on a mouse or touchpad.

218 | 219 |
220 | 221 |
222 |

enum Compositor #

223 |
224 | enum Compositor {
225 | 	unknown
226 | 	wayland
227 | 	x11
228 | 	quartz
229 | 	windows
230 | }
231 |

Compositor is the window compositor being used by the system.

232 | 233 |
234 | 235 |
236 |

enum KeyCode #

237 |
@[_allow_multiple_values]
238 |
239 | enum KeyCode {
240 | 	a             = keycode_a
241 | 	b             = keycode_b
242 | 	c             = keycode_c
243 | 	d             = keycode_d
244 | 	e             = keycode_e
245 | 	f             = keycode_f
246 | 	g             = keycode_g
247 | 	h             = keycode_h
248 | 	i             = keycode_i
249 | 	j             = keycode_j
250 | 	k             = keycode_k
251 | 	l             = keycode_l
252 | 	m             = keycode_m
253 | 	n             = keycode_n
254 | 	o             = keycode_o
255 | 	p             = keycode_p
256 | 	q             = keycode_q
257 | 	r             = keycode_r
258 | 	s             = keycode_s
259 | 	t             = keycode_t
260 | 	u             = keycode_u
261 | 	v             = keycode_v
262 | 	w             = keycode_w
263 | 	x             = keycode_x
264 | 	y             = keycode_y
265 | 	z             = keycode_z
266 | 	_0            = keycode_0
267 | 	_1            = keycode_1
268 | 	_2            = keycode_2
269 | 	_3            = keycode_3
270 | 	_4            = keycode_4
271 | 	_5            = keycode_5
272 | 	_6            = keycode_6
273 | 	_7            = keycode_7
274 | 	_8            = keycode_8
275 | 	_9            = keycode_9
276 | 	space         = keycode_space
277 | 	semicolon     = keycode_semicolon
278 | 	comma         = keycode_comma
279 | 	period        = keycode_period
280 | 	slash         = keycode_slash
281 | 	backtick      = keycode_backtick
282 | 	left_bracket  = keycode_left_bracket
283 | 	right_bracket = keycode_right_bracket
284 | 	backslash     = keycode_backslash
285 | 	quote         = keycode_quote
286 | 	hyphen        = keycode_hyphen
287 | 	equals        = keycode_equals
288 | 	return        = keycode_return
289 | 	enter         = keycode_return
290 | 	backspace     = keycode_backspace
291 | 	tab           = keycode_tab
292 | 	left_shift    = keycode_left_shift
293 | 	right_shift   = keycode_right_shift
294 | 	left_ctrl     = keycode_left_ctrl
295 | 	right_ctrl    = keycode_right_ctrl
296 | 	left_alt      = keycode_left_alt
297 | 	right_alt     = keycode_right_alt
298 | 	escape        = keycode_escape
299 | 	f1            = keycode_f1
300 | 	f2            = keycode_f2
301 | 	f3            = keycode_f3
302 | 	f4            = keycode_f4
303 | 	f5            = keycode_f5
304 | 	f6            = keycode_f6
305 | 	f7            = keycode_f7
306 | 	f8            = keycode_f8
307 | 	f9            = keycode_f9
308 | 	f10           = keycode_f10
309 | 	f11           = keycode_f11
310 | 	f12           = keycode_f12
311 | 	left_arrow    = keycode_left_arrow
312 | 	right_arrow   = keycode_right_arrow
313 | 	up_arrow      = keycode_up_arrow
314 | 	down_arrow    = keycode_down_arrow
315 | 	delete        = keycode_delete
316 | }
317 |

KeyCode is a key on a keyboard.

318 | 319 |
320 | 321 |
322 |

enum KeyModifier #

323 |
@[flag]
324 |
325 | enum KeyModifier {
326 | 	shift
327 | 	ctrl
328 | 	alt
329 | }
330 |

KeyModifier is a key which can be pressed in combination with another key.

331 | 332 |
333 | 334 |
335 |

struct DragParams #

336 |
@[params]
337 |
338 | struct DragParams {
339 | __global:
340 | 	duration time.Duration = time.millisecond * 750
341 | 	button   Button        = .left
342 | }
343 |

DragParams are the options for determining how the mouse is dragged across the screen.

344 | 345 |
346 | 347 |
348 |

struct Keyboard #

349 |
@[noinit]
350 |
351 | struct Keyboard {}
352 |

Keyboard acts as a namespace for keyboard related functions.

353 | 354 |
355 | 356 |
357 |

struct KeyboardWriteParams #

358 |
@[params]
359 |
360 | struct KeyboardWriteParams {
361 | __global:
362 | 	speed time.Duration = 50 * time.millisecond
363 | }
364 |

KeyboardWriteParams is the parameters for determining how Keyboard.write behaves.

365 | 366 |
367 | 368 |
369 |

struct Mouse #

370 |
@[noinit]
371 |
372 | struct Mouse {}
373 |

Mouse acts as a namespace for mouse related functions.

374 | 375 |
376 | 377 |
378 |

struct Pos #

379 |
380 | struct Pos {
381 | __global:
382 | 	x int
383 | 	y int
384 | }
385 |

Pos is the X and Y coordinates on a screen.

386 | 387 |
388 | 389 |
390 |

struct Screen #

391 |
@[noinit]
392 |
393 | struct Screen {}
394 |

Screen acts as a namespace for screen related functions.

395 | 396 |
397 | 398 |
399 |

struct Size #

400 |
401 | struct Size {
402 | __global:
403 | 	width  int
404 | 	height int
405 | }
406 |

Size is the width and height of a screen.

407 | 408 |
409 | 410 | 411 | 412 |
413 |
487 |
488 |
489 |
490 | 491 | 492 | 493 | 494 | --------------------------------------------------------------------------------