Compare commits

..

32 Commits

Author SHA1 Message Date
Kovid Goyal
57f8519a52 version 0.11.3 2018-07-10 21:42:01 +05:30
Kovid Goyal
056be45dd5 ... 2018-07-10 21:30:30 +05:30
Kovid Goyal
3ee22e545e macOS: Workaround for switching from fullscreen to windowed mode with the titlebar hidden causing window resizing to not work.
Fixes #711
2018-07-10 21:29:30 +05:30
Kovid Goyal
db5aa36f44 Fix deprecation warning from newer versions of wayland-scanner 2018-07-10 19:48:37 +05:30
Kovid Goyal
cc767061cc Linux: Fix compilation with python 3.7
Fixes #714
2018-07-10 18:47:05 +05:30
Kovid Goyal
1d22b75090 Fix a regression in the previous release that caused pasting large amounts of text to be duplicated
Fixes #709
2018-07-08 16:25:08 +05:30
Kovid Goyal
4313531432 macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
Fixes #359. Also, update GLFW from upstream for retina cursor setting
support.
2018-07-07 10:43:30 +05:30
Kovid Goyal
1faddeb402 Also return image dimensions when loading PNG 2018-07-07 08:04:11 +05:30
Kovid Goyal
2b035739f8 Make the PNG reader code re-useable 2018-07-07 07:46:15 +05:30
Kovid Goyal
aeed20087e Fix segfault when opening more tabs than would fit with even one character titles on the tab bar. Fixes #706 2018-07-07 05:35:56 +05:30
Kovid Goyal
a239f41495 minimal borders for the grid layout 2018-07-06 20:23:56 +05:30
Kovid Goyal
b36d6967a5 DRYer 2018-07-06 17:54:01 +05:30
Kovid Goyal
7c1d13b7db Minimal borders for vertical and horizontal layouts 2018-07-06 17:46:08 +05:30
Kovid Goyal
3e99949db8 Implement minimal borders in the Fat layout 2018-07-06 17:35:23 +05:30
Kovid Goyal
aa12a65a8f Draw only the minimum borders needed for inactive windows
Fixes #699

Note that currently this is only implemented by the Tall layout.
2018-07-06 17:30:53 +05:30
Kovid Goyal
18804efb7e Let the layout control which window borders are drawn 2018-07-06 17:14:25 +05:30
Kovid Goyal
a5d27196ea Remove unnecessary code 2018-07-06 13:17:51 +05:30
Kovid Goyal
5af6d88ffb Support drawing individual border widths 2018-07-06 13:17:51 +05:30
Kovid Goyal
472a3bc69f Refactor layout dimension to use per window decoration sizes 2018-07-06 13:17:51 +05:30
Kovid Goyal
f0f1c911ea Track window neighbors 2018-07-06 13:17:50 +05:30
Kovid Goyal
fcb0033d25 When drag-scrolling stop the scroll when the mouse button is released. 2018-07-06 13:01:49 +05:30
Kovid Goyal
c8b1e76783 When clicking in the margin/border area of a window map the click to the nearest cell in the window. Avoids selection with the mouse failing when starting the selection just outside the window. 2018-07-06 12:32:08 +05:30
Kovid Goyal
b50f621e61 DRYer 2018-07-06 10:55:03 +05:30
Kovid Goyal
77c4f5fecc When dragging to select with the mouse "grab" the mouse so that if it strays into neighboring windows, the selection is still updated
Fixes #624
2018-07-06 10:33:01 +05:30
Kovid Goyal
e27ea3337a Prevent integer wrap on xlimit 2018-07-06 09:50:34 +05:30
Kovid Goyal
23fbf6f157 Fix triple-click to select line not working when the entire line is filled
Fixes #703
2018-07-06 08:17:48 +05:30
Kovid Goyal
321e5081ed Merge branch 'reproducible-docs' of https://github.com/jamessan/kitty 2018-07-05 20:37:57 +05:30
James McCoy
33cac29f87 seq_as_rst: Sort "Choices" so they are consistently formatted 2018-07-05 11:00:54 -04:00
Kovid Goyal
12be438dea ... 2018-07-05 12:30:11 +05:30
Kovid Goyal
00092db6ad Use an enum for border colors 2018-07-05 11:52:32 +05:30
Kovid Goyal
619c3a0025 macOS: Add an option macos_window_resizable to control if kitty top-level windows are resizable using the mouse or not
Fixes #698
2018-07-03 06:49:28 +05:30
Kovid Goyal
f12697c897 Better error when kitty config dir is a file
See #697
2018-07-03 06:30:29 +05:30
36 changed files with 605 additions and 212 deletions

View File

@@ -3,6 +3,40 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
0.11.3 [2018-07-10]
------------------------------
- Draw only the minimum borders needed for inactive windows. That is only the borders
that separate the inactive window from a neighbor. Note that setting
a non-zero window margin overrides this and causes all borders to be drawn.
The old behavior of drawing all borders can be restored via the
:opt:`draw_minimal_borders` setting in kitty.conf.
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
top-level windows are resizable using the mouse or not (:iss:`698`)
- macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
(:iss:`359`)
- macOS: Workaround for switching from fullscreen to windowed mode with the
titlebar hidden causing window resizing to not work. (:iss:`711`)
- Fix triple-click to select line not working when the entire line is filled
(:iss:`703`)
- When dragging to select with the mouse "grab" the mouse so that if it strays
into neighboring windows, the selection is still updated (:pull:`624`)
- When clicking in the margin/border area of a window, map the click to the
nearest cell in the window. Avoids selection with the mouse failing when
starting the selection just outside the window.
- When drag-scrolling stop the scroll when the mouse button is released.
- Fix a regression in the previous release that caused pasting large amounts
of text to be duplicated (:iss:`709`)
0.11.2 [2018-07-01]
------------------------------

View File

