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 | |
39 | UI *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 | |
94 | void ui_bridge_stopped(UIBridgeData *bridge) |
95 | { |
96 | uv_mutex_lock(&bridge->mutex); |
97 | bridge->stopped = true; |
98 | uv_mutex_unlock(&bridge->mutex); |
99 | } |
100 | |
101 | static void ui_thread_run(void *data) |
102 | { |
103 | UIBridgeData *bridge = data; |
104 | bridge->ui_main(bridge, bridge->ui); |
105 | } |
106 | |
107 | static 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 | } |
132 | static void ui_bridge_stop_event(void **argv) |
133 | { |
134 | UI *ui = UI(argv[0]); |
135 | ui->stop(ui); |
136 | } |
137 | |
138 | static 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 | } |
145 | static 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 | |
154 | static 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 | } |
163 | static 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 | |
177 | static 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 | } |
189 | static void ui_bridge_suspend_event(void **argv) |
190 | { |
191 | UI *ui = UI(argv[0]); |
192 | ui->suspend(ui); |
193 | } |
194 | |
195 | static 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 | } |
209 | static 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 | |
220 | static void ui_bridge_inspect(UI *ui, Dictionary *info) |
221 | { |
222 | PUT(*info, "chan" , INTEGER_OBJ(0)); |
223 | } |
224 | |