diff --git a/glfw.py b/glfw.py deleted file mode 100644 index 90999e563..000000000 --- a/glfw.py +++ /dev/null @@ -1,409 +0,0 @@ -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------------------------- -# GLFW - An OpenGL framework -# API version: 3.0.1 -# WWW: http://www.glfw.org/ -# ---------------------------------------------------------------------------- -# Copyright (c) 2002-2006 Marcus Geelnard -# Copyright (c) 2006-2010 Camilla Berglund -# -# Python bindings - Copyright (c) 2013 Nicolas P. Rougier -# -# This software is provided 'as-is', without any express or implied -# warranty. In no event will the authors be held liable for any damages -# arising from the use of this software. -# -# Permission is granted to anyone to use this software for any purpose, -# including commercial applications, and to alter it and redistribute it -# freely, subject to the following restrictions: -# -# 1. The origin of this software must not be misrepresented; you must not -# claim that you wrote the original software. If you use this software -# in a product, an acknowledgment in the product documentation would -# be appreciated but is not required. -# -# 2. Altered source versions must be plainly marked as such, and must not -# be misrepresented as being the original software. -# -# 3. This notice may not be removed or altered from any source -# distribution. -# -# ----------------------------------------------------------------------------- - -# NOTE: -# This source has been modified from its original form by the vispy dev team - -import os -import ctypes.util -from collections import namedtuple -from ctypes import (Structure, POINTER, CFUNCTYPE, byref, c_char_p, c_int, - c_uint, c_double, c_float, c_ushort) - - -_glfw_file = None - -# First if there is an environment variable pointing to the library -if 'GLFW_LIBRARY' in os.environ: - if os.path.exists(os.environ['GLFW_LIBRARY']): - _glfw_file = os.path.realpath(os.environ['GLFW_LIBRARY']) - -# Else, try to find it -if _glfw_file is None: - order = ['glfw', 'glfw3'] - for check in order: - _glfw_file = ctypes.util.find_library(check) - if _glfw_file is not None: - break - -# Else, we failed and exit -if _glfw_file is None: - raise OSError('GLFW library not found') - -# Load it -_glfw = ctypes.CDLL(_glfw_file) - - -# Ensure it's new enough -def glfwGetVersion(): - major, minor, rev = c_int(0), c_int(0), c_int(0) - _glfw.glfwGetVersion(byref(major), byref(minor), byref(rev)) - return major.value, minor.value, rev.value - - -version = glfwGetVersion() - -if version[0] != 3: - version = '.'.join([str(v) for v in version]) - raise OSError('Need GLFW library version 3, found version %s' % version) - - -# --- Version ----------------------------------------------------------------- -GLFW_VERSION_MAJOR = version[0] -GLFW_VERSION_MINOR = version[1] -GLFW_VERSION_REVISION = version[2] -__version__ = GLFW_VERSION_MAJOR, GLFW_VERSION_MINOR, GLFW_VERSION_REVISION - - -# --- Structures -------------------------------------------------------------- -class GLFWvidmode(Structure): - _fields_ = [('width', c_int), - ('height', c_int), - ('redBits', c_int), - ('greenBits', c_int), - ('blueBits', c_int), - ('refreshRate', c_int)] - - -class GLFWgammaramp(Structure): - _fields_ = [('red', POINTER(c_ushort)), - ('green', POINTER(c_ushort)), - ('blue', POINTER(c_ushort)), - ('size', c_int)] - - -class GLFWwindow(Structure): - pass - - -class GLFWmonitor(Structure): - pass - - -# --- Callbacks --------------------------------------------------------------- -errorfun = CFUNCTYPE(None, c_int, c_char_p) -windowposfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int) -windowsizefun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int) -windowclosefun = CFUNCTYPE(None, POINTER(GLFWwindow)) -windowrefreshfun = CFUNCTYPE(None, POINTER(GLFWwindow)) -windowfocusfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int) -windowiconifyfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int) -framebuffersizefun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int) -mousebuttonfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int, c_int) -cursorposfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_double, c_double) -cursorenterfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int) -scrollfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_double, c_double) -keyfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int, c_int, c_int) -charfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_uint) -charmodsfun = CFUNCTYPE(None, POINTER(GLFWwindow), c_uint, c_int) -monitorfun = CFUNCTYPE(None, POINTER(GLFWmonitor), c_int) - -# --- Init -------------------------------------------------------------------- -glfwInit = _glfw.glfwInit -glfwTerminate = _glfw.glfwTerminate -# glfwGetVersion = _glfw.glfwGetVersion - -# --- Error ------------------------------------------------------------------- -# glfwSetErrorCallback = _glfw.glfwSetErrorCallback - -# --- Monitor ----------------------------------------------------------------- -# glfwGetMonitors = _glfw.glfwGetMonitors -# glfwGetMonitors.restype = POINTER(GLFWmonitor) -glfwGetPrimaryMonitor = _glfw.glfwGetPrimaryMonitor -glfwGetPrimaryMonitor.restype = POINTER(GLFWmonitor) -# glfwGetMonitorPos = _glfw.glfwGetMonitorPos -# glfwGetMonitorPhysicalSize = _glfw.glfwGetMonitorPhysicalSize -glfwGetMonitorName = _glfw.glfwGetMonitorName -glfwGetMonitorName.restype = c_char_p -# glfwSetMonitorCallback = _glfw.glfwSetMonitorCallback -# glfwGetVideoModes = _glfw.glfwGetVideoModes -# glfwGetVideoMode = _glfw.glfwGetVideoMode - -# --- Gama -------------------------------------------------------------------- -glfwSetGamma = _glfw.glfwSetGamma -# glfwGetGammaRamp = _glfw.glfwGetGammaRamp -# glfwSetGammaRamp = _glfw.glfwSetGammaRamp - -# --- Window ------------------------------------------------------------------ -glfwDefaultWindowHints = _glfw.glfwDefaultWindowHints -glfwWindowHint = _glfw.glfwWindowHint -# glfwCreateWindow = _glfw.glfwCreateWindow -# glfwDestroyWindow = _glfw.glfwDestroyWindow -glfwWindowShouldClose = _glfw.glfwWindowShouldClose -glfwSetWindowShouldClose = _glfw.glfwSetWindowShouldClose -glfwSetWindowTitle = _glfw.glfwSetWindowTitle -# glfwGetWindowPos = _glfw.glfwGetWindowPos -glfwSetWindowPos = _glfw.glfwSetWindowPos -# glfwGetWindowSize = _glfw.glfwGetWindowSize -glfwSetWindowSize = _glfw.glfwSetWindowSize -# glfwGetFramebufferSize = _glfw.glfwGetFramebufferSize -glfwIconifyWindow = _glfw.glfwIconifyWindow -glfwRestoreWindow = _glfw.glfwRestoreWindow -glfwShowWindow = _glfw.glfwShowWindow -glfwHideWindow = _glfw.glfwHideWindow -glfwGetWindowMonitor = _glfw.glfwGetWindowMonitor -glfwGetWindowAttrib = _glfw.glfwGetWindowAttrib -glfwSetWindowUserPointer = _glfw.glfwSetWindowUserPointer -glfwGetWindowUserPointer = _glfw.glfwGetWindowUserPointer -# glfwSetWindowPosCallback = _glfw.glfwSetWindowPosCallback -# glfwSetWindowSizeCallback = _glfw.glfwSetWindowSizeCallback -# glfwSetWindowCloseCallback = _glfw.glfwSetWindowCloseCallback -# glfwSetWindowRefreshCallback = _glfw.glfwSetWindowRefreshCallback -# glfwSetWindowFocusCallback = _glfw.glfwSetWindowFocusCallback -# glfwSetWindowIconifyCallback = _glfw.glfwSetWindowIconifyCallback -# glfwSetFramebufferSizeCallback = _glfw.glfwSetFramebufferSizeCallback -glfwPollEvents = _glfw.glfwPollEvents -glfwWaitEvents = _glfw.glfwWaitEvents -glfwPostEmptyEvent = _glfw.glfwPostEmptyEvent - -# --- Input ------------------------------------------------------------------- -glfwGetInputMode = _glfw.glfwGetInputMode -glfwSetInputMode = _glfw.glfwSetInputMode -glfwGetKey = _glfw.glfwGetKey -glfwGetMouseButton = _glfw.glfwGetMouseButton -# glfwGetCursorPos = _glfw.glfwGetCursorPos -glfwSetCursorPos = _glfw.glfwSetCursorPos -# glfwSetKeyCallback = _glfw.glfwSetKeyCallback -# glfwSetCharCallback = _glfw.glfwSetCharCallback -# glfwSetMouseButtonCallback = _glfw.glfwSetMouseButtonCallback -# glfwSetCursorPosCallback = _glfw.glfwSetCursorPosCallback -# glfwSetCursorEnterCallback = _glfw.glfwSetCursorEnterCallback -# glfwSetScrollCallback = _glfw.glfwSetScrollCallback -glfwJoystickPresent = _glfw.glfwJoystickPresent -# glfwGetJoystickAxes = _glfw.glfwGetJoystickAxes -# glfwGetJoystickButtons = _glfw.glfwGetJoystickButtons -glfwGetJoystickName = _glfw.glfwGetJoystickName -glfwGetJoystickName.restype = c_char_p - -# --- Clipboard --------------------------------------------------------------- -glfwSetClipboardString = _glfw.glfwSetClipboardString -glfwGetClipboardString = _glfw.glfwGetClipboardString -glfwGetClipboardString.restype = c_char_p - -# --- Timer ------------------------------------------------------------------- -glfwGetTime = _glfw.glfwGetTime -glfwGetTime.restype = c_double -glfwSetTime = _glfw.glfwSetTime - -# --- Context ----------------------------------------------------------------- -glfwMakeContextCurrent = _glfw.glfwMakeContextCurrent -glfwGetCurrentContext = _glfw.glfwGetCurrentContext -glfwSwapBuffers = _glfw.glfwSwapBuffers -glfwSwapInterval = _glfw.glfwSwapInterval -glfwExtensionSupported = _glfw.glfwExtensionSupported -glfwGetProcAddress = _glfw.glfwGetProcAddress - - -# --- Pythonizer -------------------------------------------------------------- - -# This keeps track of current windows -__windows__ = [] -__destroyed__ = [] - -# This is to prevent garbage collection on callbacks -__c_callbacks__ = {} -__py_callbacks__ = {} -__c_error_callback__ = None - - -def glfwCreateWindow(width=640, height=480, title="GLFW Window", - monitor=None, share=None): - _glfw.glfwCreateWindow.restype = POINTER(GLFWwindow) - window = _glfw.glfwCreateWindow(width, height, title, monitor, share) - __windows__.append(window) - __destroyed__.append(False) - index = __windows__.index(window) - __c_callbacks__[index] = {} - __py_callbacks__[index] = {'errorfun': None, - 'monitorfun': None, - 'windowposfun': None, - 'windowsizefun': None, - 'windowclosefun': None, - 'windowrefreshfun': None, - 'windowfocusfun': None, - 'windowiconifyfun': None, - 'framebuffersizefun': None, - 'keyfun': None, - 'charfun': None, - 'charmodsfun': None, - 'mousebuttonfun': None, - 'cursorposfun': None, - 'cursorenterfun': None, - 'scrollfun': None} - return window - - -def glfwDestroyWindow(window): - index = __windows__.index(window) - if not __destroyed__[index]: - _glfw.glfwDestroyWindow(window) - # We do not delete window from the list (or it would impact numbering) - del __c_callbacks__[index] - del __py_callbacks__[index] - # del __windows__[index] - __destroyed__[index] = True - - -def glfwGetWindowPos(window): - xpos, ypos = c_int(0), c_int(0) - _glfw.glfwGetWindowPos(window, byref(xpos), byref(ypos)) - return xpos.value, ypos.value - - -def glfwGetCursorPos(window): - xpos, ypos = c_double(0), c_double(0) - _glfw.glfwGetCursorPos(window, byref(xpos), byref(ypos)) - return int(xpos.value), int(ypos.value) - - -def glfwGetWindowSize(window): - width, height = c_int(0), c_int(0) - _glfw.glfwGetWindowSize(window, byref(width), byref(height)) - return width.value, height.value - - -def glfwGetFramebufferSize(window): - width, height = c_int(0), c_int(0) - _glfw.glfwGetFramebufferSize(window, byref(width), byref(height)) - return width.value, height.value - - -def glfwGetMonitors(): - count = c_int(0) - _glfw.glfwGetMonitors.restype = POINTER(POINTER(GLFWmonitor)) - c_monitors = _glfw.glfwGetMonitors(byref(count)) - return [c_monitors[i] for i in range(count.value)] - - -def glfwGetVideoModes(monitor): - count = c_int(0) - _glfw.glfwGetVideoModes.restype = POINTER(GLFWvidmode) - c_modes = _glfw.glfwGetVideoModes(monitor, byref(count)) - modes = [] - for i in range(count.value): - modes.append((c_modes[i].width, - c_modes[i].height, - c_modes[i].redBits, - c_modes[i].blueBits, - c_modes[i].greenBits, - c_modes[i].refreshRate)) - return modes - - -def glfwGetMonitorPos(monitor): - xpos, ypos = c_int(0), c_int(0) - _glfw.glfwGetMonitorPos(monitor, byref(xpos), byref(ypos)) - return xpos.value, ypos.value - - -def glfwGetMonitorPhysicalSize(monitor): - width, height = c_int(0), c_int(0) - _glfw.glfwGetMonitorPhysicalSize(monitor, byref(width), byref(height)) - return width.value, height.value - - -VideoMode = namedtuple('VideoMode', 'width height redBits blueBits greenBits refreshRate') - - -def glfwGetVideoMode(monitor): - _glfw.glfwGetVideoMode.restype = POINTER(GLFWvidmode) - c_mode = _glfw.glfwGetVideoMode(monitor).contents - return VideoMode( - c_mode.width, c_mode.height, c_mode.redBits, c_mode.blueBits, c_mode.greenBits, c_mode.refreshRate) - - -def GetGammaRamp(monitor): - _glfw.glfwGetGammaRamp.restype = POINTER(GLFWgammaramp) - c_gamma = _glfw.glfwGetGammaRamp(monitor).contents - gamma = {'red': [], 'green': [], 'blue': []} - if c_gamma: - for i in range(c_gamma.size): - gamma['red'].append(c_gamma.red[i]) - gamma['green'].append(c_gamma.green[i]) - gamma['blue'].append(c_gamma.blue[i]) - return gamma - - -def glfwGetJoystickAxes(joy): - count = c_int(0) - _glfw.glfwGetJoystickAxes.restype = POINTER(c_float) - c_axes = _glfw.glfwGetJoystickAxes(joy, byref(count)) - axes = [c_axes[i].value for i in range(count)] - return axes - - -def glfwGetJoystickButtons(joy): - count = c_int(0) - _glfw.glfwGetJoystickButtons.restype = POINTER(c_int) - c_buttons = _glfw.glfwGetJoystickButtons(joy, byref(count)) - buttons = [c_buttons[i].value for i in range(count)] - return buttons - - -# --- Callbacks --------------------------------------------------------------- - -def __callback__(name): - callback = 'glfwSet%sCallback' % name - fun = '%sfun' % name.lower() - code = """ -def %(callback)s(window, callback = None): - index = __windows__.index(window) - old_callback = __py_callbacks__[index]['%(fun)s'] - __py_callbacks__[index]['%(fun)s'] = callback - if callback: callback = %(fun)s(callback) - __c_callbacks__[index]['%(fun)s'] = callback - _glfw.%(callback)s(window, callback) - return old_callback""" % {'callback': callback, 'fun': fun} - return code - -exec(__callback__('Monitor')) -exec(__callback__('WindowPos')) -exec(__callback__('WindowSize')) -exec(__callback__('WindowClose')) -exec(__callback__('WindowRefresh')) -exec(__callback__('WindowFocus')) -exec(__callback__('WindowIconify')) -exec(__callback__('FramebufferSize')) -exec(__callback__('Key')) -exec(__callback__('Char')) -exec(__callback__('CharMods')) -exec(__callback__('MouseButton')) -exec(__callback__('CursorPos')) -exec(__callback__('Scroll')) - - -# Error callback does not take window parameter -def glfwSetErrorCallback(callback=None): - global __c_error_callback__ - __c_error_callback__ = errorfun(callback) - _glfw.glfwSetErrorCallback(__c_error_callback__) diff --git a/kitty/data-types.c b/kitty/data-types.c index 5dfd3e060..d2e866de4 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -22,6 +22,8 @@ static PyMethodDef module_methods[] = { {"glfw_window_hint", (PyCFunction)glfw_window_hint, METH_VARARGS, ""}, {"glfw_swap_interval", (PyCFunction)glfw_swap_interval, METH_VARARGS, ""}, {"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_NOARGS, ""}, + {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, + {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -54,6 +56,7 @@ PyInit_fast_data_types(void) { if (!init_Face(m)) return NULL; if (!add_module_gl_constants(m)) return NULL; if (!init_glfw(m)) return NULL; + if (!init_Window(m)) return NULL; PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT); diff --git a/kitty/data-types.h b/kitty/data-types.h index b5bf68a6c..7510091bb 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -176,6 +176,7 @@ typedef struct { PyTypeObject Cursor_Type; PyTypeObject Face_Type; +PyTypeObject Window_Type; typedef struct { PyObject_HEAD @@ -295,6 +296,7 @@ int init_SpriteMap(PyObject *); int init_ChangeTracker(PyObject *); int init_Screen(PyObject *); int init_Face(PyObject *); +int init_Window(PyObject *); PyObject* create_256_color_table(); PyObject* read_bytes_dump(PyObject UNUSED *, PyObject *); PyObject* read_bytes(PyObject UNUSED *, PyObject *); diff --git a/kitty/glfw.c b/kitty/glfw.c index 22d29d4af..45f46b0d3 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -5,22 +5,103 @@ */ #include "data-types.h" +#include #include -#define MAX_WINDOWS 255 -/* static PyObject* window_weakrefs[MAX_WINDOWS + 1] = {0}; */ +#define MAX_WINDOWS 256 #define CALLBACK(name, fmt, ...) \ if ((name) != NULL) { \ PyGILState_STATE _pystate = PyGILState_Ensure(); \ PyObject *_pyret = PyObject_CallFunction((name), fmt, __VA_ARGS__); \ - PyGILState_Release(_pystate); \ - if (_pyret == NULL) { PyErr_Print(); PyErr_Clear(); return; } \ + if (_pyret == NULL && PyErr_Occurred() != NULL) PyErr_Print(); \ Py_CLEAR(_pyret); \ + PyGILState_Release(_pystate); \ } +#define WINDOW_CALLBACK(name, fmt, ...) \ + Window *self = find_window(w); \ + if (self) { CALLBACK(self->name, "O" fmt, self, __VA_ARGS__); } -// Library Setup {{{ +typedef struct { + PyObject_HEAD + + GLFWwindow *window; + PyObject *framebuffer_size_callback, *char_mods_callback, *key_callback, *mouse_button_callback, *scroll_callback, *cursor_pos_callback, *window_focus_callback; +} Window; +static Window* window_weakrefs[MAX_WINDOWS] = {0}; + +static inline Window* +find_window(GLFWwindow *w) { + for(int i = 0; i < MAX_WINDOWS; i++) { + if (window_weakrefs[i] == NULL) break; + if (window_weakrefs[i]->window == w) return window_weakrefs[i]; + } + return NULL; +} + +static void +framebuffer_size_callback(GLFWwindow *w, int width, int height) { + WINDOW_CALLBACK(framebuffer_size_callback, "ii", width, height); +} + +static void +char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) { + WINDOW_CALLBACK(char_mods_callback, "Ii", codepoint, mods); +} + +static void +key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) { + WINDOW_CALLBACK(key_callback, "iiii", key, scancode, action, mods); +} + +static void +mouse_button_callback(GLFWwindow *w, int button, int action, int mods) { + WINDOW_CALLBACK(mouse_button_callback, "iii", button, action, mods); +} + +static void +scroll_callback(GLFWwindow *w, double xoffset, double yoffset) { + WINDOW_CALLBACK(scroll_callback, "dd", xoffset, yoffset); +} + +static void +cursor_pos_callback(GLFWwindow *w, double x, double y) { + WINDOW_CALLBACK(cursor_pos_callback, "dd", x, y); +} + +static void +window_focus_callback(GLFWwindow *w, int focused) { + WINDOW_CALLBACK(window_focus_callback, "O", focused ? Py_True : Py_False); +} + +static PyObject* +new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { + Window *self; + char *title; + int width, height, i; + if (!PyArg_ParseTuple(args, "iis", &width, &height, &title)) return NULL; + + self = (Window *)type->tp_alloc(type, 0); + if (self != NULL) { + self->window = glfwCreateWindow(width, height, title, NULL, NULL); + if (self->window == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create GLFWWindow"); return NULL; } + for(i = 0; i < MAX_WINDOWS; i++) { + if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; } + } + if (i >= MAX_WINDOWS) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Too many windows created"); return NULL; } + glfwSetFramebufferSizeCallback(self->window, framebuffer_size_callback); + glfwSetCharModsCallback(self->window, char_mods_callback); + glfwSetKeyCallback(self->window, key_callback); + glfwSetMouseButtonCallback(self->window, mouse_button_callback); + glfwSetScrollCallback(self->window, scroll_callback); + glfwSetCursorPosCallback(self->window, cursor_pos_callback); + glfwSetWindowFocusCallback(self->window, window_focus_callback); + } + return (PyObject*)self; +} + +// Global functions {{{ static PyObject *error_callback = NULL; static void @@ -36,6 +117,7 @@ glfw_set_error_callback(PyObject UNUSED *self, PyObject *callback) { Py_RETURN_NONE; } + PyObject* glfw_init(PyObject UNUSED *self) { PyObject *ans = glfwInit() ? Py_True: Py_False; @@ -73,8 +155,130 @@ glfw_wait_events(PyObject UNUSED *self) { Py_RETURN_NONE; } +PyObject* +glfw_post_empty_event(PyObject UNUSED *self) { + glfwPostEmptyEvent(); + Py_RETURN_NONE; +} + +PyObject* +glfw_get_physical_dpi(PyObject UNUSED *self) { + GLFWmonitor *m = glfwGetPrimaryMonitor(); + if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; } + int width = 0, height = 0; + glfwGetMonitorPhysicalSize(m, &width, &height); + if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; } + const GLFWvidmode *vm = glfwGetVideoMode(m); + if (vm == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get video mode for monitor"); return NULL; } + float dpix = vm->width / (width / 25.4); + float dpiy = vm->height / (height / 25.4); + return Py_BuildValue("ff", dpix, dpiy); +} + // }}} +static void +dealloc(Window* self) { + for(unsigned int i = 0; i < MAX_WINDOWS; i++) { + if (window_weakrefs[i] == self) window_weakrefs[i] = NULL; + } + Py_CLEAR(self->framebuffer_size_callback); Py_CLEAR(self->char_mods_callback); Py_CLEAR(self->key_callback); Py_CLEAR(self->mouse_button_callback); Py_CLEAR(self->scroll_callback); Py_CLEAR(self->cursor_pos_callback); Py_CLEAR(self->window_focus_callback); + if (self->window != NULL) glfwDestroyWindow(self->window); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject* +swap_buffers(Window *self) { + glfwSwapBuffers(self->window); + Py_RETURN_NONE; +} + +static PyObject* +make_context_current(Window *self) { + glfwMakeContextCurrent(self->window); + Py_RETURN_NONE; +} + +static PyObject* +should_close(Window *self) { + PyObject *ans = glfwWindowShouldClose(self->window) ? Py_True : Py_False; + Py_INCREF(ans); + return ans; +} + +static PyObject* +get_clipboard_string(Window *self) { + return Py_BuildValue("s", glfwGetClipboardString(self->window)); +} + +static PyObject* +set_should_close(Window *self, PyObject *args) { + int c; + if (!PyArg_ParseTuple(args, "p", &c)) return NULL; + glfwSetWindowShouldClose(self->window, c); + Py_RETURN_NONE; +} + +static PyObject* +is_key_pressed(Window *self, PyObject *args) { + int c; + if (!PyArg_ParseTuple(args, "i", &c)) return NULL; + PyObject *ans = glfwGetKey(self->window, c) == GLFW_PRESS ? Py_True : Py_False; + Py_INCREF(ans); + return ans; +} + + +static PyObject* +_set_title(Window *self, PyObject *args) { + char *title; + if(!PyArg_ParseTuple(args, "s", &title)) return NULL; + glfwSetWindowTitle(self->window, title); + Py_RETURN_NONE; +} + +// Boilerplate {{{ +#define MND(name, args) {#name, (PyCFunction)name, args, ""} + +static PyMethodDef methods[] = { + MND(swap_buffers, METH_NOARGS), + MND(get_clipboard_string, METH_NOARGS), + MND(should_close, METH_NOARGS), + MND(set_should_close, METH_VARARGS), + MND(is_key_pressed, METH_VARARGS), + MND(make_context_current, METH_NOARGS), + {"set_title", (PyCFunction)_set_title, METH_VARARGS, ""}, + {NULL} /* Sentinel */ +}; + +static PyMemberDef members[] = { +#define CBE(name) {#name, T_OBJECT_EX, offsetof(Window, name), 0, #name} + CBE(framebuffer_size_callback), + CBE(char_mods_callback), + CBE(key_callback), + CBE(mouse_button_callback), + CBE(scroll_callback), + CBE(cursor_pos_callback), + CBE(window_focus_callback), + {NULL} +#undef CBE +}; + +PyTypeObject Window_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "fast_data_types.Window", + .tp_basicsize = sizeof(Window), + .tp_dealloc = (destructor)dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "A GLFW window", + .tp_methods = methods, + .tp_members = members, + .tp_new = new, +}; + +INIT_TYPE(Window) + + // constants {{{ bool init_glfw(PyObject *m) { @@ -336,3 +540,4 @@ return true; #undef ADDC } // }}} +// }}} diff --git a/kitty/glfw.h b/kitty/glfw.h index 1043afc92..81d1bb04a 100644 --- a/kitty/glfw.h +++ b/kitty/glfw.h @@ -15,3 +15,5 @@ PyObject* glfw_terminate(PyObject UNUSED *self); PyObject* glfw_window_hint(PyObject UNUSED *self, PyObject *args); PyObject* glfw_swap_interval(PyObject UNUSED *self, PyObject *args); PyObject* glfw_wait_events(PyObject UNUSED *self); +PyObject* glfw_post_empty_event(PyObject UNUSED *self); +PyObject* glfw_get_physical_dpi(PyObject UNUSED *self); diff --git a/kitty/main.py b/kitty/main.py index f23fc9678..f25d3d90d 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -19,9 +19,8 @@ from .fast_data_types import ( GLFW_CONTEXT_VERSION_MINOR, GLFW_OPENGL_PROFILE, GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_CORE_PROFILE, GLFW_SAMPLES, glfw_set_error_callback, glfw_init, glfw_terminate, glfw_window_hint, - glfw_swap_interval, glfw_wait_events + glfw_swap_interval, glfw_wait_events, Window ) -import glfw def option_parser(): @@ -54,33 +53,29 @@ def clear_buffers(window, opts): glClearColor(bg.red / 255, bg.green / 255, bg.blue / 255, 1) glfw_swap_interval(0) glClear(GL_COLOR_BUFFER_BIT) - glfw.glfwSwapBuffers(window) + window.swap_buffers() glClear(GL_COLOR_BUFFER_BIT) glfw_swap_interval(1) def run_app(opts, args): setup_opengl() - window = glfw.glfwCreateWindow( - viewport_size.width, viewport_size.height, args.cls.encode('utf-8'), None, None) - if not window: - raise SystemExit("glfwCreateWindow failed") - glfw.glfwSetWindowTitle(window, appname.encode('utf-8')) + window = Window( + viewport_size.width, viewport_size.height, args.cls) + window.set_title(appname) + window.make_context_current() + glewInit() + tabs = TabManager(window, opts, args) + tabs.start() + clear_buffers(window, opts) try: - glfw.glfwMakeContextCurrent(window) - glewInit() - tabs = TabManager(window, opts, args) - tabs.start() - clear_buffers(window, opts) - try: - while not glfw.glfwWindowShouldClose(window): - tabs.render() - glfw.glfwSwapBuffers(window) - glfw_wait_events() - finally: - tabs.destroy() + while not window.should_close(): + tabs.render() + window.swap_buffers() + glfw_wait_events() finally: - glfw.glfwDestroyWindow(window) + tabs.destroy() + del window def on_glfw_error(code, msg): diff --git a/kitty/tabs.py b/kitty/tabs.py index e26ade165..94268fa19 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -14,12 +14,11 @@ from threading import Thread from time import monotonic from queue import Queue, Empty -import glfw from .child import Child from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size, MODIFIER_KEYS from .fast_data_types import ( glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS, - GLFW_REPEAT, GLFW_MOUSE_BUTTON_1 + GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event ) from .fonts import set_font_family from .borders import Borders, BordersProgram @@ -123,13 +122,13 @@ class TabManager(Thread): cell_size.width, cell_size.height = set_font_family(opts.font_family, opts.font_size) self.opts, self.args = opts, args self.glfw_window = glfw_window - glfw.glfwSetFramebufferSizeCallback(glfw_window, partial(self.queue_action, self.on_window_resize)) - glfw.glfwSetCharModsCallback(glfw_window, partial(self.queue_action, self.on_text_input)) - glfw.glfwSetKeyCallback(glfw_window, partial(self.queue_action, self.on_key)) - glfw.glfwSetMouseButtonCallback(glfw_window, partial(self.queue_action, self.on_mouse_button)) - glfw.glfwSetScrollCallback(glfw_window, partial(self.queue_action, self.on_mouse_scroll)) - glfw.glfwSetCursorPosCallback(glfw_window, partial(self.queue_action, self.on_mouse_move)) - glfw.glfwSetWindowFocusCallback(glfw_window, partial(self.queue_action, self.on_focus)) + glfw_window.framebuffer_size_callback = partial(self.queue_action, self.on_window_resize) + glfw_window.char_mods_callback = partial(self.queue_action, self.on_text_input) + glfw_window.key_callback = partial(self.queue_action, self.on_key) + glfw_window.mouse_button_callback = partial(self.queue_action, self.on_mouse_button) + glfw_window.scroll_callback = partial(self.queue_action, self.on_mouse_scroll) + glfw_window.cursor_pos_callback = partial(self.queue_action, self.on_mouse_move) + glfw_window.window_focus_callback = partial(self.queue_action, self.on_focus) self.tabs = deque() self.tabs.append(Tab(opts, args)) self.sprites = Sprites() @@ -156,8 +155,8 @@ class TabManager(Thread): def shutdown(self): if not self.shutting_down: self.shutting_down = True - glfw.glfwSetWindowShouldClose(self.glfw_window, True) - glfw.glfwPostEmptyEvent() + self.glfw_window.set_should_close(True) + glfw_post_empty_event() def __iter__(self): yield from iter(self.tabs) @@ -277,7 +276,7 @@ class TabManager(Thread): tab.relayout() self.pending_resize = None self.resize_gl_viewport = True - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() @property def active_tab(self): @@ -328,19 +327,19 @@ class TabManager(Thread): return w def on_mouse_button(self, window, button, action, mods): - w = self.window_for_pos(*glfw.glfwGetCursorPos(window)) + w = self.window_for_pos(*window.get_cursor_pos()) if w is not None: if button == GLFW_MOUSE_BUTTON_1 and w is not self.active_window: pass # TODO: Switch focus to this window w.on_mouse_button(window, button, action, mods) def on_mouse_move(self, window, xpos, ypos): - w = self.window_for_pos(*glfw.glfwGetCursorPos(window)) + w = self.window_for_pos(*window.get_cursor_pos()) if w is not None: w.on_mouse_move(window, xpos, ypos) def on_mouse_scroll(self, window, x, y): - w = self.window_for_pos(*glfw.glfwGetCursorPos(window)) + w = self.window_for_pos(*window.get_cursor_pos()) if w is not None: w.on_mouse_scroll(window, x, y) @@ -355,7 +354,7 @@ class TabManager(Thread): if tab is not None: if tab.title != self.glfw_window_title: self.glfw_window_title = tab.title - glfw.glfwSetWindowTitle(self.glfw_window, self.glfw_window_title.encode('utf-8')) + self.glfw_window.set_title(self.glfw_window_title) with self.sprites: self.sprites.render_dirty_cells() tab.render() diff --git a/kitty/utils.py b/kitty/utils.py index 1c88a5314..b513e93db 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -12,6 +12,7 @@ from contextlib import contextmanager from functools import lru_cache from time import monotonic +from .fast_data_types import glfw_get_physical_dpi libc = ctypes.CDLL(None) wcwidth_native = libc.wcwidth @@ -50,14 +51,9 @@ def get_logical_dpi(): def get_dpi(): - import glfw if not hasattr(get_dpi, 'ans'): - m = glfw.glfwGetPrimaryMonitor() - width, height = glfw.glfwGetMonitorPhysicalSize(m) - vmode = glfw.glfwGetVideoMode(m) - dpix = vmode.width / (width / 25.4) - dpiy = vmode.height / (height / 25.4) - get_dpi.ans = {'physical': (dpix, dpiy), 'logical': get_logical_dpi()} + pdpi = glfw_get_physical_dpi() + get_dpi.ans = {'physical': pdpi, 'logical': get_logical_dpi()} return get_dpi.ans # Color names {{{ diff --git a/kitty/window.py b/kitty/window.py index 57f749ba1..a82a824a1 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -8,14 +8,13 @@ from collections import deque from functools import partial from time import monotonic -import glfw from .char_grid import CharGrid from .constants import wakeup, tab_manager, appname, WindowGeometry from .fast_data_types import ( BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump, read_bytes, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, GLFW_KEY_LEFT_SHIFT, - GLFW_KEY_RIGHT_SHIFT + GLFW_KEY_RIGHT_SHIFT, glfw_post_empty_event ) from .terminfo import get_capabilities from .utils import sanitize_title, get_primary_selection @@ -83,7 +82,7 @@ class Window: def update_screen(self): self.char_grid.update_cell_data() - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def focus_changed(self, focused): if focused: @@ -95,7 +94,7 @@ class Window: def title_changed(self, new_title): self.title = sanitize_title(new_title or appname) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def icon_changed(self, new_icon): pass # TODO: Implement this @@ -113,7 +112,7 @@ class Window: color_changes[w] = val code += 1 self.char_grid.change_colors(color_changes) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def request_capabilities(self, q): self.write_to_child(get_capabilities(q)) @@ -121,16 +120,16 @@ class Window: def dispatch_multi_click(self, x, y): if len(self.click_queue) > 2 and self.click_queue[-1] - self.click_queue[-3] <= 2 * self.opts.click_interval: self.char_grid.multi_click(3, x, y) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() elif len(self.click_queue) > 1 and self.click_queue[-1] - self.click_queue[-2] <= self.opts.click_interval: self.char_grid.multi_click(2, x, y) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def on_mouse_button(self, window, button, action, mods): handle_event = mods == GLFW_MOD_SHIFT or not self.screen.mouse_button_tracking_enabled() if handle_event: if button == GLFW_MOUSE_BUTTON_1: - x, y = glfw.glfwGetCursorPos(window) + x, y = window.get_cursor_pos() x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) self.char_grid.update_drag(action == GLFW_PRESS, x, y) if action == GLFW_RELEASE: @@ -140,7 +139,7 @@ class Window: if action == GLFW_RELEASE: self.paste_from_selection() else: - x, y = glfw.glfwGetCursorPos(window) + x, y = window.get_cursor_pos() x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) x, y = self.char_grid.cell_for_pos(x, y) @@ -150,14 +149,14 @@ class Window: def on_mouse_scroll(self, window, x, y): handle_event = ( - glfw.glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS or - glfw.glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS or + window.is_key_pressed(GLFW_KEY_LEFT_SHIFT) or + window.is_key_pressed(GLFW_KEY_RIGHT_SHIFT) or not self.screen.mouse_button_tracking_enabled()) if handle_event: s = int(round(y * self.opts.wheel_scroll_multiplier)) if abs(s) > 0: self.char_grid.scroll(abs(s), s > 0) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() # actions {{{ @@ -170,9 +169,9 @@ class Window: self.write_to_child(text) def paste_from_clipboard(self): - text = glfw.glfwGetClipboardString(tab_manager().glfw_window) + text = tab_manager().glfw_window.get_clipboard_string() if text: - self.paste(text.decode('utf-8')) + self.paste(text) def paste_from_selection(self): text = get_primary_selection() @@ -183,27 +182,27 @@ class Window: def scroll_line_up(self): self.char_grid.scroll('line', True) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def scroll_line_down(self): self.char_grid.scroll('line', False) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def scroll_page_up(self): self.char_grid.scroll('page', True) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def scroll_page_down(self): self.char_grid.scroll('page', False) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def scroll_home(self): self.char_grid.scroll('full', True) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() def scroll_end(self): self.char_grid.scroll('full', False) - glfw.glfwPostEmptyEvent() + glfw_post_empty_event() # }}} def dump_commands(self, *a):