@@ -1886,44 +1886,44 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
NSImage* native;
NSBitmapImageRep* rep;
if (!initializeAppKit())
return GLFW_FALSE;
rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:image->width
pixelsHigh:image->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
bytesPerRow:image->width * 4
bitsPerPixel:32];
if (rep == nil)
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
if (native == nil)
return GLFW_FALSE;
memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
for (int i = 0; i < count; i++) {
const GLFWimage *src = image + i;
rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:src->width
pixelsHigh:src->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
bytesPerRow:src->width * 4
bitsPerPixel:32];
if (rep == nil)
return GLFW_FALSE;
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
[native addRepresentation:rep];
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
[native addRepresentation:rep];
[rep release];
}
cursor->ns.object = [[NSCursor alloc] initWithImage:native
hotSpot:NSMakePoint(xhot, yhot)];
[native release];
[rep release];
if (cursor->ns.object == nil)
return GLFW_FALSE;
return GLFW_TRUE;
}

View File

@@ -67,6 +67,8 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.')))
ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code'
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
for p in ans.wayland_protocols:
ans.sources.append(wayland_protocol_file_name(p))
@@ -87,7 +89,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
dest = wayland_protocol_file_name(src, ext)
dest = os.path.join(dest_dir, dest)
if newer(dest, src):
q = 'client-header' if ext == 'h' else 'code'
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
run_tool([env.wayland_scanner, q, src, dest],
desc='Generating {} ...'.format(emphasis(os.path.basename(dest))))

5
glfw/glfw3.h vendored
View File

