1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4// UI wrapper that sends requests to the UI thread.
5// Used by the built-in TUI and libnvim-based UIs.
6
7#include <assert.h>
8#include <stdbool.h>
9#include <stdio.h>
10#include <limits.h>
11
12#include "nvim/log.h"
13#include "nvim/main.h"
14#include "nvim/vim.h"
15#include "nvim/ui.h"
16#include "nvim/memory.h"
17#include "nvim/ui_bridge.h"
18#include "nvim/ugrid.h"
19#include "nvim/api/private/helpers.h"
20
21#ifdef INCLUDE_GENERATED_DECLARATIONS
22# include "ui_bridge.c.generated.h"
23#endif
24
25#define UI(b) (((UIBridgeData *)b)->ui)
26
27// Schedule a function call on the UI bridge thread.
28#define UI_BRIDGE_CALL(ui, name, argc, ...) \
29 ((UIBridgeData *)ui)->scheduler( \
30 event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui))
31
32#define INT2PTR(i) ((void *)(intptr_t)i)
33#define PTR2INT(p) ((Integer)(intptr_t)p)
34
35#ifdef INCLUDE_GENERATED_DECLARATIONS
36# include "ui_events_bridge.generated.h"
37#endif
38
39UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
40{
41 UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData));
42 rv->ui = ui;
43 rv->bridge.rgb = ui->rgb;
44 rv->bridge.stop = ui_bridge_stop;
45 rv->bridge.grid_resize = ui_bridge_grid_resize;
46 rv->bridge.grid_clear = ui_bridge_grid_clear;
47 rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto;
48 rv->bridge.mode_info_set = ui_bridge_mode_info_set;
49 rv->bridge.update_menu = ui_bridge_update_menu;
50 rv->bridge.busy_start = ui_bridge_busy_start;
51 rv->bridge.busy_stop = ui_bridge_busy_stop;
52 rv->bridge.mouse_on = ui_bridge_mouse_on;
53 rv->bridge.mouse_off = ui_bridge_mouse_off;
54 rv->bridge.mode_change = ui_bridge_mode_change;
55 rv->bridge.grid_scroll = ui_bridge_grid_scroll;
56 rv->bridge.hl_attr_define = ui_bridge_hl_attr_define;
57 rv->bridge.bell = ui_bridge_bell;
58 rv->bridge.visual_bell = ui_bridge_visual_bell;
59 rv->bridge.default_colors_set = ui_bridge_default_colors_set;
60 rv->bridge.flush = ui_bridge_flush;
61 rv->bridge.suspend = ui_bridge_suspend;
62 rv->bridge.set_title = ui_bridge_set_title;
63 rv->bridge.set_icon = ui_bridge_set_icon;
64 rv->bridge.option_set = ui_bridge_option_set;
65 rv->bridge.raw_line = ui_bridge_raw_line;
66 rv->bridge.inspect = ui_bridge_inspect;
67 rv->scheduler = scheduler;
68
69 for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
70 rv->bridge.ui_ext[i] = ui->ui_ext[i];
71 }
72
73 rv->ui_main = ui_main;
74 uv_mutex_init(&rv->mutex);
75 uv_cond_init(&rv->cond);
76 uv_mutex_lock(&rv->mutex);
77 rv->ready = false;
78
79 if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) {
80 abort();
81 }
82
83 // Suspend the main thread until CONTINUE is called by the UI thread.
84 while (!rv->ready) {
85 uv_cond_wait(&rv->cond, &rv->mutex);
86 }
87 uv_mutex_unlock(&rv->mutex);
88
89 ui_attach_impl(&rv->bridge, 0);
90
91 return &rv->bridge;
92}
93
94void ui_bridge_stopped(UIBridgeData *bridge)
95{
96 uv_mutex_lock(&bridge->mutex);
97 bridge->stopped = true;
98 uv_mutex_unlock(&bridge->mutex);
99}
100
101static void ui_thread_run(void *data)
102{
103 UIBridgeData *bridge = data;
104 bridge->ui_main(bridge, bridge->ui);
105}
106
107static void ui_bridge_stop(UI *b)
108{
109 // Detach bridge first, so that "stop" is the last event the TUI loop
110 // receives from the main thread. #8041
111 ui_detach_impl(b, 0);
112
113 UIBridgeData *bridge = (UIBridgeData *)b;
114 bool stopped = bridge->stopped = false;
115 UI_BRIDGE_CALL(b, stop, 1, b);
116 for (;;) {
117 uv_mutex_lock(&bridge->mutex);
118 stopped = bridge->stopped;
119 uv_mutex_unlock(&bridge->mutex);
120 if (stopped) { // -V547
121 break;
122 }
123 // TODO(justinmk): Remove this. Use a cond-wait above. #9274
124 loop_poll_events(&main_loop, 10); // Process one event.
125 }
126 uv_thread_join(&bridge->ui_thread);
127 uv_mutex_destroy(&bridge->mutex);
128 uv_cond_destroy(&bridge->cond);
129 xfree(bridge->ui); // Threads joined, now safe to free UI container. #7922
130 xfree(b);
131}
132static void ui_bridge_stop_event(void **argv)
133{
134 UI *ui = UI(argv[0]);
135 ui->stop(ui);
136}
137
138static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs,
139 HlAttrs cterm_attrs, Array info)
140{
141 HlAttrs *a = xmalloc(sizeof(HlAttrs));
142 *a = attrs;
143 UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a);
144}
145static void ui_bridge_hl_attr_define_event(void **argv)
146{
147 UI *ui = UI(argv[0]);
148 Array info = ARRAY_DICT_INIT;
149 ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]),
150 *((HlAttrs *)argv[2]), info);
151 xfree(argv[2]);
152}
153
154static void ui_bridge_raw_line_event(void **argv)
155{
156 UI *ui = UI(argv[0]);
157 ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]),
158 PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]),
159 (LineFlags)PTR2INT(argv[7]), argv[8], argv[9]);
160 xfree(argv[8]);
161 xfree(argv[9]);
162}
163static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row,
164 Integer startcol, Integer endcol,
165 Integer clearcol, Integer clearattr,
166 LineFlags flags, const schar_T *chunk,
167 const sattr_T *attrs)
168{
169 size_t ncol = (size_t)(endcol-startcol);
170 schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T));
171 sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T));
172 UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row),
173 INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol),
174 INT2PTR(clearattr), INT2PTR(flags), c, hl);
175}
176
177static void ui_bridge_suspend(UI *b)
178{
179 UIBridgeData *data = (UIBridgeData *)b;
180 uv_mutex_lock(&data->mutex);
181 UI_BRIDGE_CALL(b, suspend, 1, b);
182 data->ready = false;
183 // Suspend the main thread until CONTINUE is called by the UI thread.
184 while (!data->ready) {
185 uv_cond_wait(&data->cond, &data->mutex);
186 }
187 uv_mutex_unlock(&data->mutex);
188}
189static void ui_bridge_suspend_event(void **argv)
190{
191 UI *ui = UI(argv[0]);
192 ui->suspend(ui);
193}
194
195static void ui_bridge_option_set(UI *ui, String name, Object value)
196{
197 String copy_name = copy_string(name);
198 Object *copy_value = xmalloc(sizeof(Object));
199 *copy_value = copy_object(value);
200 UI_BRIDGE_CALL(ui, option_set, 4, ui, copy_name.data,
201 INT2PTR(copy_name.size), copy_value);
202 // TODO(bfredl): when/if TUI/bridge teardown is refactored to use events, the
203 // commit that introduced this special case can be reverted.
204 // For now this is needed for nvim_list_uis().
205 if (strequal(name.data, "termguicolors")) {
206 ui->rgb = value.data.boolean;
207 }
208}
209static void ui_bridge_option_set_event(void **argv)
210{
211 UI *ui = UI(argv[0]);
212 String name = (String){ .data = argv[1], .size = (size_t)argv[2] };
213 Object value = *(Object *)argv[3];
214 ui->option_set(ui, name, value);
215 api_free_string(name);
216 api_free_object(value);
217 xfree(argv[3]);
218}
219
220static void ui_bridge_inspect(UI *ui, Dictionary *info)
221{
222 PUT(*info, "chan", INTEGER_OBJ(0));
223}
224