-
Notifications
You must be signed in to change notification settings - Fork 9
/
webui.nim
884 lines (682 loc) · 28.8 KB
/
webui.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
##[
Nim wrapper for [WebUI](https://github.com/webui-dev/webui)
:author: neroist
:WebUI Version: 2.5.0-Beta
See: https://neroist.github.io/webui-docs/
]##
from webui/bindings import nil
type
Window* = distinct int
Event* = ref object
## When you use `bind()`, your application will receive an event every time
## the user clicks on the specified HTML element. The event comes with the
## `element`, which is The HTML ID of the clicked element, for example,
## `"MyButton"`, `"MyInput"`, etc. The event also comes with the element ID
## & the unique window ID.
internalImpl*: ptr bindings.Event
# vars
var
cbs: array[bindings.WEBUI_MAX_IDS, array[bindings.WEBUI_MAX_IDS, proc (e: Event)]]
## array of binded callbacks.
## Needed for `bind`
currHandler: proc (filename: string): string
## Most recent file handler set by `fileHandler=`.
## Needed for `fileHandler=`.
proc wait*() =
## Wait until all opened windows get closed.
bindings.wait()
proc exit*() =
## Close all opened windows. `wait()` will break.
bindings.exit()
proc setTimeout*(timeout: int) =
## Set the maximum time in seconds to wait for the window to connect. This
## will affect `show()` and `wait()`. Setting the timeout to `0` will cause
## WebUI to wait forever.
##
## :timeout: The maximum time in seconds to wait for browser to start.
## Set to `0` to wait forever.
bindings.setTimeout(csize_t timeout)
proc encode*(str: string): string =
## Base64 encoding. Use this to safely send text based data to the UI.
## If it fails it will return an empty string.
##
## :str: The string to encode.
var cstr = bindings.encode(cstring str)
defer: bindings.free(cstr)
result = $cstr
proc decode*(str: string): string =
## Base64 decoding. Use this to safely decode received Base64 text from the UI.
## If it fails it will return an empty string.
##
## :str: The string to decode.
var cstr = bindings.decode(cstring str)
defer: bindings.free(cstr)
result = $cstr
proc setDefaultRootFolder*(path: string): bool {.discardable.} =
## Set the default web-server root folder path for all windows.
##
## .. note:: Should be used before `webui_show()`.
##
## :path: The path to the root folder.
##
## Returns `true` on success.
bindings.setDefaultRootFolder(cstring path)
proc clean*() =
## Free all memory resources. Should be called only at the end.
bindings.clean()
proc deleteAllProfiles*() =
## Delete all local web-browser profiles folder. It should be called at the end.
bindings.deleteAllProfiles()
proc setTlsCertificate*(certificate_pem, private_key_pem: string): bool =
## Set the SSL/TLS certificate and the private key content, both in PEM
## format. This works only with `webui-2-secure` library. If set empty, WebUI
## will generate a self-signed certificate.
##
## :certificate_pem: The SSL/TLS certificate content in PEM format
## :private_key_pem: The private key content in PEM format
##
## Returns `true` if the certificate and the key are valid.
bindings.setTlsCertificate(cstring certificate_pem, cstring private_key_pem)
proc isHighContrast*(): bool =
## Get the OS's high contrast preference.
##
## Returns `true` if the OS prefers a high contrast theme.
bindings.isHighContrast()
proc browserExist*(browser: bindings.WebuiBrowser): bool =
## Check if a web browser is installed.
##
## Returns `true` if the specified browser is available.
bindings.browserExist(browser)
proc setConfig*(option: bindings.WebuiConfig; status: bool) =
## Control WebUI's behaviour via setting configuration option `option` to either
## `true` or `false`. It's better to this call at the beginning of your program.
##
## :option: The desired option from the `WebuiConfig` enum
## :status: The status of the option, `true` or `false`
bindings.setConfig(option, status)
proc setConfig*(options: openArray[bindings.WebuiConfig] or set[bindings.WebuiConfig]; status: bool) =
## Control WebUI's behaviour via setting configuration options `options` to either
## `true` or `false`. It's better to this call at the beginning of your program.
##
## :options: The desired options from the `WebuiConfig` enum
## :status: The desired status of all of the options, `true` or `false`
for option in options:
bindings.setConfig(option, status)
proc openUrl*(url: string) =
## Open an URL in the native default web browser.
##
## :url: The URL to open
bindings.openUrl(cstring url)
proc getFreePort*(): int =
## Get an available and usable free network port.
int bindings.getFreePort()
proc getMimeType*(file: string): string =
## Get the HTTP mime type of a file.
##
## :file: The name of the file (e.g. `foo.png`)
$ bindings.getMimeType(cstring file)
# ------- Impl funcs --------
# --- Event ---
# TODO why do these exist...?
func impl*(event: Event): ptr bindings.Event =
## Returns the internal implementation of `e`
event.internalImpl
func `impl=`*(event: Event, be: ptr bindings.Event) =
## Sets the internal implementation of `e`
event.internalImpl = be
# TODO add docs
proc window*(event: Event): Window = Window(int event.impl.window)
proc eventType*(event: Event): bindings.WebuiEvent = bindings.WebuiEvent(int event.impl.eventType)
proc element*(event: Event): string = $event.impl.element
proc eventNumber*(event: Event): int = int event.impl.eventNumber
proc bindId*(event: Event): int = int event.impl.bindId
proc clientId*(event: Event): int = int event.impl.clientId
proc connectionId*(event: Event): int = int event.impl.connectionId
proc cookies*(event: Event): string = $event.impl.cookies
# ---
proc getCount*(event: Event): int =
## Get how many arguments there are in an event.
##
## :event: The event
int bindings.getCount(event.impl)
proc getInt*(event: Event, index: int): int =
## Get an argument as integer at a specific index
##
## :event: The event
## :index: The argument position starting from 0
int bindings.getIntAt(event.impl, csize_t index)
proc getInt*(event: Event): int =
## Get the first argument as integer
##
## :event: The event
int bindings.getInt(event.impl)
proc getFloat*(event: Event, index: int): float =
## Get an argument as integer at a specific index
##
## :event: The event
## :index: The argument position starting from 0
float bindings.getFloatAt(event.impl, csize_t index)
proc getFloat*(event: Event): float =
## Get the first argument as a float
##
## :event: The event
float bindings.getFloat(event.impl)
proc getString*(event: Event, index: int): string =
## Get an argument as string at a specific index
##
## :event: The event
## :index: The argument position starting from 0
let
# cast[uint] returns the char* as its integer address
cptr = cast[uint](bindings.getStringAt(event.impl, csize_t index))
size = uint bindings.getSizeAt(event.impl, csize_t index)
result = newString(size)
for i in 0..<size:
result[i] = cast[ptr char](cptr + i)[]
proc getString*(event: Event): string =
## Get the first argument as string
##
## :event: The event
let
# cast[uint] returns the char* as its integer address
cptr = cast[uint](bindings.getString(event.impl))
size = uint bindings.getSize(event.impl)
result = newString(size)
for i in 0..<size:
result[i] = cast[ptr char](cptr + i)[]
proc getBool*(event: Event, index: int): bool =
## Get an argument as boolean at a specific index
##
## :event: The event
## :index: The argument position starting from 0
bool bindings.getBoolAt(event.impl, csize_t index)
proc getBool*(event: Event): bool =
## Get the first argument as boolean
##
## :event: The event
bool bindings.getBool(event.impl)
proc getSize*(event: Event, index: int): int =
## Get the size in bytes of an argument at a specific index
##
## :event: The event
## :index: The argument position starting from 0
int bindings.getSizeAt(event.impl, csize_t index)
proc getSize*(event: Event): int =
## Get size in bytes of the first argument
##
## :event: The event
int bindings.getSize(event.impl)
proc returnInt*(event: Event; integer: int) =
## Return the response to JavaScript as a integer.
##
## :event: The event to set the response for
## :integer: The int to return back to Javascript.
bindings.returnInt(event.impl, clonglong integer)
proc returnFloat*(event: Event; f: float) =
## Return the response to JavaScript as a float.
##
## :event: The event to set the response for
## :integer: The float to return back to Javascript.
bindings.returnFloat(event.impl, cdouble f)
proc returnString*(event: Event; str: string) =
## Return the response to JavaScript as a string.
##
## :event: The event to set the response for
## :str: The string to return back to Javascript.
bindings.returnString(event.impl, cstring str)
proc returnBool*(event: Event; b: bool) =
## Return the response to JavaScript as a boolean.
##
## :event: The event to set the response for
## :b: The bool to return back to Javascript.
bindings.returnBool(event.impl, b)
# -------- Window --------
proc newWindow*(): Window =
## Create a new WebUI window object.
result = Window(bindings.newWindow())
proc newWindow*(windowNumber: int): Window =
## Create a new webui window object using a specified window number.
##
## :windowNumber: The window ID (should be within the range of
## `0..<WEBUI_MAX_IDS`)
result = Window(bindings.newWindowId(csize_t windowNumber))
proc getNewWindowId*(): int =
## Get a free window number that can be used in conjuction with
## `newWindow(int)`_.
##
## Returns the first available free window number, starting from 1.
int bindings.getNewWindowId()
proc childProcessId*(window: Window): int =
## Get the ID of the last child process.
##
## :window: The window
int bindings.getChildProcessId(csize_t window)
proc parentProcessId*(window: Window): int =
## Get the ID of the parent process (The web browser may re-create
## another new process).
##
## :window: The window
int bindings.getParentProcessId(csize_t window)
proc getBestBrowser*(window: Window): bindings.WebuiBrowser =
## Get the recommended web browser to use. If running `show()`, this function
## will return the same browser that is already being used.
##
## :window: The window to get the best browser for
##
## Returns a value from the `WebuiBrowser` enum.
bindings.WebuiBrowser(bindings.getBestBrowser(csize_t window))
{.push discardable.}
proc show*(window: Window; content: string): bool =
## Show a window using embedded HTML, or a file. If the window is already
## open, it will be refreshed. This will refresh all windows in multi-client
## mode.
##
## .. important:: Please include `<script src="/webui.js"></script>` in the
## HTML for proper window communication.
##
## :window: The window to show `content` in. If the window is already
## shown, the UI will get refreshed in the same window.
## :content: The content to show in `window`. Can be a file name, URL, or a
## static HTML script.
##
## Returns `true` if showing the window is a success.
bindings.show(csize_t window, cstring content)
proc show*(window: Window; content: string; browser: bindings.WebuiBrowser): bool =
## Same as `show() <#show,Window,string>`_, but with a specific web browser.
##
## .. important:: Please include `<script src="/webui.js"></script>` in the HTML
## for proper window communication.
##
## :window: The window to show `content` in. If the window is already
## shown, the UI will get refreshed in the same window.
## :content: The content to show in `window`. Can be a file name, or a
## static HTML script.
## :browser: The browser to open the window in.
##
## Returns `true` if showing the window is a success.
bindings.showBrowser(csize_t window, cstring content, csize_t ord(browser))
proc show*(window: Window; content: string; browsers: openArray[bindings.WebuiBrowser] or set[bindings.WebuiBrowser]): bool =
## Same as `show() <#show,Window,string>`_, but with a specific set of web browsers to use.
##
## .. important:: Please include `<script src="/webui.js"></script>` in the HTML
## for proper window communication.
##
## :window: The window to show `content` in. If the window is already
## shown, the UI will get refreshed in the same window.
## :content: The content to show in `window`. Can be a file name, or a
## static HTML script.
## :browser: The browsers to open the window in.
##
## Returns `true` if showing the window is a success.
for browser in browsers:
if bindings.showBrowser(csize_t window, cstring content, csize_t ord(browser)):
return true
proc showClient*(event: Event; content: string): bool =
## Show a window using embedded HTML, or a file. If the window is already
## open, it will be refreshed. Single client.
##
## .. important:: Please include `<script src="/webui.js"></script>` in the
## HTML for proper window communication.
##
## :event: The event to use.
## :content: The content to show. Can be a file name, URL, or a
## static HTML script.
##
## Returns `true` if showing the window is a success.
bindings.showClient(event.impl, cstring content)
proc showWv*(window: Window; content: string): bool =
## Show a WebView window using embedded HTML, or a file. If the window is already
## open, it will be refreshed.
##
## .. note:: On Windows, you will need `WebView2Loader.dll`.
##
## :window: The window to show `content` in. If the window is already
## shown, the UI will get refreshed in the same window.
## :content: The content to show in `window`. Can be a file name, or a
## static HTML script.
##
## Returns `true` if showing the WebView window succeeded.
bindings.showWv(csize_t window, cstring content)
{.pop.}
proc startServer*(window: Window, path: string): string =
## Same as `show()`_, but it only starts the web server and returns the URL.
## This is useful for web apps.
##
## No window will be shown.
##
## :window: The window to start the web server for
## :path: The full path to the local root folder
##
## Returns the url of the window server.
$ bindings.startServer(csize_t window, cstring path)
proc port*(window: Window): int =
## Get the network port of a running window.
##
## This can be useful to determine the HTTP link of `webui.js`
##
## :window: The window
## :port: The web-server network port WebUI should use
int bindings.getPort(csize_t window)
proc `port=`*(window: Window, port: int) =
## Set a custom web-server network port to be used by WebUI.
## This can be useful to determine the HTTP link of `webui.js` in case
## you are trying to use WebUI with an external web-server like NGNIX
##
## :window: The window
## :port: The web-server network port WebUI should use
bindings.setPort(csize_t window, csize_t port)
proc setIcon*(window: Window; icon, mime: string) =
## Set the default embedded HTML favicon.
##
## :window: The window to set the icon for.
## :icon: The icon as string: `<svg>...</svg>`
## :mime: The MIME type of the icon
bindings.setIcon(csize_t window, cstring icon, cstring mime)
proc `public=`*(window: Window; status: bool) =
## Allow a specific window address to be accessible from a public network
##
## :window: The window
## :status: Whether or not to set public. `true` to enable, `false`
## to disable.
bindings.setPublic(csize_t window, status)
proc `kiosk=`*(window: Window; status: bool) =
## Set the window in Kiosk mode (full screen).
##
## :window: The window to enable or disable kiosk mode in.
## :status: Whether or not to enable kiosk mode. `true` to enable, `false`
## to disable.
bindings.setKiosk(csize_t window, status)
proc `runtime=`*(window: Window; runtime: bindings.WebuiRuntime) =
## Chose a runtime for .js and .ts files.
##
## :window: The window to set the runtime for.
## :runtime: The runtime to set.
bindings.setRuntime(csize_t window, csize_t ord(runtime))
proc `rootFolder=`*(window: Window; path: string): bool {.discardable.} =
## Set the web-server root folder path.
##
## :window: The window to set the root folder for.
## :path: The path to the root folder.
bindings.setRootFolder(csize_t window, cstring path)
proc `hidden=`*(window: Window; status: bool) =
## Run the window in hidden mode
##
## :window: The window to hide or show.
## :status: Whether or not to hide the window. `true` to hide, `false`
## to show.
bindings.setHide(csize_t window, status)
proc `highContrast=`*(window: Window; status: bool) =
## Setup the window with high-contrast support. Useful when you want to
## build a better high-contrast theme with CSS.
##
## :window: The window to set the high contrast theme support for.
## :status: Whether or not to support high contrast themes.
bindings.setHighContrast(csize_t window, status)
proc `eventBlocking=`*(window: Window; status: bool) =
## Control if UI events comming from this window should be processed
## one a time in a single blocking thread (`true`), or process every event in
## a new non-blocking thread (`false`). This function only affects a single window
## You may use `setConfig(wcUiEventBlocking, ...)` to update all windows.
##
## :window: The window to configure event blocking for.
## :status: Whether or not to process window events blockingly *(single vs multi-threaded)*.
bindings.setEventBlocking(csize_t window, status)
proc `proxy=`*(window: Window, proxyServer: string) =
## Set the web browser to use `proxyServer`. Must be called before `show()`.
##
## :window: The window to set the proxy server for
## :proxyServer: The proxy server to use
bindings.setProxy(csize_t window, cstring proxyServer)
proc setSize*(window: Window; width, height: int) =
## Set the window size.
##
## :window: The window.
## :width: The window width.
## :height: The window height.
bindings.setSize(csize_t window, cuint width, cuint height)
proc setPos*(window: Window; x, y: int) =
## Set the window position.
##
## :window: The window.
## :x: The window's X coordinate.
## :y: The window's Y coordinate.
bindings.setPosition(csize_t window, cuint x, cuint y)
proc `size=`*(window: Window; size: tuple[width, height: int]) {.deprecated: "Use `setSize` instead".} =
## Alias for `setSize`_
bindings.setSize(csize_t window, cuint size.width, cuint size.height)
proc `pos=`*(window: Window; pos: tuple[x, y: int]) {.deprecated: "Use `setPos` instead".} =
## Alias for `setPos`_
bindings.setPosition(csize_t window, cuint pos.x, cuint pos.y)
proc close*(window: Window) =
## Close a specific window only. The window object will still exist.
##
## :window: The window to close.
bindings.close(csize_t window)
proc closeClient*(event: Event) =
## Close a specific client.
##
## :event: The event object.
bindings.closeClient(event.impl)
proc destroy*(window: Window) =
## Close a specific window and free all memory resources.
##
## :window: The window to destroy.
bindings.destroy(csize_t window)
proc shown*(window: Window): bool =
## Return if window `window` is still running
##
## :window: The window to return `true` if still running.
bindings.isShown(csize_t window)
proc script*(window: Window; script: string; timeout: int = 0, bufferLen: static[int] = 1024 * 8): tuple[data: string; error: bool] =
## Run Javascript code `script` and return the result
##
## Returns a tuple containing the response (`data`) and whether or not
## there was an error (`error`, `true` if an error occured, `false` otherwise).
## If an error occured, the error message will be held in `data`.
##
## :window: The window to run the Javascript code in.
## :script: The Javascript code to execute.
## :timeout: The execution timeout in seconds.
## :bufferLen: How large to make the buffer for the response. Default is
## 8 kibibytes. (For larger responses make `bufferLen` larger)
var buffer: array[bufferLen, char]
let error = bindings.script(csize_t window, cstring script, csize_t timeout, cast[cstring](addr buffer), csize_t bufferLen)
result.data = newString(buffer.find('\0'))
for i in 0 ..< result.data.len:
result.data[i] = buffer[i]
# webui returns `false` in case of an error, we want to return `true`
result.error = not error
proc scriptClient*(event: Event; script: string; timeout: int = 0, bufferLen: static[int] = 1024 * 8): tuple[data: string; error: bool] =
## Run Javascript code `script` and return the result
##
## Returns a tuple containing the response (`data`) and whether or not
## there was an error (`error`, `true` if an error occured, `false` otherwise).
## If an error occured, the error message will be held in `data`.
##
## :event: The event.
## :script: The Javascript code to execute.
## :timeout: The execution timeout in seconds.
## :bufferLen: How large to make the buffer for the response. Default is
## 8 kibibytes. (For larger responses make `bufferLen` larger)
var buffer: array[bufferLen, char]
let error = bindings.scriptClient(event.impl, cstring script, csize_t timeout, cast[cstring](addr buffer), csize_t bufferLen)
result.data = newString(buffer.find('\0'))
for i in 0 ..< result.data.len:
result.data[i] = buffer[i]
# webui returns `false` in case of an error, we want to return `true`
result.error = not error
proc run*(window: Window; script: string) =
## Run JavaScript quickly without waiting for the response. All clients.
##
## :window: The window to run the Javascript code in.
## :script: The Javascript code to execute.
bindings.run(csize_t window, cstring script)
proc runClient*(event: Event; script: string) =
## Run JavaScript quickly without waiting for the response. All clients.
##
## :event: The event.
## :script: The Javascript code to execute.
bindings.runClient(event.impl, cstring script)
# proc interfaceHandler(window: csize_t; eventType: csize_t; element: cstring; data: cstring; eventNumber: csize_t) {.cdecl.} =
# var event = bindings.Event()
#
# event.element = element
# event.window = window
# event.data = data
# event.eventType = eventType
# event.eventNumber = eventNumber
#
# var e = Event(
# internalImpl: addr event
# )
#
# cbs[bindings.interfaceGetWindowId(window)][bindings.interfaceGetBindId(window, element)](e)
proc bindHandler(e: ptr bindings.Event) {.cdecl.} =
var event = new Event
event.impl = e
cbs[int bindings.interfaceGetWindowId(e.window)][int e.bindId](event)
proc `bind`*(window: Window; element: string; `func`: proc (e: Event)) =
## Bind a specific html element and a JavaScript object with a backend
## function. Empty `element` will bind all events.
##
## Each element can have only one function bound to it.
##
## :window: The window to bind the function onto.
## :element: The HTML element / JavaScript object to bind the function `func`
## to. For HTML elements, `func` will be called on click events.
## An empty element means `func` will be bound to all events.
## :func: The function to bind to `element`.
let bid = int bindings.bind(csize_t window, cstring element, bindHandler)
let wid = int bindings.interfaceGetWindowId(csize_t window)
cbs[wid][bid] = `func`
proc `bind`*(window: Window; element: string; `func`: proc ()) =
window.bind(element, proc (e: Event) = `func`())
proc `bind`*(window: Window; element: string; `func`: proc (): string) =
window.bind(element, proc (e: Event) = e.returnString(`func`()))
proc `bind`*(window: Window; element: string; `func`: proc (): int) =
window.bind(element, proc (e: Event) = e.returnInt(`func`()))
proc `bind`*(window: Window; element: string; `func`: proc (): float) =
window.bind(element, proc (e: Event) = e.returnFloat(`func`()))
proc `bind`*(window: Window; element: string; `func`: proc (): bool) =
window.bind(element, proc (e: Event) = e.returnBool(`func`()))
proc `bind`*(window: Window; element: string; `func`: proc (e: Event): string) =
window.bind(
element,
proc (e: Event) =
let res = `func`(e)
e.returnString(res)
)
proc `bind`*(window: Window; element: string; `func`: proc (e: Event): int) =
window.bind(
element,
proc (e: Event) =
let res = `func`(e)
e.returnInt(res)
)
proc `bind`*(window: Window; element: string; `func`: proc (e: Event): float) =
window.bind(
element,
proc (e: Event) =
let res = `func`(e)
e.returnFloat(res)
)
proc `bind`*(window: Window; element: string; `func`: proc (e: Event): bool) =
## Bind `func` to element `element` and automatically pass return value of `func` to Javascript.
##
## :window: The window to bind the function onto.
## :element: The element to bind the function `func` to. `func` will be
## called on click events. An empty element means `func` will
## be called on all events.
## :func: The function to bind to `element`.
window.bind(
element,
proc (e: Event) =
let res = `func`(e)
e.returnBool(res)
)
proc fileHandlerImpl(filename: cstring, length: ptr cint): pointer {.cdecl.} =
let content = currHandler($filename)
if content.len == 0:
return nil
# Always set length for memory safety, especially with binaries with '\0' inside
length[] = cint content.len
# Use webui_malloc to ensure memory safety
let mem = bindings.malloc(csize_t content.len)
copyMem(mem, cstring content, content.len)
return mem
proc `fileHandler=`*(window: Window; handler: proc (filename: string): string) =
## Set a custom handler to serve files. This custom handler should return the
## full HTTP headers and body.
##
## :window: The window to set the file handler.
## :runtime: The file handler callback/proc.
currHandler = handler
bindings.setFileHandler(csize_t window, fileHandlerImpl)
proc setFileHandler*(window: Window; handler: proc (filename: string): string) =
## Same as `fileHandler=`, but targeted towards use with `do` notation
window.fileHandler = handler
proc sendRaw*(window: Window; function: string; raw: pointer; size: uint) =
## Safely send raw data to the UI. All clients.
##
## :window: The window to send the raw data to.
## :function: The JavaScript function to receive raw data: `function myFunc(myData){}`
## :raw: The raw data buffer.
## :size: The size of the raw data in bytes.
bindings.sendRaw(csize_t window, cstring function, raw, csize_t size)
proc sendRawClient*(event: Event; function: string; raw: pointer; size: uint) =
## Safely send raw data to the UI. All clients.
##
## :event: The event.
## :function: The JavaScript function to receive raw data: `function myFunc(myData){}`
## :raw: The raw data buffer.
## :size: The size of the raw data in bytes.
bindings.sendRawClient(event.impl, cstring function, raw, csize_t size)
proc setPosition*(window: Window; x, y: int) =
## Set window position
##
## :window: The window to set the size for.
## :x: What to set the window's X to.
## :y: What to set the window's Y to.
bindings.setPosition(csize_t window, cuint x, cuint y)
proc setProfile*(window: Window; name, path: string) =
## Set the web browser profile to use. An empty `name` and `path` means
## the default user profile.
##
## .. note:: Needs to be called before `webui_show()`.
##
## :window: The window to set the browser profile for.
## :name: The web browser profile name.
## :path: The web browser profile full path.
runnableExamples:
window.setProfile("Bar", "/Home/Foo/Bar")
window.setProfile("", "")
bindings.setProfile(csize_t window, cstring name, cstring path)
proc url*(window: Window): string =
## Get the full current URL.
##
## :window: The window to get the URL from
$ bindings.getUrl(csize_t window)
proc navigate*(window: Window, url: string) =
## Navigate to a specific URL. All clients.
##
## :window: The window to navigate on
## :url: The URL to navigate to
bindings.navigate(csize_t window, cstring url)
proc navigateClient*(event: Event, url: string) =
## Navigate to a specific URL. Single client.
##
## :window: The window to navigate on
## :url: The URL to navigate to
bindings.navigateClient(event.impl, cstring url)
proc deleteProfile*(window: Window) =
## Delete a specific window web-browser local folder profile.
##
## :window: The window whose profile will be deleted
bindings.deleteProfile(csize_t window)
export
bindings.WebuiEvent,
bindings.WebuiBrowser,
bindings.WebuiRuntime,
bindings.WebuiConfig,
bindings.WEBUI_VERSION,
bindings.WEBUI_MAX_IDS