Files
kitty/kitty/state.c
Kovid Goyal 9d48fa9126 hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched
We now buffer the key events until the kitten tells us it is ready.
Without this the key presses are delivered to the underlying window
as the kitten's overlay window was not being focused until the kitten
set the ready message.

Fixes #7089
2024-12-12 13:11:12 +05:30

1555 lines
55 KiB
C

/*
* state.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "cleanup.h"
#include "options/to-c-generated.h"
#include <math.h>
#include <sys/mman.h>
GlobalState global_state = {{0}};
#define REMOVER(array, qid, count, destroy, capacity) { \
for (size_t i = 0; i < count; i++) { \
if (array[i].id == qid) { \
destroy(array + i); \
zero_at_i(array, i); \
remove_i_from_array(array, i, count); \
break; \
} \
}}
#define WITH_OS_WINDOW(os_window_id) \
for (size_t o = 0; o < global_state.num_os_windows; o++) { \
OSWindow *os_window = global_state.os_windows + o; \
if (os_window->id == os_window_id) {
#define END_WITH_OS_WINDOW break; }}
#define WITH_TAB(os_window_id, tab_id) \
for (size_t o = 0, tab_found = 0; o < global_state.num_os_windows && !tab_found; o++) { \
OSWindow *osw = global_state.os_windows + o; \
if (osw->id == os_window_id) { \
for (size_t t = 0; t < osw->num_tabs; t++) { \
if (osw->tabs[t].id == tab_id) { \
Tab *tab = osw->tabs + t;
#define END_WITH_TAB tab_found = 1; break; }}}}
#define WITH_WINDOW(os_window_id, tab_id, window_id) \
for (size_t o = 0, window_found = 0; o < global_state.num_os_windows && !window_found; o++) { \
OSWindow *osw = global_state.os_windows + o; \
if (osw->id == os_window_id) { \
for (size_t t = 0; t < osw->num_tabs && !window_found; t++) { \
if (osw->tabs[t].id == tab_id) { \
Tab *tab = osw->tabs + t; \
for (size_t w = 0; w < tab->num_windows; w++) { \
if (tab->windows[w].id == window_id) { \
Window *window = tab->windows + w;
#define END_WITH_WINDOW window_found = 1; break; }}}}}}
#define WITH_OS_WINDOW_REFS \
id_type cb_window_id = 0, focused_window_id = 0; \
if (global_state.callback_os_window) cb_window_id = global_state.callback_os_window->id; \
#define END_WITH_OS_WINDOW_REFS \
if (cb_window_id || focused_window_id) { \
global_state.callback_os_window = NULL; \
for (size_t wn = 0; wn < global_state.num_os_windows; wn++) { \
OSWindow *wp = global_state.os_windows + wn; \
if (wp->id == cb_window_id && cb_window_id) global_state.callback_os_window = wp; \
}}
static double
dpi_for_os_window(OSWindow *os_window) {
double dpi = (os_window->fonts_data->logical_dpi_x + os_window->fonts_data->logical_dpi_y) / 2.;
if (dpi == 0) dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.;
return dpi;
}
static double
dpi_for_os_window_id(id_type os_window_id) {
double dpi = 0;
if (os_window_id) {
WITH_OS_WINDOW(os_window_id)
dpi = dpi_for_os_window(os_window);
END_WITH_OS_WINDOW
}
if (dpi == 0) {
dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.;
}
return dpi;
}
static long
pt_to_px_for_os_window(double pt, OSWindow *w) {
const double dpi = dpi_for_os_window(w);
return ((long)round((pt * (dpi / 72.0))));
}
static long
pt_to_px(double pt, id_type os_window_id) {
const double dpi = dpi_for_os_window_id(os_window_id);
return ((long)round((pt * (dpi / 72.0))));
}
OSWindow*
current_os_window(void) {
if (global_state.callback_os_window) return global_state.callback_os_window;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
if (global_state.os_windows[i].is_focused) return global_state.os_windows + i;
}
return global_state.os_windows;
}
static id_type
last_focused_os_window_id(void) {
id_type ans = 0, max_fc_count = 0;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
if (w->last_focused_counter > max_fc_count) {
ans = w->id; max_fc_count = w->last_focused_counter;
}
}
return ans;
}
static id_type
current_focused_os_window_id(void) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
if (w->is_focused) { return w->id; }
}
return 0;
}
OSWindow*
os_window_for_id(id_type os_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
if (w->id == os_window_id) return w;
}
return NULL;
}
OSWindow*
os_window_for_kitty_window(id_type kitty_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
for (size_t t = 0; t < w->num_tabs; t++) {
Tab *tab = w->tabs + t;
for (size_t c = 0; c < tab->num_windows; c++) {
if (tab->windows[c].id == kitty_window_id) return w;
}
}
}
return NULL;
}
Window*
window_for_window_id(id_type kitty_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
for (size_t t = 0; t < w->num_tabs; t++) {
Tab *tab = w->tabs + t;
for (size_t c = 0; c < tab->num_windows; c++) {
if (tab->windows[c].id == kitty_window_id) return tab->windows + c;
}
}
}
return NULL;
}
static void
free_bgimage_bitmap(BackgroundImage *bgimage) {
if (!bgimage->bitmap) return;
if (bgimage->mmap_size) {
if (munmap(bgimage->bitmap, bgimage->mmap_size) != 0) log_error("Failed to unmap BackgroundImage with error: %s", strerror(errno));
} else free(bgimage->bitmap);
bgimage->bitmap = NULL;
bgimage->mmap_size = 0;
}
static void
send_bgimage_to_gpu(BackgroundImageLayout layout, BackgroundImage *bgimage) {
RepeatStrategy r = REPEAT_DEFAULT;
switch (layout) {
case SCALED:
case CLAMPED:
case CENTER_CLAMPED:
case CENTER_SCALED:
r = REPEAT_CLAMP; break;
case MIRRORED:
r = REPEAT_MIRROR; break;
case TILING:
r = REPEAT_DEFAULT; break;
}
bgimage->texture_id = 0;
size_t delta = bgimage->mmap_size ? bgimage->mmap_size - ((size_t)4) * bgimage->width * bgimage->height : 0;
send_image_to_gpu(&bgimage->texture_id, bgimage->bitmap + delta, bgimage->width,
bgimage->height, false, true, OPT(background_image_linear), r);
free_bgimage_bitmap(bgimage);
}
static void
free_bgimage(BackgroundImage **bgimage, bool release_texture) {
if (*bgimage && (*bgimage)->refcnt) {
(*bgimage)->refcnt--;
if ((*bgimage)->refcnt == 0) {
free_bgimage_bitmap(*bgimage);
if (release_texture) free_texture(&(*bgimage)->texture_id);
free(*bgimage);
}
}
bgimage = NULL;
}
OSWindow*
add_os_window(void) {
WITH_OS_WINDOW_REFS
ensure_space_for(&global_state, os_windows, OSWindow, global_state.num_os_windows + 1, capacity, 1, true);
OSWindow *ans = global_state.os_windows + global_state.num_os_windows++;
zero_at_ptr(ans);
ans->id = ++global_state.os_window_id_counter;
ans->tab_bar_render_data.vao_idx = create_cell_vao();
ans->background_opacity = OPT(background_opacity);
ans->created_at = monotonic();
bool wants_bg = OPT(background_image) && OPT(background_image)[0] != 0;
if (wants_bg) {
if (!global_state.bgimage) {
global_state.bgimage = calloc(1, sizeof(BackgroundImage));
if (!global_state.bgimage) fatal("Out of memory allocating the global bg image object");
global_state.bgimage->refcnt++;
if (image_path_to_bitmap(OPT(background_image), &global_state.bgimage->bitmap, &global_state.bgimage->width, &global_state.bgimage->height, &global_state.bgimage->mmap_size)) {
send_bgimage_to_gpu(OPT(background_image_layout), global_state.bgimage);
}
}
if (global_state.bgimage->texture_id) {
ans->bgimage = global_state.bgimage;
ans->bgimage->refcnt++;
}
}
END_WITH_OS_WINDOW_REFS
return ans;
}
static id_type
add_tab(id_type os_window_id) {
WITH_OS_WINDOW(os_window_id)
make_os_window_context_current(os_window);
ensure_space_for(os_window, tabs, Tab, os_window->num_tabs + 1, capacity, 1, true);
zero_at_i(os_window->tabs, os_window->num_tabs);
os_window->tabs[os_window->num_tabs].id = ++global_state.tab_id_counter;
os_window->tabs[os_window->num_tabs].border_rects.vao_idx = create_border_vao();
return os_window->tabs[os_window->num_tabs++].id;
END_WITH_OS_WINDOW
return 0;
}
static void
create_gpu_resources_for_window(Window *w) {
w->render_data.vao_idx = create_cell_vao();
}
static void
release_gpu_resources_for_window(Window *w) {
if (w->render_data.vao_idx > -1) remove_vao(w->render_data.vao_idx);
w->render_data.vao_idx = -1;
}
static bool
set_window_logo(Window *w, const char *path, const ImageAnchorPosition pos, float alpha, bool is_default, char *png_data, size_t png_data_size) {
bool ok = false;
if (path && path[0]) {
window_logo_id_t wl = find_or_create_window_logo(global_state.all_window_logos, path, png_data, png_data_size);
if (wl) {
if (w->window_logo.id) decref_window_logo(global_state.all_window_logos, w->window_logo.id);
w->window_logo.id = wl;
w->window_logo.position = pos;
w->window_logo.alpha = alpha;
ok = true;
}
} else {
if (w->window_logo.id) {
decref_window_logo(global_state.all_window_logos, w->window_logo.id);
w->window_logo.id = 0;
}
ok = true;
}
w->window_logo.using_default = is_default;
if (ok && w->render_data.screen) w->render_data.screen->is_dirty = true;
return ok;
}
static void
initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
w->id = ++global_state.window_id_counter;
w->visible = true;
w->title = title;
Py_XINCREF(title);
if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0)) {
log_error("Failed to load default window logo: %s", OPT(default_window_logo));
if (PyErr_Occurred()) PyErr_Print();
}
if (init_gpu_resources) create_gpu_resources_for_window(w);
else {
w->render_data.vao_idx = -1;
}
}
static id_type
add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
WITH_TAB(os_window_id, tab_id);
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
make_os_window_context_current(osw);
zero_at_i(tab->windows, tab->num_windows);
initialize_window(tab->windows + tab->num_windows, title, true);
return tab->windows[tab->num_windows++].id;
END_WITH_TAB;
return 0;
}
static void
update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) {
WITH_WINDOW(os_window_id, tab_id, window_id)
Py_CLEAR(window->title);
window->title = title;
Py_XINCREF(window->title);
END_WITH_WINDOW;
}
void
set_os_window_title_from_window(Window *w, OSWindow *os_window) {
if (os_window->disallow_title_changes || os_window->title_is_overriden) return;
if (w->title && w->title != os_window->window_title) {
Py_XDECREF(os_window->window_title);
os_window->window_title = w->title;
Py_INCREF(os_window->window_title);
set_os_window_title(os_window, PyUnicode_AsUTF8(w->title));
}
}
void
update_os_window_title(OSWindow *os_window) {
if (os_window->num_tabs) {
Tab *tab = os_window->tabs + os_window->active_tab;
if (tab->num_windows) {
Window *w = tab->windows + tab->active_window;
set_os_window_title_from_window(w, os_window);
}
}
}
static void
destroy_window(Window *w) {
free(w->pending_clicks.clicks); zero_at_ptr(&w->pending_clicks);
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
Py_CLEAR(w->title_bar_data.last_drawn_title_object_id);
free(w->title_bar_data.buf); w->title_bar_data.buf = NULL;
Py_CLEAR(w->url_target_bar_data.last_drawn_title_object_id);
free(w->url_target_bar_data.buf); w->url_target_bar_data.buf = NULL;
release_gpu_resources_for_window(w);
if (w->window_logo.id) {
decref_window_logo(global_state.all_window_logos, w->window_logo.id);
w->window_logo.id = 0;
}
}
static void
remove_window_inner(Tab *tab, id_type id) {
id_type active_window_id = 0;
if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id;
REMOVER(tab->windows, id, tab->num_windows, destroy_window, tab->capacity);
if (active_window_id) {
for (unsigned int w = 0; w < tab->num_windows; w++) {
if (tab->windows[w].id == active_window_id) {
tab->active_window = w; break;
}
}
}
}
static void
remove_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
make_os_window_context_current(osw);
remove_window_inner(tab, id);
END_WITH_TAB;
}
typedef struct {
unsigned int num_windows, capacity;
Window *windows;
} DetachedWindows;
static DetachedWindows detached_windows = {0};
static void
add_detached_window(Window *w) {
ensure_space_for(&detached_windows, windows, Window, detached_windows.num_windows + 1, capacity, 8, true);
memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window));
}
static void
detach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) {
if (tab->windows[i].id == id) {
make_os_window_context_current(osw);
release_gpu_resources_for_window(&tab->windows[i]);
add_detached_window(tab->windows + i);
zero_at_i(tab->windows, i);
remove_i_from_array(tab->windows, i, tab->num_windows);
break;
}
}
END_WITH_TAB;
}
static void
resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
if (screen) {
screen->cell_size.width = os_window->fonts_data->cell_width;
screen->cell_size.height = os_window->fonts_data->cell_height;
screen_dirty_sprite_positions(screen);
if (has_graphics) screen_rescale_images(screen);
}
}
static void
attach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < detached_windows.num_windows; i++) {
if (detached_windows.windows[i].id == id) {
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
Window *w = tab->windows + tab->num_windows++;
memcpy(w, detached_windows.windows + i, sizeof(Window));
zero_at_i(detached_windows.windows, i);
remove_i_from_array(detached_windows.windows, i, detached_windows.num_windows);
make_os_window_context_current(osw);
create_gpu_resources_for_window(w);
if (
w->render_data.screen->cell_size.width != osw->fonts_data->cell_width ||
w->render_data.screen->cell_size.height != osw->fonts_data->cell_height
) resize_screen(osw, w->render_data.screen, true);
else screen_dirty_sprite_positions(w->render_data.screen);
w->render_data.screen->reload_all_gpu_data = true;
break;
}
}
END_WITH_TAB;
}
static void
destroy_tab(Tab *tab) {
for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id);
remove_vao(tab->border_rects.vao_idx);
free(tab->border_rects.rect_buf); tab->border_rects.rect_buf = NULL;
free(tab->windows); tab->windows = NULL;
}
static void
remove_tab_inner(OSWindow *os_window, id_type id) {
id_type active_tab_id = 0;
if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id;
make_os_window_context_current(os_window);
REMOVER(os_window->tabs, id, os_window->num_tabs, destroy_tab, os_window->capacity);
if (active_tab_id) {
for (unsigned int i = 0; i < os_window->num_tabs; i++) {
if (os_window->tabs[i].id == active_tab_id) {
os_window->active_tab = i; break;
}
}
}
}
static void
remove_tab(id_type os_window_id, id_type id) {
WITH_OS_WINDOW(os_window_id)
remove_tab_inner(os_window, id);
END_WITH_OS_WINDOW
}
static void
destroy_os_window_item(OSWindow *w) {
for (size_t t = w->num_tabs; t > 0; t--) {
Tab *tab = w->tabs + t - 1;
remove_tab_inner(w, tab->id);
}
Py_CLEAR(w->window_title); Py_CLEAR(w->tab_bar_render_data.screen);
remove_vao(w->tab_bar_render_data.vao_idx);
free(w->tabs); w->tabs = NULL;
free_bgimage(&w->bgimage, true);
w->bgimage = NULL;
}
bool
remove_os_window(id_type os_window_id) {
bool found = false;
WITH_OS_WINDOW(os_window_id)
found = true;
make_os_window_context_current(os_window);
END_WITH_OS_WINDOW
if (found) {
WITH_OS_WINDOW_REFS
REMOVER(global_state.os_windows, os_window_id, global_state.num_os_windows, destroy_os_window_item, global_state.capacity);
END_WITH_OS_WINDOW_REFS
update_os_window_references();
}
return found;
}
static void
mark_os_window_dirty(id_type os_window_id) {
WITH_OS_WINDOW(os_window_id)
os_window->needs_render = true;
END_WITH_OS_WINDOW
}
static void
set_active_tab(id_type os_window_id, unsigned int idx) {
WITH_OS_WINDOW(os_window_id)
os_window->active_tab = idx;
os_window->needs_render = true;
END_WITH_OS_WINDOW
}
static void
set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
WITH_WINDOW(os_window_id, tab_id, window_id)
(void)window;
tab->active_window = w;
osw->needs_render = true;
set_os_window_chrome(osw);
END_WITH_WINDOW;
}
static bool
buffer_keys_in_window(id_type os_window_id, id_type tab_id, id_type window_id, bool enable) {
WITH_WINDOW(os_window_id, tab_id, window_id)
window->buffered_keys.enabled = enable;
if (!enable) dispatch_buffered_keys(window);
return true;
END_WITH_WINDOW;
return false;
}
static bool
set_redirect_keys_to_overlay(id_type os_window_id, id_type tab_id, id_type window_id, id_type overlay_id) {
WITH_WINDOW(os_window_id, tab_id, window_id)
window->redirect_keys_to_overlay = overlay_id;
return true;
END_WITH_WINDOW;
return false;
}
static void
swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) {
WITH_OS_WINDOW(os_window_id)
Tab t = os_window->tabs[b];
os_window->tabs[b] = os_window->tabs[a];
os_window->tabs[a] = t;
END_WITH_OS_WINDOW
}
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) {
WITH_TAB(os_window_id, tab_id)
BorderRects *br = &tab->border_rects;
br->is_dirty = true;
if (!left && !top && !right && !bottom) { br->num_border_rects = 0; return; }
ensure_space_for(br, rect_buf, BorderRect, br->num_border_rects + 1, capacity, 32, false);
BorderRect *r = br->rect_buf + br->num_border_rects++;
r->left = gl_pos_x(left, osw->viewport_width);
r->top = gl_pos_y(top, osw->viewport_height);
r->right = r->left + gl_size(right - left, osw->viewport_width);
r->bottom = r->top - gl_size(bottom - top, osw->viewport_height);
r->color = color;
END_WITH_TAB
}
void
os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
if (!OPT(tab_bar_hidden) && os_window->num_tabs >= OPT(tab_bar_min_tabs)) {
long margin_outer = pt_to_px_for_os_window(OPT(tab_bar_margin_height.outer), os_window);
long margin_inner = pt_to_px_for_os_window(OPT(tab_bar_margin_height.inner), os_window);
switch(OPT(tab_bar_edge)) {
case TOP_EDGE:
central->left = 0; central->right = os_window->viewport_width - 1;
central->top = os_window->fonts_data->cell_height + margin_inner + margin_outer;
central->bottom = os_window->viewport_height - 1;
central->top = MIN(central->top, central->bottom);
tab_bar->top = margin_outer;
break;
default:
central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
long bottom = os_window->viewport_height - os_window->fonts_data->cell_height - 1 - margin_inner - margin_outer;
central->bottom = MAX(0, bottom);
tab_bar->top = central->bottom + 1 + margin_inner;
break;
}
tab_bar->left = central->left; tab_bar->right = central->right;
tab_bar->bottom = tab_bar->top + os_window->fonts_data->cell_height - 1;
} else {
zero_at_ptr(tab_bar);
central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
central->bottom = os_window->viewport_height - 1;
}
}
void
mark_os_window_for_close(OSWindow* w, CloseRequest cr) {
global_state.has_pending_closes = true;
w->close_request = cr;
}
static bool
owners_for_window_id(id_type window_id, OSWindow **os_window, Tab **tab) {
if (os_window) *os_window = NULL;
if (tab) *tab = NULL;
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *osw = global_state.os_windows + o;
for (size_t t = 0; t < osw->num_tabs; t++) {
Tab *qtab = osw->tabs + t;
for (size_t w = 0; w < qtab->num_windows; w++) {
Window *window = qtab->windows + w;
if (window->id == window_id) {
if (os_window) *os_window = osw;
if (tab) *tab = qtab;
return true;
}}}}
return false;
}
bool
make_window_context_current(id_type window_id) {
OSWindow *os_window;
if (owners_for_window_id(window_id, &os_window, NULL)) {
make_os_window_context_current(os_window);
return true;
}
return false;
}
void
dispatch_pending_clicks(id_type timer_id UNUSED, void *data UNUSED) {
bool dispatched = false;
do { // dispatching a click can cause windows/tabs/etc to close so do it one at a time.
const monotonic_t now = monotonic();
dispatched = false;
for (size_t o = 0; o < global_state.num_os_windows && !dispatched; o++) {
OSWindow *osw = global_state.os_windows + o;
for (size_t t = 0; t < osw->num_tabs && !dispatched; t++) {
Tab *qtab = osw->tabs + t;
for (size_t w = 0; w < qtab->num_windows && !dispatched; w++) {
Window *window = qtab->windows + w;
for (size_t i = 0; i < window->pending_clicks.num && !dispatched; i++) {
if (now - window->pending_clicks.clicks[i].at >= OPT(click_interval)) {
dispatched = true;
send_pending_click_to_window(window, i);
}
}
}
}
}
} while (dispatched);
}
bool
update_ime_position_for_window(id_type window_id, bool force, int update_focus) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *osw = global_state.os_windows + o;
for (size_t t = 0; t < osw->num_tabs; t++) {
Tab *qtab = osw->tabs + t;
for (size_t w = 0; w < qtab->num_windows; w++) {
Window *window = qtab->windows + w;
if (window->id == window_id) {
// The screen may not be ready after the new window is created and focused, and still needs to enable IME.
if ((window->render_data.screen && (force || osw->is_focused)) || update_focus > 0) {
OSWindow *orig = global_state.callback_os_window;
global_state.callback_os_window = osw;
if (update_focus) update_ime_focus(osw, update_focus > 0);
if (update_focus >= 0 && window->render_data.screen) {
update_ime_position(window, window->render_data.screen);
}
global_state.callback_os_window = orig;
return true;
}
return false;
}
}
}
}
return false;
}
// Python API {{{
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
#define ONE_UINT(name) PYWRAP1(name) { name((unsigned int)PyLong_AsUnsignedLong(args)); Py_RETURN_NONE; }
#define TWO_UINT(name) PYWRAP1(name) { unsigned int a, b; PA("II", &a, &b); name(a, b); Py_RETURN_NONE; }
#define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#define TWO_ID(name) PYWRAP1(name) { id_type a, b; PA("KK", &a, &b); name(a, b); Py_RETURN_NONE; }
#define THREE_ID(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#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; }
#define K(name) PYWRAP1(name) { id_type a; PA("K", &a); name(a); Py_RETURN_NONE; }
#define KI(name) PYWRAP1(name) { id_type a; unsigned int b; PA("KI", &a, &b); name(a, b); Py_RETURN_NONE; }
#define KII(name) PYWRAP1(name) { id_type a; unsigned int b, c; PA("KII", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#define KKI(name) PYWRAP1(name) { id_type a, b; unsigned int c; PA("KKI", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#define KKK(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#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; }
#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; }
#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; }
#define BOOL_SET(name) PYWRAP1(set_##name) { global_state.name = PyObject_IsTrue(args); Py_RETURN_NONE; }
#define dict_iter(d) { \
PyObject *key, *value; Py_ssize_t pos = 0; \
while (PyDict_Next(d, &pos, &key, &value))
PYWRAP1(update_ime_position_for_window) {
id_type window_id;
int force = 0;
int update_focus = 0;
PA("K|pi", &window_id, &force, &update_focus);
if (update_ime_position_for_window(window_id, force, update_focus)) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
PYWRAP0(next_window_id) {
return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1);
}
PYWRAP0(last_focused_os_window_id) {
return PyLong_FromUnsignedLongLong(last_focused_os_window_id());
}
PYWRAP0(current_focused_os_window_id) {
return PyLong_FromUnsignedLongLong(current_focused_os_window_id());
}
PYWRAP1(handle_for_window_id) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
return PyLong_FromVoidPtr(os_window->handle);
END_WITH_OS_WINDOW
PyErr_SetString(PyExc_ValueError, "No such window");
return NULL;
}
PYWRAP0(get_options) {
if (!global_state.options_object) {
PyErr_SetString(PyExc_RuntimeError, "Must call set_options() before using get_options()");
return NULL;
}
Py_INCREF(global_state.options_object);
return global_state.options_object;
}
PYWRAP1(set_options) {
PyObject *opts;
int is_wayland = 0, debug_rendering = 0, debug_font_fallback = 0;
PA("O|ppp", &opts, &is_wayland, &debug_rendering, &debug_font_fallback);
if (opts == Py_None) {
Py_CLEAR(global_state.options_object);
Py_RETURN_NONE;
}
global_state.is_wayland = is_wayland ? true : false;
#ifdef __APPLE__
global_state.has_render_frames = true;
#endif
if (global_state.is_wayland) global_state.has_render_frames = true;
global_state.debug_rendering = debug_rendering ? true : false;
global_state.debug_font_fallback = debug_font_fallback ? true : false;
if (!convert_opts_from_python_opts(opts, &global_state.opts)) return NULL;
global_state.options_object = opts;
Py_INCREF(global_state.options_object);
Py_RETURN_NONE;
}
PYWRAP1(set_ignore_os_keyboard_processing) {
set_ignore_os_keyboard_processing(PyObject_IsTrue(args));
Py_RETURN_NONE;
}
static void
init_window_render_data(OSWindow *osw, const WindowGeometry *g, WindowRenderData *d) {
d->dx = gl_size(osw->fonts_data->cell_width, osw->viewport_width);
d->dy = gl_size(osw->fonts_data->cell_height, osw->viewport_height);
d->xstart = gl_pos_x(g->left, osw->viewport_width);
d->ystart = gl_pos_y(g->top, osw->viewport_height);
}
PYWRAP1(set_tab_bar_render_data) {
WindowRenderData d = {0};
WindowGeometry g = {0};
id_type os_window_id;
PA("KOIIII", &os_window_id, &d.screen, &g.left, &g.top, &g.right, &g.bottom);
WITH_OS_WINDOW(os_window_id)
Py_CLEAR(os_window->tab_bar_render_data.screen);
d.vao_idx = os_window->tab_bar_render_data.vao_idx;
init_window_render_data(os_window, &g, &d);
os_window->tab_bar_render_data = d;
Py_INCREF(os_window->tab_bar_render_data.screen);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
static PyTypeObject RegionType;
static PyStructSequence_Field region_fields[] = {
{"left", ""}, {"top", ""}, {"right", ""}, {"bottom", ""}, {"width", ""}, {"height", ""}, {NULL, NULL}
};
static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6};
static PyObject*
wrap_region(Region *r) {
PyObject *ans = PyStructSequence_New(&RegionType);
if (ans) {
PyStructSequence_SET_ITEM(ans, 0, PyLong_FromUnsignedLong(r->left));
PyStructSequence_SET_ITEM(ans, 1, PyLong_FromUnsignedLong(r->top));
PyStructSequence_SET_ITEM(ans, 2, PyLong_FromUnsignedLong(r->right));
PyStructSequence_SET_ITEM(ans, 3, PyLong_FromUnsignedLong(r->bottom));
PyStructSequence_SET_ITEM(ans, 4, PyLong_FromUnsignedLong(r->right - r->left + 1));
PyStructSequence_SET_ITEM(ans, 5, PyLong_FromUnsignedLong(r->bottom - r->top + 1));
}
return ans;
}
PYWRAP1(viewport_for_window) {
id_type os_window_id;
int vw = 100, vh = 100;
unsigned int cell_width = 1, cell_height = 1;
PA("K", &os_window_id);
Region central = {0}, tab_bar = {0};
WITH_OS_WINDOW(os_window_id)
os_window_regions(os_window, &central, &tab_bar);
vw = os_window->viewport_width; vh = os_window->viewport_height;
cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height;
goto end;
END_WITH_OS_WINDOW
end:
return Py_BuildValue("NNiiII", wrap_region(&central), wrap_region(&tab_bar), vw, vh, cell_width, cell_height);
}
PYWRAP1(cell_size_for_window) {
id_type os_window_id;
unsigned int cell_width = 0, cell_height = 0;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height;
goto end;
END_WITH_OS_WINDOW
end:
return Py_BuildValue("II", cell_width, cell_height);
}
PYWRAP1(os_window_has_background_image) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
if (os_window->bgimage && os_window->bgimage->texture_id > 0) { Py_RETURN_TRUE; }
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(mark_os_window_for_close) {
id_type os_window_id;
CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED;
PA("K|i", &os_window_id, &cr);
WITH_OS_WINDOW(os_window_id)
mark_os_window_for_close(os_window, cr);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(set_application_quit_request) {
CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED;
PA("|i", &cr);
global_state.quit_request = cr;
global_state.has_pending_closes = true;
request_tick_callback();
Py_RETURN_NONE;
}
PYWRAP0(current_application_quit_request) {
return Py_BuildValue("i", global_state.quit_request);
}
PYWRAP1(focus_os_window) {
id_type os_window_id;
int also_raise = 1;
const char *activation_token = NULL;
PA("K|pz", &os_window_id, &also_raise, &activation_token);
WITH_OS_WINDOW(os_window_id)
if (!os_window->is_focused || (activation_token && activation_token[0])) focus_os_window(os_window, also_raise, activation_token);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(run_with_activation_token) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *os_window = global_state.os_windows + o;
if (os_window->is_focused) {
run_with_activation_token_in_os_window(os_window, args);
Py_RETURN_TRUE;
}
}
id_type os_window_id = last_focused_os_window_id();
if (!os_window_id) {
if (!global_state.num_os_windows) Py_RETURN_FALSE;
os_window_id = global_state.os_windows[0].id;
}
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *os_window = global_state.os_windows + o;
if (os_window->id == os_window_id) {
run_with_activation_token_in_os_window(os_window, args);
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
PYWRAP1(set_os_window_chrome) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
set_os_window_chrome(os_window);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(mark_tab_bar_dirty) {
id_type os_window_id = PyLong_AsUnsignedLongLong(args);
WITH_OS_WINDOW(os_window_id)
os_window->tab_bar_data_updated = false;
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(change_background_opacity) {
id_type os_window_id;
float opacity;
PA("Kf", &os_window_id, &opacity);
WITH_OS_WINDOW(os_window_id)
os_window->background_opacity = opacity;
if (!os_window->redraw_count) os_window->redraw_count++;
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(background_opacity_of) {
id_type os_window_id = PyLong_AsUnsignedLongLong(args);
WITH_OS_WINDOW(os_window_id)
return PyFloat_FromDouble((double)os_window->background_opacity);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(set_window_padding) {
id_type os_window_id, tab_id, window_id;
unsigned int left, top, right, bottom;
PA("KKKIIII", &os_window_id, &tab_id, &window_id, &left, &top, &right, &bottom);
WITH_WINDOW(os_window_id, tab_id, window_id);
window->padding.left = left; window->padding.top = top; window->padding.right = right; window->padding.bottom = bottom;
END_WITH_WINDOW;
Py_RETURN_NONE;
}
PYWRAP1(set_window_render_data) {
#define A(name) &(d.name)
#define B(name) &(g.name)
id_type os_window_id, tab_id, window_id;
WindowRenderData d = {0};
WindowGeometry g = {0};
PA("KKKOIIII", &os_window_id, &tab_id, &window_id, A(screen), B(left), B(top), B(right), B(bottom));
WITH_WINDOW(os_window_id, tab_id, window_id);
Py_CLEAR(window->render_data.screen);
d.vao_idx = window->render_data.vao_idx;
init_window_render_data(osw, &g, &d);
window->render_data = d;
window->geometry = g;
Py_INCREF(window->render_data.screen);
END_WITH_WINDOW;
Py_RETURN_NONE;
#undef A
#undef B
}
PYWRAP1(update_window_visibility) {
id_type os_window_id, tab_id, window_id;
int visible;
PA("KKKp", &os_window_id, &tab_id, &window_id, &visible);
WITH_WINDOW(os_window_id, tab_id, window_id);
bool was_visible = window->visible & 1;
window->visible = visible & 1;
if (!was_visible && window->visible) global_state.check_for_active_animated_images = true;
END_WITH_WINDOW;
Py_RETURN_NONE;
}
PYWRAP1(sync_os_window_title) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
update_os_window_title(os_window);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(set_os_window_title) {
id_type os_window_id;
PyObject *title;
PA("KU", &os_window_id, &title);
WITH_OS_WINDOW(os_window_id)
if (os_window->disallow_title_changes) break;
if (PyUnicode_GetLength(title)) {
os_window->title_is_overriden = true;
Py_XDECREF(os_window->window_title);
os_window->window_title = title;
Py_INCREF(title);
set_os_window_title(os_window, PyUnicode_AsUTF8(title));
} else {
os_window->title_is_overriden = false;
if (os_window->window_title) set_os_window_title(os_window, PyUnicode_AsUTF8(os_window->window_title));
update_os_window_title(os_window);
}
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(get_os_window_title) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
if (os_window->window_title) return Py_BuildValue("O", os_window->window_title);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(pt_to_px) {
double pt;
id_type os_window_id = 0;
PA("d|K", &pt, &os_window_id);
return PyLong_FromLong(pt_to_px(pt, os_window_id));
}
PYWRAP1(global_font_size) {
double set_val = -1;
PA("|d", &set_val);
if (set_val > 0) OPT(font_size) = set_val;
return Py_BuildValue("d", OPT(font_size));
}
PYWRAP1(os_window_font_size) {
id_type os_window_id;
int force = 0;
double new_sz = -1;
PA("K|dp", &os_window_id, &new_sz, &force);
WITH_OS_WINDOW(os_window_id)
if (new_sz > 0 && (force || new_sz != os_window->fonts_data->font_sz_in_pts)) {
double xdpi, ydpi; float xscale, yscale;
get_os_window_content_scale(os_window, &xdpi, &ydpi, &xscale, &yscale);
os_window->fonts_data = load_fonts_data(new_sz, xdpi, ydpi);
send_prerendered_sprites_for_window(os_window);
resize_screen(os_window, os_window->tab_bar_render_data.screen, false);
for (size_t ti = 0; ti < os_window->num_tabs; ti++) {
Tab *tab = os_window->tabs + ti;
for (size_t wi = 0; wi < tab->num_windows; wi++) {
Window *w = tab->windows + wi;
resize_screen(os_window, w->render_data.screen, true);
}
}
os_window_update_size_increments(os_window);
// On Wayland with CSD title needs to be re-rendered in a different font size
if (os_window->window_title && global_state.is_wayland) set_os_window_title(os_window, NULL);
}
return Py_BuildValue("d", os_window->fonts_data->font_sz_in_pts);
END_WITH_OS_WINDOW
return Py_BuildValue("d", 0.0);
}
PYWRAP1(set_os_window_size) {
id_type os_window_id;
int width, height;
PA("Kii", &os_window_id, &width, &height);
WITH_OS_WINDOW(os_window_id)
set_os_window_size(os_window, width, height);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(get_os_window_size) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
double xdpi, ydpi;
float xscale, yscale;
int width, height, fw, fh;
get_os_window_size(os_window, &width, &height, &fw, &fh);
get_os_window_content_scale(os_window, &xdpi, &ydpi, &xscale, &yscale);
unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height;
return Py_BuildValue("{si si si si sf sf sd sd sI sI}",
"width", width, "height", height, "framebuffer_width", fw, "framebuffer_height", fh,
"xscale", xscale, "yscale", yscale, "xdpi", xdpi, "ydpi", ydpi,
"cell_width", cell_width, "cell_height", cell_height);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(get_os_window_pos) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
int x, y;
get_os_window_pos(os_window, &x, &y);
return Py_BuildValue("ii", x, y);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(set_os_window_pos) {
id_type os_window_id;
int x, y;
PA("Kii", &os_window_id, &x, &y);
WITH_OS_WINDOW(os_window_id)
set_os_window_pos(os_window, x, y);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
PYWRAP1(set_boss) {
Py_CLEAR(global_state.boss);
global_state.boss = args;
Py_INCREF(global_state.boss);
Py_RETURN_NONE;
}
PYWRAP0(get_boss) {
if (global_state.boss) {
Py_INCREF(global_state.boss);
return global_state.boss;
}
Py_RETURN_NONE;
}
PYWRAP0(apply_options_update) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *os_window = global_state.os_windows + o;
get_platform_dependent_config_values(os_window->handle);
os_window->background_opacity = OPT(background_opacity);
if (!os_window->redraw_count) os_window->redraw_count++;
for (size_t t = 0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) {
Window *window = tab->windows + w;
if (window->window_logo.using_default) {
set_window_logo(window, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0);
}
}
}
}
Py_RETURN_NONE;
}
PYWRAP1(patch_global_colors) {
PyObject *spec;
int configured;
if (!PyArg_ParseTuple(args, "Op", &spec, &configured)) return NULL;
#define P(name) { \
PyObject *val = PyDict_GetItemString(spec, #name); \
if (val) { \
if (val == Py_None) OPT(name) = 0; \
else if (PyLong_Check(val)) OPT(name) = PyLong_AsLong(val); \
} \
}
P(active_border_color); P(inactive_border_color); P(bell_border_color); P(tab_bar_background); P(tab_bar_margin_color);
if (configured) {
P(background); P(url_color);
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
}
PYWRAP1(update_tab_bar_edge_colors) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
if (os_window->tab_bar_render_data.screen) {
if (get_line_edge_colors(os_window->tab_bar_render_data.screen, &os_window->tab_bar_edge_color.left, &os_window->tab_bar_edge_color.right)) { Py_RETURN_TRUE; }
}
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
static PyObject*
pyset_background_image(PyObject *self UNUSED, PyObject *args) {
const char *path;
PyObject *layout_name = NULL;
PyObject *os_window_ids;
int configured = 0;
char *png_data = NULL; Py_ssize_t png_data_size = 0;
PA("zO!|pOy#", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size);
size_t size;
BackgroundImageLayout layout = PyUnicode_Check(layout_name) ? bglayout(layout_name) : OPT(background_image_layout);
BackgroundImage *bgimage = NULL;
if (path) {
bgimage = calloc(1, sizeof(BackgroundImage));
if (!bgimage) return PyErr_NoMemory();
bool ok;
if (png_data && png_data_size) {
ok = png_from_data(png_data, png_data_size, path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size);
} else {
ok = image_path_to_bitmap(path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &bgimage->mmap_size);
}
if (!ok) {
PyErr_Format(PyExc_ValueError, "Failed to load image from: %s", path);
free(bgimage);
return NULL;
}
send_bgimage_to_gpu(layout, bgimage);
bgimage->refcnt++;
}
if (configured) {
free_bgimage(&global_state.bgimage, true);
global_state.bgimage = bgimage;
if (bgimage) bgimage->refcnt++;
OPT(background_image_layout) = layout;
}
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(os_window_ids); i++) {
id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(os_window_ids, i));
WITH_OS_WINDOW(os_window_id)
make_os_window_context_current(os_window);
free_bgimage(&os_window->bgimage, true);
os_window->bgimage = bgimage;
os_window->render_calls = 0;
if (bgimage) bgimage->refcnt++;
END_WITH_OS_WINDOW
}
if (bgimage) free_bgimage(&bgimage, true);
Py_RETURN_NONE;
}
PYWRAP0(destroy_global_data) {
Py_CLEAR(global_state.boss);
free(global_state.os_windows); global_state.os_windows = NULL;
Py_RETURN_NONE;
}
PYWRAP0(wakeup_main_loop) {
wakeup_main_loop();
Py_RETURN_NONE;
}
static void
destroy_mock_window(PyObject *capsule) {
Window *w = PyCapsule_GetPointer(capsule, "Window");
if (w) {
destroy_window(w);
PyMem_Free(w);
}
}
static PyObject*
pycreate_mock_window(PyObject *self UNUSED, PyObject *args) {
Screen *screen;
PyObject *title = NULL;
if (!PyArg_ParseTuple(args, "O|U", &screen, &title)) return NULL;
Window *w = PyMem_Calloc(sizeof(Window), 1);
if (!w) return NULL;
Py_INCREF(screen);
PyObject *ans = PyCapsule_New(w, "Window", destroy_mock_window);
if (ans != NULL) {
initialize_window(w, title, false);
w->render_data.screen = screen;
}
return ans;
}
static bool
click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) {
bool clicked = false;
WITH_WINDOW(os_window_id, tab_id, window_id);
clicked = mouse_open_url(window);
END_WITH_WINDOW;
return clicked;
}
static bool
click_mouse_cmd_output(id_type os_window_id, id_type tab_id, id_type window_id, int select_cmd_output) {
bool handled = false;
WITH_WINDOW(os_window_id, tab_id, window_id);
handled = mouse_set_last_visited_cmd_output(window);
if (select_cmd_output && handled) handled = mouse_select_cmd_output(window);
END_WITH_WINDOW;
return handled;
}
static bool
move_cursor_to_mouse_if_in_prompt(id_type os_window_id, id_type tab_id, id_type window_id) {
bool moved = false;
WITH_WINDOW(os_window_id, tab_id, window_id);
moved = move_cursor_to_mouse_if_at_shell_prompt(window);
END_WITH_WINDOW;
return moved;
}
static PyObject*
pyupdate_pointer_shape(PyObject *self UNUSED, PyObject *args) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id);
OSWindow *orig = global_state.callback_os_window;
global_state.callback_os_window = os_window;
update_mouse_pointer_shape();
global_state.callback_os_window = orig;
END_WITH_OS_WINDOW;
Py_RETURN_NONE;
}
static PyObject*
pymouse_selection(PyObject *self UNUSED, PyObject *args) {
id_type os_window_id, tab_id, window_id;
int code, button;
PA("KKKii", &os_window_id, &tab_id, &window_id, &code, &button);
WITH_WINDOW(os_window_id, tab_id, window_id);
mouse_selection(window, code, button);
END_WITH_WINDOW;
Py_RETURN_NONE;
}
PYWRAP1(set_window_logo) {
id_type os_window_id, tab_id, window_id;
const char *path; PyObject *position;
float alpha = 0.5;
char *png_data = NULL; Py_ssize_t png_data_size = 0;
PA("KKKsUf|y#", &os_window_id, &tab_id, &window_id, &path, &position, &alpha, &png_data, &png_data_size);
bool ok = false;
WITH_WINDOW(os_window_id, tab_id, window_id);
ok = set_window_logo(window, path, PyObject_IsTrue(position) ? bganchor(position) : OPT(window_logo_position), (0 <= alpha && alpha <= 1) ? alpha : OPT(window_logo_alpha), false, png_data, png_data_size);
END_WITH_WINDOW;
if (ok) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
PYWRAP1(click_mouse_url) {
id_type a, b, c; PA("KKK", &a, &b, &c);
if (click_mouse_url(a, b, c)) { Py_RETURN_TRUE; }
Py_RETURN_FALSE;
}
PYWRAP1(click_mouse_cmd_output) {
id_type a, b, c; int d; PA("KKKp", &a, &b, &c, &d);
if (click_mouse_cmd_output(a, b, c, d)) { Py_RETURN_TRUE; }
Py_RETURN_FALSE;
}
PYWRAP1(move_cursor_to_mouse_if_in_prompt) {
id_type a, b, c; PA("KKK", &a, &b, &c);
if (move_cursor_to_mouse_if_in_prompt(a, b, c)) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
PYWRAP1(redirect_mouse_handling) {
global_state.redirect_mouse_handling = PyObject_IsTrue(args) ? true : false;
Py_RETURN_NONE;
}
PYWRAP1(buffer_keys_in_window) {
int enabled = 1;
id_type a, b, c; PA("KKK|p", &a, &b, &c, &enabled);
if (buffer_keys_in_window(a, b, c, enabled)) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window)
THREE_ID(detach_window)
THREE_ID(attach_window)
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
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) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); }
TWO_ID(remove_tab)
KI(set_active_tab)
K(mark_os_window_dirty)
KKK(set_active_window)
KII(swap_tabs)
KK5I(add_borders_rect)
KKKK(set_redirect_keys_to_overlay)
static PyObject*
os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) {
RAII_PyObject(ans, PyDict_New());
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
RAII_PyObject(key, PyLong_FromUnsignedLongLong(w->id));
RAII_PyObject(val, PyLong_FromUnsignedLongLong(w->last_focused_counter));
if (!key || !val) return NULL;
if (PyDict_SetItem(ans, key, val) != 0) return NULL;
}
Py_INCREF(ans);
return ans;
}
static PyObject*
get_mouse_data_for_window(PyObject *self UNUSED, PyObject *args) {
id_type os_window_id, tab_id, window_id;
PA("KKK", &os_window_id, &tab_id, &window_id);
WITH_WINDOW(os_window_id, tab_id, window_id)
return Py_BuildValue("{sI sI sO}", "cell_x", window->mouse_pos.cell_x, "cell_y", window->mouse_pos.cell_y,
"in_left_half_of_cell", window->mouse_pos.in_left_half_of_cell ? Py_True: Py_False);
END_WITH_WINDOW
Py_RETURN_NONE;
}
#define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
#define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
static PyMethodDef module_methods[] = {
M(os_window_focus_counters, METH_NOARGS),
M(get_mouse_data_for_window, METH_VARARGS),
MW(update_pointer_shape, METH_VARARGS),
MW(current_os_window, METH_NOARGS),
MW(next_window_id, METH_NOARGS),
MW(last_focused_os_window_id, METH_NOARGS),
MW(current_focused_os_window_id, METH_NOARGS),
MW(set_options, METH_VARARGS),
MW(get_options, METH_NOARGS),
MW(click_mouse_url, METH_VARARGS),
MW(click_mouse_cmd_output, METH_VARARGS),
MW(move_cursor_to_mouse_if_in_prompt, METH_VARARGS),
MW(redirect_mouse_handling, METH_O),
MW(mouse_selection, METH_VARARGS),
MW(set_window_logo, METH_VARARGS),
MW(set_ignore_os_keyboard_processing, METH_O),
MW(handle_for_window_id, METH_VARARGS),
MW(update_ime_position_for_window, METH_VARARGS),
MW(pt_to_px, METH_VARARGS),
MW(add_tab, METH_O),
MW(add_window, METH_VARARGS),
MW(update_window_title, METH_VARARGS),
MW(remove_tab, METH_VARARGS),
MW(remove_window, METH_VARARGS),
MW(detach_window, METH_VARARGS),
MW(attach_window, METH_VARARGS),
MW(set_active_tab, METH_VARARGS),
MW(mark_os_window_dirty, METH_VARARGS),
MW(set_redirect_keys_to_overlay, METH_VARARGS),
MW(buffer_keys_in_window, METH_VARARGS),
MW(set_active_window, METH_VARARGS),
MW(swap_tabs, METH_VARARGS),
MW(add_borders_rect, METH_VARARGS),
MW(set_tab_bar_render_data, METH_VARARGS),
MW(set_window_render_data, METH_VARARGS),
MW(set_window_padding, METH_VARARGS),
MW(viewport_for_window, METH_VARARGS),
MW(cell_size_for_window, METH_VARARGS),
MW(os_window_has_background_image, METH_VARARGS),
MW(mark_os_window_for_close, METH_VARARGS),
MW(set_application_quit_request, METH_VARARGS),
MW(current_application_quit_request, METH_NOARGS),
MW(set_os_window_chrome, METH_VARARGS),
MW(focus_os_window, METH_VARARGS),
MW(mark_tab_bar_dirty, METH_O),
MW(run_with_activation_token, METH_O),
MW(change_background_opacity, METH_VARARGS),
MW(background_opacity_of, METH_O),
MW(update_window_visibility, METH_VARARGS),
MW(sync_os_window_title, METH_VARARGS),
MW(get_os_window_title, METH_VARARGS),
MW(set_os_window_title, METH_VARARGS),
MW(get_os_window_pos, METH_VARARGS),
MW(set_os_window_pos, METH_VARARGS),
MW(global_font_size, METH_VARARGS),
MW(set_background_image, METH_VARARGS),
MW(os_window_font_size, METH_VARARGS),
MW(set_os_window_size, METH_VARARGS),
MW(get_os_window_size, METH_VARARGS),
MW(update_tab_bar_edge_colors, METH_VARARGS),
MW(set_boss, METH_O),
MW(get_boss, METH_NOARGS),
MW(apply_options_update, METH_NOARGS),
MW(patch_global_colors, METH_VARARGS),
MW(create_mock_window, METH_VARARGS),
MW(destroy_global_data, METH_NOARGS),
MW(wakeup_main_loop, METH_NOARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
static void
finalize(void) {
while(detached_windows.num_windows--) {
destroy_window(&detached_windows.windows[detached_windows.num_windows]);
}
if (detached_windows.windows) free(detached_windows.windows);
detached_windows.capacity = 0;
#define F(x) free(OPT(x)); OPT(x) = NULL;
F(background_image); F(bell_path); F(bell_theme); F(default_window_logo);
#undef F
Py_CLEAR(global_state.options_object);
free_animation(OPT(animation.cursor));
free_animation(OPT(animation.visual_bell));
// we leak the texture here since it is not guaranteed
// that freeing the texture will work during shutdown and
// the GPU driver should take care of it when the OpenGL context is
// destroyed.
free_bgimage(&global_state.bgimage, false);
free_window_logo_table(&global_state.all_window_logos);
global_state.bgimage = NULL;
free_allocs_in_options(&global_state.opts);
}
bool
init_state(PyObject *module) {
OPT(font_size) = 11.0;
#ifdef __APPLE__
#define DPI 72.0
#else
#define DPI 96.0
#endif
global_state.default_dpi.x = DPI; global_state.default_dpi.y = DPI;
global_state.all_window_logos = alloc_window_logo_table();
if (!global_state.all_window_logos) { PyErr_NoMemory(); return false; }
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
if (PyStructSequence_InitType2(&RegionType, &region_desc) != 0) return false;
Py_INCREF((PyObject *) &RegionType);
PyModule_AddObject(module, "Region", (PyObject *) &RegionType);
PyModule_AddIntConstant(module, "IMPERATIVE_CLOSE_REQUESTED", IMPERATIVE_CLOSE_REQUESTED);
PyModule_AddIntConstant(module, "NO_CLOSE_REQUESTED", NO_CLOSE_REQUESTED);
PyModule_AddIntConstant(module, "CLOSE_BEING_CONFIRMED", CLOSE_BEING_CONFIRMED);
PyModule_AddIntMacro(module, WINDOW_NORMAL);
PyModule_AddIntMacro(module, WINDOW_FULLSCREEN);
PyModule_AddIntMacro(module, WINDOW_MAXIMIZED);
PyModule_AddIntMacro(module, WINDOW_MINIMIZED);
register_at_exit_cleanup_func(STATE_CLEANUP_FUNC, finalize);
return true;
}
// }}}