1 /*
2  * state.c
3  * Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #include "cleanup.h"
9 #include "options/to-c-generated.h"
10 #include <math.h>
11 
12 GlobalState global_state = {{0}};
13 
14 #define REMOVER(array, qid, count, destroy, capacity) { \
15     for (size_t i = 0; i < count; i++) { \
16         if (array[i].id == qid) { \
17             destroy(array + i); \
18             zero_at_i(array, i); \
19             remove_i_from_array(array, i, count); \
20             break; \
21         } \
22     }}
23 
24 #define WITH_OS_WINDOW(os_window_id) \
25     for (size_t o = 0; o < global_state.num_os_windows; o++) { \
26         OSWindow *os_window = global_state.os_windows + o; \
27         if (os_window->id == os_window_id) {
28 #define END_WITH_OS_WINDOW break; }}
29 
30 #define WITH_TAB(os_window_id, tab_id) \
31     for (size_t o = 0, tab_found = 0; o < global_state.num_os_windows && !tab_found; o++) { \
32         OSWindow *osw = global_state.os_windows + o; \
33         if (osw->id == os_window_id) { \
34             for (size_t t = 0; t < osw->num_tabs; t++) { \
35                 if (osw->tabs[t].id == tab_id) { \
36                     Tab *tab = osw->tabs + t;
37 #define END_WITH_TAB tab_found = 1; break; }}}}
38 
39 #define WITH_WINDOW(os_window_id, tab_id, window_id) \
40     for (size_t o = 0, window_found = 0; o < global_state.num_os_windows && !window_found; o++) { \
41         OSWindow *osw = global_state.os_windows + o; \
42         if (osw->id == os_window_id) { \
43             for (size_t t = 0; t < osw->num_tabs && !window_found; t++) { \
44                 if (osw->tabs[t].id == tab_id) { \
45                     Tab *tab = osw->tabs + t; \
46                     for (size_t w = 0; w < tab->num_windows; w++) { \
47                         if (tab->windows[w].id == window_id) { \
48                             Window *window = tab->windows + w;
49 #define END_WITH_WINDOW window_found = 1; break; }}}}}}
50 
51 
52 #define WITH_OS_WINDOW_REFS \
53     id_type cb_window_id = 0, focused_window_id = 0; \
54     if (global_state.callback_os_window) cb_window_id = global_state.callback_os_window->id; \
55 
56 #define END_WITH_OS_WINDOW_REFS \
57     if (cb_window_id || focused_window_id) { \
58         global_state.callback_os_window = NULL; \
59         for (size_t wn = 0; wn < global_state.num_os_windows; wn++) { \
60             OSWindow *wp = global_state.os_windows + wn; \
61             if (wp->id == cb_window_id && cb_window_id) global_state.callback_os_window = wp; \
62     }}
63 
64 static double
dpi_for_os_window(OSWindow * os_window)65 dpi_for_os_window(OSWindow *os_window) {
66     double dpi = (os_window->logical_dpi_x + os_window->logical_dpi_y) / 2.;
67     if (dpi == 0) dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.;
68     return dpi;
69 }
70 
71 static double
dpi_for_os_window_id(id_type os_window_id)72 dpi_for_os_window_id(id_type os_window_id) {
73     double dpi = 0;
74     if (os_window_id) {
75         WITH_OS_WINDOW(os_window_id)
76             dpi = dpi_for_os_window(os_window);
77         END_WITH_OS_WINDOW
78     }
79     if (dpi == 0) {
80         dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.;
81     }
82     return dpi;
83 }
84 
85 static long
pt_to_px_for_os_window(double pt,OSWindow * w)86 pt_to_px_for_os_window(double pt, OSWindow *w) {
87     const double dpi = dpi_for_os_window(w);
88     return ((long)round((pt * (dpi / 72.0))));
89 }
90 
91 static long
pt_to_px(double pt,id_type os_window_id)92 pt_to_px(double pt, id_type os_window_id) {
93     const double dpi = dpi_for_os_window_id(os_window_id);
94     return ((long)round((pt * (dpi / 72.0))));
95 }
96 
97 
98 OSWindow*
current_os_window()99 current_os_window() {
100     if (global_state.callback_os_window) return global_state.callback_os_window;
101     for (size_t i = 0; i < global_state.num_os_windows; i++) {
102         if (global_state.os_windows[i].is_focused) return global_state.os_windows + i;
103     }
104     return global_state.os_windows;
105 }
106 
107 OSWindow*
os_window_for_kitty_window(id_type kitty_window_id)108 os_window_for_kitty_window(id_type kitty_window_id) {
109     for (size_t i = 0; i < global_state.num_os_windows; i++) {
110         OSWindow *w = global_state.os_windows + i;
111         for (size_t t = 0; t < w->num_tabs; t++) {
112             Tab *tab = w->tabs + t;
113             for (size_t c = 0; c < tab->num_windows; c++) {
114                 if (tab->windows[c].id == kitty_window_id) return w;
115             }
116         }
117     }
118     return NULL;
119 }
120 
121 Window*
window_for_window_id(id_type kitty_window_id)122 window_for_window_id(id_type kitty_window_id) {
123     for (size_t i = 0; i < global_state.num_os_windows; i++) {
124         OSWindow *w = global_state.os_windows + i;
125         for (size_t t = 0; t < w->num_tabs; t++) {
126             Tab *tab = w->tabs + t;
127             for (size_t c = 0; c < tab->num_windows; c++) {
128                 if (tab->windows[c].id == kitty_window_id) return tab->windows + c;
129             }
130         }
131     }
132     return NULL;
133 }
134 
135 static void
send_bgimage_to_gpu(BackgroundImageLayout layout,BackgroundImage * bgimage)136 send_bgimage_to_gpu(BackgroundImageLayout layout, BackgroundImage *bgimage) {
137     RepeatStrategy r;
138     switch (layout) {
139         case SCALED:
140             r = REPEAT_CLAMP; break;
141         case MIRRORED:
142             r = REPEAT_MIRROR; break;
143         case TILING:
144         default:
145             r = REPEAT_DEFAULT; break;
146     }
147     bgimage->texture_id = 0;
148     send_image_to_gpu(&bgimage->texture_id, bgimage->bitmap, bgimage->width,
149             bgimage->height, false, true, OPT(background_image_linear), r);
150     free(bgimage->bitmap); bgimage->bitmap = NULL;
151 }
152 
153 static void
free_bgimage(BackgroundImage ** bgimage,bool release_texture)154 free_bgimage(BackgroundImage **bgimage, bool release_texture) {
155     if (*bgimage && (*bgimage)->refcnt) {
156         (*bgimage)->refcnt--;
157         if ((*bgimage)->refcnt == 0) {
158             free((*bgimage)->bitmap); (*bgimage)->bitmap = NULL;
159             if (release_texture) free_texture(&(*bgimage)->texture_id);
160             free(*bgimage);
161         }
162     }
163     bgimage = NULL;
164 }
165 
166 OSWindow*
add_os_window()167 add_os_window() {
168     WITH_OS_WINDOW_REFS
169     ensure_space_for(&global_state, os_windows, OSWindow, global_state.num_os_windows + 1, capacity, 1, true);
170     OSWindow *ans = global_state.os_windows + global_state.num_os_windows++;
171     zero_at_ptr(ans);
172     ans->id = ++global_state.os_window_id_counter;
173     ans->tab_bar_render_data.vao_idx = create_cell_vao();
174     ans->gvao_idx = create_graphics_vao();
175     ans->background_opacity = OPT(background_opacity);
176 
177     bool wants_bg = OPT(background_image) && OPT(background_image)[0] != 0;
178     if (wants_bg) {
179         if (!global_state.bgimage) {
180             global_state.bgimage = calloc(1, sizeof(BackgroundImage));
181             if (!global_state.bgimage) fatal("Out of memory allocating the global bg image object");
182             global_state.bgimage->refcnt++;
183             size_t size;
184             if (png_path_to_bitmap(OPT(background_image), &global_state.bgimage->bitmap, &global_state.bgimage->width, &global_state.bgimage->height, &size)) {
185                 send_bgimage_to_gpu(OPT(background_image_layout), global_state.bgimage);
186             }
187         }
188         if (global_state.bgimage->texture_id) {
189             ans->bgimage = global_state.bgimage;
190             ans->bgimage->refcnt++;
191         }
192     }
193 
194     ans->font_sz_in_pts = OPT(font_size);
195     END_WITH_OS_WINDOW_REFS
196     return ans;
197 }
198 
199 static id_type
add_tab(id_type os_window_id)200 add_tab(id_type os_window_id) {
201     WITH_OS_WINDOW(os_window_id)
202         make_os_window_context_current(os_window);
203         ensure_space_for(os_window, tabs, Tab, os_window->num_tabs + 1, capacity, 1, true);
204         zero_at_i(os_window->tabs, os_window->num_tabs);
205         os_window->tabs[os_window->num_tabs].id = ++global_state.tab_id_counter;
206         os_window->tabs[os_window->num_tabs].border_rects.vao_idx = create_border_vao();
207         return os_window->tabs[os_window->num_tabs++].id;
208     END_WITH_OS_WINDOW
209     return 0;
210 }
211 
212 static void
create_gpu_resources_for_window(Window * w)213 create_gpu_resources_for_window(Window *w) {
214     w->render_data.vao_idx = create_cell_vao();
215     w->render_data.gvao_idx = create_graphics_vao();
216 }
217 
218 static void
release_gpu_resources_for_window(Window * w)219 release_gpu_resources_for_window(Window *w) {
220     if (w->render_data.vao_idx > -1) remove_vao(w->render_data.vao_idx);
221     w->render_data.vao_idx = -1;
222     if (w->render_data.gvao_idx > -1) remove_vao(w->render_data.gvao_idx);
223     w->render_data.gvao_idx = -1;
224 }
225 
226 static void
initialize_window(Window * w,PyObject * title,bool init_gpu_resources)227 initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
228     w->id = ++global_state.window_id_counter;
229     w->visible = true;
230     w->title = title;
231     Py_XINCREF(title);
232     if (init_gpu_resources) create_gpu_resources_for_window(w);
233     else {
234         w->render_data.vao_idx = -1;
235         w->render_data.gvao_idx = -1;
236     }
237 }
238 
239 static id_type
add_window(id_type os_window_id,id_type tab_id,PyObject * title)240 add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
241     WITH_TAB(os_window_id, tab_id);
242         ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
243         make_os_window_context_current(osw);
244         zero_at_i(tab->windows, tab->num_windows);
245         initialize_window(tab->windows + tab->num_windows, title, true);
246         return tab->windows[tab->num_windows++].id;
247     END_WITH_TAB;
248     return 0;
249 }
250 
251 static void
update_window_title(id_type os_window_id,id_type tab_id,id_type window_id,PyObject * title)252 update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) {
253     WITH_TAB(os_window_id, tab_id);
254     for (size_t i = 0; i < tab->num_windows; i++) {
255         if (tab->windows[i].id == window_id) {
256             Py_CLEAR(tab->windows[i].title);
257             tab->windows[i].title = title;
258             Py_INCREF(tab->windows[i].title);
259             break;
260         }
261     }
262     END_WITH_TAB;
263 }
264 
265 void
set_os_window_title_from_window(Window * w,OSWindow * os_window)266 set_os_window_title_from_window(Window *w, OSWindow *os_window) {
267     if (os_window->disallow_title_changes) return;
268     if (w->title && w->title != os_window->window_title) {
269         Py_XDECREF(os_window->window_title);
270         os_window->window_title = w->title;
271         Py_INCREF(os_window->window_title);
272         set_os_window_title(os_window, PyUnicode_AsUTF8(w->title));
273     }
274 }
275 
276 void
update_os_window_title(OSWindow * os_window)277 update_os_window_title(OSWindow *os_window) {
278     if (os_window->num_tabs) {
279         Tab *tab = os_window->tabs + os_window->active_tab;
280         if (tab->num_windows) {
281             Window *w = tab->windows + tab->active_window;
282             set_os_window_title_from_window(w, os_window);
283         }
284     }
285 }
286 
287 static void
destroy_window(Window * w)288 destroy_window(Window *w) {
289     Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
290     release_gpu_resources_for_window(w);
291 }
292 
293 static void
remove_window_inner(Tab * tab,id_type id)294 remove_window_inner(Tab *tab, id_type id) {
295     id_type active_window_id = 0;
296     if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id;
297     REMOVER(tab->windows, id, tab->num_windows, destroy_window, tab->capacity);
298     if (active_window_id) {
299         for (unsigned int w = 0; w < tab->num_windows; w++) {
300             if (tab->windows[w].id == active_window_id) {
301                 tab->active_window = w; break;
302             }
303         }
304     }
305 }
306 
307 static void
remove_window(id_type os_window_id,id_type tab_id,id_type id)308 remove_window(id_type os_window_id, id_type tab_id, id_type id) {
309     WITH_TAB(os_window_id, tab_id);
310         make_os_window_context_current(osw);
311         remove_window_inner(tab, id);
312     END_WITH_TAB;
313 }
314 
315 typedef struct {
316     unsigned int num_windows, capacity;
317     Window *windows;
318 } DetachedWindows;
319 
320 static DetachedWindows detached_windows = {0};
321 
322 
323 static void
add_detached_window(Window * w)324 add_detached_window(Window *w) {
325     ensure_space_for(&detached_windows, windows, Window, detached_windows.num_windows + 1, capacity, 8, true);
326     memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window));
327 }
328 
329 static void
detach_window(id_type os_window_id,id_type tab_id,id_type id)330 detach_window(id_type os_window_id, id_type tab_id, id_type id) {
331     WITH_TAB(os_window_id, tab_id);
332         for (size_t i = 0; i < tab->num_windows; i++) {
333             if (tab->windows[i].id == id) {
334                 make_os_window_context_current(osw);
335                 release_gpu_resources_for_window(&tab->windows[i]);
336                 add_detached_window(tab->windows + i);
337                 zero_at_i(tab->windows, i);
338                 remove_i_from_array(tab->windows, i, tab->num_windows);
339                 break;
340             }
341         }
342     END_WITH_TAB;
343 }
344 
345 
346 static void
resize_screen(OSWindow * os_window,Screen * screen,bool has_graphics)347 resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
348     if (screen) {
349         screen->cell_size.width = os_window->fonts_data->cell_width;
350         screen->cell_size.height = os_window->fonts_data->cell_height;
351         screen_dirty_sprite_positions(screen);
352         if (has_graphics) screen_rescale_images(screen);
353     }
354 }
355 
356 static void
attach_window(id_type os_window_id,id_type tab_id,id_type id)357 attach_window(id_type os_window_id, id_type tab_id, id_type id) {
358     WITH_TAB(os_window_id, tab_id);
359         for (size_t i = 0; i < detached_windows.num_windows; i++) {
360             if (detached_windows.windows[i].id == id) {
361                 ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
362                 Window *w = tab->windows + tab->num_windows++;
363                 memcpy(w, detached_windows.windows + i, sizeof(Window));
364                 zero_at_i(detached_windows.windows, i);
365                 remove_i_from_array(detached_windows.windows, i, detached_windows.num_windows);
366                 make_os_window_context_current(osw);
367                 create_gpu_resources_for_window(w);
368                 if (
369                     w->render_data.screen->cell_size.width != osw->fonts_data->cell_width ||
370                     w->render_data.screen->cell_size.height != osw->fonts_data->cell_height
371                 ) resize_screen(osw, w->render_data.screen, true);
372                 else screen_dirty_sprite_positions(w->render_data.screen);
373                 w->render_data.screen->reload_all_gpu_data = true;
374                 break;
375             }
376         }
377     END_WITH_TAB;
378 }
379 
380 static void
destroy_tab(Tab * tab)381 destroy_tab(Tab *tab) {
382     for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id);
383     remove_vao(tab->border_rects.vao_idx);
384     free(tab->border_rects.rect_buf); tab->border_rects.rect_buf = NULL;
385     free(tab->windows); tab->windows = NULL;
386 }
387 
388 static void
remove_tab_inner(OSWindow * os_window,id_type id)389 remove_tab_inner(OSWindow *os_window, id_type id) {
390     id_type active_tab_id = 0;
391     if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id;
392     make_os_window_context_current(os_window);
393     REMOVER(os_window->tabs, id, os_window->num_tabs, destroy_tab, os_window->capacity);
394     if (active_tab_id) {
395         for (unsigned int i = 0; i < os_window->num_tabs; i++) {
396             if (os_window->tabs[i].id == active_tab_id) {
397                 os_window->active_tab = i; break;
398             }
399         }
400     }
401 }
402 
403 static void
remove_tab(id_type os_window_id,id_type id)404 remove_tab(id_type os_window_id, id_type id) {
405     WITH_OS_WINDOW(os_window_id)
406         remove_tab_inner(os_window, id);
407     END_WITH_OS_WINDOW
408 }
409 
410 static void
destroy_os_window_item(OSWindow * w)411 destroy_os_window_item(OSWindow *w) {
412     for (size_t t = w->num_tabs; t > 0; t--) {
413         Tab *tab = w->tabs + t - 1;
414         remove_tab_inner(w, tab->id);
415     }
416     Py_CLEAR(w->window_title); Py_CLEAR(w->tab_bar_render_data.screen);
417     if (w->offscreen_texture_id) free_texture(&w->offscreen_texture_id);
418     if (w->offscreen_framebuffer) free_framebuffer(&w->offscreen_framebuffer);
419     remove_vao(w->tab_bar_render_data.vao_idx);
420     remove_vao(w->gvao_idx);
421     free(w->tabs); w->tabs = NULL;
422     free_bgimage(&w->bgimage, true);
423     w->bgimage = NULL;
424 }
425 
426 bool
remove_os_window(id_type os_window_id)427 remove_os_window(id_type os_window_id) {
428     bool found = false;
429     WITH_OS_WINDOW(os_window_id)
430         found = true;
431         make_os_window_context_current(os_window);
432     END_WITH_OS_WINDOW
433     if (found) {
434         WITH_OS_WINDOW_REFS
435             REMOVER(global_state.os_windows, os_window_id, global_state.num_os_windows, destroy_os_window_item, global_state.capacity);
436         END_WITH_OS_WINDOW_REFS
437         update_os_window_references();
438     }
439     return found;
440 }
441 
442 
443 static void
set_active_tab(id_type os_window_id,unsigned int idx)444 set_active_tab(id_type os_window_id, unsigned int idx) {
445     WITH_OS_WINDOW(os_window_id)
446         os_window->active_tab = idx;
447         os_window->needs_render = true;
448     END_WITH_OS_WINDOW
449 }
450 
451 static void
set_active_window(id_type os_window_id,id_type tab_id,id_type window_id)452 set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
453     WITH_WINDOW(os_window_id, tab_id, window_id)
454         (void)window;
455         tab->active_window = w;
456         osw->needs_render = true;
457     END_WITH_WINDOW;
458 }
459 
460 static void
swap_tabs(id_type os_window_id,unsigned int a,unsigned int b)461 swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) {
462     WITH_OS_WINDOW(os_window_id)
463         Tab t = os_window->tabs[b];
464         os_window->tabs[b] = os_window->tabs[a];
465         os_window->tabs[a] = t;
466     END_WITH_OS_WINDOW
467 }
468 
469 static void
add_borders_rect(id_type os_window_id,id_type tab_id,uint32_t left,uint32_t top,uint32_t right,uint32_t bottom,uint32_t color)470 add_borders_rect(id_type os_window_id, id_type tab_id, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom, uint32_t color) {
471     WITH_TAB(os_window_id, tab_id)
472         BorderRects *br = &tab->border_rects;
473         br->is_dirty = true;
474         if (!left && !top && !right && !bottom) { br->num_border_rects = 0; return; }
475         ensure_space_for(br, rect_buf, BorderRect, br->num_border_rects + 1, capacity, 32, false);
476         BorderRect *r = br->rect_buf + br->num_border_rects++;
477         r->left = left; r->right = right; r->top = top; r->bottom = bottom; r->color = color;
478     END_WITH_TAB
479 }
480 
481 
482 void
os_window_regions(OSWindow * os_window,Region * central,Region * tab_bar)483 os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
484     if (!OPT(tab_bar_hidden) && os_window->num_tabs >= OPT(tab_bar_min_tabs)) {
485         long margin_outer = pt_to_px_for_os_window(OPT(tab_bar_margin_height.outer), os_window);
486         long margin_inner = pt_to_px_for_os_window(OPT(tab_bar_margin_height.inner), os_window);
487         switch(OPT(tab_bar_edge)) {
488             case TOP_EDGE:
489                 central->left = 0;  central->right = os_window->viewport_width - 1;
490                 central->top = os_window->fonts_data->cell_height + margin_inner + margin_outer;
491                 central->bottom = os_window->viewport_height - 1;
492                 central->top = MIN(central->top, central->bottom);
493                 tab_bar->top = margin_outer;
494                 break;
495             default:
496                 central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
497                 long bottom = os_window->viewport_height - os_window->fonts_data->cell_height - 1 - margin_inner - margin_outer;
498                 central->bottom = MAX(0, bottom);
499                 tab_bar->top = central->bottom + 1 + margin_inner;
500                 break;
501         }
502         tab_bar->left = central->left; tab_bar->right = central->right;
503         tab_bar->bottom = tab_bar->top + os_window->fonts_data->cell_height - 1;
504     } else {
505         zero_at_ptr(tab_bar);
506         central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
507         central->bottom = os_window->viewport_height - 1;
508     }
509 }
510 
511 void
mark_os_window_for_close(OSWindow * w,CloseRequest cr)512 mark_os_window_for_close(OSWindow* w, CloseRequest cr) {
513     global_state.has_pending_closes = true;
514     w->close_request = cr;
515 }
516 
517 static bool
owners_for_window_id(id_type window_id,OSWindow ** os_window,Tab ** tab)518 owners_for_window_id(id_type window_id, OSWindow **os_window, Tab **tab) {
519     if (os_window) *os_window = NULL;
520     if (tab) *tab = NULL;
521     for (size_t o = 0; o < global_state.num_os_windows; o++) {
522         OSWindow *osw = global_state.os_windows + o;
523         for (size_t t = 0; t < osw->num_tabs; t++) {
524             Tab *qtab = osw->tabs + t;
525             for (size_t w = 0; w < qtab->num_windows; w++) {
526                 Window *window = qtab->windows + w;
527                 if (window->id == window_id) {
528                     if (os_window) *os_window = osw;
529                     if (tab) *tab = qtab;
530                     return true;
531     }}}}
532     return false;
533 }
534 
535 
536 bool
make_window_context_current(id_type window_id)537 make_window_context_current(id_type window_id) {
538     OSWindow *os_window;
539     if (owners_for_window_id(window_id, &os_window, NULL)) {
540         make_os_window_context_current(os_window);
541         return true;
542     }
543     return false;
544 }
545 
546 void
send_pending_click_to_window_id(id_type timer_id UNUSED,void * data)547 send_pending_click_to_window_id(id_type timer_id UNUSED, void *data) {
548     id_type window_id = *((id_type*)data);
549     for (size_t o = 0; o < global_state.num_os_windows; o++) {
550         OSWindow *osw = global_state.os_windows + o;
551         for (size_t t = 0; t < osw->num_tabs; t++) {
552             Tab *qtab = osw->tabs + t;
553             for (size_t w = 0; w < qtab->num_windows; w++) {
554                 Window *window = qtab->windows + w;
555                 if (window->id == window_id) {
556                     send_pending_click_to_window(window, data);
557                     return;
558                 }
559             }
560         }
561     }
562 }
563 
564 
565 // Python API {{{
566 #define PYWRAP0(name) static PyObject* py##name(PYNOARG)
567 #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
568 #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
569 #define ONE_UINT(name) PYWRAP1(name) { name((unsigned int)PyLong_AsUnsignedLong(args)); Py_RETURN_NONE; }
570 #define TWO_UINT(name) PYWRAP1(name) { unsigned int a, b; PA("II", &a, &b); name(a, b); Py_RETURN_NONE; }
571 #define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
572 #define TWO_ID(name) PYWRAP1(name) { id_type a, b; PA("KK", &a, &b); name(a, b); Py_RETURN_NONE; }
573 #define THREE_ID(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
574 #define THREE_ID_OBJ(name) PYWRAP1(name) { id_type a, b, c; PyObject *o; PA("KKKO", &a, &b, &c, &o); name(a, b, c, o); Py_RETURN_NONE; }
575 #define KI(name) PYWRAP1(name) { id_type a; unsigned int b; PA("KI", &a, &b); name(a, b); Py_RETURN_NONE; }
576 #define KII(name) PYWRAP1(name) { id_type a; unsigned int b, c; PA("KII", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
577 #define KKI(name) PYWRAP1(name) { id_type a, b; unsigned int c; PA("KKI", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
578 #define KKK(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
579 #define KKII(name) PYWRAP1(name) { id_type a, b; unsigned int c, d; PA("KKII", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; }
580 #define KKKK(name) PYWRAP1(name) { id_type a, b, c, d; PA("KKKK", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; }
581 #define KK5I(name) PYWRAP1(name) { id_type a, b; unsigned int c, d, e, f, g; PA("KKIIIII", &a, &b, &c, &d, &e, &f, &g); name(a, b, c, d, e, f, g); Py_RETURN_NONE; }
582 #define BOOL_SET(name) PYWRAP1(set_##name) { global_state.name = PyObject_IsTrue(args); Py_RETURN_NONE; }
583 #define dict_iter(d) { \
584     PyObject *key, *value; Py_ssize_t pos = 0; \
585     while (PyDict_Next(d, &pos, &key, &value))
586 
PYWRAP0(next_window_id)587 PYWRAP0(next_window_id) {
588     return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1);
589 }
590 
PYWRAP1(handle_for_window_id)591 PYWRAP1(handle_for_window_id) {
592     id_type os_window_id;
593     PA("K", &os_window_id);
594     WITH_OS_WINDOW(os_window_id)
595         return PyLong_FromVoidPtr(os_window->handle);
596     END_WITH_OS_WINDOW
597     PyErr_SetString(PyExc_ValueError, "No such window");
598     return NULL;
599 }
600 
601 static PyObject* options_object = NULL;
602 
PYWRAP0(get_options)603 PYWRAP0(get_options) {
604     if (!options_object) {
605         PyErr_SetString(PyExc_RuntimeError, "Must call set_options() before using get_options()");
606         return NULL;
607     }
608     Py_INCREF(options_object);
609     return options_object;
610 }
611 
PYWRAP1(set_options)612 PYWRAP1(set_options) {
613     PyObject *opts;
614     int is_wayland = 0, debug_rendering = 0, debug_font_fallback = 0;
615     PA("O|ppp", &opts, &is_wayland, &debug_rendering, &debug_font_fallback);
616     if (opts == Py_None) {
617         Py_CLEAR(options_object);
618         Py_RETURN_NONE;
619     }
620     global_state.is_wayland = is_wayland ? true : false;
621 #ifdef __APPLE__
622     global_state.has_render_frames = true;
623 #endif
624     if (global_state.is_wayland) global_state.has_render_frames = true;
625     global_state.debug_rendering = debug_rendering ? true : false;
626     global_state.debug_font_fallback = debug_font_fallback ? true : false;
627     if (!convert_opts_from_python_opts(opts, &global_state.opts)) return NULL;
628     options_object = opts;
629     Py_INCREF(options_object);
630     Py_RETURN_NONE;
631 }
632 
633 BOOL_SET(in_sequence_mode)
634 
PYWRAP1(set_tab_bar_render_data)635 PYWRAP1(set_tab_bar_render_data) {
636     ScreenRenderData d = {0};
637     id_type os_window_id;
638     PA("KffffO", &os_window_id, &d.xstart, &d.ystart, &d.dx, &d.dy, &d.screen);
639     WITH_OS_WINDOW(os_window_id)
640         Py_CLEAR(os_window->tab_bar_render_data.screen);
641         d.vao_idx = os_window->tab_bar_render_data.vao_idx;
642         os_window->tab_bar_render_data = d;
643         Py_INCREF(os_window->tab_bar_render_data.screen);
644     END_WITH_OS_WINDOW
645     Py_RETURN_NONE;
646 }
647 
648 static PyTypeObject RegionType;
649 static PyStructSequence_Field region_fields[] = {
650     {"left", ""}, {"top", ""}, {"right", ""}, {"bottom", ""}, {"width", ""}, {"height", ""}, {NULL, NULL}
651 };
652 static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6};
653 
654 static PyObject*
wrap_region(Region * r)655 wrap_region(Region *r) {
656     PyObject *ans = PyStructSequence_New(&RegionType);
657     if (ans) {
658         PyStructSequence_SET_ITEM(ans, 0, PyLong_FromUnsignedLong(r->left));
659         PyStructSequence_SET_ITEM(ans, 1, PyLong_FromUnsignedLong(r->top));
660         PyStructSequence_SET_ITEM(ans, 2, PyLong_FromUnsignedLong(r->right));
661         PyStructSequence_SET_ITEM(ans, 3, PyLong_FromUnsignedLong(r->bottom));
662         PyStructSequence_SET_ITEM(ans, 4, PyLong_FromUnsignedLong(r->right - r->left + 1));
663         PyStructSequence_SET_ITEM(ans, 5, PyLong_FromUnsignedLong(r->bottom - r->top + 1));
664     }
665     return ans;
666 }
667 
PYWRAP1(viewport_for_window)668 PYWRAP1(viewport_for_window) {
669     id_type os_window_id;
670     int vw = 100, vh = 100;
671     unsigned int cell_width = 1, cell_height = 1;
672     PA("K", &os_window_id);
673     Region central = {0}, tab_bar = {0};
674     WITH_OS_WINDOW(os_window_id)
675         os_window_regions(os_window, &central, &tab_bar);
676         vw = os_window->viewport_width; vh = os_window->viewport_height;
677         cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height;
678         goto end;
679     END_WITH_OS_WINDOW
680 end:
681     return Py_BuildValue("NNiiII", wrap_region(&central), wrap_region(&tab_bar), vw, vh, cell_width, cell_height);
682 }
683 
PYWRAP1(cell_size_for_window)684 PYWRAP1(cell_size_for_window) {
685     id_type os_window_id;
686     unsigned int cell_width = 0, cell_height = 0;
687     PA("K", &os_window_id);
688     WITH_OS_WINDOW(os_window_id)
689         cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height;
690         goto end;
691     END_WITH_OS_WINDOW
692 end:
693     return Py_BuildValue("II", cell_width, cell_height);
694 }
695 
696 
PYWRAP1(os_window_has_background_image)697 PYWRAP1(os_window_has_background_image) {
698     id_type os_window_id;
699     PA("K", &os_window_id);
700     WITH_OS_WINDOW(os_window_id)
701         if (os_window->bgimage && os_window->bgimage->texture_id > 0) { Py_RETURN_TRUE; }
702     END_WITH_OS_WINDOW
703     Py_RETURN_FALSE;
704 }
705 
PYWRAP1(mark_os_window_for_close)706 PYWRAP1(mark_os_window_for_close) {
707     id_type os_window_id;
708     CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED;
709     PA("K|i", &os_window_id, &cr);
710     WITH_OS_WINDOW(os_window_id)
711         mark_os_window_for_close(os_window, cr);
712         Py_RETURN_TRUE;
713     END_WITH_OS_WINDOW
714     Py_RETURN_FALSE;
715 }
716 
PYWRAP1(set_application_quit_request)717 PYWRAP1(set_application_quit_request) {
718     CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED;
719     PA("|i", &cr);
720     global_state.quit_request = cr;
721     global_state.has_pending_closes = true;
722     request_tick_callback();
723     Py_RETURN_NONE;
724 }
725 
PYWRAP0(current_application_quit_request)726 PYWRAP0(current_application_quit_request) {
727     return Py_BuildValue("i", global_state.quit_request);
728 }
729 
PYWRAP1(focus_os_window)730 PYWRAP1(focus_os_window) {
731     id_type os_window_id;
732     int also_raise = 1;
733     PA("K|p", &os_window_id, &also_raise);
734     WITH_OS_WINDOW(os_window_id)
735         if (!os_window->is_focused) focus_os_window(os_window, also_raise);
736         Py_RETURN_TRUE;
737     END_WITH_OS_WINDOW
738     Py_RETURN_FALSE;
739 }
740 
PYWRAP1(set_titlebar_color)741 PYWRAP1(set_titlebar_color) {
742     id_type os_window_id;
743     unsigned int color;
744     int use_system_color = 0;
745     PA("KI|p", &os_window_id, &color, &use_system_color);
746     WITH_OS_WINDOW(os_window_id)
747         set_titlebar_color(os_window, color, use_system_color);
748         Py_RETURN_TRUE;
749     END_WITH_OS_WINDOW
750     Py_RETURN_FALSE;
751 }
752 
PYWRAP1(mark_tab_bar_dirty)753 PYWRAP1(mark_tab_bar_dirty) {
754     id_type os_window_id = PyLong_AsUnsignedLongLong(args);
755     WITH_OS_WINDOW(os_window_id)
756         os_window->tab_bar_data_updated = false;
757     END_WITH_OS_WINDOW
758     Py_RETURN_NONE;
759 }
760 
PYWRAP1(change_background_opacity)761 PYWRAP1(change_background_opacity) {
762     id_type os_window_id;
763     float opacity;
764     PA("Kf", &os_window_id, &opacity);
765     WITH_OS_WINDOW(os_window_id)
766         os_window->background_opacity = opacity;
767         os_window->is_damaged = true;
768         Py_RETURN_TRUE;
769     END_WITH_OS_WINDOW
770     Py_RETURN_FALSE;
771 }
772 
PYWRAP1(background_opacity_of)773 PYWRAP1(background_opacity_of) {
774     id_type os_window_id = PyLong_AsUnsignedLongLong(args);
775     WITH_OS_WINDOW(os_window_id)
776         return PyFloat_FromDouble((double)os_window->background_opacity);
777     END_WITH_OS_WINDOW
778     Py_RETURN_NONE;
779 }
780 
PYWRAP1(set_window_padding)781 PYWRAP1(set_window_padding) {
782     id_type os_window_id, tab_id, window_id;
783     unsigned int left, top, right, bottom;
784     PA("KKKIIII", &os_window_id, &tab_id, &window_id, &left, &top, &right, &bottom);
785     WITH_WINDOW(os_window_id, tab_id, window_id);
786         window->padding.left = left; window->padding.top = top; window->padding.right = right; window->padding.bottom = bottom;
787     END_WITH_WINDOW;
788     Py_RETURN_NONE;
789 }
790 
PYWRAP1(set_window_render_data)791 PYWRAP1(set_window_render_data) {
792 #define A(name) &(d.name)
793 #define B(name) &(g.name)
794     id_type os_window_id, tab_id, window_id;
795     ScreenRenderData d = {0};
796     WindowGeometry g = {0};
797     PA("KKKffffOIIII", &os_window_id, &tab_id, &window_id, A(xstart), A(ystart), A(dx), A(dy), A(screen), B(left), B(top), B(right), B(bottom));
798 
799     WITH_WINDOW(os_window_id, tab_id, window_id);
800         Py_CLEAR(window->render_data.screen);
801         d.vao_idx = window->render_data.vao_idx;
802         d.gvao_idx = window->render_data.gvao_idx;
803         window->render_data = d;
804         window->geometry = g;
805         Py_INCREF(window->render_data.screen);
806     END_WITH_WINDOW;
807     Py_RETURN_NONE;
808 #undef A
809 #undef B
810 }
811 
PYWRAP1(update_window_visibility)812 PYWRAP1(update_window_visibility) {
813     id_type os_window_id, tab_id, window_id;
814     int visible;
815     PA("KKKp", &os_window_id, &tab_id, &window_id, &visible);
816     WITH_WINDOW(os_window_id, tab_id, window_id);
817         bool was_visible = window->visible & 1;
818         window->visible = visible & 1;
819         if (!was_visible && window->visible) global_state.check_for_active_animated_images = true;
820     END_WITH_WINDOW;
821     Py_RETURN_NONE;
822 }
823 
824 
PYWRAP1(sync_os_window_title)825 PYWRAP1(sync_os_window_title) {
826     id_type os_window_id;
827     PA("K", &os_window_id);
828     WITH_OS_WINDOW(os_window_id)
829         update_os_window_title(os_window);
830     END_WITH_OS_WINDOW
831     Py_RETURN_NONE;
832 }
833 
834 
PYWRAP1(pt_to_px)835 PYWRAP1(pt_to_px) {
836     double pt;
837     id_type os_window_id = 0;
838     PA("d|K", &pt, &os_window_id);
839     return PyLong_FromLong(pt_to_px(pt, os_window_id));
840 }
841 
PYWRAP1(global_font_size)842 PYWRAP1(global_font_size) {
843     double set_val = -1;
844     PA("|d", &set_val);
845     if (set_val > 0) OPT(font_size) = set_val;
846     return Py_BuildValue("d", OPT(font_size));
847 }
848 
PYWRAP1(os_window_font_size)849 PYWRAP1(os_window_font_size) {
850     id_type os_window_id;
851     int force = 0;
852     double new_sz = -1;
853     PA("K|dp", &os_window_id, &new_sz, &force);
854     WITH_OS_WINDOW(os_window_id)
855         if (new_sz > 0 && (force || new_sz != os_window->font_sz_in_pts)) {
856             os_window->font_sz_in_pts = new_sz;
857             os_window->fonts_data = NULL;
858             os_window->fonts_data = load_fonts_data(os_window->font_sz_in_pts, os_window->logical_dpi_x, os_window->logical_dpi_y);
859             send_prerendered_sprites_for_window(os_window);
860             resize_screen(os_window, os_window->tab_bar_render_data.screen, false);
861             for (size_t ti = 0; ti < os_window->num_tabs; ti++) {
862                 Tab *tab = os_window->tabs + ti;
863                 for (size_t wi = 0; wi < tab->num_windows; wi++) {
864                     Window *w = tab->windows + wi;
865                     resize_screen(os_window, w->render_data.screen, true);
866                 }
867             }
868             os_window_update_size_increments(os_window);
869         }
870         return Py_BuildValue("d", os_window->font_sz_in_pts);
871     END_WITH_OS_WINDOW
872     return Py_BuildValue("d", 0.0);
873 }
874 
PYWRAP1(set_os_window_size)875 PYWRAP1(set_os_window_size) {
876     id_type os_window_id;
877     int width, height;
878     PA("Kii", &os_window_id, &width, &height);
879     WITH_OS_WINDOW(os_window_id)
880         set_os_window_size(os_window, width, height);
881         Py_RETURN_TRUE;
882     END_WITH_OS_WINDOW
883     Py_RETURN_FALSE;
884 }
885 
PYWRAP1(get_os_window_size)886 PYWRAP1(get_os_window_size) {
887     id_type os_window_id;
888     PA("K", &os_window_id);
889     WITH_OS_WINDOW(os_window_id)
890         double xdpi, ydpi;
891         float xscale, yscale;
892         int width, height, fw, fh;
893         get_os_window_size(os_window, &width, &height, &fw, &fh);
894         get_os_window_content_scale(os_window, &xdpi, &ydpi, &xscale, &yscale);
895         unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height;
896         return Py_BuildValue("{si si si si sf sf sd sd sI sI}",
897             "width", width, "height", height, "framebuffer_width", fw, "framebuffer_height", fh,
898             "xscale", xscale, "yscale", yscale, "xdpi", xdpi, "ydpi", ydpi,
899             "cell_width", cell_width, "cell_height", cell_height);
900     END_WITH_OS_WINDOW
901     Py_RETURN_NONE;
902 }
903 
904 
PYWRAP1(set_boss)905 PYWRAP1(set_boss) {
906     Py_CLEAR(global_state.boss);
907     global_state.boss = args;
908     Py_INCREF(global_state.boss);
909     Py_RETURN_NONE;
910 }
911 
PYWRAP0(get_boss)912 PYWRAP0(get_boss) {
913     if (global_state.boss) {
914         Py_INCREF(global_state.boss);
915         return global_state.boss;
916     }
917     Py_RETURN_NONE;
918 }
919 
PYWRAP0(apply_options_update)920 PYWRAP0(apply_options_update) {
921     for (size_t o = 0; o < global_state.num_os_windows; o++) {
922         OSWindow *os_window = global_state.os_windows + o;
923         get_platform_dependent_config_values(os_window->handle);
924         os_window->background_opacity = OPT(background_opacity);
925         os_window->is_damaged = true;
926         break;
927     }
928     Py_RETURN_NONE;
929 }
930 
PYWRAP1(patch_global_colors)931 PYWRAP1(patch_global_colors) {
932     PyObject *spec;
933     int configured;
934     if (!PyArg_ParseTuple(args, "Op", &spec, &configured)) return NULL;
935 #define P(name) { \
936     PyObject *val = PyDict_GetItemString(spec, #name); \
937     if (val) { \
938         OPT(name) = PyLong_AsLong(val); \
939     } \
940 }
941     P(active_border_color); P(inactive_border_color); P(bell_border_color);
942     if (configured) {
943         P(background); P(url_color);
944         P(mark1_background); P(mark1_foreground); P(mark2_background); P(mark2_foreground);
945         P(mark3_background); P(mark3_foreground);
946     }
947     if (PyErr_Occurred()) return NULL;
948     Py_RETURN_NONE;
949 }
950 
951 static PyObject*
pyset_background_image(PyObject * self UNUSED,PyObject * args)952 pyset_background_image(PyObject *self UNUSED, PyObject *args) {
953     const char *path;
954     PyObject *layout_name = NULL;
955     PyObject *os_window_ids;
956     int configured = 0;
957     PA("zO!|pU", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name);
958     size_t size;
959     BackgroundImageLayout layout = layout_name ? bglayout(layout_name) : OPT(background_image_layout);
960     BackgroundImage *bgimage = NULL;
961     if (path) {
962         bgimage = calloc(1, sizeof(BackgroundImage));
963         if (!bgimage) return PyErr_NoMemory();
964         if (!png_path_to_bitmap(path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size)) {
965             PyErr_Format(PyExc_ValueError, "Failed to load image from: %s", path);
966             free(bgimage);
967             return NULL;
968         }
969         send_bgimage_to_gpu(layout, bgimage);
970         bgimage->refcnt++;
971     }
972     if (configured) {
973         free_bgimage(&global_state.bgimage, true);
974         global_state.bgimage = bgimage;
975         if (bgimage) bgimage->refcnt++;
976         OPT(background_image_layout) = layout;
977     }
978     for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(os_window_ids); i++) {
979         id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(os_window_ids, i));
980         WITH_OS_WINDOW(os_window_id)
981             make_os_window_context_current(os_window);
982             free_bgimage(&os_window->bgimage, true);
983             os_window->bgimage = bgimage;
984             os_window->render_calls = 0;
985             if (bgimage) bgimage->refcnt++;
986         END_WITH_OS_WINDOW
987     }
988     if (bgimage) free_bgimage(&bgimage, true);
989     Py_RETURN_NONE;
990 }
991 
PYWRAP0(destroy_global_data)992 PYWRAP0(destroy_global_data) {
993     Py_CLEAR(global_state.boss);
994     free(global_state.os_windows); global_state.os_windows = NULL;
995     Py_RETURN_NONE;
996 }
997 
998 static void
destroy_mock_window(PyObject * capsule)999 destroy_mock_window(PyObject *capsule) {
1000     Window *w = PyCapsule_GetPointer(capsule, "Window");
1001     if (w) {
1002         destroy_window(w);
1003         PyMem_Free(w);
1004     }
1005 }
1006 
1007 static PyObject*
pycreate_mock_window(PyObject * self UNUSED,PyObject * args)1008 pycreate_mock_window(PyObject *self UNUSED, PyObject *args) {
1009     Screen *screen;
1010     PyObject *title = NULL;
1011     if (!PyArg_ParseTuple(args, "O|U", &screen, &title)) return NULL;
1012     Window *w = PyMem_Calloc(sizeof(Window), 1);
1013     if (!w) return NULL;
1014     Py_INCREF(screen);
1015     PyObject *ans = PyCapsule_New(w, "Window", destroy_mock_window);
1016     if (ans != NULL) {
1017         initialize_window(w, title, false);
1018         w->render_data.screen = screen;
1019     }
1020     return ans;
1021 }
1022 
1023 static void
click_mouse_url(id_type os_window_id,id_type tab_id,id_type window_id)1024 click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) {
1025     WITH_WINDOW(os_window_id, tab_id, window_id);
1026     mouse_open_url(window);
1027     END_WITH_WINDOW;
1028 }
1029 
1030 static PyObject*
pymouse_selection(PyObject * self UNUSED,PyObject * args)1031 pymouse_selection(PyObject *self UNUSED, PyObject *args) {
1032     id_type os_window_id, tab_id, window_id;
1033     int code, button;
1034     PA("KKKii", &os_window_id, &tab_id, &window_id, &code, &button);
1035     WITH_WINDOW(os_window_id, tab_id, window_id);
1036     mouse_selection(window, code, button);
1037     END_WITH_WINDOW;
1038     Py_RETURN_NONE;
1039 }
1040 
1041 THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window)1042 THREE_ID(remove_window)
1043 THREE_ID(click_mouse_url)
1044 THREE_ID(detach_window)
1045 THREE_ID(attach_window)
1046 PYWRAP1(resolve_key_mods) { int mods, kitty_mod; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(kitty_mod, mods)); }
PYWRAP1(add_tab)1047 PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
PYWRAP1(add_window)1048 PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); }
PYWRAP0(current_os_window)1049 PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); }
1050 TWO_ID(remove_tab)
1051 KI(set_active_tab)
1052 KKK(set_active_window)
1053 KII(swap_tabs)
1054 KK5I(add_borders_rect)
1055 
1056 #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
1057 #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
1058 
1059 static PyMethodDef module_methods[] = {
1060     MW(current_os_window, METH_NOARGS),
1061     MW(next_window_id, METH_NOARGS),
1062     MW(set_options, METH_VARARGS),
1063     MW(get_options, METH_NOARGS),
1064     MW(click_mouse_url, METH_VARARGS),
1065     MW(mouse_selection, METH_VARARGS),
1066     MW(set_in_sequence_mode, METH_O),
1067     MW(resolve_key_mods, METH_VARARGS),
1068     MW(handle_for_window_id, METH_VARARGS),
1069     MW(pt_to_px, METH_VARARGS),
1070     MW(add_tab, METH_O),
1071     MW(add_window, METH_VARARGS),
1072     MW(update_window_title, METH_VARARGS),
1073     MW(remove_tab, METH_VARARGS),
1074     MW(remove_window, METH_VARARGS),
1075     MW(detach_window, METH_VARARGS),
1076     MW(attach_window, METH_VARARGS),
1077     MW(set_active_tab, METH_VARARGS),
1078     MW(set_active_window, METH_VARARGS),
1079     MW(swap_tabs, METH_VARARGS),
1080     MW(add_borders_rect, METH_VARARGS),
1081     MW(set_tab_bar_render_data, METH_VARARGS),
1082     MW(set_window_render_data, METH_VARARGS),
1083     MW(set_window_padding, METH_VARARGS),
1084     MW(viewport_for_window, METH_VARARGS),
1085     MW(cell_size_for_window, METH_VARARGS),
1086     MW(os_window_has_background_image, METH_VARARGS),
1087     MW(mark_os_window_for_close, METH_VARARGS),
1088     MW(set_application_quit_request, METH_VARARGS),
1089     MW(current_application_quit_request, METH_NOARGS),
1090     MW(set_titlebar_color, METH_VARARGS),
1091     MW(focus_os_window, METH_VARARGS),
1092     MW(mark_tab_bar_dirty, METH_O),
1093     MW(change_background_opacity, METH_VARARGS),
1094     MW(background_opacity_of, METH_O),
1095     MW(update_window_visibility, METH_VARARGS),
1096     MW(sync_os_window_title, METH_VARARGS),
1097     MW(global_font_size, METH_VARARGS),
1098     MW(set_background_image, METH_VARARGS),
1099     MW(os_window_font_size, METH_VARARGS),
1100     MW(set_os_window_size, METH_VARARGS),
1101     MW(get_os_window_size, METH_VARARGS),
1102     MW(set_boss, METH_O),
1103     MW(get_boss, METH_NOARGS),
1104     MW(apply_options_update, METH_NOARGS),
1105     MW(patch_global_colors, METH_VARARGS),
1106     MW(create_mock_window, METH_VARARGS),
1107     MW(destroy_global_data, METH_NOARGS),
1108 
1109     {NULL, NULL, 0, NULL}        /* Sentinel */
1110 };
1111 
1112 static void
finalize(void)1113 finalize(void) {
1114     while(detached_windows.num_windows--) {
1115         destroy_window(&detached_windows.windows[detached_windows.num_windows]);
1116     }
1117     if (detached_windows.windows) free(detached_windows.windows);
1118     detached_windows.capacity = 0;
1119     if (OPT(background_image)) free(OPT(background_image));
1120     // we leak the texture here since it is not guaranteed
1121     // that freeing the texture will work during shutdown and
1122     // the GPU driver should take care of it when the OpenGL context is
1123     // destroyed.
1124     free_bgimage(&global_state.bgimage, false);
1125     global_state.bgimage = NULL;
1126     free_url_prefixes();
1127     free(OPT(select_by_word_characters)); OPT(select_by_word_characters) = NULL;
1128     free(OPT(url_excluded_characters)); OPT(url_excluded_characters) = NULL;
1129 }
1130 
1131 bool
init_state(PyObject * module)1132 init_state(PyObject *module) {
1133     OPT(font_size) = 11.0;
1134 #ifdef __APPLE__
1135 #define DPI 72.0
1136 #else
1137 #define DPI 96.0
1138 #endif
1139     global_state.default_dpi.x = DPI; global_state.default_dpi.y = DPI;
1140     if (PyModule_AddFunctions(module, module_methods) != 0) return false;
1141     if (PyStructSequence_InitType2(&RegionType, &region_desc) != 0) return false;
1142     Py_INCREF((PyObject *) &RegionType);
1143     PyModule_AddObject(module, "Region", (PyObject *) &RegionType);
1144     PyModule_AddIntConstant(module, "IMPERATIVE_CLOSE_REQUESTED", IMPERATIVE_CLOSE_REQUESTED);
1145     PyModule_AddIntConstant(module, "NO_CLOSE_REQUESTED", NO_CLOSE_REQUESTED);
1146     PyModule_AddIntConstant(module, "CLOSE_BEING_CONFIRMED", CLOSE_BEING_CONFIRMED);
1147     register_at_exit_cleanup_func(STATE_CLEANUP_FUNC, finalize);
1148     return true;
1149 }
1150 // }}}
1151