@@ -4123,6 +4123,7 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
* @param[in] image The desired cursor image.
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
* @param[in] count The number of images. Used on Cocoa for retina cursors. The first image should be the 1:1 scale image.
* @return The handle of the created cursor, or `NULL` if an
* [error](@ref error_handling) occurred.
*
@@ -4138,11 +4139,11 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
* @sa @ref glfwDestroyCursor
* @sa @ref glfwCreateStandardCursor
*
* @since Added in version 3.1.
* @since Added in version 3.1. Changed in 4.0 to add the count parameter.
*
* @ingroup input
*/
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count);
/*! @brief Creates a cursor with a standard shape.
*

5
glfw/input.c vendored
View File

@@ -799,11 +799,12 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos)
}
}
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count)
{
_GLFWcursor* cursor;
assert(image != NULL);
assert(count > 0);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -811,7 +812,7 @@ GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
cursor->next = _glfw.cursorListHead;
_glfw.cursorListHead = cursor;
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot))
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot, count))
{
glfwDestroyCursor((GLFWcursor*) cursor);
return NULL;

2
glfw/internal.h vendored
View File

@@ -600,7 +600,7 @@ void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, int xhot, int yhot);
const GLFWimage* image, int xhot, int yhot, int count);
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);

2
glfw/null_window.c vendored
View File

@@ -267,7 +267,7 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
return GLFW_TRUE;
}

2
glfw/win32_window.c vendored
View File

@@ -1839,7 +1839,7 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
if (!cursor->win32.handle)

2
glfw/wl_window.c vendored
View File

@@ -1257,7 +1257,7 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
cursor->wl.buffer = createShmBuffer(image);
cursor->wl.width = image->width;

2
glfw/x11_window.c vendored
View File

@@ -2684,7 +2684,7 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
if (!cursor->x11.handle)

View File

@@ -2,7 +2,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import partial
from itertools import chain
from .fast_data_types import (
@@ -10,6 +9,16 @@ from .fast_data_types import (
)
from .utils import load_shaders
try:
from enum import IntFlag
except Exception:
from enum import IntEnum as IntFlag
class BorderColor(IntFlag):
# See the border vertex shader for how these flags become actual colors
default_bg, active, inactive, window_bg, bell = ((1 << i) for i in range(5))
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
@@ -19,15 +28,15 @@ def horizontal_edge(os_window_id, tab_id, color, height, left, right, top):
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
def edge(func, os_window_id, tab_id, color, sz, a, b):
return partial(func, os_window_id, tab_id, color, sz, a, b)
def border(os_window_id, tab_id, color, sz, left, top, right, bottom):
horz = edge(horizontal_edge, os_window_id, tab_id, color, sz, left, right)
horz(top), horz(bottom - sz) # top, bottom edges
vert = edge(vertical_edge, os_window_id, tab_id, color, sz, top, bottom)
vert(left), vert(right - sz) # left, right edges
def draw_edges(os_window_id, tab_id, colors, width, geometry, base_width=0):
left = geometry.left - (width + base_width)
top = geometry.top - (width + base_width)
right = geometry.right + (width + base_width)
bottom = geometry.bottom + (width + base_width)
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top)
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width)
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left)
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width)
def load_borders_program():
@@ -52,28 +61,31 @@ class Borders:
extra_blank_rects,
draw_window_borders=True
):
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 1)
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
for br in chain(current_layout.blank_rects, extra_blank_rects):
add_borders_rect(self.os_window_id, self.tab_id, *br, 1)
add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg)
bw, pw = self.border_width, self.padding_width
fw = bw + pw
if bw + pw <= 0:
return
draw_borders = bw > 0 and draw_window_borders and len(windows) > 1
if draw_borders:
border_data = current_layout.resolve_borders(windows, active_window)
if fw > 0:
for w in windows:
g = w.geometry
if bw > 0 and draw_window_borders:
# Draw the border rectangles
color = 2 if w is active_window else (16 if w.needs_attention else 4)
border(
self.os_window_id, self.tab_id,
color, bw, g.left - fw, g.top - fw, g.right + fw,
g.bottom + fw
)
if pw > 0:
# Draw the background rectangles over the padding region
color = w.screen.color_profile.default_bg
border(
self.os_window_id, self.tab_id,
(color << 8) | 8, pw, g.left - pw, g.top - pw, g.right + pw,
g.bottom + pw
)
for i, w in enumerate(windows):
g = w.geometry
window_bg = w.screen.color_profile.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders:
# Draw the border rectangles
if w is active_window:
color = BorderColor.active
else:
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges(
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw)
if pw > 0:
# Draw the background rectangles over the padding region
colors = (window_bg, window_bg, window_bg, window_bg)
draw_edges(
self.os_window_id, self.tab_id, colors, pw, g)

View File

@@ -25,6 +25,7 @@ from .fast_data_types import (
set_clipboard_string, set_in_sequence_mode, toggle_fullscreen
)
from .keys import get_shortcut, shortcut_matches
from .layout import set_draw_minimal_borders
from .remote_control import handle_cmd
from .rgb import Color, color_from_int
from .session import create_session
@@ -76,6 +77,7 @@ class DumpCommands: # {{{
class Boss:
def __init__(self, os_window_id, opts, args, cached_values, new_os_window_trigger):
set_draw_minimal_borders(opts)
self.window_id_map = WeakValueDictionary()
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.pending_sequences = None

View File

@@ -976,7 +976,12 @@ write_to_child(int fd, Screen *screen) {
written = screen->write_buf_used;
}
}
screen->write_buf_used -= written;
if (written) {
screen->write_buf_used -= written;
if (screen->write_buf_used) {
memmove(screen->write_buf, screen->write_buf + written, screen->write_buf_used);
}
}
screen_mutex(unlock, write);
}

View File

@@ -454,7 +454,7 @@ def seq_as_rst(seq, usage, message, appname, heading_char='-'):
if defval is not None:
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
if 'choices' in opt:
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(opt['choices'])), ' ' * 4))
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(sorted(opt['choices']))), ' ' * 4))
a('')
text = '\n'.join(blocks)

View File

@@ -202,14 +202,19 @@ cocoa_update_title(PyObject *pytitle) {
}
bool
cocoa_make_window_resizable(void *w) {
cocoa_make_window_resizable(void *w, bool resizable) {
NSWindow *window = (NSWindow*)w;
@try {
[window setStyleMask:
[window styleMask] | NSWindowStyleMaskResizable];
if (resizable) {
[window setStyleMask:
[window styleMask] | NSWindowStyleMaskResizable];
} else {
[window setStyleMask:
[window styleMask] & ~NSWindowStyleMaskResizable];
}
} @catch (NSException *e) {
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
log_error("Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
return false;
}
return true;

View File

@@ -449,6 +449,13 @@ o('window_border_width', 1.0, option_type=positive_float, long_text=_('''
The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.'''))
o('draw_minimal_borders', True, long_text=_('''
Draw only the minimum borders needed. This means that only the minimum
needed borders for inactive windows are drawn. That is only the borders
that separate the inactive window from a neighbor. Note that setting
a non-zero window margin overrides this and causes all borders to be drawn.
'''))
o('window_margin_width', 0.0, option_type=positive_float, long_text=_('''
The window margin (in pts) (blank area outside the border)'''))
@@ -715,7 +722,10 @@ kitty will stay running, even with no open windows, as is the expected
behavior on macOS.
'''))
o('macos_window_resizable', True, long_text=_('''
Disable this if you want kitty top-level (OS) windows to not be resizable
on macOS.
'''))
# }}}
g('shortcuts') # {{{

View File

@@ -10,7 +10,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss
appname = 'kitty'
version = (0, 11, 2)
version = (0, 11, 3)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat
@@ -58,7 +58,10 @@ def _get_config_dir():
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config'))
ans = os.path.join(candidate, appname)
os.makedirs(ans, exist_ok=True)
try:
os.makedirs(ans, exist_ok=True)
except FileExistsError:
raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans))
return ans
@@ -102,6 +105,7 @@ def wakeup():
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
terminfo_dir = os.path.join(base_dir, 'terminfo')
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
beam_cursor_data_file = os.path.join(base_dir, 'logo', 'beam-cursor.png')
try:
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
except KeyError:

View File

@@ -211,6 +211,7 @@ extern bool init_shaders(PyObject *module);
extern bool init_mouse(PyObject *module);
extern bool init_kittens(PyObject *module);
extern bool init_logging(PyObject *module);
extern bool init_png_reader(PyObject *module);
#ifdef __APPLE__
extern int init_CoreText(PyObject *);
extern bool init_cocoa(PyObject *module);
@@ -246,6 +247,7 @@ PyInit_fast_data_types(void) {
if (!init_shaders(m)) return NULL;
if (!init_mouse(m)) return NULL;
if (!init_kittens(m)) return NULL;
if (!init_png_reader(m)) return NULL;
#ifdef __APPLE__
if (!init_CoreText(m)) return NULL;
if (!init_cocoa(m)) return NULL;

View File

@@ -175,7 +175,7 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
if (!missing_ok) { PyErr_SetString(PyExc_KeyError, "font descriptor is missing the key: " #key); return NULL; } \
} else key = conv(t); \
}
char *path = NULL;
const char *path = NULL;
long index = 0;
bool hinting = false;
long hint_style = 0;

2
kitty/glfw-wrapper.h generated
View File

@@ -1679,7 +1679,7 @@ typedef void (*glfwSetCursorPos_func)(GLFWwindow*, double, double);
glfwSetCursorPos_func glfwSetCursorPos_impl;
#define glfwSetCursorPos glfwSetCursorPos_impl
typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int);
typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int, int);
glfwCreateCursor_func glfwCreateCursor_impl;
#define glfwCreateCursor glfwCreateCursor_impl

View File

@@ -8,7 +8,7 @@
#include "fonts.h"
#include <structmember.h>
#include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w);
extern bool cocoa_make_window_resizable(void *w, bool);
extern void cocoa_create_global_menu(void);
extern void cocoa_set_hide_from_tasks(void);
extern void cocoa_set_titlebar_color(void *w, color_type color);
@@ -202,6 +202,7 @@ push_focus_history(OSWindow *w) {
static void
window_focus_callback(GLFWwindow *w, int focused) {
global_state.active_drag_in_window = 0;
if (!set_callback_window(w)) return;
global_state.callback_os_window->is_focused = focused ? true : false;
if (focused) {
@@ -455,8 +456,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
if (OPT(macos_hide_from_tasks)) cocoa_set_hide_from_tasks();
#endif
#define CC(dest, shape) {\
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); }}
if (!dest##_cursor) { \
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); } \
}}
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
#undef CC
is_first_window = false;
@@ -492,10 +495,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwSetWindowFocusCallback(glfw_window, window_focus_callback);
glfwSetDropCallback(glfw_window, drop_callback);
#ifdef __APPLE__
if (OPT(macos_hide_titlebar)) {
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
else log_error("Failed to load glfwGetCocoaWindow");
}
if (glfwGetCocoaWindow) cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window), OPT(macos_window_resizable));
else log_error("Failed to load glfwGetCocoaWindow");
#endif
double now = monotonic();
w->is_focused = true;
@@ -678,6 +679,9 @@ toggle_fullscreen(PYNOARG) {
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
#ifdef __APPLE__
if (glfwGetCocoaWindow) cocoa_make_window_resizable(glfwGetCocoaWindow(w->handle), OPT(macos_window_resizable));
#endif
Py_RETURN_FALSE;
}
}
@@ -840,6 +844,39 @@ set_smallest_allowed_resize(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE;
}
static PyObject*
set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
int shape;
int x=0, y=0;
Py_ssize_t sz;
PyObject *images;
if (!PyArg_ParseTuple(args, "iO!|ii", &shape, &PyTuple_Type, &images, &x, &y)) return NULL;
static GLFWimage gimages[16] = {{0}};
size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages));
for (size_t i = 0; i < count; i++) {
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL;
if (gimages[i].width * gimages[i].height * 4 != sz) {
PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height");
return NULL;
}
}
#define CASE(which, dest) {\
case which: \
standard_cursor = glfwCreateCursor(gimages, x, y, count); \
if (standard_cursor == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create custom cursor"); return NULL; } \
break; \
}
switch(shape) {
CASE(GLFW_IBEAM_CURSOR, standard_cursor);
CASE(GLFW_HAND_CURSOR, click_cursor);
CASE(GLFW_ARROW_CURSOR, arrow_cursor);
default:
PyErr_SetString(PyExc_ValueError, "Unknown cursor shape");
return NULL;
}
Py_RETURN_NONE;
}
#ifdef __APPLE__
void
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
@@ -849,6 +886,7 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
// Boilerplate {{{
static PyMethodDef module_methods[] = {
METHODB(set_custom_cursor, METH_VARARGS),
METHODB(set_smallest_allowed_resize, METH_VARARGS),
METHODB(create_os_window, METH_VARARGS),
METHODB(set_default_window_icon, METH_VARARGS),
@@ -892,6 +930,7 @@ init_glfw(PyObject *m) {
ADDC(GLFW_PRESS);
ADDC(GLFW_REPEAT);
ADDC(GLFW_TRUE); ADDC(GLFW_FALSE);
ADDC(GLFW_IBEAM_CURSOR); ADDC(GLFW_HAND_CURSOR); ADDC(GLFW_ARROW_CURSOR);
// --- Keys --------------------------------------------------------------------

View File

@@ -15,8 +15,8 @@
#include <stdlib.h>
#include <zlib.h>
#include <png.h>
#include <structmember.h>
#include "png-reader.h"
PyTypeObject GraphicsManager_Type;
#define STORAGE_LIMIT (320 * (1024 * 1024))
@@ -227,87 +227,14 @@ err:
return ok;
}
struct fake_file { uint8_t *buf; size_t sz, cur; };
static void
read_png_from_buffer(png_structp png, png_bytep out, png_size_t length) {
struct fake_file *f = png_get_io_ptr(png);
if (f) {
size_t amt = MIN(length, f->sz - f->cur);
memcpy(out, f->buf + f->cur, amt);
f->cur += amt;
}
}
static void
read_png_error_handler(png_structp png_ptr, png_const_charp msg) {
jmp_buf *jb;
set_add_response("EBADPNG", msg);
jb = png_get_error_ptr(png_ptr);
if (jb == NULL) fatal("read_png_error_handler: could not retrieve jmp_buf");
longjmp(*jb, 1);
}
static void
read_png_warn_handler(png_structp UNUSED png_ptr, png_const_charp UNUSED msg) {
// ignore warnings
}
struct png_jmp_data { uint8_t *decompressed; bool ok; png_bytep *row_pointers; int width, height; size_t sz; };
static void
inflate_png_inner(struct png_jmp_data *d, uint8_t *buf, size_t bufsz) {
struct fake_file f = {.buf = buf, .sz = bufsz};
png_structp png = NULL;
png_infop info = NULL;
jmp_buf jb;
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &jb, read_png_error_handler, read_png_warn_handler);
if (!png) ABRT(ENOMEM, "Failed to create PNG read structure");
info = png_create_info_struct(png);
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
if (setjmp(jb)) goto err;
png_set_read_fn(png, &f, read_png_from_buffer);
png_read_info(png, info);
png_byte color_type, bit_depth;
d->width = png_get_image_width(png, info);
d->height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);
// Ensure we get RGBA data out of libpng
if (bit_depth == 16) png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
// These color_type don't have an alpha channel then fill it with 0xff.
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png);
png_read_update_info(png, info);
int rowbytes = png_get_rowbytes(png, info);
d->sz = rowbytes * d->height * sizeof(png_byte);
d->decompressed = malloc(d->sz + 16);
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
d->row_pointers = malloc(d->height * sizeof(png_bytep));
if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG");
for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes;
png_read_image(png, d->row_pointers);
d->ok = true;
err:
if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL);
return;
png_error_handler(const char *code, const char *msg) {
set_add_response(code, "%s", msg);
}
static inline bool
inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz) {
struct png_jmp_data d = {0};
png_read_data d = {.err_handler=png_error_handler};
inflate_png_inner(&d, buf, bufsz);
if (d.ok) {
free_load_data(&img->load_data);

View File

@@ -14,6 +14,9 @@ from .fast_data_types import (
# Utils {{{
central = Region((0, 0, 199, 199, 200, 200))
cell_width = cell_height = 20
all_borders = True, True, True, True
no_borders = False, False, False, False
draw_minimal_borders = False
def idx_for_id(win_id, windows):
@@ -22,26 +25,30 @@ def idx_for_id(win_id, windows):
return i
def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False, bias=None):
def set_draw_minimal_borders(opts):
global draw_minimal_borders
draw_minimal_borders = opts.draw_minimal_borders and opts.window_margin_width == 0
def layout_dimension(start_at, length, cell_length, decoration_pairs, left_align=False, bias=None):
number_of_windows = len(decoration_pairs)
number_of_cells = length // cell_length
border_length += padding_length
space_needed_for_border = number_of_windows * 2 * border_length
space_needed_for_padding = number_of_windows * 2 * margin_length
space_needed = space_needed_for_padding + space_needed_for_border
space_needed_for_decorations = sum(map(sum, decoration_pairs))
extra = length - number_of_cells * cell_length
while extra < space_needed:
while extra < space_needed_for_decorations:
number_of_cells -= 1
extra = length - number_of_cells * cell_length
cells_per_window = number_of_cells // number_of_windows
extra -= space_needed
extra -= space_needed_for_decorations
pos = start_at
if not left_align:
pos += extra // 2
pos += border_length + margin_length
def calc_window_length(cells_in_window):
def calc_window_geom(i, cells_in_window):
nonlocal pos
pos += decoration_pairs[i][0]
inner_length = cells_in_window * cell_length
return 2 * (border_length + margin_length) + inner_length
return inner_length + decoration_pairs[i][1]
if bias is not None and number_of_windows > 1 and len(bias) == number_of_windows and cells_per_window > 5:
cells_map = [int(b * number_of_cells) for b in bias]
@@ -51,21 +58,15 @@ def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_
break
cells_map[mini] += 1
cells_map[maxi] -= 1
else:
cells_map = list(repeat(cells_per_window, number_of_windows))
extra = number_of_cells - sum(cells_map)
if extra:
cells_map[-1] += extra
for cells_per_window in cells_map:
window_length = calc_window_length(cells_per_window)
yield pos, cells_per_window
pos += window_length
return
window_length = calc_window_length(cells_per_window)
extra = number_of_cells - (cells_per_window * number_of_windows)
while number_of_windows > 0:
number_of_windows -= 1
yield pos, cells_per_window + (extra if number_of_windows == 0 else 0)
extra = number_of_cells - sum(cells_map)
if extra > 0:
cells_map[-1] += extra
for i, cells_per_window in enumerate(cells_map):
window_length = calc_window_geom(i, cells_per_window)
yield pos, cells_per_window
pos += window_length
@@ -83,9 +84,9 @@ def window_geometry(xstart, xnum, ystart, ynum):
return WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_width * xnum, bottom=ystart + cell_height * ynum)
def layout_single_window(margin_length, padding_length):
xstart, xnum = next(layout_dimension(central.left, central.width, cell_width, margin_length=margin_length, padding_length=padding_length))
ystart, ynum = next(layout_dimension(central.top, central.height, cell_height, margin_length=margin_length, padding_length=padding_length))
def layout_single_window(xdecoration_pairs, ydecoration_pairs):
xstart, xnum = next(layout_dimension(central.left, central.width, cell_width, xdecoration_pairs))
ystart, ynum = next(layout_dimension(central.top, central.height, cell_height, ydecoration_pairs))
return window_geometry(xstart, xnum, ystart, ynum)
@@ -338,19 +339,20 @@ class Layout: # {{{
# Utils {{{
def layout_single_window(self, w):
mw = self.margin_width if self.single_window_margin_width < 0 else self.single_window_margin_width
wg = layout_single_window(mw, self.padding_width)
decoration_pairs = ((self.padding_width + mw, self.padding_width + mw),)
wg = layout_single_window(decoration_pairs, decoration_pairs)
w.set_geometry(0, wg)
self.blank_rects = blank_rects_for_window(w)
def xlayout(self, num, bias=None):
return layout_dimension(
central.left, central.width, cell_width, num, self.border_width,
margin_length=self.margin_width, padding_length=self.padding_width, bias=bias)
decoration = self.margin_width + self.border_width + self.padding_width
decoration_pairs = tuple(repeat((decoration, decoration), num))
return layout_dimension(central.left, central.width, cell_width, decoration_pairs, bias=bias)
def ylayout(self, num, left_align=True, bias=None):
return layout_dimension(
central.top, central.height, cell_height, num, self.border_width, left_align=left_align,
margin_length=self.margin_width, padding_length=self.padding_width, bias=bias)
decoration = self.margin_width + self.border_width + self.padding_width
decoration_pairs = tuple(repeat((decoration, decoration), num))
return layout_dimension(central.top, central.height, cell_height, decoration_pairs, bias=bias)
def simple_blank_rects(self, first_window, last_window):
br = self.blank_rects
@@ -365,6 +367,17 @@ class Layout: # {{{
def do_layout(self, windows, active_window_idx):
raise NotImplementedError()
def resolve_borders(self, windows, active_window):
if draw_minimal_borders:
needs_borders_map = {w.id: (w is active_window or w.needs_attention) for w in windows}
yield from self.minimal_borders(windows, active_window, needs_borders_map)
else:
yield from Layout.minimal_borders(self, windows, active_window, None)
def minimal_borders(self, windows, active_window, needs_borders_map):
for w in windows:
yield all_borders
# }}}
@@ -376,7 +389,8 @@ class Stack(Layout): # {{{
def do_layout(self, windows, active_window_idx):
mw = self.margin_width if self.single_window_margin_width < 0 else self.single_window_margin_width
wg = layout_single_window(mw, self.padding_width)
decoration_pairs = ((mw + self.padding_width, mw + self.padding_width),)
wg = layout_single_window(decoration_pairs, decoration_pairs)
for i, w in enumerate(windows):
w.set_geometry(i, wg)
if w.is_visible_in_layout:
@@ -389,6 +403,8 @@ class Tall(Layout): # {{{
name = 'tall'
vlayout = Layout.ylayout
main_is_horizontal = True
only_between_border = False, False, False, True
only_main_border = False, False, True, False
def remove_all_biases(self):
self.main_bias = list(self.layout_opts['bias'])
@@ -452,6 +468,26 @@ class Tall(Layout): # {{{
self.between_blank_rect(windows[0], windows[1])
# left bottom blank rect
self.bottom_blank_rect(windows[0])
def minimal_borders(self, windows, active_window, needs_borders_map):
last_i = len(windows) - 1
for i, w in enumerate(windows):
if needs_borders_map[w.id]:
yield all_borders
continue
if i == 0:
if last_i == 1 and needs_borders_map[windows[1].id]:
yield no_borders
else:
yield self.only_main_border
continue
if i == last_i:
yield no_borders
break
if needs_borders_map[windows[i+1].id]:
yield no_borders
else:
yield self.only_between_border
# }}}
@@ -460,6 +496,8 @@ class Fat(Tall): # {{{
name = 'fat'
vlayout = Layout.xlayout
main_is_horizontal = False
only_between_border = False, False, True, False
only_main_border = False, False, False, True
def do_layout(self, windows, active_window_idx):
if len(windows) == 1:
@@ -576,6 +614,9 @@ class Grid(Layout): # {{{
if n == 1:
return self.layout_single_window(windows[0])
ncols, nrows, special_rows, special_col = self.calc_grid_size(n)
layout_data = n, ncols, nrows, special_rows, special_col
for w in windows:
w.layout_data = layout_data
win_col_map = []
@@ -597,12 +638,57 @@ class Grid(Layout): # {{{
for i in range(ncols - 1):
self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0])
def minimal_borders(self, windows, active_window, needs_borders_map):
try:
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
except Exception:
n = -1
if n != len(windows):
# Something bad happened
yield from Layout.minimal_borders(self, windows, active_window, needs_borders_map)
return
blank_row = [None for i in range(ncols)]
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
wi = iter(windows)
pos_map = {}
col_counts = []
for col in range(ncols):
rows = special_rows if col == special_col else nrows
for row in range(rows):
matrix[row][col] = wid = next(wi).id
pos_map[wid] = row, col
col_counts.append(rows)
class Vertical(Layout):
for w in windows:
wid = w.id
if needs_borders_map[wid]:
yield all_borders
continue
row, col = pos_map[wid]
if col + 1 < ncols:
next_col_has_different_count = col_counts[col + 1] != col_counts[col]
right_neighbor_id = matrix[row][col+1]
else:
right_neighbor_id = None
next_col_has_different_count = False
try:
bottom_neighbor_id = matrix[row+1][col]
except IndexError:
bottom_neighbor_id = None
yield (
False, False,
(right_neighbor_id is not None and not needs_borders_map[right_neighbor_id]) or next_col_has_different_count,
bottom_neighbor_id is not None and not needs_borders_map[bottom_neighbor_id]
)
# }}}
class Vertical(Layout): # {{{
name = 'vertical'
main_is_horizontal = False
vlayout = Layout.ylayout
only_between_border = False, False, False, True
def variable_layout(self, num_windows, biased_map):
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows else None)
@@ -641,12 +727,28 @@ class Vertical(Layout):
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
def minimal_borders(self, windows, active_window, needs_borders_map):
last_i = len(windows) - 1
for i, w in enumerate(windows):
if needs_borders_map[w.id]:
yield all_borders
continue
if i == last_i:
yield no_borders
break
if needs_borders_map[windows[i+1].id]:
yield no_borders
else:
yield self.only_between_border
# }}}
class Horizontal(Vertical):
class Horizontal(Vertical): # {{{
name = 'horizontal'
main_is_horizontal = True
vlayout = Layout.xlayout
only_between_border = False, False, True, False
def do_layout(self, windows, active_window_idx):
window_count = len(windows)

View File

@@ -12,12 +12,13 @@ from .boss import Boss
from .cli import create_opts, parse_args
from .config import cached_values_for, initial_window_size_func
from .constants import (
appname, config_dir, glfw_path, is_macos, is_wayland, kitty_exe,
logo_data_file
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
is_wayland, kitty_exe, logo_data_file
)
from .fast_data_types import (
GLFW_MOD_SUPER, create_os_window, free_font_data, glfw_init,
glfw_terminate, set_default_window_icon, set_options
GLFW_IBEAM_CURSOR, GLFW_MOD_SUPER, create_os_window, free_font_data,
glfw_init, glfw_terminate, load_png_data, set_custom_cursor,
set_default_window_icon, set_options
)
from .fonts.box_drawing import set_scale
from .fonts.render import set_font_family
@@ -28,6 +29,21 @@ from .utils import (
from .window import load_shader_programs
def set_custom_ibeam_cursor():
with open(beam_cursor_data_file, 'rb') as f:
data = f.read()
rgba_data, width, height = load_png_data(data)
c2x = os.path.splitext(beam_cursor_data_file)
with open(c2x[0] + '@2x' + c2x[1], 'rb') as f:
data = f.read()
rgba_data2, width2, height2 = load_png_data(data)
images = (rgba_data, width, height), (rgba_data2, width2, height2)
try:
set_custom_cursor(GLFW_IBEAM_CURSOR, images, 4, 8)
except Exception as e:
log_error('Failed to set custom beam cursor with error: {}'.format(e))
def talk_to_instance(args):
import json
import socket
@@ -100,6 +116,8 @@ def get_new_os_window_trigger(opts):
def _run_app(opts, args):
new_os_window_trigger = get_new_os_window_trigger(opts)
if is_macos:
set_custom_ibeam_cursor()
with cached_values_for(run_app.cached_values_name) as cached_values:
with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback:
window_id = create_os_window(

View File

@@ -119,6 +119,16 @@ contains_mouse(Window *w, OSWindow *os_window) {
return (w->visible && window_left(w, os_window) <= x && x <= window_right(w, os_window) && window_top(w, os_window) <= y && y <= window_bottom(w, os_window));
}
static inline double
distance_to_window(Window *w, OSWindow *os_window) {
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
double cx = (window_left(w, os_window) + window_right(w, os_window)) / 2.0;
double cy = (window_top(w, os_window) + window_bottom(w, os_window)) / 2.0;
return (x - cx) * (x - cx) + (y - cy) * (y - cy);
}
static bool clamp_to_window = false;
static inline bool
cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) {
WindowGeometry *g = &w->geometry;
@@ -128,6 +138,10 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) {
double mouse_x = global_state.callback_os_window->mouse_x;
double mouse_y = global_state.callback_os_window->mouse_y;
double left = window_left(w, os_window), top = window_top(w, os_window), right = window_right(w, os_window), bottom = window_bottom(w, os_window);
if (clamp_to_window) {
mouse_x = MIN(MAX(mouse_x, left), right);
mouse_y = MIN(MAX(mouse_y, top), bottom);
}
if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false;
if (mouse_x >= g->right) qx = screen->columns - 1;
else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / os_window->fonts_data->cell_width);
@@ -147,10 +161,15 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
Screen *screen = w->render_data.screen;
if (from_button) {
if (is_release) {
global_state.active_drag_in_window = 0;
w->last_drag_scroll_at = 0;
if (screen->selection.in_progress)
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true);
}
else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
else {
global_state.active_drag_in_window = w->id;
screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
}
} else if (screen->selection.in_progress) {
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
}
@@ -340,10 +359,18 @@ HANDLER(handle_button_event) {
}
}
static inline int
currently_pressed_button() {
for (int i = 0; i < GLFW_MOUSE_BUTTON_5; i++) {
if (global_state.callback_os_window->mouse_button_pressed[i]) return i;
}
return -1;
}
HANDLER(handle_event) {
switch(button) {
case -1:
for (int i = 0; i < GLFW_MOUSE_BUTTON_5; i++) { if (global_state.callback_os_window->mouse_button_pressed[i]) { button = i; break; } }
button = currently_pressed_button();
handle_move_event(w, button, modifiers, window_idx);
break;
case GLFW_MOUSE_BUTTON_LEFT:
@@ -372,6 +399,16 @@ mouse_in_region(Region *r) {
return true;
}
static inline Window*
window_for_id(id_type window_id) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->windows + i;
if (w->id == window_id) return w;
}
return NULL;
}
static inline Window*
window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
Region central, tab_bar;
@@ -386,7 +423,21 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
}
}
return NULL;
}
static inline Window*
closest_window_for_event(unsigned int *window_idx) {
Window *ans = NULL;
double closest_distance = UINT_MAX;
if (global_state.callback_os_window->num_tabs > 0) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->windows + i;
double d = distance_to_window(w, global_state.callback_os_window);
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
}
}
return ans;
}
void
@@ -405,12 +456,34 @@ mouse_event(int button, int modifiers) {
MouseShape old_cursor = mouse_cursor_shape;
bool in_tab_bar;
unsigned int window_idx = 0;
Window *w = window_for_event(&window_idx, &in_tab_bar);
Window *w = NULL;
if (button == -1 && global_state.active_drag_in_window) { // drag move
w = window_for_id(global_state.active_drag_in_window);
if (w) {
button = currently_pressed_button();
if (button == GLFW_MOUSE_BUTTON_LEFT) {
clamp_to_window = true;
handle_move_event(w, button, modifiers, window_idx);
clamp_to_window = false;
return;
}
}
}
w = window_for_event(&window_idx, &in_tab_bar);
if (in_tab_bar) {
mouse_cursor_shape = HAND;
handle_tab_bar_mouse(button, modifiers);
} else if(w) {
handle_event(w, button, modifiers, window_idx);
} else if (button == GLFW_MOUSE_BUTTON_LEFT && global_state.callback_os_window->mouse_button_pressed[button]) {
// initial click, clamp it to the closest window
w = closest_window_for_event(&window_idx);
if (w) {
clamp_to_window = true;
handle_event(w, button, modifiers, window_idx);
clamp_to_window = false;
}
}
if (mouse_cursor_shape != old_cursor) {
set_mouse_cursor(mouse_cursor_shape);

126
kitty/png-reader.c Normal file
View File

@@ -0,0 +1,126 @@
/*
* png-reader.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "png-reader.h"
struct fake_file { const uint8_t *buf; size_t sz, cur; };
static void
read_png_from_buffer(png_structp png, png_bytep out, png_size_t length) {
struct fake_file *f = png_get_io_ptr(png);
if (f) {
size_t amt = MIN(length, f->sz - f->cur);
memcpy(out, f->buf + f->cur, amt);
f->cur += amt;
}
}
struct custom_error_handler {
jmp_buf jb;
png_error_handler_func err_handler;
};
static void
read_png_error_handler(png_structp png_ptr, png_const_charp msg) {
struct custom_error_handler *eh;
eh = png_get_error_ptr(png_ptr);
if (eh == NULL) fatal("read_png_error_handler: could not retrieve error handler");
if(eh->err_handler) eh->err_handler("EBADPNG", msg);
longjmp(eh->jb, 1);
}
static void
read_png_warn_handler(png_structp UNUSED png_ptr, png_const_charp UNUSED msg) {
// ignore warnings
}
#define ABRT(code, msg) { if(d->err_handler) d->err_handler(#code, msg); goto err; }
void
inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) {
struct fake_file f = {.buf = buf, .sz = bufsz};
png_structp png = NULL;
png_infop info = NULL;
struct custom_error_handler eh = {.err_handler = d->err_handler};
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &eh, read_png_error_handler, read_png_warn_handler);
if (!png) ABRT(ENOMEM, "Failed to create PNG read structure");
info = png_create_info_struct(png);
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
if (setjmp(eh.jb)) goto err;
png_set_read_fn(png, &f, read_png_from_buffer);
png_read_info(png, info);
png_byte color_type, bit_depth;
d->width = png_get_image_width(png, info);
d->height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);
// Ensure we get RGBA data out of libpng
if (bit_depth == 16) png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
// These color_type don't have an alpha channel then fill it with 0xff.
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png);
png_read_update_info(png, info);
int rowbytes = png_get_rowbytes(png, info);
d->sz = rowbytes * d->height * sizeof(png_byte);
d->decompressed = malloc(d->sz + 16);
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
d->row_pointers = malloc(d->height * sizeof(png_bytep));
if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG");
for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes;
png_read_image(png, d->row_pointers);
d->ok = true;
err:
if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL);
return;
}
static void
png_error_handler(const char *code, const char *msg) {
PyErr_Format(PyExc_ValueError, "[%s] %s", code, msg);
}
static PyObject*
load_png_data(PyObject *self UNUSED, PyObject *args) {
Py_ssize_t sz;
const char *data;
if (!PyArg_ParseTuple(args, "s#", &data, &sz)) return NULL;
png_read_data d = {.err_handler=png_error_handler};
inflate_png_inner(&d, (const uint8_t*)data, sz);
PyObject *ans = NULL;
if (d.ok && !PyErr_Occurred()) {
ans = Py_BuildValue("y#ii", d.decompressed, (int)d.sz, d.width, d.height);
} else {
if (!PyErr_Occurred()) PyErr_SetString(PyExc_ValueError, "Unknown error while reading PNG data");
}
free(d.decompressed);
free(d.row_pointers);
return ans;
}
static PyMethodDef module_methods[] = {
METHODB(load_png_data, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_png_reader(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

21
kitty/png-reader.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "data-types.h"
#include <png.h>
typedef void(*png_error_handler_func)(const char*, const char*);
typedef struct {
uint8_t *decompressed;
bool ok;
png_bytep *row_pointers;
int width, height;
size_t sz;
png_error_handler_func err_handler;
} png_read_data;
void inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz);

View File

@@ -1764,7 +1764,7 @@ screen_selection_range_for_line(Screen *self, index_type y, index_type *start, i
index_type xlimit = line->xnum, xstart = 0;
while (xlimit > 0 && CHAR_IS_BLANK(line->cpu_cells[xlimit - 1].ch)) xlimit--;
while (xstart < xlimit && CHAR_IS_BLANK(line->cpu_cells[xstart].ch)) xstart++;
*start = xstart; *end = xlimit;
*start = xstart; *end = xlimit > 0 ? xlimit - 1 : 0;
return true;
}

View File

@@ -387,6 +387,7 @@ PYWRAP1(set_options) {
S(macos_option_as_alt, PyObject_IsTrue);
S(macos_hide_titlebar, PyObject_IsTrue);
S(macos_quit_when_last_window_closed, PyObject_IsTrue);
S(macos_window_resizable, PyObject_IsTrue);
S(x11_hide_window_decorations, PyObject_IsTrue);
S(macos_hide_from_tasks, PyObject_IsTrue);

View File

@@ -23,7 +23,7 @@ typedef struct {
color_type url_color, background, active_border_color, inactive_border_color, bell_border_color;
double repaint_delay, input_delay;
bool focus_follows_mouse;
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks, x11_hide_window_decorations, macos_quit_when_last_window_closed;
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks, x11_hide_window_decorations, macos_quit_when_last_window_closed, macos_window_resizable;
int adjust_line_height_px, adjust_column_width_px;
float adjust_line_height_frac, adjust_column_width_frac;
float background_opacity, dim_opacity;
@@ -141,6 +141,7 @@ typedef struct {
bool in_sequence_mode;
double font_sz_in_pts;
struct { double x, y; } default_dpi;
id_type active_drag_in_window;
} GlobalState;
extern GlobalState global_state;

View File

@@ -153,7 +153,7 @@ class TabBar:
s = self.screen
s.cursor.x = 0
s.erase_in_line(2, False)
max_title_length = (self.screen_geometry.xnum // max(1, len(data))) - 1
max_title_length = max(1, (self.screen_geometry.xnum // max(1, len(data))) - 1)
cr = []
last_tab = data[-1] if data else None

View File

@@ -7,7 +7,7 @@ import os
import sys
import weakref
from collections import deque
from enum import Enum
from enum import IntEnum
from .child import cwd_of_process
from .config import build_ansi_color_table
@@ -33,7 +33,7 @@ from .utils import (
)
class DynamicColor(Enum):
class DynamicColor(IntEnum):
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
@@ -94,6 +94,7 @@ class Window:
def __init__(self, tab, child, opts, args, override_title=None):
self.action_on_close = None
self.layout_data = None
self.needs_attention = False
self.override_title = override_title
self.overlay_window_id = None
@@ -101,12 +102,12 @@ class Window:
self.default_title = os.path.basename(child.argv[0] or appname)
self.child_title = self.default_title
self.id = add_window(tab.os_window_id, tab.id, self.title)
self.clipboard_control_buffers = {'p': '', 'c': ''}
if not self.id:
raise Exception('No tab with id: {} in OS Window: {} was found, or the window counter wrapped'.format(tab.id, tab.os_window_id))
self.tab_id = tab.id
self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab)
self.clipboard_control_buffers = {'p': '', 'c': ''}
self.destroyed = False
self.click_queue = deque(maxlen=3)
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)

View File

@@ -10,7 +10,7 @@ from base64 import standard_b64decode, standard_b64encode
from io import BytesIO
from kitty.fast_data_types import (
parse_bytes, set_send_to_gpu, shm_unlink, shm_write
load_png_data, parse_bytes, set_send_to_gpu, shm_unlink, shm_write
)
from . import BaseTest
@@ -209,8 +209,12 @@ class TestGraphics(BaseTest):
def test_load_png_simple(self):
# 1x1 transparent PNG
png_data = standard_b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==')
expected = b'\x00\xff\xff\x7f'
self.ae(load_png_data(png_data), (expected, 1, 1))
s, g, l, sl = load_helpers(self)
sl(png_data, f=100, expecting_data=b'\x00\xff\xff\x7f')
sl(png_data, f=100, expecting_data=expected)
# test error handling for loading bad png data
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, b'dsfsdfsfsfd')
def test_image_put(self):
cw, ch = 10, 20

BIN
logo/beam-cursor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

BIN
logo/beam-cursor@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -584,6 +584,8 @@ def package(args, for_bundle=False, sh_launcher=False):
subprocess.check_call(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'])
shutil.copy2('__main__.py', libdir)
shutil.copy2('logo/kitty.rgba', os.path.join(libdir, 'logo'))
shutil.copy2('logo/beam-cursor.png', os.path.join(libdir, 'logo'))
shutil.copy2('logo/beam-cursor@2x.png', os.path.join(libdir, 'logo'))
def src_ignore(parent, entries):
return [