Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f8519a52 | ||
|
|
056be45dd5 | ||
|
|
3ee22e545e | ||
|
|
db5aa36f44 | ||
|
|
cc767061cc | ||
|
|
1d22b75090 | ||
|
|
4313531432 | ||
|
|
1faddeb402 | ||
|
|
2b035739f8 | ||
|
|
aeed20087e | ||
|
|
a239f41495 | ||
|
|
b36d6967a5 | ||
|
|
7c1d13b7db | ||
|
|
3e99949db8 | ||
|
|
aa12a65a8f | ||
|
|
18804efb7e | ||
|
|
a5d27196ea | ||
|
|
5af6d88ffb | ||
|
|
472a3bc69f | ||
|
|
f0f1c911ea | ||
|
|
fcb0033d25 | ||
|
|
c8b1e76783 | ||
|
|
b50f621e61 | ||
|
|
77c4f5fecc | ||
|
|
e27ea3337a | ||
|
|
23fbf6f157 | ||
|
|
321e5081ed | ||
|
|
33cac29f87 | ||
|
|
12be438dea | ||
|
|
00092db6ad | ||
|
|
619c3a0025 | ||
|
|
f12697c897 |
@@ -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]
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
5
glfw/glfw3.h
vendored
@@ -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
5
glfw/input.c
vendored
@@ -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
2
glfw/internal.h
vendored
@@ -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
2
glfw/null_window.c
vendored
@@ -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
2
glfw/win32_window.c
vendored
@@ -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
2
glfw/wl_window.c
vendored
@@ -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
2
glfw/x11_window.c
vendored
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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') # {{{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
2
kitty/glfw-wrapper.h
generated
@@ -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
|
||||
|
||||
|
||||
53
kitty/glfw.c
53
kitty/glfw.c
@@ -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 --------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
176
kitty/layout.py
176
kitty/layout.py
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
126
kitty/png-reader.c
Normal 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
21
kitty/png-reader.h
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
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
BIN
logo/beam-cursor@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
2
setup.py
2
setup.py
@@ -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 [
|
||||
|
||||
Reference in New Issue
Block a user