Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81bda736d8 | ||
|
|
ec3998a9b8 | ||
|
|
01be77e0bc | ||
|
|
6f40b8d0a1 | ||
|
|
a083aa04b2 | ||
|
|
1f33348cf6 | ||
|
|
a45651933c | ||
|
|
ab57fbff73 | ||
|
|
6b3f774f05 | ||
|
|
1ef2880c6c | ||
|
|
ace152d8a3 | ||
|
|
8d08036a9d | ||
|
|
b0876fae64 | ||
|
|
01633776d3 | ||
|
|
e4202228eb | ||
|
|
79d5049273 | ||
|
|
9120e3b790 | ||
|
|
974e6140b3 | ||
|
|
1fd338cc6c | ||
|
|
bc61a81d7a | ||
|
|
06b30d1cc3 | ||
|
|
785973bb5e | ||
|
|
78efefcea1 | ||
|
|
1bb6242020 | ||
|
|
1af426bf84 | ||
|
|
dc4762a69a | ||
|
|
a785b77da9 | ||
|
|
1342767805 | ||
|
|
a3fc3233cd | ||
|
|
8093d70643 | ||
|
|
9d9ec84b54 | ||
|
|
6b26088c27 | ||
|
|
43af715caa | ||
|
|
c6eea488b1 | ||
|
|
b1c3999a81 | ||
|
|
4f19403518 | ||
|
|
4d1b537f78 | ||
|
|
dd54db47a9 | ||
|
|
44775e4644 | ||
|
|
805921d6a3 | ||
|
|
d7cd6edaa5 | ||
|
|
6a472bd6da | ||
|
|
d0c6ce9fd7 | ||
|
|
df48e2f9e2 | ||
|
|
2b4b3bbf18 | ||
|
|
6ea5027bf6 | ||
|
|
384ccb4fc7 | ||
|
|
ff763b099e | ||
|
|
741ae4c04b | ||
|
|
d84ec941c0 | ||
|
|
f0b5dd9799 | ||
|
|
cec4371b51 | ||
|
|
c0ff39c59d | ||
|
|
022634f2d9 | ||
|
|
392f576d07 | ||
|
|
6d59e2125e | ||
|
|
6476a1d2e0 | ||
|
|
ca998fb4b0 | ||
|
|
48631c4ea6 | ||
|
|
294be2a772 | ||
|
|
533688a67a | ||
|
|
4309e2371d | ||
|
|
ea30c84240 | ||
|
|
d4c2b13dda | ||
|
|
1decae2938 | ||
|
|
145ec86d3b | ||
|
|
e17c96b20f | ||
|
|
2cfcefd9eb | ||
|
|
356e950122 | ||
|
|
64dee63b73 | ||
|
|
431d013d32 | ||
|
|
40eb8455d0 | ||
|
|
c46b36509b | ||
|
|
f7ff63132d | ||
|
|
e17036a7a5 | ||
|
|
e678c41f56 | ||
|
|
ee3462592a | ||
|
|
521f921424 | ||
|
|
ee47a10b37 | ||
|
|
b81700144d | ||
|
|
9f28dd61d3 | ||
|
|
dad7d366b3 | ||
|
|
209b78a3ec | ||
|
|
17f34d1f95 | ||
|
|
26699cd9ef | ||
|
|
907db09a58 | ||
|
|
d506fa0565 | ||
|
|
de1a0d48a6 | ||
|
|
a20e5fefd3 | ||
|
|
44558560f9 | ||
|
|
677f154f03 | ||
|
|
0adaf064e7 | ||
|
|
3c828feef2 | ||
|
|
4d3c5e61fd | ||
|
|
67baefb52a | ||
|
|
d509a4fa8c | ||
|
|
cad1c12b7b | ||
|
|
9f751d8715 | ||
|
|
cf8a1dff33 | ||
|
|
973d98457f | ||
|
|
ce262f810d | ||
|
|
bd31c2b484 | ||
|
|
ef0e610802 | ||
|
|
3040d4c83f | ||
|
|
c9252e8639 | ||
|
|
e2e701460b | ||
|
|
f0c3641ebd | ||
|
|
025f6d7701 | ||
|
|
c68516ca8d | ||
|
|
a9e739e08d | ||
|
|
cc15ff73b7 | ||
|
|
fd3a4536e7 | ||
|
|
75caafb5ac | ||
|
|
cf2f8165eb | ||
|
|
a2f80740d7 | ||
|
|
c7703791d1 | ||
|
|
c39e9e9c61 | ||
|
|
713059319c | ||
|
|
d7a7509da6 | ||
|
|
57e6e7b2c5 | ||
|
|
d675977f10 | ||
|
|
7ef911e17d | ||
|
|
9a384c5045 | ||
|
|
2b3753d99c | ||
|
|
d173ab9f45 | ||
|
|
fbd0e8e26a | ||
|
|
50414b333a | ||
|
|
ee2fdb3d25 | ||
|
|
2b884377d4 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -23,7 +23,7 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Enviroment details**
|
||||
**Environment details**
|
||||
OS: Name and version of operating system(s)
|
||||
|
||||
```
|
||||
|
||||
2
.github/workflows/ci.py
vendored
2
.github/workflows/ci.py
vendored
@@ -106,7 +106,7 @@ def main():
|
||||
if is_bundle:
|
||||
setup_bundle_env()
|
||||
else:
|
||||
if not is_macos:
|
||||
if not is_macos and 'pythonLocation' in os.environ:
|
||||
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
|
||||
action = sys.argv[-1]
|
||||
if action in ('build', 'package'):
|
||||
|
||||
41
.github/workflows/codeql-analysis.yml
vendored
Normal file
41
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 22 * * 5'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
6
Brewfile
6
Brewfile
@@ -1,5 +1,5 @@
|
||||
pkg-config
|
||||
python
|
||||
imagemagick
|
||||
harfbuzz
|
||||
python
|
||||
imagemagick
|
||||
harfbuzz
|
||||
sphinx-doc
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
### Reporting bugs
|
||||
|
||||
Please first search existing bug reports (especially closed ones) for a report
|
||||
that matches your issue.
|
||||
that matches your issue.
|
||||
|
||||
When reporting a bug, provide full details of your environment, that means, at
|
||||
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal
|
||||
|
||||
@@ -62,7 +62,12 @@ def build_c_extensions(ext_dir, args):
|
||||
|
||||
|
||||
def run_tests(path_to_kitty, cwd_on_failure):
|
||||
ret = run(PYTHON, 'test.py', cwd=cwd_on_failure)
|
||||
kw = {'cwd': cwd_on_failure}
|
||||
if not ismacos:
|
||||
# this is needed for the spawn test which starts an interpreter
|
||||
# using the kitty launcher.
|
||||
kw['PYTHONHOME'] = PREFIX
|
||||
ret = run(PYTHON, 'test.py', **kw)
|
||||
if ret != 0:
|
||||
os.chdir(cwd_on_failure)
|
||||
print(
|
||||
|
||||
@@ -165,7 +165,7 @@ def create_tarfile(env, compression_level='9'):
|
||||
base = OUTPUT_DIR
|
||||
try:
|
||||
shutil.rmtree(base)
|
||||
except EnvironmentError as err:
|
||||
except OSError as err:
|
||||
if err.errno != errno.ENOENT:
|
||||
raise
|
||||
os.mkdir(base)
|
||||
|
||||
@@ -241,7 +241,7 @@
|
||||
"hash": "sha256:4675a79f091020817a98fd0484e7208c8762242266967f55a67776936c2e294d",
|
||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"name": "wayland-protocols",
|
||||
|
||||
@@ -4,6 +4,71 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.18.2 [2020-07-28]
|
||||
--------------------
|
||||
|
||||
- X11: Improve handling of multiple keyboards. Now pressing a modifier key in
|
||||
one keyboard and a normal key in another works (:iss:`2362`). Don't rebuild
|
||||
keymaps on new keyboard events that only change geometry (:iss:`2787`).
|
||||
Better handling of multiple keyboards with incompatible layouts (:iss:`2726`)
|
||||
|
||||
- Improve anti-aliasing of triangular box drawing characters, noticeable on
|
||||
low-resolution screens (:iss:`2844`)
|
||||
|
||||
- Fix ``kitty @ send-text`` not working reliably when using a socket for remote
|
||||
control (:iss:`2852`)
|
||||
|
||||
- Implement support for box drawing rounded-corners characters (:iss:`2240`)
|
||||
|
||||
- Allow setting the class for new OS windows in a session file
|
||||
|
||||
- When a character from the Unicode Dingbat block is followed by a space, use
|
||||
the extra space to render a larger version of the character (:iss:`2850`)
|
||||
|
||||
- macOS: Fix the LC_TYPE env var being set to UTF-8 on systems in which the
|
||||
language and country code do not form a valid locale (:iss:`1233`)
|
||||
|
||||
- macOS: Fix :kbd:`cmd+plus` not changing font size (:iss:`2839`)
|
||||
|
||||
- Make neighboring window selection in grid and splits layouts more intelligent
|
||||
(:pull:`2840`)
|
||||
|
||||
- Allow passing the current selection to kittens (:iss:`2796`)
|
||||
|
||||
- Fix pre-edit text not always being cleared with ibus input (:iss:`2862`)
|
||||
|
||||
- Allow setting the :opt:`background_opacity` of new OS windows created via
|
||||
:option:`kitty --single-instance` using the :option:`kitty --override` command line
|
||||
argument (:iss:`2806`)
|
||||
|
||||
- Fix the CSI J (Erase in display ED) escape code not removing line continued
|
||||
markers (:iss:`2809`)
|
||||
|
||||
- hints kitten: In linenumber mode expand paths that starts with ~
|
||||
(:iss:`2822`)
|
||||
|
||||
- Fix ``launch --location=last`` not working (:iss:`2841`)
|
||||
|
||||
- Fix incorrect centering when a PUA or symbol glyph is followed by more than one space
|
||||
|
||||
- Have the :opt:`confirm_os_window_close` option also apply when closing tabs
|
||||
with multiple windows (:iss:`2857`)
|
||||
|
||||
- Add support for legacy DECSET codes 47, 1047 and 1048 (:pull:`2871`)
|
||||
|
||||
- macOS: no longer render emoji 20% below the baseline. This caused some emoji
|
||||
to be cut-off and also look misaligned with very high cells (:iss:`2873`)
|
||||
|
||||
- macOS: Make the window id of OS windows available in the ``WINDOWID``
|
||||
environment variable (:pull:`2877`)
|
||||
|
||||
- Wayland: Fix a regression in 0.18.0 that could cause crashes related to mouse
|
||||
cursors in some rare circumstances (:iss:`2810`)
|
||||
|
||||
- Fix change in window size that does not change number of cells not being
|
||||
reported to the kernel (:iss:`2880`)
|
||||
|
||||
|
||||
0.18.1 [2020-06-23]
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -346,6 +346,8 @@ For example:
|
||||
new_os_window
|
||||
# set new window size to 80x25 cells
|
||||
os_window_size 80c 25c
|
||||
# set the --class for the new OS window
|
||||
os_window_class mywindow
|
||||
launch sh
|
||||
# Make the current window the active (focused) window
|
||||
focus
|
||||
|
||||
@@ -106,3 +106,6 @@ Miscellaneous
|
||||
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
|
||||
reload key bindings from :file:`kitty.conf` without needing to restart
|
||||
kitty
|
||||
|
||||
`kitti3 <https://github.com/LandingEllipse/kitti3>`_
|
||||
allow using kitty as a drop-down terminal under the i3 window manager
|
||||
|
||||
@@ -58,7 +58,8 @@ You can pass arguments to kittens by defining them in the map directive in
|
||||
These will be available as the ``args`` parameter in the ``main()`` and
|
||||
``handle_result()`` functions. Note also that the current working directory
|
||||
of the kitten is set to the working directory of whatever program is
|
||||
running in the active kitty window.
|
||||
running in the active kitty window. The special argument ``@selection``
|
||||
is replaced by the currently selected text in the active kitty window.
|
||||
|
||||
|
||||
Passing the contents of the screen to the kitten
|
||||
@@ -87,7 +88,8 @@ This will send the plain text of the active window to the kitten's
|
||||
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
|
||||
instead. If you want line wrap markers as well, use ``screen-ansi``
|
||||
or just ``screen``. For the scrollback buffer as well, use
|
||||
``history``, ``ansi-history`` or ``screen-history``.
|
||||
``history``, ``ansi-history`` or ``screen-history``. To get
|
||||
the currently selected text, use ``selection``.
|
||||
|
||||
|
||||
Using kittens to script kitty, without any terminal UI
|
||||
|
||||
@@ -24,7 +24,7 @@ a leading period and the index for the match if you don't like to use arrow
|
||||
keys.
|
||||
|
||||
You can switch between modes using either the function keys or by pressing
|
||||
:kbd:`Ctrl+Tab` and :kbd:`Ctrl+Shift+Tab`.
|
||||
:kbd:`Ctrl+[` and :kbd:`Ctrl+]`.
|
||||
|
||||
|
||||
Command Line Interface
|
||||
|
||||
@@ -5,7 +5,7 @@ Launching programs in new windows/tabs
|
||||
|
||||
|
||||
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
|
||||
in news windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
|
||||
in new windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
|
||||
It is very powerful and allows sending the contents of
|
||||
the current window to the launched program, as well as many other options.
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
|
||||
Further, since it is invalid, it should be ignored by terminal emulators
|
||||
that do not support this extension, thereby making it safe to use, simply
|
||||
always send it before starting a new OSC 52 paste, even if you aren't chunking
|
||||
up large pastes, that way kitty wont concatenate your paste, and it will have
|
||||
up large pastes, that way kitty won't concatenate your paste, and it will have
|
||||
no ill-effects in other terminal emulators.
|
||||
|
||||
In case you're using software that can't be easily adapted to this
|
||||
|
||||
@@ -669,8 +669,6 @@ int _glfwPlatformInit(void)
|
||||
if (!initializeTIS())
|
||||
return false;
|
||||
|
||||
_glfwInitJoysticksNS();
|
||||
|
||||
_glfwPollMonitorsNS();
|
||||
return true;
|
||||
|
||||
@@ -723,7 +721,6 @@ void _glfwPlatformTerminate(void)
|
||||
free(_glfw.ns.clipboardString);
|
||||
|
||||
_glfwTerminateNSGL();
|
||||
_glfwTerminateJoysticksNS();
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
4
glfw/cocoa_joystick.h
vendored
4
glfw/cocoa_joystick.h
vendored
@@ -44,7 +44,3 @@ typedef struct _GLFWjoystickNS
|
||||
CFMutableArrayRef hats;
|
||||
} _GLFWjoystickNS;
|
||||
|
||||
|
||||
void _glfwInitJoysticksNS(void);
|
||||
void _glfwTerminateJoysticksNS(void);
|
||||
|
||||
|
||||
@@ -233,6 +233,19 @@ static void matchCallback(void* context UNUSED,
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (page == kHIDPage_Simulation)
|
||||
{
|
||||
switch (usage)
|
||||
{
|
||||
case kHIDUsage_Sim_Accelerator:
|
||||
case kHIDUsage_Sim_Brake:
|
||||
case kHIDUsage_Sim_Throttle:
|
||||
case kHIDUsage_Sim_Rudder:
|
||||
case kHIDUsage_Sim_Steering:
|
||||
target = axes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
|
||||
target = buttons;
|
||||
|
||||
@@ -291,12 +304,10 @@ static void removeCallback(void* context UNUSED,
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize joystick interface
|
||||
//
|
||||
void _glfwInitJoysticksNS(void)
|
||||
bool _glfwPlatformInitJoysticks(void)
|
||||
{
|
||||
CFMutableArrayRef matching;
|
||||
const long usages[] =
|
||||
@@ -315,7 +326,7 @@ void _glfwInitJoysticksNS(void)
|
||||
if (!matching)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++)
|
||||
@@ -370,26 +381,24 @@ void _glfwInitJoysticksNS(void)
|
||||
// Execute the run loop once in order to register any initially-attached
|
||||
// joysticks
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Close all opened joystick handles
|
||||
//
|
||||
void _glfwTerminateJoysticksNS(void)
|
||||
void _glfwPlatformTerminateJoysticks(void)
|
||||
{
|
||||
int jid;
|
||||
|
||||
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
||||
closeJoystick(_glfw.joysticks + jid);
|
||||
|
||||
CFRelease(_glfw.ns.hidManager);
|
||||
_glfw.ns.hidManager = NULL;
|
||||
if (_glfw.ns.hidManager)
|
||||
{
|
||||
CFRelease(_glfw.ns.hidManager);
|
||||
_glfw.ns.hidManager = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
|
||||
{
|
||||
if (mode & _GLFW_POLL_AXES)
|
||||
|
||||
@@ -279,14 +279,20 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
|
||||
CFSTR("IOFBCurrentPixelCount"),
|
||||
kCFAllocatorDefault,
|
||||
kNilOptions);
|
||||
if (!clockRef || !countRef)
|
||||
break;
|
||||
|
||||
uint32_t clock = 0, count = 0;
|
||||
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
|
||||
CFNumberGetValue(countRef, kCFNumberIntType, &count);
|
||||
CFRelease(clockRef);
|
||||
CFRelease(countRef);
|
||||
|
||||
if (clockRef)
|
||||
{
|
||||
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
|
||||
CFRelease(clockRef);
|
||||
}
|
||||
|
||||
if (countRef)
|
||||
{
|
||||
CFNumberGetValue(countRef, kCFNumberIntType, &count);
|
||||
CFRelease(countRef);
|
||||
}
|
||||
|
||||
if (clock > 0 && count > 0)
|
||||
refreshRate = clock / (double) count;
|
||||
|
||||
5
glfw/cocoa_platform.h
vendored
5
glfw/cocoa_platform.h
vendored
@@ -96,16 +96,11 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
|
||||
#include "posix_thread.h"
|
||||
#include "cocoa_joystick.h"
|
||||
#include "nsgl_context.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
|
||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||
|
||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
|
||||
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
|
||||
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
|
||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns
|
||||
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns
|
||||
|
||||
@@ -1854,6 +1854,11 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
[window->ns.object setLevel:NSNormalWindowLevel];
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||
{
|
||||
[window->ns.object setIgnoresMouseEvents:enabled];
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||
{
|
||||
return (float) [window->ns.object alphaValue];
|
||||
@@ -2105,6 +2110,47 @@ const char* _glfwPlatformGetClipboardString(void)
|
||||
return _glfw.ns.clipboardString;
|
||||
}
|
||||
|
||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
||||
{
|
||||
if (_glfw.egl.ANGLE_platform_angle)
|
||||
{
|
||||
int type = 0;
|
||||
|
||||
if (_glfw.egl.ANGLE_platform_angle_opengl)
|
||||
{
|
||||
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
|
||||
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
|
||||
}
|
||||
|
||||
if (_glfw.egl.ANGLE_platform_angle_metal)
|
||||
{
|
||||
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL)
|
||||
type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE;
|
||||
}
|
||||
|
||||
if (type)
|
||||
{
|
||||
*attribs = calloc(3, sizeof(EGLint));
|
||||
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
|
||||
(*attribs)[1] = type;
|
||||
(*attribs)[2] = EGL_NONE;
|
||||
return EGL_PLATFORM_ANGLE_ANGLE;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
||||
{
|
||||
return EGL_DEFAULT_DISPLAY;
|
||||
}
|
||||
|
||||
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
||||
{
|
||||
return window->ns.layer;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
|
||||
|
||||
68
glfw/egl_context.c
vendored
68
glfw/egl_context.c
vendored
@@ -273,6 +273,8 @@ static void destroyContextEGL(_GLFWwindow* window)
|
||||
bool _glfwInitEGL(void)
|
||||
{
|
||||
int i;
|
||||
EGLint* attribs = NULL;
|
||||
const char* extensions;
|
||||
const char* sonames[] =
|
||||
{
|
||||
#if defined(_GLFW_EGL_LIBRARY)
|
||||
@@ -351,7 +353,51 @@ bool _glfwInitEGL(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
_glfw.egl.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY);
|
||||
extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
|
||||
if (extensions && eglGetError() == EGL_SUCCESS)
|
||||
_glfw.egl.EXT_client_extensions = true;
|
||||
|
||||
if (_glfw.egl.EXT_client_extensions)
|
||||
{
|
||||
_glfw.egl.EXT_platform_base =
|
||||
_glfwStringInExtensionString("EGL_EXT_platform_base", extensions);
|
||||
_glfw.egl.EXT_platform_x11 =
|
||||
_glfwStringInExtensionString("EGL_EXT_platform_x11", extensions);
|
||||
_glfw.egl.EXT_platform_wayland =
|
||||
_glfwStringInExtensionString("EGL_EXT_platform_wayland", extensions);
|
||||
_glfw.egl.ANGLE_platform_angle =
|
||||
_glfwStringInExtensionString("EGL_ANGLE_platform_angle", extensions);
|
||||
_glfw.egl.ANGLE_platform_angle_opengl =
|
||||
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_opengl", extensions);
|
||||
_glfw.egl.ANGLE_platform_angle_d3d =
|
||||
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_d3d", extensions);
|
||||
_glfw.egl.ANGLE_platform_angle_vulkan =
|
||||
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_vulkan", extensions);
|
||||
_glfw.egl.ANGLE_platform_angle_metal =
|
||||
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_metal", extensions);
|
||||
}
|
||||
|
||||
if (_glfw.egl.EXT_platform_base)
|
||||
{
|
||||
_glfw.egl.GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)
|
||||
eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||
_glfw.egl.CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)
|
||||
eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
|
||||
}
|
||||
|
||||
_glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs);
|
||||
if (_glfw.egl.platform)
|
||||
{
|
||||
_glfw.egl.display =
|
||||
eglGetPlatformDisplayEXT(_glfw.egl.platform,
|
||||
_glfwPlatformGetEGLNativeDisplay(),
|
||||
attribs);
|
||||
}
|
||||
else
|
||||
_glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay());
|
||||
|
||||
free(attribs);
|
||||
|
||||
if (_glfw.egl.display == EGL_NO_DISPLAY)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
@@ -419,6 +465,7 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
EGLint attribs[40];
|
||||
EGLConfig config;
|
||||
EGLContext share = NULL;
|
||||
EGLNativeWindowType native;
|
||||
int index = 0;
|
||||
|
||||
if (!_glfw.egl.display)
|
||||
@@ -554,11 +601,20 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
|
||||
setAttrib(EGL_NONE, EGL_NONE);
|
||||
|
||||
window->context.egl.surface =
|
||||
eglCreateWindowSurface(_glfw.egl.display,
|
||||
config,
|
||||
_GLFW_EGL_NATIVE_WINDOW,
|
||||
attribs);
|
||||
native = _glfwPlatformGetEGLNativeWindow(window);
|
||||
// HACK: ANGLE does not implement eglCreatePlatformWindowSurfaceEXT
|
||||
// despite reporting EGL_EXT_platform_base
|
||||
if (_glfw.egl.platform && _glfw.egl.platform != EGL_PLATFORM_ANGLE_ANGLE)
|
||||
{
|
||||
window->context.egl.surface =
|
||||
eglCreatePlatformWindowSurfaceEXT(_glfw.egl.display, config, native, attribs);
|
||||
}
|
||||
else
|
||||
{
|
||||
window->context.egl.surface =
|
||||
eglCreateWindowSurface(_glfw.egl.display, config, native, attribs);
|
||||
}
|
||||
|
||||
if (window->context.egl.surface == EGL_NO_SURFACE)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
|
||||
54
glfw/egl_context.h
vendored
54
glfw/egl_context.h
vendored
@@ -25,26 +25,10 @@
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
#if defined(_GLFW_USE_EGLPLATFORM_H)
|
||||
#include <EGL/eglplatform.h>
|
||||
#elif defined(_GLFW_WIN32)
|
||||
#if defined(_GLFW_WIN32)
|
||||
#define EGLAPIENTRY __stdcall
|
||||
typedef HDC EGLNativeDisplayType;
|
||||
typedef HWND EGLNativeWindowType;
|
||||
#elif defined(_GLFW_COCOA)
|
||||
#define EGLAPIENTRY
|
||||
typedef void* EGLNativeDisplayType;
|
||||
typedef id EGLNativeWindowType;
|
||||
#elif defined(_GLFW_X11)
|
||||
#define EGLAPIENTRY
|
||||
typedef Display* EGLNativeDisplayType;
|
||||
typedef Window EGLNativeWindowType;
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
#define EGLAPIENTRY
|
||||
typedef struct wl_display* EGLNativeDisplayType;
|
||||
typedef struct wl_egl_window* EGLNativeWindowType;
|
||||
#else
|
||||
#error "No supported EGL platform selected"
|
||||
#define EGLAPIENTRY
|
||||
#endif
|
||||
|
||||
#define EGL_SUCCESS 0x3000
|
||||
@@ -108,6 +92,17 @@ typedef struct wl_egl_window* EGLNativeWindowType;
|
||||
#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097
|
||||
#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0
|
||||
#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098
|
||||
#define EGL_PLATFORM_X11_EXT 0x31d5
|
||||
#define EGL_PLATFORM_WAYLAND_EXT 0x31d8
|
||||
#define EGL_PLATFORM_ANGLE_ANGLE 0x3202
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320d
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320e
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450
|
||||
#define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489
|
||||
#define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348f
|
||||
|
||||
typedef int EGLint;
|
||||
typedef unsigned int EGLBoolean;
|
||||
@@ -117,6 +112,9 @@ typedef void* EGLContext;
|
||||
typedef void* EGLDisplay;
|
||||
typedef void* EGLSurface;
|
||||
|
||||
typedef void* EGLNativeDisplayType;
|
||||
typedef void* EGLNativeWindowType;
|
||||
|
||||
// EGL function pointer typedefs
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
|
||||
@@ -153,9 +151,10 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
||||
#define eglQueryString _glfw.egl.QueryString
|
||||
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
||||
|
||||
#define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl
|
||||
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl
|
||||
|
||||
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
|
||||
typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay,EGLConfig,void*,const EGLint*);
|
||||
#define eglGetPlatformDisplayEXT _glfw.egl.GetPlatformDisplayEXT
|
||||
#define eglCreatePlatformWindowSurfaceEXT _glfw.egl.CreatePlatformWindowSurfaceEXT
|
||||
|
||||
// EGL-specific per-context data
|
||||
//
|
||||
@@ -173,6 +172,7 @@ typedef struct _GLFWcontextEGL
|
||||
//
|
||||
typedef struct _GLFWlibraryEGL
|
||||
{
|
||||
EGLenum platform;
|
||||
EGLDisplay display;
|
||||
EGLint major, minor;
|
||||
bool prefix;
|
||||
@@ -182,6 +182,15 @@ typedef struct _GLFWlibraryEGL
|
||||
bool KHR_gl_colorspace;
|
||||
bool KHR_get_all_proc_addresses;
|
||||
bool KHR_context_flush_control;
|
||||
bool EXT_client_extensions;
|
||||
bool EXT_platform_base;
|
||||
bool EXT_platform_x11;
|
||||
bool EXT_platform_wayland;
|
||||
bool ANGLE_platform_angle;
|
||||
bool ANGLE_platform_angle_opengl;
|
||||
bool ANGLE_platform_angle_d3d;
|
||||
bool ANGLE_platform_angle_vulkan;
|
||||
bool ANGLE_platform_angle_metal;
|
||||
|
||||
void* handle;
|
||||
|
||||
@@ -203,6 +212,9 @@ typedef struct _GLFWlibraryEGL
|
||||
PFN_eglQueryString QueryString;
|
||||
PFN_eglGetProcAddress GetProcAddress;
|
||||
|
||||
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
|
||||
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT;
|
||||
|
||||
} _GLFWlibraryEGL;
|
||||
|
||||
|
||||
|
||||
42
glfw/glfw3.h
vendored
42
glfw/glfw3.h
vendored
@@ -831,7 +831,7 @@ extern "C" {
|
||||
* @analysis Application programmer error. Fix the offending call.
|
||||
*/
|
||||
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
||||
/*! @brief The reuqested feature is not provided by the platform.
|
||||
/*! @brief The requested feature is not provided by the platform.
|
||||
*
|
||||
* The requested feature is not provided by the platform, so GLFW is unable to
|
||||
* implement it. The documentation for each function notes if it could emit
|
||||
@@ -932,11 +932,19 @@ extern "C" {
|
||||
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
||||
*/
|
||||
#define GLFW_FOCUS_ON_SHOW 0x0002000C
|
||||
|
||||
/*! @brief Mouse input transparency window hint and attribute
|
||||
*
|
||||
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
|
||||
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
|
||||
*/
|
||||
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
|
||||
|
||||
/*! @brief Occlusion window attribute
|
||||
*
|
||||
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
|
||||
*/
|
||||
#define GLFW_OCCLUDED 0x0002000D
|
||||
#define GLFW_OCCLUDED 0x0002000E
|
||||
/*! @brief Framebuffer bit depth hint.
|
||||
*
|
||||
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
|
||||
@@ -1054,12 +1062,17 @@ extern "C" {
|
||||
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
|
||||
*/
|
||||
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
|
||||
/*! @brief OpenGL debug context hint and attribute.
|
||||
/*! @brief Debug mode context hint and attribute.
|
||||
*
|
||||
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
|
||||
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
|
||||
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
|
||||
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
|
||||
*/
|
||||
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
|
||||
#define GLFW_CONTEXT_DEBUG 0x00022007
|
||||
/*! @brief Legacy name for compatibility.
|
||||
*
|
||||
* This is an alias for compatibility with earlier versions.
|
||||
*/
|
||||
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
|
||||
/*! @brief OpenGL profile hint and attribute.
|
||||
*
|
||||
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
|
||||
@@ -1142,6 +1155,14 @@ extern "C" {
|
||||
#define GLFW_EGL_CONTEXT_API 0x00036002
|
||||
#define GLFW_OSMESA_CONTEXT_API 0x00036003
|
||||
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
|
||||
|
||||
/*! @defgroup shapes Standard cursor shapes
|
||||
* @brief Standard system cursor shapes.
|
||||
*
|
||||
@@ -1175,8 +1196,12 @@ typedef enum {
|
||||
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
|
||||
*/
|
||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||
/*! @brief ANGLE rendering backend init hint.
|
||||
*
|
||||
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
|
||||
*/
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050003
|
||||
/*! @brief macOS specific init hint.
|
||||
*
|
||||
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
|
||||
@@ -3723,6 +3748,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
|
||||
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib),
|
||||
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and
|
||||
* [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
||||
* [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib)
|
||||
*
|
||||
* Some of these attributes are ignored for full screen windows. The new
|
||||
* value will take effect if the window is later made windowed.
|
||||
|
||||
9
glfw/init.c
vendored
9
glfw/init.c
vendored
@@ -53,8 +53,8 @@ static GLFWerrorfun _glfwErrorCallback;
|
||||
static _GLFWinitconfig _glfwInitHints =
|
||||
{
|
||||
true, // hat buttons
|
||||
GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend
|
||||
false, // debug keyboard
|
||||
true, // enable joystick
|
||||
{
|
||||
true, // macOS menu bar
|
||||
true // macOS bundle chdir
|
||||
@@ -92,6 +92,7 @@ static void terminate(void)
|
||||
_glfw.mappingCount = 0;
|
||||
|
||||
_glfwTerminateVulkan();
|
||||
_glfwPlatformTerminateJoysticks();
|
||||
_glfwPlatformTerminate();
|
||||
|
||||
_glfw.initialized = false;
|
||||
@@ -275,12 +276,12 @@ GLFWAPI void glfwInitHint(int hint, int value)
|
||||
{
|
||||
switch (hint)
|
||||
{
|
||||
case GLFW_ENABLE_JOYSTICKS:
|
||||
_glfwInitHints.enableJoysticks = value;
|
||||
return;
|
||||
case GLFW_JOYSTICK_HAT_BUTTONS:
|
||||
_glfwInitHints.hatButtons = value;
|
||||
return;
|
||||
case GLFW_ANGLE_PLATFORM_TYPE:
|
||||
_glfwInitHints.angleType = value;
|
||||
return;
|
||||
case GLFW_DEBUG_KEYBOARD:
|
||||
_glfwInitHints.debugKeyboard = value;
|
||||
return;
|
||||
|
||||
47
glfw/input.c
vendored
47
glfw/input.c
vendored
@@ -44,6 +44,22 @@
|
||||
#define _GLFW_JOYSTICK_BUTTON 2
|
||||
#define _GLFW_JOYSTICK_HATBIT 3
|
||||
|
||||
// Initializes the platform joystick API if it has not been already
|
||||
//
|
||||
static bool initJoysticks(void)
|
||||
{
|
||||
if (!_glfw.joysticksInitialized)
|
||||
{
|
||||
if (!_glfwPlatformInitJoysticks())
|
||||
{
|
||||
_glfwPlatformTerminateJoysticks();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _glfw.joysticksInitialized = true;
|
||||
}
|
||||
|
||||
// Finds a mapping based on joystick GUID
|
||||
//
|
||||
static _GLFWmapping* findMapping(const char* guid)
|
||||
@@ -1101,6 +1117,9 @@ GLFWAPI int glfwJoystickPresent(int jid)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return false;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return false;
|
||||
@@ -1126,6 +1145,9 @@ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
@@ -1155,6 +1177,9 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
@@ -1188,6 +1213,9 @@ GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
@@ -1214,6 +1242,9 @@ GLFWAPI const char* glfwGetJoystickName(int jid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
@@ -1239,6 +1270,9 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
@@ -1284,6 +1318,10 @@ GLFWAPI void* glfwGetJoystickUserPointer(int jid)
|
||||
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
_GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun);
|
||||
return cbfun;
|
||||
}
|
||||
@@ -1363,6 +1401,9 @@ GLFWAPI int glfwJoystickIsGamepad(int jid)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return false;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return false;
|
||||
@@ -1388,6 +1429,9 @@ GLFWAPI const char* glfwGetGamepadName(int jid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return NULL;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
@@ -1420,6 +1464,9 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initJoysticks())
|
||||
return false;
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return false;
|
||||
|
||||
23
glfw/internal.h
vendored
23
glfw/internal.h
vendored
@@ -198,6 +198,9 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
||||
#error "No supported window creation API selected"
|
||||
#endif
|
||||
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
|
||||
#define remove_i_from_array(array, i, count) { \
|
||||
(count)--; \
|
||||
if ((i) < (count)) { \
|
||||
@@ -271,8 +274,8 @@ struct _GLFWerror
|
||||
struct _GLFWinitconfig
|
||||
{
|
||||
bool hatButtons;
|
||||
int angleType;
|
||||
bool debugKeyboard;
|
||||
bool enableJoysticks;
|
||||
struct {
|
||||
bool menubar;
|
||||
bool chdir;
|
||||
@@ -299,6 +302,7 @@ struct _GLFWwndconfig
|
||||
bool maximized;
|
||||
bool centerCursor;
|
||||
bool focusOnShow;
|
||||
bool mousePassthrough;
|
||||
bool scaleToMonitor;
|
||||
struct {
|
||||
bool retina;
|
||||
@@ -392,9 +396,9 @@ struct _GLFWcontext
|
||||
// This is defined in the context API's context.h
|
||||
_GLFW_PLATFORM_CONTEXT_STATE
|
||||
// This is defined in egl_context.h
|
||||
_GLFW_EGL_CONTEXT_STATE;
|
||||
_GLFWcontextEGL egl;
|
||||
// This is defined in osmesa_context.h
|
||||
_GLFW_OSMESA_CONTEXT_STATE;
|
||||
_GLFWcontextOSMesa osmesa;
|
||||
};
|
||||
|
||||
// Window and context structure
|
||||
@@ -409,6 +413,7 @@ struct _GLFWwindow
|
||||
bool autoIconify;
|
||||
bool floating;
|
||||
bool focusOnShow;
|
||||
bool mousePassthrough;
|
||||
bool shouldClose;
|
||||
void* userPointer;
|
||||
GLFWid id;
|
||||
@@ -569,6 +574,7 @@ struct _GLFWlibrary
|
||||
_GLFWmonitor** monitors;
|
||||
int monitorCount;
|
||||
|
||||
bool joysticksInitialized;
|
||||
_GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1];
|
||||
_GLFWmapping* mappings;
|
||||
int mappingCount;
|
||||
@@ -613,9 +619,9 @@ struct _GLFWlibrary
|
||||
// This is defined in the platform's joystick.h
|
||||
_GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE
|
||||
// This is defined in egl_context.h
|
||||
_GLFW_EGL_LIBRARY_CONTEXT_STATE;
|
||||
_GLFWlibraryEGL egl;
|
||||
// This is defined in osmesa_context.h
|
||||
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
|
||||
_GLFWlibraryOSMesa osmesa;
|
||||
};
|
||||
|
||||
// Global state shared between compilation units of GLFW
|
||||
@@ -662,6 +668,8 @@ void _glfwPlatformSetPrimarySelectionString(const char* string);
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||
#endif
|
||||
|
||||
bool _glfwPlatformInitJoysticks(void);
|
||||
void _glfwPlatformTerminateJoysticks(void);
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
||||
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
||||
|
||||
@@ -713,6 +721,7 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled);
|
||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled);
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled);
|
||||
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled);
|
||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
||||
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
|
||||
|
||||
@@ -721,6 +730,10 @@ void _glfwPlatformWaitEvents(void);
|
||||
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
|
||||
void _glfwPlatformPostEmptyEvent(void);
|
||||
|
||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs);
|
||||
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void);
|
||||
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window);
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
|
||||
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
|
||||
VkPhysicalDevice device,
|
||||
|
||||
98
glfw/linux_joystick.c
vendored
98
glfw/linux_joystick.c
vendored
@@ -264,9 +264,50 @@ static int compareJoysticks(const void* fp, const void* sp)
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize joystick interface
|
||||
//
|
||||
bool _glfwInitJoysticksLinux(void)
|
||||
void _glfwDetectJoystickConnectionLinux(void)
|
||||
{
|
||||
if (_glfw.linjs.inotify <= 0)
|
||||
return;
|
||||
|
||||
ssize_t offset = 0;
|
||||
char buffer[16384];
|
||||
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
|
||||
|
||||
while (size > offset)
|
||||
{
|
||||
regmatch_t match;
|
||||
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
|
||||
|
||||
offset += sizeof(struct inotify_event) + e->len;
|
||||
|
||||
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
|
||||
continue;
|
||||
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
|
||||
|
||||
if (e->mask & (IN_CREATE | IN_ATTRIB))
|
||||
openJoystickDevice(path);
|
||||
else if (e->mask & IN_DELETE)
|
||||
{
|
||||
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
||||
{
|
||||
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
|
||||
{
|
||||
closeJoystick(_glfw.joysticks + jid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool _glfwPlatformInitJoysticks(void)
|
||||
{
|
||||
const char* dirname = "/dev/input";
|
||||
|
||||
@@ -320,9 +361,7 @@ bool _glfwInitJoysticksLinux(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Close all opened joystick handles
|
||||
//
|
||||
void _glfwTerminateJoysticksLinux(void)
|
||||
void _glfwPlatformTerminateJoysticks(void)
|
||||
{
|
||||
int jid;
|
||||
|
||||
@@ -333,61 +372,16 @@ void _glfwTerminateJoysticksLinux(void)
|
||||
closeJoystick(js);
|
||||
}
|
||||
|
||||
regfree(&_glfw.linjs.regex);
|
||||
|
||||
if (_glfw.linjs.inotify > 0)
|
||||
{
|
||||
if (_glfw.linjs.watch > 0)
|
||||
inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
|
||||
|
||||
close(_glfw.linjs.inotify);
|
||||
regfree(&_glfw.linjs.regex);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwDetectJoystickConnectionLinux(void)
|
||||
{
|
||||
|
||||
if (_glfw.linjs.inotify <= 0)
|
||||
return;
|
||||
|
||||
ssize_t offset = 0;
|
||||
char buffer[16384];
|
||||
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
|
||||
|
||||
while (size > offset)
|
||||
{
|
||||
regmatch_t match;
|
||||
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
|
||||
|
||||
offset += sizeof(struct inotify_event) + e->len;
|
||||
|
||||
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
|
||||
continue;
|
||||
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
|
||||
|
||||
if (e->mask & (IN_CREATE | IN_ATTRIB))
|
||||
openJoystickDevice(path);
|
||||
else if (e->mask & IN_DELETE)
|
||||
{
|
||||
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
||||
{
|
||||
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
|
||||
{
|
||||
closeJoystick(_glfw.joysticks + jid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED)
|
||||
{
|
||||
// Read all queued events (non-blocking)
|
||||
|
||||
3
glfw/linux_joystick.h
vendored
3
glfw/linux_joystick.h
vendored
@@ -55,7 +55,4 @@ typedef struct _GLFWlibraryLinux
|
||||
bool dropped;
|
||||
} _GLFWlibraryLinux;
|
||||
|
||||
|
||||
bool _glfwInitJoysticksLinux(void);
|
||||
void _glfwTerminateJoysticksLinux(void);
|
||||
void _glfwDetectJoystickConnectionLinux(void);
|
||||
|
||||
9
glfw/null_joystick.c
vendored
9
glfw/null_joystick.c
vendored
@@ -33,6 +33,15 @@
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int _glfwPlatformInitJoysticks(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformTerminateJoysticks(void)
|
||||
{
|
||||
}
|
||||
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js UNUSED, int mode UNUSED)
|
||||
{
|
||||
return false;
|
||||
|
||||
4
glfw/null_monitor.c
vendored
4
glfw/null_monitor.c
vendored
@@ -90,9 +90,9 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED,
|
||||
const GLFWvidmode mode = getVideoMode();
|
||||
|
||||
if (xpos)
|
||||
*xpos = 10;
|
||||
*xpos = 0;
|
||||
if (ypos)
|
||||
ypos = 0;
|
||||
*ypos = 10;
|
||||
if (width)
|
||||
*width = mode.width;
|
||||
if (height)
|
||||
|
||||
3
glfw/null_platform.h
vendored
3
glfw/null_platform.h
vendored
@@ -34,10 +34,7 @@
|
||||
#define _GLFW_PLATFORM_CONTEXT_STATE
|
||||
#define _GLFW_PLATFORM_CURSOR_STATE
|
||||
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
|
||||
#define _GLFW_EGL_CONTEXT_STATE
|
||||
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE
|
||||
|
||||
#include "osmesa_context.h"
|
||||
#include "posix_time.h"
|
||||
#include "posix_thread.h"
|
||||
#include "null_joystick.h"
|
||||
|
||||
4
glfw/null_window.c
vendored
4
glfw/null_window.c
vendored
@@ -372,6 +372,10 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
window->null.floating = enabled;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||
{
|
||||
return window->null.opacity;
|
||||
|
||||
4
glfw/osmesa_context.h
vendored
4
glfw/osmesa_context.h
vendored
@@ -54,10 +54,6 @@ typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*);
|
||||
#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer
|
||||
#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress
|
||||
|
||||
#define _GLFW_OSMESA_CONTEXT_STATE _GLFWcontextOSMesa osmesa
|
||||
#define _GLFW_OSMESA_LIBRARY_CONTEXT_STATE _GLFWlibraryOSMesa osmesa
|
||||
|
||||
|
||||
// OSMesa-specific per-context data
|
||||
//
|
||||
typedef struct _GLFWcontextOSMesa
|
||||
|
||||
35
glfw/window.c
vendored
35
glfw/window.c
vendored
@@ -240,13 +240,14 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
window->videoMode.blueBits = fbconfig.blueBits;
|
||||
window->videoMode.refreshRate = _glfw.hints.refreshRate;
|
||||
|
||||
window->monitor = (_GLFWmonitor*) monitor;
|
||||
window->resizable = wndconfig.resizable;
|
||||
window->decorated = wndconfig.decorated;
|
||||
window->autoIconify = wndconfig.autoIconify;
|
||||
window->floating = wndconfig.floating;
|
||||
window->focusOnShow = wndconfig.focusOnShow;
|
||||
window->cursorMode = GLFW_CURSOR_NORMAL;
|
||||
window->monitor = (_GLFWmonitor*) monitor;
|
||||
window->resizable = wndconfig.resizable;
|
||||
window->decorated = wndconfig.decorated;
|
||||
window->autoIconify = wndconfig.autoIconify;
|
||||
window->floating = wndconfig.floating;
|
||||
window->focusOnShow = wndconfig.focusOnShow;
|
||||
window->mousePassthrough = wndconfig.mousePassthrough;
|
||||
window->cursorMode = GLFW_CURSOR_NORMAL;
|
||||
|
||||
window->minwidth = GLFW_DONT_CARE;
|
||||
window->minheight = GLFW_DONT_CARE;
|
||||
@@ -273,6 +274,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
}
|
||||
}
|
||||
|
||||
if (wndconfig.mousePassthrough)
|
||||
_glfwPlatformSetWindowMousePassthrough(window, true);
|
||||
|
||||
if (window->monitor)
|
||||
{
|
||||
if (wndconfig.centerCursor)
|
||||
@@ -422,6 +426,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
case GLFW_FOCUS_ON_SHOW:
|
||||
_glfw.hints.window.focusOnShow = value ? true : false;
|
||||
return;
|
||||
case GLFW_MOUSE_PASSTHROUGH:
|
||||
_glfw.hints.window.mousePassthrough = value ? true : false;
|
||||
return;
|
||||
case GLFW_CLIENT_API:
|
||||
_glfw.hints.context.client = value;
|
||||
return;
|
||||
@@ -440,7 +447,7 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
case GLFW_OPENGL_FORWARD_COMPAT:
|
||||
_glfw.hints.context.forward = value ? true : false;
|
||||
return;
|
||||
case GLFW_OPENGL_DEBUG_CONTEXT:
|
||||
case GLFW_CONTEXT_DEBUG:
|
||||
_glfw.hints.context.debug = value ? true : false;
|
||||
return;
|
||||
case GLFW_CONTEXT_NO_ERROR:
|
||||
@@ -905,6 +912,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
|
||||
return _glfwPlatformWindowHovered(window);
|
||||
case GLFW_FOCUS_ON_SHOW:
|
||||
return window->focusOnShow;
|
||||
case GLFW_MOUSE_PASSTHROUGH:
|
||||
return window->mousePassthrough;
|
||||
case GLFW_TRANSPARENT_FRAMEBUFFER:
|
||||
return _glfwPlatformFramebufferTransparent(window);
|
||||
case GLFW_OCCLUDED:
|
||||
@@ -931,7 +940,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
|
||||
return window->context.robustness;
|
||||
case GLFW_OPENGL_FORWARD_COMPAT:
|
||||
return window->context.forward;
|
||||
case GLFW_OPENGL_DEBUG_CONTEXT:
|
||||
case GLFW_CONTEXT_DEBUG:
|
||||
return window->context.debug;
|
||||
case GLFW_OPENGL_PROFILE:
|
||||
return window->context.profile;
|
||||
@@ -985,6 +994,14 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value)
|
||||
}
|
||||
else if (attrib == GLFW_FOCUS_ON_SHOW)
|
||||
window->focusOnShow = value;
|
||||
else if (attrib == GLFW_MOUSE_PASSTHROUGH)
|
||||
{
|
||||
if (window->mousePassthrough == value)
|
||||
return;
|
||||
|
||||
window->mousePassthrough = value;
|
||||
_glfwPlatformSetWindowMousePassthrough(window, value);
|
||||
}
|
||||
else
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib);
|
||||
}
|
||||
|
||||
181
glfw/wl_cursors.c
vendored
181
glfw/wl_cursors.c
vendored
@@ -1,7 +1,5 @@
|
||||
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
|
||||
|
||||
#include "wl_cursors.h"
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
@@ -10,155 +8,58 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
struct wl_cursor_theme *theme;
|
||||
int px;
|
||||
int refcount;
|
||||
} _themeData;
|
||||
|
||||
struct _wlCursorThemeManager {
|
||||
size_t count;
|
||||
/** Pointer to the head of an unsorted array of themes with no sentinel.
|
||||
*
|
||||
* The lack of sort (and thus forcing a linear search) is intentional;
|
||||
* in most cases, users are likely to have 1-2 different cursor sizes loaded.
|
||||
* For those cases, we get no benefit from sorting and added constant overheads.
|
||||
*
|
||||
* Don't change this to a flexible array member because that complicates growing/shrinking.
|
||||
*/
|
||||
_themeData *themes;
|
||||
};
|
||||
|
||||
static void
|
||||
_themeInit(_themeData *dest, const char *name, int px) {
|
||||
dest->px = px;
|
||||
dest->refcount = 1;
|
||||
if (_glfw.wl.shm) {
|
||||
dest->theme = wl_cursor_theme_load(name, px, _glfw.wl.shm);
|
||||
if(!dest->theme) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Unable to load cursor theme");
|
||||
static int
|
||||
pixels_from_scale(int scale) {
|
||||
static bool queried_env = false;
|
||||
static int factor = 32;
|
||||
if (!queried_env) {
|
||||
const char *env = getenv("XCURSOR_SIZE");
|
||||
if (env) {
|
||||
const int retval = atoi(env);
|
||||
if (retval > 0 && retval < 2048) factor = 32;
|
||||
}
|
||||
} else {
|
||||
dest->theme = NULL;
|
||||
queried_env = true;
|
||||
}
|
||||
return factor * scale;
|
||||
}
|
||||
|
||||
static struct wl_cursor_theme*
|
||||
_themeAdd(int px, _wlCursorThemeManager *manager) {
|
||||
++manager->count;
|
||||
_themeData *temp = realloc(manager->themes, sizeof(_themeData)*manager->count);
|
||||
if (!temp) {
|
||||
_glfwInputError(GLFW_OUT_OF_MEMORY,
|
||||
"OOM during cursor theme management.");
|
||||
return NULL;
|
||||
} else {
|
||||
manager->themes = temp;
|
||||
_themeInit(manager->themes + manager->count-1,
|
||||
getenv("XCURSOR_THEME"),
|
||||
px);
|
||||
return manager->themes[manager->count-1].theme;
|
||||
}
|
||||
}
|
||||
|
||||
//WARNING: No input safety checks.
|
||||
static inline void _themeInc(_themeData
|
||||
*theme) {
|
||||
++(theme->refcount);
|
||||
}
|
||||
struct wl_cursor_theme*
|
||||
glfw_wlc_theme_for_scale(int scale) {
|
||||
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
|
||||
for (size_t i = 0; i < t->count; i++) {
|
||||
if (t->themes[i].scale == scale) return t->themes[i].theme;
|
||||
}
|
||||
|
||||
// WARNING: No input safety checks.
|
||||
// In particular, doesn't check if theme is actually managed by the manager.
|
||||
static void
|
||||
_themeDec(_themeData *theme, _wlCursorThemeManager *manager) {
|
||||
if (--(theme->refcount) == 0) {
|
||||
wl_cursor_theme_destroy(theme->theme);
|
||||
if (--(manager->count) > 0) {
|
||||
const _themeData *last_theme = (manager->themes)+(manager->count);
|
||||
*theme = *last_theme;
|
||||
_themeData *temp = realloc(manager->themes, (manager->count)*sizeof(_themeData));
|
||||
// We're shrinking here, so it's not catastrophic if realloc fails.
|
||||
if (temp) manager->themes = temp;
|
||||
} else {
|
||||
free(manager->themes);
|
||||
manager->themes = NULL;
|
||||
if (t->count >= t->capacity) {
|
||||
t->themes = realloc(t->themes, sizeof(GLFWWLCursorTheme) * (t->count + 16));
|
||||
if (!t->themes) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes");
|
||||
return NULL;
|
||||
}
|
||||
t->capacity = t->count + 16;
|
||||
}
|
||||
}
|
||||
|
||||
static _wlCursorThemeManager _default = {0};
|
||||
|
||||
_wlCursorThemeManager*
|
||||
_wlCursorThemeManagerDefault() {
|
||||
return &_default;
|
||||
struct wl_cursor_theme *ans = wl_cursor_theme_load(getenv("XCURSOR_THEME"), pixels_from_scale(scale), _glfw.wl.shm);
|
||||
if (!ans) {
|
||||
_glfwInputError(
|
||||
GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d",
|
||||
scale, pixels_from_scale(scale)
|
||||
);
|
||||
return NULL;
|
||||
}
|
||||
GLFWWLCursorTheme *theme = t->themes + t->count++;
|
||||
theme->scale = scale;
|
||||
theme->theme = ans;
|
||||
return ans;
|
||||
}
|
||||
|
||||
void
|
||||
_wlCursorThemeManagerDestroy(_wlCursorThemeManager *manager) {
|
||||
if (manager) {
|
||||
for (size_t i = 0; i < manager->count; ++i) {
|
||||
wl_cursor_theme_destroy(manager->themes[i].theme);
|
||||
}
|
||||
free(manager->themes);
|
||||
}
|
||||
}
|
||||
glfw_wlc_destroy(void) {
|
||||
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
|
||||
|
||||
static struct wl_cursor_theme*
|
||||
_wlCursorThemeManagerGet(_wlCursorThemeManager *manager, int px) {
|
||||
_themeData *themedata = NULL;
|
||||
for (size_t i = 0; i < manager->count; ++i) {
|
||||
_themeData *temp = manager->themes+i;
|
||||
if (temp->px == px) {
|
||||
themedata = temp;
|
||||
break;
|
||||
}
|
||||
for (size_t i = 0; i < t->count; i++) {
|
||||
wl_cursor_theme_destroy(t->themes[i].theme);
|
||||
}
|
||||
if (themedata != NULL) {
|
||||
_themeInc(themedata);
|
||||
return themedata->theme;
|
||||
}
|
||||
return _themeAdd(px, manager);
|
||||
}
|
||||
|
||||
struct wl_cursor_theme*
|
||||
_wlCursorThemeManage(_wlCursorThemeManager *manager, struct wl_cursor_theme *theme, int px) {
|
||||
//WARNING: Multiple returns.
|
||||
if (manager == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (theme != NULL) {
|
||||
// Search for the provided theme in the manager.
|
||||
_themeData *themedata = NULL;
|
||||
for (size_t i = 0; i < manager->count; ++i) {
|
||||
_themeData *temp = manager->themes+i;
|
||||
if (temp->theme == theme) {
|
||||
themedata = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (themedata != NULL) {
|
||||
// Search succeeded. Check if we can avoid unnecessary operations.
|
||||
if (themedata->px == px) return theme;
|
||||
_themeDec(themedata, manager);
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland internal: managed theme isn't in the provided manager");
|
||||
return theme;
|
||||
//^ This is probably the sanest behavior for this situation: do nothing.
|
||||
}
|
||||
}
|
||||
return px > 0 ? _wlCursorThemeManagerGet(manager, px) : NULL;
|
||||
}
|
||||
|
||||
int
|
||||
_wlCursorPxFromScale(int scale) {
|
||||
const char *envStr = getenv("XCURSOR_SIZE");
|
||||
if(envStr != NULL) {
|
||||
const int retval = atoi(envStr);
|
||||
//^ atoi here is fine since 0 is an invalid value.
|
||||
if(retval > 0 && retval <= INT_MAX/scale) {
|
||||
return retval*scale;
|
||||
}
|
||||
}
|
||||
return 32*scale;
|
||||
free(t->themes);
|
||||
t->themes = NULL; t->capacity = 0; t->count = 0;
|
||||
}
|
||||
|
||||
32
glfw/wl_cursors.h
vendored
32
glfw/wl_cursors.h
vendored
@@ -2,29 +2,17 @@
|
||||
|
||||
#include <wayland-cursor.h>
|
||||
|
||||
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
|
||||
typedef struct {
|
||||
struct wl_cursor_theme *theme;
|
||||
int scale;
|
||||
} GLFWWLCursorTheme;
|
||||
|
||||
/** Returns a pointer to a wlCursorThemeManagerInstance.
|
||||
* Repeatedly calling this function will return the same instance.
|
||||
*
|
||||
* The retrieved instance must be destroyed with _wlCursorThemeManagerDestroy.
|
||||
*/
|
||||
_wlCursorThemeManager* _wlCursorThemeManagerDefault(void);
|
||||
|
||||
/** Set a wl_cursor_theme pointer variable to a pointer to a managed cursor theme.
|
||||
* Pass the desired px as the third argument.
|
||||
* Returns a pointer to a managed theme, or NULL if the desired px is 0 or an error occurs.
|
||||
*
|
||||
* The passed theme pointer must either be NULL or a pointer to a theme managed by the passed manager.
|
||||
* The provided pointer may be invalidated if it's non-NULL.
|
||||
*/
|
||||
struct wl_cursor_theme*
|
||||
_wlCursorThemeManage(_wlCursorThemeManager*, struct wl_cursor_theme*, int);
|
||||
typedef struct {
|
||||
GLFWWLCursorTheme *themes;
|
||||
size_t count, capacity;
|
||||
} GLFWWLCursorThemes;
|
||||
|
||||
void _wlCursorThemeManagerDestroy(_wlCursorThemeManager*);
|
||||
|
||||
/** Helper method to determine the appropriate size in pixels for a given scale.
|
||||
*
|
||||
* Reads XCURSOR_SIZE if it's set and is valid, else defaults to 32*scale.
|
||||
*/
|
||||
int _wlCursorPxFromScale(int);
|
||||
struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale);
|
||||
void glfw_wlc_destroy(void);
|
||||
|
||||
34
glfw/wl_init.c
vendored
34
glfw/wl_init.c
vendored
@@ -148,12 +148,9 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
const int scale = window->wl.scale;
|
||||
|
||||
window->wl.cursorTheme = _wlCursorThemeManage(
|
||||
_glfw.wl.cursorThemeManager,
|
||||
window->wl.cursorTheme,
|
||||
_wlCursorPxFromScale(scale)
|
||||
);
|
||||
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
|
||||
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
|
||||
if (!theme) return;
|
||||
cursor = _glfwLoadCursor(shape, theme);
|
||||
if (!cursor) return;
|
||||
// TODO: handle animated cursors too.
|
||||
image = cursor->images[0];
|
||||
@@ -337,9 +334,9 @@ static void pointerHandleAxis(void* data UNUSED,
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
||||
x = wl_fixed_to_double(value) * -1;
|
||||
x = -wl_fixed_to_double(value);
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = wl_fixed_to_double(value) * -1;
|
||||
y = -wl_fixed_to_double(value);
|
||||
|
||||
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
@@ -771,13 +768,6 @@ int _glfwPlatformInit(void)
|
||||
// Sync so we got all initial output events
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
|
||||
#ifdef __linux__
|
||||
if (_glfw.hints.init.enableJoysticks) {
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!_glfw.wl.wmBase)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -787,13 +777,14 @@ int _glfwPlatformInit(void)
|
||||
|
||||
if (_glfw.wl.shm)
|
||||
{
|
||||
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
|
||||
_glfw.wl.cursorSurface =
|
||||
_glfw.wl.cursorSurface =
|
||||
wl_compositor_create_surface(_glfw.wl.compositor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfw.wl.cursorThemeManager = NULL;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to find Wayland SHM");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -801,9 +792,6 @@ int _glfwPlatformInit(void)
|
||||
|
||||
void _glfwPlatformTerminate(void)
|
||||
{
|
||||
#ifdef __linux__
|
||||
_glfwTerminateJoysticksLinux();
|
||||
#endif
|
||||
_glfwTerminateEGL();
|
||||
if (_glfw.wl.egl.handle)
|
||||
{
|
||||
@@ -822,9 +810,7 @@ void _glfwPlatformTerminate(void)
|
||||
|
||||
if (_glfw.wl.cursorSurface)
|
||||
wl_surface_destroy(_glfw.wl.cursorSurface);
|
||||
if (_glfw.wl.cursorThemeManager) {
|
||||
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
|
||||
}
|
||||
glfw_wlc_destroy();
|
||||
if (_glfw.wl.subcompositor)
|
||||
wl_subcompositor_destroy(_glfw.wl.subcompositor);
|
||||
if (_glfw.wl.compositor)
|
||||
|
||||
11
glfw/wl_platform.h
vendored
11
glfw/wl_platform.h
vendored
@@ -50,8 +50,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#endif
|
||||
#include "backend_utils.h"
|
||||
#include "xkb_glfw.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
#include "wl_cursors.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
@@ -66,9 +64,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||
|
||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->wl.native)
|
||||
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.wl.display)
|
||||
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl
|
||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl
|
||||
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl
|
||||
@@ -137,7 +132,6 @@ typedef struct _GLFWwindowWayland
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
double cursorPosX, cursorPosY;
|
||||
struct wl_cursor_theme* cursorTheme;
|
||||
|
||||
char* title;
|
||||
char appId[256];
|
||||
@@ -267,8 +261,7 @@ typedef struct _GLFWlibraryWayland
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
char* primarySelectionString;
|
||||
|
||||
_wlCursorThemeManager* cursorThemeManager;
|
||||
GLFWWLCursorThemes cursor_themes;
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -292,7 +285,7 @@ typedef struct _GLFWcursorWayland
|
||||
struct wl_buffer* buffer;
|
||||
int width, height;
|
||||
int xhot, yhot;
|
||||
int currentImage;
|
||||
unsigned int currentImage;
|
||||
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
|
||||
int scale;
|
||||
/** Cursor shape stored to allow late cursor loading in setCursorImage. */
|
||||
|
||||
80
glfw/wl_window.c
vendored
80
glfw/wl_window.c
vendored
@@ -41,11 +41,6 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
static void
|
||||
load_cursor_theme(_GLFWwindow* window) {
|
||||
window->wl.cursorTheme = _wlCursorThemeManage(
|
||||
_glfw.wl.cursorThemeManager, window->wl.cursorTheme, _wlCursorPxFromScale(window->wl.scale));
|
||||
}
|
||||
|
||||
static void
|
||||
setCursorImage(_GLFWwindow* window)
|
||||
@@ -63,18 +58,23 @@ setCursorImage(_GLFWwindow* window)
|
||||
} else
|
||||
{
|
||||
if (cursorWayland->scale != scale) {
|
||||
if (!window->wl.cursorTheme) load_cursor_theme(window);
|
||||
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
|
||||
struct wl_cursor *newCursor = NULL;
|
||||
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
|
||||
if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme);
|
||||
if (newCursor != NULL) {
|
||||
cursorWayland->cursor = newCursor;
|
||||
cursorWayland->scale = scale;
|
||||
cursorWayland->currentImage = 0;
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
|
||||
}
|
||||
}
|
||||
if (!cursorWayland->cursor)
|
||||
if (!cursorWayland->cursor || !cursorWayland->cursor->image_count)
|
||||
return;
|
||||
if (cursorWayland->currentImage >= cursorWayland->cursor->image_count) cursorWayland->currentImage = 0;
|
||||
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
||||
if (!image) image = cursorWayland->cursor->images[0];
|
||||
if (!image) return;
|
||||
buffer = wl_cursor_image_get_buffer(image);
|
||||
if (image->delay && window->cursor) {
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
|
||||
@@ -132,7 +132,6 @@ static bool checkScaleChange(_GLFWwindow* window)
|
||||
{
|
||||
window->wl.scale = scale;
|
||||
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
||||
load_cursor_theme(window);
|
||||
setCursorImage(window);
|
||||
return true;
|
||||
}
|
||||
@@ -764,14 +763,18 @@ animateCursorImage(id_type timer_id UNUSED, void *data UNUSED) {
|
||||
|
||||
static void
|
||||
abortOnFatalError(int last_error) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
|
||||
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
|
||||
else {
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
window = window->next;
|
||||
static bool abort_called = false;
|
||||
if (!abort_called) {
|
||||
abort_called = true;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
|
||||
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
|
||||
else {
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
window = window->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ensure the tick callback is called
|
||||
@@ -937,12 +940,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->wl.cursorTheme) {
|
||||
_wlCursorThemeManage(_glfw.wl.cursorThemeManager,
|
||||
window->wl.cursorTheme,
|
||||
0);
|
||||
window->wl.cursorTheme = NULL;
|
||||
}
|
||||
if (window == _glfw.wl.pointerFocus)
|
||||
{
|
||||
_glfw.wl.pointerFocus = NULL;
|
||||
@@ -1075,8 +1072,10 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
|
||||
int* width, int* height)
|
||||
{
|
||||
_glfwPlatformGetWindowSize(window, width, height);
|
||||
*width *= window->wl.scale;
|
||||
*height *= window->wl.scale;
|
||||
if (width)
|
||||
*width *= window->wl.scale;
|
||||
if (height)
|
||||
*height *= window->wl.scale;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
@@ -1263,6 +1262,19 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNU
|
||||
"Wayland: Window attribute setting not implemented yet");
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor);
|
||||
wl_surface_set_input_region(window->wl.surface, region);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
else
|
||||
wl_surface_set_input_region(window->wl.surface, 0);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
return 1.f;
|
||||
@@ -2008,6 +2020,24 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
|
||||
{
|
||||
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland)
|
||||
return EGL_PLATFORM_WAYLAND_EXT;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
||||
{
|
||||
return _glfw.wl.display;
|
||||
}
|
||||
|
||||
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
||||
{
|
||||
return window->wl.native;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)
|
||||
|
||||
36
glfw/x11_init.c
vendored
36
glfw/x11_init.c
vendored
@@ -327,6 +327,30 @@ static bool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so");
|
||||
#else
|
||||
_glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6");
|
||||
#endif
|
||||
if (_glfw.x11.xshape.handle)
|
||||
{
|
||||
glfw_dlsym(_glfw.x11.xshape.QueryExtension, _glfw.x11.xshape.handle, "XShapeQueryExtension");
|
||||
glfw_dlsym(_glfw.x11.xshape.ShapeCombineRegion, _glfw.x11.xshape.handle, "XShapeCombineRegion");
|
||||
glfw_dlsym(_glfw.x11.xshape.QueryVersion, _glfw.x11.xshape.handle, "XShapeQueryVersion");
|
||||
|
||||
if (XShapeQueryExtension(_glfw.x11.display,
|
||||
&_glfw.x11.xshape.errorBase,
|
||||
&_glfw.x11.xshape.eventBase))
|
||||
{
|
||||
if (XShapeQueryVersion(_glfw.x11.display,
|
||||
&_glfw.x11.xshape.major,
|
||||
&_glfw.x11.xshape.minor))
|
||||
{
|
||||
_glfw.x11.xshape.available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_glfw.x11.xkb.major = 1;
|
||||
_glfw.x11.xkb.minor = 0;
|
||||
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
|
||||
@@ -629,15 +653,6 @@ int _glfwPlatformInit(void)
|
||||
_glfw.x11.helperWindowHandle = createHelperWindow();
|
||||
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
||||
|
||||
#if defined(__linux__)
|
||||
if (_glfw.hints.init.enableJoysticks) {
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return false;
|
||||
if (_glfw.linjs.inotify > 0)
|
||||
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwPollMonitorsX11();
|
||||
return true;
|
||||
}
|
||||
@@ -716,9 +731,6 @@ void _glfwPlatformTerminate(void)
|
||||
_glfwTerminateEGL();
|
||||
_glfwTerminateGLX();
|
||||
|
||||
#if defined(__linux__)
|
||||
_glfwTerminateJoysticksLinux();
|
||||
#endif
|
||||
finalizePollData(&_glfw.x11.eventLoopData);
|
||||
}
|
||||
|
||||
|
||||
2
glfw/x11_monitor.c
vendored
2
glfw/x11_monitor.c
vendored
@@ -169,7 +169,7 @@ void _glfwPollMonitorsX11(void)
|
||||
if (widthMM <= 0 || heightMM <= 0)
|
||||
{
|
||||
// HACK: If RandR does not provide a physical size, assume the
|
||||
// X11 default 96 DPI and calcuate from the CRTC viewport
|
||||
// X11 default 96 DPI and calculate from the CRTC viewport
|
||||
// NOTE: These members are affected by rotation, unlike the mode
|
||||
// info and output info members
|
||||
widthMM = (int) (ci->width * 25.4f / 96.f);
|
||||
|
||||
31
glfw/x11_platform.h
vendored
31
glfw/x11_platform.h
vendored
@@ -51,6 +51,9 @@
|
||||
// The XInput extension provides raw mouse motion input
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
// The Shape extension provides custom window shapes
|
||||
#include <X11/extensions/shape.h>
|
||||
|
||||
// The libxkb library is used for improved keyboard support
|
||||
#include "xkb_glfw.h"
|
||||
#include "backend_utils.h"
|
||||
@@ -125,6 +128,16 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const
|
||||
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
|
||||
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat
|
||||
|
||||
typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*);
|
||||
typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*);
|
||||
typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int);
|
||||
typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int);
|
||||
|
||||
#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension
|
||||
#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion
|
||||
#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion
|
||||
#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask
|
||||
|
||||
typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
|
||||
typedef VkFlags VkXcbSurfaceCreateFlagsKHR;
|
||||
|
||||
@@ -153,8 +166,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "glx_context.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
#if defined(__linux__)
|
||||
#include "linux_joystick.h"
|
||||
#else
|
||||
@@ -165,9 +176,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||
|
||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->x11.handle)
|
||||
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.x11.display)
|
||||
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11
|
||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11
|
||||
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11
|
||||
@@ -382,6 +390,19 @@ typedef struct _GLFWlibraryX11
|
||||
PFN_XRenderFindVisualFormat FindVisualFormat;
|
||||
} xrender;
|
||||
|
||||
struct {
|
||||
bool available;
|
||||
void* handle;
|
||||
int major;
|
||||
int minor;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
PFN_XShapeQueryExtension QueryExtension;
|
||||
PFN_XShapeCombineRegion ShapeCombineRegion;
|
||||
PFN_XShapeQueryVersion QueryVersion;
|
||||
PFN_XShapeCombineMask ShapeCombineMask;
|
||||
} xshape;
|
||||
|
||||
EventLoopData eventLoopData;
|
||||
|
||||
} _GLFWlibraryX11;
|
||||
|
||||
108
glfw/x11_window.c
vendored
108
glfw/x11_window.c
vendored
@@ -1176,15 +1176,26 @@ static void processEvent(XEvent *event)
|
||||
else if (event->type == _glfw.x11.xkb.eventBase)
|
||||
{
|
||||
XkbEvent *kb_event = (XkbEvent*)event;
|
||||
if (kb_event->any.device != (unsigned int)_glfw.x11.xkb.keyboard_device_id) return;
|
||||
switch(kb_event->any.xkb_type) {
|
||||
case XkbNewKeyboardNotify: {
|
||||
int32_t old_id = _glfw.x11.xkb.keyboard_device_id;
|
||||
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
||||
if (old_id != _glfw.x11.xkb.keyboard_device_id) keymap_dirty = true;
|
||||
XkbNewKeyboardNotifyEvent *newkb_event = (XkbNewKeyboardNotifyEvent*)kb_event;
|
||||
if (_glfw.hints.init.debugKeyboard) printf(
|
||||
"Got XkbNewKeyboardNotify event with changes: key codes: %d geometry: %d device id: %d\n",
|
||||
!!(newkb_event->changed & XkbNKN_KeycodesMask), !!(newkb_event->changed & XkbNKN_GeometryMask),
|
||||
!!(newkb_event->changed & XkbNKN_DeviceIDMask));
|
||||
if (newkb_event->changed & XkbNKN_DeviceIDMask) {
|
||||
keymap_dirty = true;
|
||||
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
||||
}
|
||||
if (newkb_event->changed & XkbNKN_KeycodesMask) {
|
||||
keymap_dirty = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* fallthrough */
|
||||
case XkbMapNotify:
|
||||
{
|
||||
if (_glfw.hints.init.debugKeyboard) printf("Got XkbMapNotify event, keymaps will be reloaded\n");
|
||||
keymap_dirty = true;
|
||||
return;
|
||||
}
|
||||
@@ -2449,13 +2460,19 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
int rootX, rootY, childX, childY;
|
||||
unsigned int mask;
|
||||
|
||||
if (!XQueryPointer(_glfw.x11.display, w,
|
||||
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_glfwGrabErrorHandlerX11();
|
||||
|
||||
if (w == window->x11.handle)
|
||||
const Bool result = XQueryPointer(_glfw.x11.display, w,
|
||||
&root, &w, &rootX, &rootY,
|
||||
&childX, &childY, &mask);
|
||||
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
|
||||
if (_glfw.x11.errorCode == BadWindow)
|
||||
w = _glfw.x11.root;
|
||||
else if (!result)
|
||||
return false;
|
||||
else if (w == window->x11.handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2569,6 +2586,25 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||
{
|
||||
if (!_glfw.x11.xshape.available)
|
||||
return;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
Region region = XCreateRegion();
|
||||
XShapeCombineRegion(_glfw.x11.display, window->x11.handle,
|
||||
ShapeInput, 0, 0, region, ShapeSet);
|
||||
XDestroyRegion(region);
|
||||
}
|
||||
else
|
||||
{
|
||||
XShapeCombineMask(_glfw.x11.display, window->x11.handle,
|
||||
ShapeInput, 0, 0, None, ShapeSet);
|
||||
}
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||
{
|
||||
float opacity = 1.f;
|
||||
@@ -2617,7 +2653,8 @@ _glfwDispatchX11Events(void) {
|
||||
unsigned dispatched = 0;
|
||||
|
||||
#if defined(__linux__)
|
||||
if (_glfw.hints.init.enableJoysticks) _glfwDetectJoystickConnectionLinux();
|
||||
if (_glfw.joysticksInitialized)
|
||||
_glfwDetectJoystickConnectionLinux();
|
||||
#endif
|
||||
dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAfterFlush));
|
||||
|
||||
@@ -2844,6 +2881,55 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
}
|
||||
|
||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
||||
{
|
||||
if (_glfw.egl.ANGLE_platform_angle)
|
||||
{
|
||||
int type = 0;
|
||||
|
||||
if (_glfw.egl.ANGLE_platform_angle_opengl)
|
||||
{
|
||||
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
|
||||
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
|
||||
}
|
||||
|
||||
if (_glfw.egl.ANGLE_platform_angle_vulkan)
|
||||
{
|
||||
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN)
|
||||
type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE;
|
||||
}
|
||||
|
||||
if (type)
|
||||
{
|
||||
*attribs = calloc(5, sizeof(EGLint));
|
||||
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
|
||||
(*attribs)[1] = type;
|
||||
(*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE;
|
||||
(*attribs)[3] = EGL_PLATFORM_X11_EXT;
|
||||
(*attribs)[4] = EGL_NONE;
|
||||
return EGL_PLATFORM_ANGLE_ANGLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11)
|
||||
return EGL_PLATFORM_X11_EXT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
||||
{
|
||||
return _glfw.x11.display;
|
||||
}
|
||||
|
||||
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.egl.platform)
|
||||
return &window->x11.handle;
|
||||
else
|
||||
return (EGLNativeWindowType) window->x11.handle;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (!_glfw.vk.KHR_surface)
|
||||
|
||||
56
glfw/xkb_glfw.c
vendored
56
glfw/xkb_glfw.c
vendored
@@ -371,9 +371,31 @@ load_compose_tables(_GLFWXKBData *xkb) {
|
||||
xkb_compose_table_unref(compose_table);
|
||||
}
|
||||
|
||||
static inline xkb_mod_mask_t
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
||||
size_t i = 0;
|
||||
xkb_mod_mask_t ans = 0;
|
||||
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
||||
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||
i++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static void
|
||||
update_modifiers(_GLFWXKBData *xkb) {
|
||||
XKBStateGroup *group = &xkb->states;
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||
const char *err;
|
||||
debug("Loading new XKB keymaps\n");
|
||||
release_keyboard_data(xkb);
|
||||
err = load_keymaps(xkb, map_str);
|
||||
if (err) {
|
||||
@@ -396,36 +418,17 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||
S(numLock, XKB_MOD_NAME_NUM);
|
||||
#undef S
|
||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||
size_t capacity = arraysz(xkb->unknownModifiers), j = 0;
|
||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||
}
|
||||
xkb->states.modifiers = 0;
|
||||
xkb->states.activeUnknownModifiers = 0;
|
||||
update_modifiers(xkb);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline xkb_mod_mask_t
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
||||
size_t i = 0;
|
||||
xkb_mod_mask_t ans = 0;
|
||||
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
||||
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||
i++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static void
|
||||
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
||||
if (!xkb->keymap) return;
|
||||
@@ -434,7 +437,7 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m
|
||||
// We have to update the groups in clean_state, as they change for
|
||||
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
||||
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||
update_modifiers(xkb, &xkb->states);
|
||||
update_modifiers(xkb);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -503,14 +506,19 @@ static inline const char*
|
||||
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
||||
static char buf[512];
|
||||
char *p = buf, *s;
|
||||
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
|
||||
#define pr(x) { \
|
||||
int num_needed = -1; \
|
||||
ssize_t space_left = sizeof(buf) - (p - buf) - 1; \
|
||||
if (space_left > 0) num_needed = snprintf(p, space_left, "%s", x); \
|
||||
if (num_needed > 0) p += num_needed; \
|
||||
}
|
||||
pr(name); pr(": ");
|
||||
s = p;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
|
||||
xkb_mod_mask_t m = 1 << i;
|
||||
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
|
||||
}
|
||||
if (p == s) pr("none");
|
||||
if (p == s) { pr("none"); }
|
||||
else p--;
|
||||
pr(" ");
|
||||
#undef pr
|
||||
|
||||
@@ -559,7 +559,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
|
||||
for m, g in zip(data['match'], data['groupdicts']):
|
||||
if m:
|
||||
path, line = g['path'], g['line']
|
||||
path = path.split(':')[-1]
|
||||
path = os.path.expanduser(path.split(':')[-1])
|
||||
line = int(line)
|
||||
break
|
||||
else:
|
||||
|
||||
@@ -341,7 +341,7 @@ def request_from_clipboard(use_primary: bool = False) -> str:
|
||||
return '\x1b]52;{};?\x07'.format('p' if use_primary else 'c')
|
||||
|
||||
|
||||
# Boilerplate to make operations availble via Handler.cmd {{{
|
||||
# Boilerplate to make operations available via Handler.cmd {{{
|
||||
|
||||
|
||||
def writer(handler: HandlerType, func: Callable) -> Callable:
|
||||
|
||||
@@ -20,7 +20,7 @@ from kitty.config import cached_values_for
|
||||
from kitty.constants import config_dir
|
||||
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
|
||||
from kitty.key_encoding import (
|
||||
CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
|
||||
CTRL, PRESS, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
|
||||
)
|
||||
from kitty.typing import BossType
|
||||
from kitty.utils import ScreenSize, get_editor
|
||||
@@ -38,6 +38,8 @@ UP = K['UP']
|
||||
DOWN = K['DOWN']
|
||||
LEFT = K['LEFT']
|
||||
RIGHT = K['RIGHT']
|
||||
RIGHT_BRACKET = K['RIGHT_BRACKET']
|
||||
LEFT_BRACKET = K['LEFT_BRACKET']
|
||||
TAB = K['TAB']
|
||||
ESCAPE = K['ESCAPE']
|
||||
F1 = K['F1']
|
||||
@@ -478,7 +480,7 @@ class UnicodeInput(Handler):
|
||||
return
|
||||
if key_event is enter_key:
|
||||
self.quit_loop(0)
|
||||
elif key_event.type is RELEASE:
|
||||
elif key_event.type is PRESS:
|
||||
if not key_event.mods:
|
||||
if key_event.key is ESCAPE:
|
||||
self.quit_loop(1)
|
||||
@@ -492,8 +494,8 @@ class UnicodeInput(Handler):
|
||||
self.switch_mode(FAVORITES)
|
||||
elif key_event.key is F12 and self.mode is FAVORITES:
|
||||
self.edit_favorites()
|
||||
elif key_event.mods == CTRL and key_event.key is TAB:
|
||||
self.next_mode()
|
||||
elif key_event.mods == CTRL and key_event.key in (TAB, RIGHT_BRACKET, LEFT_BRACKET):
|
||||
self.next_mode(-1 if key_event.key is LEFT_BRACKET else 1)
|
||||
elif key_event.mods == CTRL | SHIFT and key_event.key is TAB:
|
||||
self.next_mode(-1)
|
||||
|
||||
|
||||
@@ -49,14 +49,15 @@ from .typing import PopenType, TypedDict
|
||||
from .utils import (
|
||||
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
|
||||
log_error, open_url, parse_address_spec, parse_uri_list,
|
||||
remove_socket_file, safe_print, set_primary_selection, single_instance,
|
||||
startup_notification_handler
|
||||
platform_window_id, remove_socket_file, safe_print, set_primary_selection,
|
||||
single_instance, startup_notification_handler
|
||||
)
|
||||
from .window import MatchPatternType, Window
|
||||
|
||||
|
||||
class OSWindowDict(TypedDict):
|
||||
id: int
|
||||
platform_window_id: Optional[int]
|
||||
is_focused: bool
|
||||
tabs: List[TabDict]
|
||||
|
||||
@@ -195,7 +196,7 @@ class Boss:
|
||||
) -> int:
|
||||
if os_window_id is None:
|
||||
opts_for_size = opts_for_size or getattr(startup_session, 'os_window_size', None) or self.opts
|
||||
wclass = wclass or self.args.cls or appname
|
||||
wclass = wclass or getattr(startup_session, 'os_window_class', None) or self.args.cls or appname
|
||||
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
|
||||
os_window_id = create_os_window(
|
||||
initial_window_size_func(opts_for_size, self.cached_values),
|
||||
@@ -212,6 +213,7 @@ class Boss:
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'platform_window_id': platform_window_id(os_window_id),
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
@@ -382,6 +384,8 @@ class Boss:
|
||||
args.directory = os.path.join(data['cwd'], args.directory)
|
||||
for session in create_sessions(opts, args, respect_cwd=True):
|
||||
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id)
|
||||
if opts.background_opacity != self.opts.background_opacity:
|
||||
self._set_os_window_background_opacity(os_window_id, opts.background_opacity)
|
||||
if data.get('notify_on_os_window_death'):
|
||||
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, data['notify_on_os_window_death'])
|
||||
else:
|
||||
@@ -442,8 +446,34 @@ class Boss:
|
||||
def close_tab(self, tab: Optional[Tab] = None) -> None:
|
||||
tab = tab or self.active_tab
|
||||
if tab:
|
||||
for window in tab:
|
||||
self.close_window(window)
|
||||
self.confirm_tab_close(tab)
|
||||
|
||||
def confirm_tab_close(self, tab: Tab) -> None:
|
||||
windows = tuple(tab)
|
||||
needs_confirmation = self.opts.confirm_os_window_close > 0 and len(windows) >= self.opts.confirm_os_window_close
|
||||
if not needs_confirmation:
|
||||
self.close_tab_no_confirm(tab)
|
||||
return
|
||||
self._run_kitten('ask', ['--type=yesno', '--message', _(
|
||||
'Are you sure you want to close this tab, it has {}'
|
||||
' windows running?').format(len(windows))],
|
||||
window=tab.active_window,
|
||||
custom_callback=partial(self.handle_close_tab_confirmation, tab.id)
|
||||
)
|
||||
|
||||
def handle_close_tab_confirmation(self, tab_id: int, data: Dict[str, Any], *a: Any) -> None:
|
||||
if data['response'] != 'y':
|
||||
return
|
||||
for tab in self.all_tabs:
|
||||
if tab.id == tab_id:
|
||||
break
|
||||
else:
|
||||
return
|
||||
self.close_tab_no_confirm(tab)
|
||||
|
||||
def close_tab_no_confirm(self, tab: Tab) -> None:
|
||||
for window in tab:
|
||||
self.close_window(window)
|
||||
|
||||
def toggle_fullscreen(self) -> None:
|
||||
toggle_fullscreen()
|
||||
@@ -851,6 +881,9 @@ class Boss:
|
||||
add_history='history' in type_of_input,
|
||||
add_wrap_markers='screen' in type_of_input
|
||||
).encode('utf-8')
|
||||
elif type_of_input == 'selection':
|
||||
sel = self.data_for_at(which='@selection', window=w)
|
||||
data = sel.encode('utf-8') if sel else None
|
||||
elif type_of_input is None:
|
||||
data = None
|
||||
else:
|
||||
@@ -858,9 +891,16 @@ class Boss:
|
||||
else:
|
||||
data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8')
|
||||
copts = common_opts_as_dict(self.opts)
|
||||
final_args: List[str] = []
|
||||
for x in args:
|
||||
if x == '@selection':
|
||||
sel = self.data_for_at(which='@selection', window=w)
|
||||
if sel:
|
||||
x = sel
|
||||
final_args.append(x)
|
||||
overlay_window = tab.new_special_window(
|
||||
SpecialWindow(
|
||||
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + args,
|
||||
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + final_args,
|
||||
stdin=data,
|
||||
env={
|
||||
'KITTY_COMMON_OPTS': json.dumps(copts),
|
||||
|
||||
@@ -40,7 +40,7 @@ static void (*parse_func)(Screen*, PyObject*, monotonic_t);
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t sz;
|
||||
int fd;
|
||||
id_type peer_id;
|
||||
} Message;
|
||||
|
||||
typedef struct {
|
||||
@@ -71,9 +71,8 @@ static const Child EMPTY_CHILD = {0};
|
||||
pthread_mutex_##op(&screen->which##_buf_lock);
|
||||
#define children_mutex(op) \
|
||||
pthread_mutex_##op(&children_lock);
|
||||
#define peer_mutex(op) \
|
||||
pthread_mutex_##op(&talk_data.peer_lock);
|
||||
|
||||
#define talk_mutex(op) \
|
||||
pthread_mutex_##op(&talk_lock);
|
||||
|
||||
|
||||
static Child children[MAX_CHILDREN] = {{0}};
|
||||
@@ -82,7 +81,7 @@ static Child add_queue[MAX_CHILDREN] = {{0}}, remove_queue[MAX_CHILDREN] = {{0}}
|
||||
static unsigned long remove_notify[MAX_CHILDREN] = {0};
|
||||
static size_t add_queue_count = 0, remove_queue_count = 0;
|
||||
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
|
||||
static pthread_mutex_t children_lock;
|
||||
static pthread_mutex_t children_lock, talk_lock;
|
||||
static bool kill_signal_received = false;
|
||||
static ChildMonitor *the_monitor = NULL;
|
||||
static uint8_t drain_buf[1024];
|
||||
@@ -131,6 +130,10 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
|
||||
return NULL;
|
||||
}
|
||||
if ((ret = pthread_mutex_init(&talk_lock, NULL)) != 0) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Failed to create talk_lock mutex: %s", strerror(ret));
|
||||
return NULL;
|
||||
}
|
||||
self = (ChildMonitor *)type->tp_alloc(type, 0);
|
||||
if (!init_loop_data(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (!install_signal_handlers(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
@@ -152,7 +155,13 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
|
||||
static void
|
||||
dealloc(ChildMonitor* self) {
|
||||
if (self->messages) {
|
||||
for (size_t i = 0; i < self->messages_count; i++) free(self->messages[i].data);
|
||||
free(self->messages); self->messages = NULL;
|
||||
self->messages_count = 0; self->messages_capacity = 0;
|
||||
}
|
||||
pthread_mutex_destroy(&children_lock);
|
||||
pthread_mutex_destroy(&talk_lock);
|
||||
Py_CLEAR(self->dump_callback);
|
||||
Py_CLEAR(self->death_notify);
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
@@ -174,7 +183,7 @@ wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) {
|
||||
|
||||
static void* io_loop(void *data);
|
||||
static void* talk_loop(void *data);
|
||||
static void send_response(int fd, const char *msg, size_t msg_sz);
|
||||
static void send_response(id_type peer_id, const char *msg, size_t msg_sz);
|
||||
static void wakeup_talk_loop(bool);
|
||||
static bool talk_thread_started = false;
|
||||
|
||||
@@ -324,7 +333,6 @@ parse_input(ChildMonitor *self) {
|
||||
size_t count = 0, remove_count = 0;
|
||||
bool input_read = false;
|
||||
monotonic_t now = monotonic();
|
||||
PyObject *msg = NULL;
|
||||
children_mutex(lock);
|
||||
while (remove_queue_count) {
|
||||
remove_queue_count--;
|
||||
@@ -333,18 +341,6 @@ parse_input(ChildMonitor *self) {
|
||||
FREE_CHILD(remove_queue[remove_queue_count]);
|
||||
}
|
||||
|
||||
if (UNLIKELY(self->messages_count)) {
|
||||
msg = PyTuple_New(self->messages_count);
|
||||
if (msg) {
|
||||
for (size_t i = 0; i < self->messages_count; i++) {
|
||||
Message *m = self->messages + i;
|
||||
PyTuple_SET_ITEM(msg, i, Py_BuildValue("y#i", m->data, (int)m->sz, m->fd));
|
||||
free(m->data); m->data = NULL; m->sz = 0;
|
||||
}
|
||||
self->messages_count = 0;
|
||||
} else fatal("Out of memory");
|
||||
}
|
||||
|
||||
if (UNLIKELY(kill_signal_received)) {
|
||||
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
|
||||
global_state.has_pending_closes = true;
|
||||
@@ -358,15 +354,35 @@ parse_input(ChildMonitor *self) {
|
||||
}
|
||||
}
|
||||
children_mutex(unlock);
|
||||
if (msg) {
|
||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(msg); i++) {
|
||||
PyObject *resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "O", PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 0));
|
||||
int peer_fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 1));
|
||||
if (resp && PyBytes_Check(resp)) send_response(peer_fd, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
||||
else { send_response(peer_fd, NULL, 0); if (!resp) PyErr_Print(); }
|
||||
|
||||
Message *msgs = NULL;
|
||||
size_t msgs_count = 0;
|
||||
talk_mutex(lock);
|
||||
if (UNLIKELY(self->messages_count)) {
|
||||
msgs = malloc(sizeof(Message) * self->messages_count);
|
||||
if (msgs) {
|
||||
memcpy(msgs, self->messages, sizeof(Message) * self->messages_count);
|
||||
msgs_count = self->messages_count;
|
||||
}
|
||||
memset(self->messages, 0, sizeof(Message) * self->messages_capacity);
|
||||
self->messages_count = 0;
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
|
||||
if (msgs_count) {
|
||||
for (size_t i = 0; i < msgs_count; i++) {
|
||||
Message *msg = msgs + i;
|
||||
PyObject *resp = NULL;
|
||||
if (msg->data) {
|
||||
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#", msg->data, (int)msg->sz);
|
||||
free(msg->data);
|
||||
if (!resp) PyErr_Print();
|
||||
}
|
||||
if (resp && PyBytes_Check(resp)) send_response(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
||||
else send_response(msg->peer_id, NULL, 0);
|
||||
Py_CLEAR(resp);
|
||||
}
|
||||
Py_CLEAR(msg);
|
||||
free(msgs); msgs = NULL;
|
||||
}
|
||||
|
||||
while(remove_count) {
|
||||
@@ -503,7 +519,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
|
||||
int d = monotonic_t_to_ms(OPT(cursor_blink_interval));
|
||||
int n = t / d;
|
||||
do_draw_cursor = n % 2 == 0 ? true : false;
|
||||
monotonic_t bucket = ms_to_monotonic_t((n + 1) * d);
|
||||
monotonic_t bucket = ms_to_monotonic_t((monotonic_t)(n + 1) * d);
|
||||
monotonic_t delay = bucket - time_since_start_blink;
|
||||
set_maximum_wait(delay);
|
||||
}
|
||||
@@ -766,7 +782,7 @@ thread_write(void *x) {
|
||||
if (pos < data->sz) {
|
||||
log_error("Failed to write all data to STDIN of child process with error: %s", strerror(errno));
|
||||
}
|
||||
safe_close(data->fd);
|
||||
safe_close(data->fd, __FILE__, __LINE__);
|
||||
free_twd(data);
|
||||
return 0;
|
||||
}
|
||||
@@ -783,7 +799,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
|
||||
data->fd = fd;
|
||||
memcpy(data->buf, buf, data->sz);
|
||||
int ret = pthread_create(&thread, NULL, thread_write, data);
|
||||
if (ret != 0) { safe_close(fd); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
|
||||
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
|
||||
pthread_detach(thread);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -1017,7 +1033,7 @@ hangup(pid_t pid) {
|
||||
|
||||
static inline void
|
||||
cleanup_child(ssize_t i) {
|
||||
safe_close(children[i].fd);
|
||||
safe_close(children[i].fd, __FILE__, __LINE__);
|
||||
hangup(children[i].pid);
|
||||
}
|
||||
|
||||
@@ -1279,36 +1295,33 @@ io_loop(void *data) {
|
||||
// {{{ Talk thread functions
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t capacity, used;
|
||||
id_type id;
|
||||
size_t num_of_unresponded_messages_sent_to_main_thread;
|
||||
bool finished_reading;
|
||||
int fd;
|
||||
bool finished, close_socket, is_peer_command;
|
||||
} PeerReadData;
|
||||
static PeerReadData empty_prd = {.fd = -1, 0};
|
||||
struct {
|
||||
char *data;
|
||||
size_t capacity, used, command_end;
|
||||
bool finished;
|
||||
} read;
|
||||
struct {
|
||||
char *data;
|
||||
size_t capacity, used;
|
||||
bool failed;
|
||||
} write;
|
||||
} Peer;
|
||||
static id_type peer_id_counter = 0;
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t sz, pos;
|
||||
int fd;
|
||||
bool finished;
|
||||
} PeerWriteData;
|
||||
static PeerWriteData empty_pwd = {.fd = -1, 0};
|
||||
|
||||
typedef struct {
|
||||
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
|
||||
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
|
||||
struct pollfd *fds;
|
||||
PeerReadData *reads;
|
||||
PeerWriteData *writes;
|
||||
PeerWriteData *queued_writes;
|
||||
size_t num_peers, peers_capacity;
|
||||
Peer *peers;
|
||||
LoopData loop_data;
|
||||
pthread_mutex_t peer_lock;
|
||||
} TalkData;
|
||||
|
||||
static TalkData talk_data = {0};
|
||||
|
||||
typedef struct pollfd PollFD;
|
||||
#define PEER_LIMIT 256
|
||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s); }
|
||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
|
||||
|
||||
static inline bool
|
||||
accept_peer(int listen_fd, bool shutting_down) {
|
||||
@@ -1318,13 +1331,12 @@ accept_peer(int listen_fd, bool shutting_down) {
|
||||
if (!shutting_down) perror("accept() on talk socket failed!");
|
||||
return false;
|
||||
}
|
||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
||||
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
|
||||
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
|
||||
talk_data.num_talk_fds++;
|
||||
if (talk_data.num_peers < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, peers, Peer, talk_data.num_peers + 8, peers_capacity, 8, false);
|
||||
Peer *p = talk_data.peers + talk_data.num_peers++;
|
||||
memset(p, 0, sizeof(Peer));
|
||||
p->fd = peer; p->id = ++peer_id_counter;
|
||||
if (!p->id) p->id = ++peer_id_counter;
|
||||
} else {
|
||||
log_error("Too many peers want to talk, ignoring one.");
|
||||
nuke_socket(peer);
|
||||
@@ -1332,148 +1344,102 @@ accept_peer(int listen_fd, bool shutting_down) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
free_peer(Peer *peer) {
|
||||
free(peer->read.data); peer->read.data = NULL;
|
||||
free(peer->write.data); peer->write.data = NULL;
|
||||
if (peer->fd > -1) { nuke_socket(peer->fd); peer->fd = -1; }
|
||||
}
|
||||
|
||||
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
|
||||
|
||||
static inline void
|
||||
queue_peer_message(ChildMonitor *self, char *buf, size_t sz, int fd) {
|
||||
children_mutex(lock);
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
|
||||
queue_peer_message(ChildMonitor *self, Peer *peer) {
|
||||
talk_mutex(lock);
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
|
||||
Message *m = self->messages + self->messages_count++;
|
||||
m->data = buf; m->sz = sz; m->fd = fd;
|
||||
children_mutex(unlock);
|
||||
memset(m, 0, sizeof(Message));
|
||||
if (peer->read.used) {
|
||||
m->data = malloc(peer->read.used);
|
||||
if (m->data) {
|
||||
memcpy(m->data, peer->read.data, peer->read.used);
|
||||
m->sz = peer->read.used;
|
||||
}
|
||||
}
|
||||
m->peer_id = peer->id;
|
||||
peer->num_of_unresponded_messages_sent_to_main_thread++;
|
||||
talk_mutex(unlock);
|
||||
wakeup_main_loop();
|
||||
}
|
||||
|
||||
static inline void
|
||||
dispatch_peer_command(ChildMonitor *self, PeerReadData *rd, int fd) {
|
||||
size_t end = 0;
|
||||
for (size_t i = 0; i < rd->used - 1; i++) {
|
||||
if (rd->data[i] == 0x1b && rd->data[i+1] == '\\') {
|
||||
end = i + 2;
|
||||
break;
|
||||
static inline bool
|
||||
has_complete_peer_command(Peer *peer) {
|
||||
peer->read.command_end = 0;
|
||||
if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) {
|
||||
for (size_t i = sizeof(KITTY_CMD_PREFIX)-1; i < peer->read.used - 1; i++) {
|
||||
if (peer->read.data[i] == 0x1b && peer->read.data[i+1] == '\\') {
|
||||
peer->read.command_end = i + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!end) return;
|
||||
char *buf = malloc(end + 8);
|
||||
if (buf) {
|
||||
memcpy(buf, rd->data, end);
|
||||
queue_peer_message(self, buf, end, fd);
|
||||
}
|
||||
rd->is_peer_command = false;
|
||||
if (rd->used > end) {
|
||||
rd->used -= end;
|
||||
memmove(rd->data, rd->data + end, rd->used);
|
||||
if (rd->used >= sizeof(KITTY_CMD_PREFIX) - 1 && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
|
||||
} else rd->used = 0;
|
||||
return peer->read.command_end ? true : false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
read_from_peer(ChildMonitor *self, int s) {
|
||||
bool read_finished = false;
|
||||
for (size_t i = 0; i < talk_data.num_reads; i++) {
|
||||
PeerReadData *rd = talk_data.reads + i;
|
||||
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
|
||||
if (rd->fd == s) {
|
||||
if (rd->used >= rd->capacity) {
|
||||
if (rd->capacity >= 64 * 1024) failed("Ignoring too large message from peer");
|
||||
rd->capacity = MAX(8192u, rd->capacity * 2);
|
||||
rd->data = realloc(rd->data, rd->capacity);
|
||||
if (!rd->data) failed("Out of memory");
|
||||
}
|
||||
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
|
||||
if (n == 0) {
|
||||
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
|
||||
read_finished = true; rd->finished = true;
|
||||
if (rd->used) queue_peer_message(self, rd->data, rd->used, s);
|
||||
else free(rd->data);
|
||||
rd->data = NULL;
|
||||
} else if (n < 0) {
|
||||
if (errno != EINTR) {
|
||||
perror("Error reading from talk peer");
|
||||
failed("");
|
||||
}
|
||||
} else {
|
||||
if (!rd->used && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
|
||||
rd->used += n;
|
||||
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
static inline void
|
||||
dispatch_peer_command(ChildMonitor *self, Peer *peer) {
|
||||
if (peer->read.command_end) {
|
||||
size_t used = peer->read.used;
|
||||
peer->read.used = peer->read.command_end;
|
||||
queue_peer_message(self, peer);
|
||||
peer->read.used = used;
|
||||
if (peer->read.used > peer->read.command_end) {
|
||||
peer->read.used -= peer->read.command_end;
|
||||
memmove(peer->read.data, peer->read.data + peer->read.command_end, peer->read.used);
|
||||
} else peer->read.used = 0;
|
||||
peer->read.command_end = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
read_from_peer(ChildMonitor *self, Peer *peer) {
|
||||
#define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; }
|
||||
if (peer->read.used >= peer->read.capacity) {
|
||||
if (peer->read.capacity >= 64 * 1024) failed("Ignoring too large message from peer");
|
||||
peer->read.capacity = MAX(8192u, peer->read.capacity * 2);
|
||||
peer->read.data = realloc(peer->read.data, peer->read.capacity);
|
||||
if (!peer->read.data) failed("Out of memory");
|
||||
}
|
||||
ssize_t n = recv(peer->fd, peer->read.data + peer->read.used, peer->read.capacity - peer->read.used, 0);
|
||||
if (n == 0) {
|
||||
peer->read.finished = true;
|
||||
shutdown(peer->fd, SHUT_RD);
|
||||
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
|
||||
queue_peer_message(self, peer);
|
||||
free(peer->read.data); peer->read.data = NULL;
|
||||
peer->read.used = 0; peer->read.capacity = 0;
|
||||
} else if (n < 0) {
|
||||
if (errno != EINTR) failed(strerror(errno));
|
||||
} else {
|
||||
peer->read.used += n;
|
||||
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
|
||||
}
|
||||
#undef failed
|
||||
return read_finished;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
write_to_peer(int fd) {
|
||||
bool write_finished = false;
|
||||
for (size_t i = 0; i < talk_data.num_writes; i++) {
|
||||
PeerWriteData *wd = talk_data.writes + i;
|
||||
#define failed(msg) { write_finished = true; log_error("%s", msg); wd->finished = true; break; }
|
||||
if (wd->fd == fd) {
|
||||
ssize_t n = send(fd, wd->data + wd->pos, wd->sz - wd->pos, MSG_NOSIGNAL);
|
||||
if (n == 0) { failed("send() to peer failed to send any data"); }
|
||||
else if (n < 0) {
|
||||
if (errno != EINTR) { perror("write() to peer socket failed with error"); failed(""); }
|
||||
} else {
|
||||
wd->pos += n;
|
||||
if (wd->pos >= wd->sz) { write_finished = true; wd->finished = true; }
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
#undef failed
|
||||
return write_finished;
|
||||
}
|
||||
|
||||
static inline void
|
||||
remove_poll_fd(int fd) {
|
||||
size_t count = talk_data.num_talk_fds + talk_data.num_listen_fds;
|
||||
for (size_t i = talk_data.num_listen_fds; i < count; i++) {
|
||||
struct pollfd *pfd = talk_data.fds + i;
|
||||
if (pfd->fd == fd) {
|
||||
size_t num_to_right = count - 1 - i;
|
||||
if (num_to_right) memmove(talk_data.fds + i, talk_data.fds + i + 1, num_to_right * sizeof(struct pollfd));
|
||||
talk_data.num_talk_fds--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
prune_finished_reads(void) {
|
||||
if (!talk_data.num_reads) return;
|
||||
for (ssize_t i = talk_data.num_reads - 1; i >= 0; i--) {
|
||||
PeerReadData *rd = talk_data.reads + i;
|
||||
if (rd->finished) {
|
||||
remove_poll_fd(rd->fd);
|
||||
if (rd->close_socket) { nuke_socket(rd->fd); }
|
||||
else shutdown(rd->fd, SHUT_RD);
|
||||
free(rd->data);
|
||||
ssize_t num_to_right = talk_data.num_reads - 1 - i;
|
||||
if (num_to_right > 0) memmove(talk_data.reads + i, talk_data.reads + i + 1, num_to_right * sizeof(PeerReadData));
|
||||
else talk_data.reads[i] = empty_prd;
|
||||
talk_data.num_reads--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
prune_finished_writes(void) {
|
||||
if (!talk_data.num_writes) return;
|
||||
for (ssize_t i = talk_data.num_writes - 1; i >= 0; i--) {
|
||||
PeerWriteData *wd = talk_data.writes + i;
|
||||
if (wd->finished) {
|
||||
remove_poll_fd(wd->fd);
|
||||
shutdown(wd->fd, SHUT_WR); safe_close(wd->fd);
|
||||
free(wd->data);
|
||||
ssize_t num_to_right = talk_data.num_writes - 1 - i;
|
||||
if (num_to_right > 0) memmove(talk_data.writes + i, talk_data.writes + i + 1, num_to_right * sizeof(PeerWriteData));
|
||||
else talk_data.writes[i] = empty_pwd;
|
||||
talk_data.num_writes--;
|
||||
}
|
||||
write_to_peer(Peer *peer) {
|
||||
talk_mutex(lock);
|
||||
ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL);
|
||||
if (n == 0) { log_error("send() to peer failed to send any data"); peer->write.used = 0; peer->write.failed = true; }
|
||||
else if (n < 0) {
|
||||
if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; }
|
||||
} else {
|
||||
if ((size_t)n > peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n);
|
||||
peer->write.used -= n;
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1481,92 +1447,107 @@ wakeup_talk_loop(bool in_signal_handler) {
|
||||
if (talk_thread_started) wakeup_loop(&talk_data.loop_data, in_signal_handler, "talk_loop");
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
move_queued_writes(void) {
|
||||
while (talk_data.num_queued_writes) {
|
||||
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
|
||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
||||
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
|
||||
talk_data.writes[talk_data.num_writes++] = *src;
|
||||
talk_data.num_talk_fds++;
|
||||
} else {
|
||||
log_error("Cannot send response to peer, too many peers");
|
||||
free(src->data); nuke_socket(src->fd);
|
||||
prune_peers(void) {
|
||||
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||
size_t idx = talk_data.num_peers - 1 - i;
|
||||
Peer *p = talk_data.peers + idx;
|
||||
if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) {
|
||||
free_peer(p);
|
||||
remove_i_from_array(talk_data.peers, idx, talk_data.num_peers);
|
||||
}
|
||||
*src = empty_pwd;
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
talk_loop(void *data) {
|
||||
// The talk thread loop
|
||||
|
||||
ChildMonitor *self = (ChildMonitor*)data;
|
||||
set_thread_name("KittyPeerMon");
|
||||
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
|
||||
if (!init_loop_data(&talk_data.loop_data)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
|
||||
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
|
||||
PollFD fds[PEER_LIMIT + 8] = {0};
|
||||
size_t num_listen_fds = 0, num_peer_fds = 0;
|
||||
#define add_listener(which) \
|
||||
if (self->which > -1) { \
|
||||
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
|
||||
fds[num_listen_fds].fd = self->which; fds[num_listen_fds++].events = POLLIN; \
|
||||
}
|
||||
add_listener(talk_fd); add_listener(listen_fd);
|
||||
#undef add_listener
|
||||
talk_data.fds[talk_data.num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN;
|
||||
fds[num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; fds[num_listen_fds++].events = POLLIN;
|
||||
|
||||
while (LIKELY(!self->shutting_down)) {
|
||||
for (size_t i = 0; i < talk_data.num_listen_fds + talk_data.num_talk_fds; i++) { talk_data.fds[i].revents = 0; }
|
||||
int ret = poll(talk_data.fds, talk_data.num_listen_fds + talk_data.num_talk_fds, -1);
|
||||
num_peer_fds = 0;
|
||||
if (talk_data.num_peers > 0) {
|
||||
talk_mutex(lock);
|
||||
prune_peers();
|
||||
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||
Peer *p = talk_data.peers + i;
|
||||
if (!p->read.finished || p->write.used) {
|
||||
fds[num_listen_fds + num_peer_fds].fd = p->fd;
|
||||
fds[num_listen_fds + num_peer_fds].revents = 0;
|
||||
int flags = 0;
|
||||
if (!p->read.finished) flags |= POLLIN;
|
||||
if (p->write.used) flags |= POLLOUT;
|
||||
fds[num_listen_fds + num_peer_fds++].events = flags;
|
||||
}
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
}
|
||||
for (size_t i = 0; i < num_listen_fds; i++) fds[i].revents = 0;
|
||||
int ret = poll(fds, num_listen_fds + num_peer_fds, -1);
|
||||
if (ret > 0) {
|
||||
bool has_finished_reads = false, has_finished_writes = false;
|
||||
for (size_t i = 0; i < talk_data.num_listen_fds - 1; i++) {
|
||||
if (talk_data.fds[i].revents & POLLIN) {if (!accept_peer(talk_data.fds[i].fd, self->shutting_down)) goto end; }
|
||||
for (size_t i = 0; i < num_listen_fds - 1; i++) {
|
||||
if (fds[i].revents & POLLIN) {
|
||||
if (!accept_peer(fds[i].fd, self->shutting_down)) goto end;
|
||||
}
|
||||
}
|
||||
if (talk_data.fds[talk_data.num_listen_fds - 1].revents & POLLIN) drain_fd(talk_data.fds[talk_data.num_listen_fds - 1].fd); // wakeup
|
||||
for (size_t i = talk_data.num_listen_fds; i < talk_data.num_talk_fds + talk_data.num_listen_fds; i++) {
|
||||
if (talk_data.fds[i].revents & (POLLIN | POLLHUP)) { if (read_from_peer(self, talk_data.fds[i].fd)) has_finished_reads = true; }
|
||||
if (talk_data.fds[i].revents & POLLOUT) { if (write_to_peer(talk_data.fds[i].fd)) has_finished_writes = true; }
|
||||
if (fds[num_listen_fds - 1].revents & POLLIN) {
|
||||
drain_fd(fds[num_listen_fds - 1].fd); // wakeup
|
||||
}
|
||||
for (size_t i = num_listen_fds, k = 0; i < num_peer_fds + num_listen_fds; i++, k++) {
|
||||
Peer *p = talk_data.peers + k;
|
||||
if (fds[i].revents & (POLLIN | POLLHUP)) read_from_peer(self, p);
|
||||
if (fds[i].revents & POLLOUT) write_to_peer(p);
|
||||
if (fds[i].revents & POLLNVAL) {
|
||||
p->read.finished = true;
|
||||
p->write.failed = true; p->write.used = 0;
|
||||
}
|
||||
}
|
||||
if (has_finished_reads) prune_finished_reads();
|
||||
if (has_finished_writes) prune_finished_writes();
|
||||
peer_mutex(lock);
|
||||
if (talk_data.num_queued_writes) move_queued_writes();
|
||||
peer_mutex(unlock);
|
||||
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
|
||||
}
|
||||
end:
|
||||
free_loop_data(&talk_data.loop_data);
|
||||
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
|
||||
for (size_t i = 0; i < talk_data.num_peers; i++) free_peer(talk_data.peers + i);
|
||||
free(talk_data.peers);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
|
||||
bool ok = false;
|
||||
peer_mutex(lock);
|
||||
if (talk_data.num_queued_writes < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
|
||||
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
|
||||
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
|
||||
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
|
||||
memcpy(talk_data.queued_writes[talk_data.num_queued_writes].data, msg, msg_sz);
|
||||
talk_data.queued_writes[talk_data.num_queued_writes].sz = msg_sz;
|
||||
talk_data.queued_writes[talk_data.num_queued_writes++].fd = fd;
|
||||
ok = true;
|
||||
}
|
||||
} else log_error("Cannot send response to peer, too many peers");
|
||||
peer_mutex(unlock);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void
|
||||
send_response(int fd, const char *msg, size_t msg_sz) {
|
||||
if (msg == NULL) { shutdown(fd, SHUT_WR); safe_close(fd); return; }
|
||||
if (!add_peer_writer(fd, msg, msg_sz)) { shutdown(fd, SHUT_WR); safe_close(fd); }
|
||||
else wakeup_talk_loop(false);
|
||||
send_response(id_type peer_id, const char *msg, size_t msg_sz) {
|
||||
bool wakeup = false;
|
||||
talk_mutex(lock);
|
||||
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||
Peer *peer = talk_data.peers + i;
|
||||
if (peer->id == peer_id) {
|
||||
if (peer->num_of_unresponded_messages_sent_to_main_thread) peer->num_of_unresponded_messages_sent_to_main_thread--;
|
||||
if (!peer->write.failed) {
|
||||
if (peer->write.capacity - peer->write.used < msg_sz) {
|
||||
void *data = realloc(peer->write.data, peer->write.capacity + msg_sz);
|
||||
if (data) {
|
||||
peer->write.data = data;
|
||||
peer->write.capacity += msg_sz;
|
||||
} else fatal("Out of memory");
|
||||
}
|
||||
memcpy(peer->write.data + peer->write.used, msg, msg_sz);
|
||||
peer->write.used += msg_sz;
|
||||
}
|
||||
wakeup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
if (wakeup) wakeup_talk_loop(false);
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
@@ -99,28 +99,28 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
// On BSD open() does not establish the controlling terminal
|
||||
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
||||
#endif
|
||||
safe_close(tfd);
|
||||
safe_close(tfd, __FILE__, __LINE__);
|
||||
|
||||
// Redirect stdin/stdout/stderr to the pty
|
||||
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||
if (stdin_read_fd > -1) {
|
||||
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
safe_close(stdin_read_fd);
|
||||
safe_close(stdin_write_fd);
|
||||
safe_close(stdin_read_fd, __FILE__, __LINE__);
|
||||
safe_close(stdin_write_fd, __FILE__, __LINE__);
|
||||
} else {
|
||||
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
}
|
||||
safe_close(slave);
|
||||
safe_close(master);
|
||||
safe_close(slave, __FILE__, __LINE__);
|
||||
safe_close(master, __FILE__, __LINE__);
|
||||
|
||||
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
|
||||
safe_close(ready_write_fd);
|
||||
safe_close(ready_write_fd, __FILE__, __LINE__);
|
||||
wait_for_terminal_ready(ready_read_fd);
|
||||
safe_close(ready_read_fd);
|
||||
safe_close(ready_read_fd, __FILE__, __LINE__);
|
||||
|
||||
// Close any extra fds inherited from parent
|
||||
for (int c = 3; c < 201; c++) safe_close(c);
|
||||
for (int c = 3; c < 201; c++) safe_close(c, __FILE__, __LINE__);
|
||||
|
||||
environ = env;
|
||||
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
|
||||
|
||||
@@ -210,6 +210,8 @@ class Child:
|
||||
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
|
||||
if env is None:
|
||||
env = self._final_env = default_env().copy()
|
||||
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not sys._xoptions.get('lc_ctype_before_python'):
|
||||
del env['LC_CTYPE']
|
||||
env.update(self.env)
|
||||
env['TERM'] = self.opts.term
|
||||
env['COLORTERM'] = 'truecolor'
|
||||
|
||||
@@ -34,6 +34,14 @@ def screen_bell() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def screen_normal_keypad_mode() -> None:
|
||||
write('\x1b>')
|
||||
|
||||
|
||||
def screen_alternate_keypad_mode() -> None:
|
||||
write('\x1b=')
|
||||
|
||||
|
||||
def screen_cursor_position(y: int, x: int) -> None:
|
||||
write(CSI + '%s;%sH' % (y, x))
|
||||
|
||||
@@ -80,6 +88,18 @@ def screen_indexn(n: int) -> None:
|
||||
write(CSI + '%dS' % n)
|
||||
|
||||
|
||||
def screen_delete_characters(count: int) -> None:
|
||||
write(CSI + '%dP' % count)
|
||||
|
||||
|
||||
def screen_insert_characters(count: int) -> None:
|
||||
write(CSI + '%d@' % count)
|
||||
|
||||
|
||||
def screen_scroll(count: int) -> None:
|
||||
write(CSI + '%dS' % count)
|
||||
|
||||
|
||||
def screen_erase_in_display(how: int, private: bool) -> None:
|
||||
write(CSI + ('?' if private else '') + str(how) + 'J')
|
||||
|
||||
@@ -108,6 +128,10 @@ def screen_linefeed() -> None:
|
||||
write('\n')
|
||||
|
||||
|
||||
def screen_tab() -> None:
|
||||
write('\t')
|
||||
|
||||
|
||||
def screen_backspace() -> None:
|
||||
write('\x08')
|
||||
|
||||
|
||||
@@ -390,6 +390,12 @@ cocoa_focus_window(void *w) {
|
||||
[window makeKeyWindow];
|
||||
}
|
||||
|
||||
long
|
||||
cocoa_window_number(void *w) {
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
return [window windowNumber];
|
||||
}
|
||||
|
||||
size_t
|
||||
cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz) {
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
|
||||
@@ -783,10 +783,10 @@ Note that this does not currently work on Wayland.
|
||||
'''))
|
||||
|
||||
o('confirm_os_window_close', 0, option_type=positive_int, long_text=_('''
|
||||
Ask for confirmation when closing an OS window that has at least this
|
||||
Ask for confirmation when closing an OS window or a tab that has at least this
|
||||
number of kitty windows in it. A value of zero disables confirmation.
|
||||
This confirmation also applies to requests to quit the entire application (all
|
||||
OS windows, via the quite action).
|
||||
OS windows, via the quit action).
|
||||
'''))
|
||||
# }}}
|
||||
|
||||
@@ -1402,6 +1402,7 @@ g('shortcuts.fonts') # {{{
|
||||
k('increase_font_size', 'kitty_mod+equal', 'change_font_size all +2.0', _('Increase font size'))
|
||||
if is_macos:
|
||||
k('increase_font_size', 'cmd+plus', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
|
||||
k('increase_font_size', 'cmd+shift+equal', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
|
||||
k('decrease_font_size', 'kitty_mod+minus', 'change_font_size all -2.0', _('Decrease font size'))
|
||||
if is_macos:
|
||||
k('decrease_font_size', 'cmd+minus', 'change_font_size all -2.0', _('Decrease font size'), add_to_docs=False)
|
||||
|
||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
appname: str = 'kitty'
|
||||
version: Version = Version(0, 18, 1)
|
||||
version: Version = Version(0, 18, 2)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -110,10 +110,10 @@
|
||||
#define ESC_DECRC '8'
|
||||
|
||||
// Set normal keypad mode
|
||||
#define ESC_DECPNM '>'
|
||||
#define ESC_DECKPNM '>'
|
||||
|
||||
// Set alternate keypad mode
|
||||
#define ESC_DECPAM '='
|
||||
#define ESC_DECKPAM '='
|
||||
|
||||
// ECMA-48 CSI sequences.
|
||||
// ---------------------
|
||||
|
||||
@@ -409,7 +409,7 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt
|
||||
CGContextSetTextDrawingMode(ctx, kCGTextFill);
|
||||
CGGlyph glyph = glyph_id;
|
||||
CGContextSetTextMatrix(ctx, transform);
|
||||
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - 1.2f * baseline)); // lower the emoji a bit so its bottom is not on the baseline
|
||||
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - baseline));
|
||||
CGPoint p = CGPointMake(0, 0);
|
||||
CTFontDrawGlyphs(font, &glyph, &p, 1, ctx);
|
||||
CGContextRelease(ctx);
|
||||
|
||||
@@ -142,7 +142,7 @@ close_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
TTY_ARGS
|
||||
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
|
||||
free(termios_p);
|
||||
safe_close(fd);
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,5 +312,10 @@ void play_canberra_sound(const char *which_sound, const char *event_id);
|
||||
SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int, unsigned int);
|
||||
SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE);
|
||||
|
||||
static inline void safe_close(int fd) { while(close(fd) != 0 && errno == EINTR); }
|
||||
static inline void safe_close(int fd, const char* file UNUSED, const int line UNUSED) {
|
||||
#if 0
|
||||
printf("Closing fd: %d from file: %s line: %d\n", fd, file, line);
|
||||
#endif
|
||||
while(close(fd) != 0 && errno == EINTR);
|
||||
}
|
||||
void log_event(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
||||
|
||||
@@ -275,7 +275,7 @@ GLFW_CONTEXT_VERSION_MINOR: int
|
||||
GLFW_CONTEXT_REVISION: int
|
||||
GLFW_CONTEXT_ROBUSTNESS: int
|
||||
GLFW_OPENGL_FORWARD_COMPAT: int
|
||||
GLFW_OPENGL_DEBUG_CONTEXT: int
|
||||
GLFW_CONTEXT_DEBUG: int
|
||||
GLFW_OPENGL_PROFILE: int
|
||||
GLFW_OPENGL_API: int
|
||||
GLFW_OPENGL_ES_API: int
|
||||
@@ -781,6 +781,10 @@ def x11_window_id(os_window_id: int) -> int:
|
||||
pass
|
||||
|
||||
|
||||
def cocoa_window_id(os_window_id: int) -> int:
|
||||
pass
|
||||
|
||||
|
||||
def swap_tabs(os_window_id: int, a: int, b: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y
|
||||
|
||||
static inline void
|
||||
clear_canvas(FontGroup *fg) {
|
||||
if (fg->canvas) memset(fg->canvas, 0, CELLS_IN_CANVAS * fg->cell_width * fg->cell_height * sizeof(pixel));
|
||||
if (fg->canvas) memset(fg->canvas, 0, sizeof(pixel) * CELLS_IN_CANVAS * fg->cell_width * fg->cell_height);
|
||||
}
|
||||
|
||||
|
||||
@@ -457,7 +457,7 @@ calc_cell_metrics(FontGroup *fg) {
|
||||
fg->cell_width = cell_width; fg->cell_height = cell_height;
|
||||
fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness, fg->strikethrough_position = strikethrough_position, fg->strikethrough_thickness = strikethrough_thickness;
|
||||
free(fg->canvas);
|
||||
fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
|
||||
fg->canvas = calloc((size_t)CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
|
||||
if (!fg->canvas) fatal("Out of memory allocating canvas for font group");
|
||||
}
|
||||
|
||||
@@ -1132,6 +1132,11 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_non_emoji_dingbat(char_type ch) {
|
||||
return 0x2700 <= ch && ch <= 0x27bf && !is_emoji(ch);
|
||||
}
|
||||
|
||||
void
|
||||
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \
|
||||
@@ -1154,7 +1159,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
|
||||
|
||||
if (
|
||||
cell_font_idx != MISSING_FONT &&
|
||||
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && is_private_use(cpu_cell->ch)))
|
||||
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && (is_private_use(cpu_cell->ch))) || is_non_emoji_dingbat(cpu_cell->ch))
|
||||
) {
|
||||
unsigned int desired_cells = 1;
|
||||
if (cell_font_idx > 0) {
|
||||
@@ -1168,7 +1173,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
|
||||
unsigned int num_spaces = 0;
|
||||
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
|
||||
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
|
||||
&& num_spaces < desired_cells
|
||||
&& num_spaces + 1 < desired_cells
|
||||
&& i + num_spaces + 1 < line->xnum) {
|
||||
num_spaces++;
|
||||
// We have a private use char followed by space(s), render it as a multi-cell ligature.
|
||||
@@ -1388,7 +1393,7 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
||||
PyObject *cells;
|
||||
if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL;
|
||||
size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i;
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, 4 * cell_width * cell_height * num_cells);
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, (size_t)4 * cell_width * cell_height * num_cells);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
pixel *dest = (pixel*)PyBytes_AS_STRING(ans);
|
||||
for (r = 0; r < cell_height; r++) {
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
#
|
||||
|
||||
import math
|
||||
from functools import partial as p
|
||||
from functools import partial as p, wraps
|
||||
from itertools import repeat
|
||||
from typing import (
|
||||
Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
|
||||
Any, Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
|
||||
Sequence, Tuple, cast
|
||||
)
|
||||
|
||||
@@ -146,18 +146,49 @@ def cross(buf: BufType, width: int, height: int, a: int = 1, b: int = 1, c: int
|
||||
half_vline(buf, width, height, level=d, which='bottom')
|
||||
|
||||
|
||||
def downsample(src: BufType, dest: BufType, dest_width: int, dest_height: int, factor: int = 4) -> None:
|
||||
src_width = 4 * dest_width
|
||||
|
||||
def average_intensity_in_src(dest_x: int, dest_y: int) -> int:
|
||||
src_y = dest_y * factor
|
||||
src_x = dest_x * factor
|
||||
total = 0
|
||||
for y in range(src_y, src_y + factor):
|
||||
offset = src_width * y
|
||||
for x in range(src_x, src_x + factor):
|
||||
total += src[offset + x]
|
||||
return total // (factor * factor)
|
||||
|
||||
for y in range(dest_height):
|
||||
offset = dest_width * y
|
||||
for x in range(dest_width):
|
||||
dest[offset + x] = min(255, dest[offset + x] + average_intensity_in_src(x, y))
|
||||
|
||||
|
||||
def supersampled(supersample_factor: int = 4) -> Callable:
|
||||
# Anti-alias the drawing performed by the wrapped function by
|
||||
# using supersampling
|
||||
|
||||
class SSByteArray(bytearray):
|
||||
supersample_factor = 1
|
||||
|
||||
def create_wrapper(f: Callable) -> Callable:
|
||||
@wraps(f)
|
||||
def supersampled_wrapper(buf: BufType, width: int, height: int, *args: Any, **kw: Any) -> None:
|
||||
w, h = supersample_factor * width, supersample_factor * height
|
||||
ssbuf = SSByteArray(w * h)
|
||||
ssbuf.supersample_factor = supersample_factor
|
||||
f(ssbuf, w, h, *args, **kw)
|
||||
downsample(ssbuf, buf, width, height, factor=supersample_factor)
|
||||
return supersampled_wrapper
|
||||
return create_wrapper
|
||||
|
||||
|
||||
def fill_region(buf: BufType, width: int, height: int, xlimits: Iterable[Iterable[float]]) -> None:
|
||||
for y in range(height):
|
||||
offset = y * width
|
||||
for x, (upper, lower) in enumerate(xlimits):
|
||||
buf[x + offset] = 255 if upper <= y <= lower else 0
|
||||
# Anti-alias the boundary, simple y-axis anti-aliasing
|
||||
for x, limits in enumerate(xlimits):
|
||||
for yf in limits:
|
||||
for ypx in range(int(math.floor(yf)), int(math.ceil(yf)) + 1):
|
||||
if 0 <= ypx < height:
|
||||
off = ypx * width + x
|
||||
buf[off] = min(255, buf[off] + int((1 - abs(yf - ypx)) * 255))
|
||||
|
||||
|
||||
def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
|
||||
@@ -170,6 +201,7 @@ def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
|
||||
return y
|
||||
|
||||
|
||||
@supersampled()
|
||||
def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
||||
ay1, by1, y2 = 0, height - 1, height // 2
|
||||
if left:
|
||||
@@ -182,6 +214,7 @@ def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
||||
fill_region(buf, width, height, xlimits)
|
||||
|
||||
|
||||
@supersampled()
|
||||
def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
|
||||
if corner == 'top-right' or corner == 'bottom-left':
|
||||
diagonal_y = line_equation(0, 0, width - 1, height - 1)
|
||||
@@ -198,85 +231,35 @@ def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
|
||||
fill_region(buf, width, height, xlimits)
|
||||
|
||||
|
||||
def antialiased_1px_line(buf: BufType, width: int, height: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None:
|
||||
# Draw an antialiased line using the Wu algorithm
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
dx, dy = x2 - x1, y2 - y1
|
||||
off_limit = height * width
|
||||
steep = abs(dx) < abs(dy)
|
||||
def thick_line(buf: BufType, width: int, height: int, thickness_in_pixels: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None:
|
||||
if p1[0] > p2[0]:
|
||||
p1, p2 = p2, p1
|
||||
leq = line_equation(*p1, *p2)
|
||||
delta, extra = divmod(thickness_in_pixels, 2)
|
||||
|
||||
if steep:
|
||||
x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx
|
||||
|
||||
def p(x: int, y: int) -> Tuple[int, int]:
|
||||
return y, x
|
||||
else:
|
||||
def p(x: int, y: int) -> Tuple[int, int]:
|
||||
return x, y
|
||||
|
||||
if x2 < x1:
|
||||
x1, x2, y1, y2 = x2, x1, y2, y1
|
||||
|
||||
def fpart(x: float) -> float:
|
||||
return x - int(x)
|
||||
|
||||
def rfpart(x: float) -> float:
|
||||
return 1 - fpart(x)
|
||||
|
||||
def putpixel(p: Tuple[int, int], alpha: float) -> None:
|
||||
x, y = p
|
||||
off = int(x + y * width)
|
||||
if 0 <= off < off_limit:
|
||||
buf[off] = int(min(buf[off] + (alpha * 255), 255))
|
||||
|
||||
def draw_endpoint(pt: Tuple[int, int]) -> int:
|
||||
x, y = pt
|
||||
xend = round(x)
|
||||
yend = y + grad * (xend - x)
|
||||
xgap = rfpart(x + 0.5)
|
||||
px, py = int(xend), int(yend)
|
||||
putpixel(p(px, py), rfpart(yend) * xgap)
|
||||
putpixel(p(px, py+1), fpart(yend) * xgap)
|
||||
return px
|
||||
|
||||
grad = dy/dx
|
||||
intery = y1 + rfpart(x1) * grad
|
||||
|
||||
xstart = draw_endpoint(p(*p1))
|
||||
xend = draw_endpoint(p(*p2))
|
||||
|
||||
if xstart > xend:
|
||||
xstart, xend = xend, xstart
|
||||
xstart += 1
|
||||
|
||||
for x in range(xstart, xend):
|
||||
y = int(intery)
|
||||
putpixel(p(x, y), rfpart(intery))
|
||||
putpixel(p(x, y+1), fpart(intery))
|
||||
intery += grad
|
||||
|
||||
|
||||
def antialiased_line(buf: BufType, width: int, height: int, p1: Tuple[int, int], p2: Tuple[int, int], level: int = 1) -> None:
|
||||
th = thickness(level)
|
||||
if th < 2:
|
||||
return antialiased_1px_line(buf, width, height, p1, p2)
|
||||
(x1, y1), (x2, y2) = p1, p2
|
||||
dh = th // 2
|
||||
items = range(-dh, dh + (th % 2))
|
||||
for delta in items:
|
||||
antialiased_1px_line(buf, width, height, (x1, y1 + delta), (x2, y2 + delta))
|
||||
for x in range(p1[0], p2[0] + 1):
|
||||
if 0 <= x < width:
|
||||
y_p = leq(x)
|
||||
r = range(int(y_p) - delta, int(y_p) + delta + extra)
|
||||
for y in r:
|
||||
if 0 <= y < height:
|
||||
buf[x + y * width] = 255
|
||||
|
||||
|
||||
@supersampled()
|
||||
def cross_line(buf: BufType, width: int, height: int, left: bool = True, level: int = 1) -> None:
|
||||
if left:
|
||||
p1, p2 = (0, 0), (width - 1, height - 1)
|
||||
else:
|
||||
p1, p2 = (width - 1, 0), (0, height - 1)
|
||||
antialiased_line(buf, width, height, p1, p2, level=level)
|
||||
supersample_factor = getattr(buf, 'supersample_factor')
|
||||
thick_line(buf, width, height, supersample_factor * thickness(level), p1, p2)
|
||||
|
||||
|
||||
@supersampled()
|
||||
def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', level: int = 1) -> None:
|
||||
supersample_factor = getattr(buf, 'supersample_factor')
|
||||
thickness_in_pixels = thickness(level) * supersample_factor
|
||||
my = (height - 1) // 2
|
||||
if which == 'tl':
|
||||
p1 = 0, 0
|
||||
@@ -290,7 +273,7 @@ def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', le
|
||||
else:
|
||||
p2 = width - 1, height - 1
|
||||
p1 = 0, my
|
||||
antialiased_line(buf, width, height, p1, p2, level=level)
|
||||
thick_line(buf, width, height, thickness_in_pixels, p1, p2)
|
||||
|
||||
|
||||
BezierFunc = Callable[[float], float]
|
||||
@@ -360,6 +343,7 @@ def get_bezier_limits(bezier_x: BezierFunc, bezier_y: BezierFunc) -> Generator[T
|
||||
yield upper, lower
|
||||
|
||||
|
||||
@supersampled()
|
||||
def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
||||
c1x = find_bezier_for_D(width, height)
|
||||
start = (0, 0)
|
||||
@@ -380,6 +364,53 @@ def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
||||
buf[offset + dest_x] = mbuf[offset + src_x]
|
||||
|
||||
|
||||
def draw_parametrized_curve(buf: BufType, width: int, height: int, thickness_in_pixels: int, xfunc: BezierFunc, yfunc: BezierFunc) -> None:
|
||||
num_samples = height*4
|
||||
seen = set()
|
||||
delta, extra = divmod(thickness_in_pixels, 2)
|
||||
for i in range(num_samples + 1):
|
||||
t = (i / num_samples)
|
||||
p = x_p, y_p = int(xfunc(t)), int(yfunc(t))
|
||||
if p in seen:
|
||||
continue
|
||||
seen.add(p)
|
||||
for y in range(int(y_p) - delta, int(y_p) + delta + extra):
|
||||
if 0 <= y < height:
|
||||
offset = y * width
|
||||
for x in range(int(x_p) - delta, int(x_p) + delta + extra):
|
||||
if 0 <= x < width:
|
||||
pos = offset + x
|
||||
buf[pos] = min(255, buf[pos] + 255)
|
||||
|
||||
|
||||
@supersampled()
|
||||
def rounded_corner(buf: BufType, width: int, height: int, level: int = 1, which: str = '╭') -> None:
|
||||
supersample_factor = getattr(buf, 'supersample_factor')
|
||||
thickness_in_pixels = thickness(level) * supersample_factor
|
||||
if which == '╭':
|
||||
start = width // 2, height - 1
|
||||
end = width - 1, height // 2
|
||||
c1 = width // 2, int(0.75 * height)
|
||||
c2 = width // 2, height // 2 + 1
|
||||
elif which == '╮':
|
||||
start = 0, height // 2
|
||||
end = width // 2, height - 1
|
||||
c1 = width // 2, height // 2 + 1
|
||||
c2 = width // 2, int(0.75 * height)
|
||||
elif which == '╰':
|
||||
start = width // 2, 0
|
||||
end = width - 1, height // 2
|
||||
c1 = width // 2, int(0.25 * height)
|
||||
c2 = width // 2 - 1, height // 2 - 1
|
||||
elif which == '╯':
|
||||
start = 0, height // 2
|
||||
end = width // 2, 0
|
||||
c1 = width // 2 - 1, height // 2 - 1
|
||||
c2 = width // 2, int(0.25 * height)
|
||||
xfunc, yfunc = cubic_bezier(start, end, c1, c2)
|
||||
draw_parametrized_curve(buf, width, height, thickness_in_pixels, xfunc, yfunc)
|
||||
|
||||
|
||||
def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', only: Optional[str] = None) -> Tuple[int, int]:
|
||||
x1, x2 = (0, width // 2) if which == 'left' else (width // 2, width)
|
||||
gap = thickness(level + 1, horizontal=False)
|
||||
@@ -591,17 +622,29 @@ box_chars: Dict[str, List[Callable]] = {
|
||||
'': [p(corner_triangle, corner='top-right')],
|
||||
'═': [dhline],
|
||||
'║': [dvline],
|
||||
|
||||
'╞': [vline, p(half_dhline, which='right')],
|
||||
|
||||
'╡': [vline, half_dhline],
|
||||
|
||||
'╥': [hline, p(half_dvline, which='bottom')],
|
||||
|
||||
'╨': [hline, half_dvline],
|
||||
|
||||
'╪': [vline, half_dhline, p(half_dhline, which='right')],
|
||||
|
||||
'╫': [hline, half_dvline, p(half_dvline, which='bottom')],
|
||||
|
||||
'╬': [p(inner_corner, which=x) for x in 'tl tr bl br'.split()],
|
||||
|
||||
'╠': [p(inner_corner, which='tr'), p(inner_corner, which='br'), p(dvline, only='left')],
|
||||
|
||||
'╣': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')],
|
||||
|
||||
'╦': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')],
|
||||
|
||||
'╩': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')],
|
||||
|
||||
'╱': [p(cross_line, left=False)],
|
||||
'╲': [cross_line],
|
||||
'╳': [cross_line, p(cross_line, left=False)],
|
||||
@@ -643,8 +686,8 @@ t, f = 1, 3
|
||||
for start in '┌┐└┘':
|
||||
for i, (hlevel, vlevel) in enumerate(((t, t), (f, t), (t, f), (f, f))):
|
||||
box_chars[chr(ord(start) + i)] = [p(corner, which=start, hlevel=hlevel, vlevel=vlevel)]
|
||||
for ch, c in zip('╭╮╯╰', '┌┐┘└'):
|
||||
box_chars[ch] = [p(corner, which=c)] # TODO: Make these rounded
|
||||
for ch in '╭╮╯╰':
|
||||
box_chars[ch] = [p(rounded_corner, which=ch)]
|
||||
|
||||
for i, (a_, b_, c_, d_) in enumerate((
|
||||
(t, t, t, t), (f, t, t, t), (t, f, t, t), (f, f, t, t), (t, t, f, t), (t, t, t, f), (t, t, f, f),
|
||||
|
||||
@@ -457,7 +457,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
|
||||
// it matters
|
||||
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
|
||||
int factor = (int)ceilf(ratio);
|
||||
uint8_t *dest = calloc(4, width * cell_height);
|
||||
uint8_t *dest = calloc(4, (size_t)width * cell_height);
|
||||
if (dest == NULL) fatal("Out of memory");
|
||||
uint8_t *d = dest;
|
||||
|
||||
@@ -497,7 +497,8 @@ static inline bool
|
||||
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
|
||||
(void)baseline;
|
||||
unsigned short best = 0, diff = USHRT_MAX;
|
||||
for (short i = 0; i < self->face->num_fixed_sizes; i++) {
|
||||
const short limit = self->face->num_fixed_sizes;
|
||||
for (short i = 0; i < limit; i++) {
|
||||
unsigned short w = self->face->available_sizes[i].width;
|
||||
unsigned short d = w > (unsigned short)cell_width ? w - (unsigned short)cell_width : (unsigned short)cell_width - w;
|
||||
if (d < diff) {
|
||||
|
||||
41
kitty/glfw-wrapper.h
generated
41
kitty/glfw-wrapper.h
generated
@@ -569,7 +569,7 @@
|
||||
* @analysis Application programmer error. Fix the offending call.
|
||||
*/
|
||||
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
||||
/*! @brief The reuqested feature is not provided by the platform.
|
||||
/*! @brief The requested feature is not provided by the platform.
|
||||
*
|
||||
* The requested feature is not provided by the platform, so GLFW is unable to
|
||||
* implement it. The documentation for each function notes if it could emit
|
||||
@@ -670,11 +670,19 @@
|
||||
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
||||
*/
|
||||
#define GLFW_FOCUS_ON_SHOW 0x0002000C
|
||||
|
||||
/*! @brief Mouse input transparency window hint and attribute
|
||||
*
|
||||
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
|
||||
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
|
||||
*/
|
||||
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
|
||||
|
||||
/*! @brief Occlusion window attribute
|
||||
*
|
||||
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
|
||||
*/
|
||||
#define GLFW_OCCLUDED 0x0002000D
|
||||
#define GLFW_OCCLUDED 0x0002000E
|
||||
/*! @brief Framebuffer bit depth hint.
|
||||
*
|
||||
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
|
||||
@@ -792,12 +800,17 @@
|
||||
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
|
||||
*/
|
||||
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
|
||||
/*! @brief OpenGL debug context hint and attribute.
|
||||
/*! @brief Debug mode context hint and attribute.
|
||||
*
|
||||
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
|
||||
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
|
||||
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
|
||||
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
|
||||
*/
|
||||
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
|
||||
#define GLFW_CONTEXT_DEBUG 0x00022007
|
||||
/*! @brief Legacy name for compatibility.
|
||||
*
|
||||
* This is an alias for compatibility with earlier versions.
|
||||
*/
|
||||
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
|
||||
/*! @brief OpenGL profile hint and attribute.
|
||||
*
|
||||
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
|
||||
@@ -880,6 +893,14 @@
|
||||
#define GLFW_EGL_CONTEXT_API 0x00036002
|
||||
#define GLFW_OSMESA_CONTEXT_API 0x00036003
|
||||
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
|
||||
|
||||
/*! @defgroup shapes Standard cursor shapes
|
||||
* @brief Standard system cursor shapes.
|
||||
*
|
||||
@@ -913,8 +934,12 @@ typedef enum {
|
||||
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
|
||||
*/
|
||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||
/*! @brief ANGLE rendering backend init hint.
|
||||
*
|
||||
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
|
||||
*/
|
||||
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050003
|
||||
/*! @brief macOS specific init hint.
|
||||
*
|
||||
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
|
||||
|
||||
47
kitty/glfw.c
47
kitty/glfw.c
@@ -11,6 +11,7 @@
|
||||
#include "glfw-wrapper.h"
|
||||
extern bool cocoa_make_window_resizable(void *w, bool);
|
||||
extern void cocoa_focus_window(void *w);
|
||||
extern long cocoa_window_number(void *w);
|
||||
extern void cocoa_create_global_menu(void);
|
||||
extern void cocoa_hide_window_title(void *w);
|
||||
extern void cocoa_hide_titlebar(void *w);
|
||||
@@ -364,6 +365,7 @@ static void
|
||||
application_close_requested_callback(int flags) {
|
||||
if (flags) {
|
||||
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
|
||||
global_state.has_pending_closes = true;
|
||||
request_tick_callback();
|
||||
} else {
|
||||
if (global_state.quit_request == NO_CLOSE_REQUESTED) {
|
||||
@@ -589,7 +591,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#endif
|
||||
if (!global_state.is_wayland) {
|
||||
// On Wayland windows dont get a content scale until they receive an enterEvent anyway
|
||||
// which wont happen until the event loop ticks, so using a temp window is useless.
|
||||
// which won't happen until the event loop ticks, so using a temp window is useless.
|
||||
temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
|
||||
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
|
||||
}
|
||||
@@ -813,9 +815,6 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
||||
if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; }
|
||||
glfwSetErrorCallback(error_callback);
|
||||
glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard);
|
||||
// Joysticks cause slow startup on some linux systems, see
|
||||
// https://github.com/kovidgoyal/kitty/issues/830
|
||||
glfwInitHint(GLFW_ENABLE_JOYSTICKS, 0);
|
||||
OPT(debug_keyboard) = debug_keyboard != 0;
|
||||
#ifdef __APPLE__
|
||||
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0);
|
||||
@@ -1025,18 +1024,35 @@ x11_display(PYNOARG) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static OSWindow*
|
||||
find_os_window(PyObject *os_wid) {
|
||||
id_type os_window_id = PyLong_AsUnsignedLongLong(os_wid);
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (w->id == os_window_id) return w;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||
if (glfwGetX11Window) {
|
||||
id_type os_window_id = PyLong_AsUnsignedLongLong(os_wid);
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (w->id == os_window_id) return Py_BuildValue("l", (long)glfwGetX11Window(w->handle));
|
||||
}
|
||||
}
|
||||
else { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
|
||||
PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found");
|
||||
OSWindow *w = find_os_window(os_wid);
|
||||
if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; }
|
||||
if (!glfwGetX11Window) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
|
||||
return Py_BuildValue("l", (long)glfwGetX11Window(w->handle));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||
OSWindow *w = find_os_window(os_wid);
|
||||
if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; }
|
||||
if (!glfwGetCocoaWindow) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetCocoaWindow"); return NULL; }
|
||||
#ifdef __APPLE__
|
||||
return Py_BuildValue("l", (long)cocoa_window_number(glfwGetCocoaWindow(w->handle)));
|
||||
#else
|
||||
PyErr_SetString(PyExc_RuntimeError, "cocoa_window_id() is only supported on Mac");
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -1078,7 +1094,7 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
|
||||
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) {
|
||||
if ((Py_ssize_t)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;
|
||||
}
|
||||
@@ -1220,6 +1236,7 @@ static PyMethodDef module_methods[] = {
|
||||
#ifndef __APPLE__
|
||||
METHODB(dbus_send_notification, METH_VARARGS),
|
||||
#endif
|
||||
METHODB(cocoa_window_id, METH_O),
|
||||
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
|
||||
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
|
||||
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
|
||||
@@ -1531,7 +1548,7 @@ init_glfw(PyObject *m) {
|
||||
ADDC(GLFW_CONTEXT_REVISION);
|
||||
ADDC(GLFW_CONTEXT_ROBUSTNESS);
|
||||
ADDC(GLFW_OPENGL_FORWARD_COMPAT);
|
||||
ADDC(GLFW_OPENGL_DEBUG_CONTEXT);
|
||||
ADDC(GLFW_CONTEXT_DEBUG);
|
||||
ADDC(GLFW_OPENGL_PROFILE);
|
||||
|
||||
// ---
|
||||
|
||||
@@ -348,7 +348,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
break;
|
||||
case RGB:
|
||||
case RGBA:
|
||||
img->load_data.data_sz = g->data_width * g->data_height * (fmt / 8);
|
||||
img->load_data.data_sz = (size_t)g->data_width * g->data_height * (fmt / 8);
|
||||
if (!img->load_data.data_sz) ABRT(EINVAL, "Zero width/height not allowed");
|
||||
img->load_data.is_4byte_aligned = fmt == RGBA || (img->width % 4 == 0);
|
||||
img->load_data.is_opaque = fmt == RGB;
|
||||
@@ -404,7 +404,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
else fd = open(fname, O_CLOEXEC | O_RDONLY);
|
||||
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
|
||||
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
||||
safe_close(fd);
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
if (tt == 't') {
|
||||
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
|
||||
else unlink(fname);
|
||||
@@ -461,7 +461,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
} else img->load_data.data = img->load_data.mapped_file;
|
||||
}
|
||||
}
|
||||
size_t required_sz = (img->load_data.is_opaque ? 3 : 4) * img->width * img->height;
|
||||
size_t required_sz = (size_t)(img->load_data.is_opaque ? 3 : 4) * img->width * img->height;
|
||||
if (img->load_data.data_sz != required_sz) ABRT(EINVAL, "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", img->width, img->height, img->load_data.data_sz, required_sz);
|
||||
if (LIKELY(img->data_loaded && send_to_gpu)) {
|
||||
send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->load_data.is_opaque, img->load_data.is_4byte_aligned, false, REPEAT_CLAMP);
|
||||
@@ -891,12 +891,12 @@ W(shm_write) {
|
||||
int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
int ret = ftruncate(fd, sz);
|
||||
if (ret != 0) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
if (addr == MAP_FAILED) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
memcpy(addr, data, sz);
|
||||
if (munmap(addr, sz) != 0) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
safe_close(fd);
|
||||
if (munmap(addr, sz) != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ on_key_input(GLFWkeyevent *ev) {
|
||||
schedule_write_to_child(w->id, 1, text, strlen(text));
|
||||
debug("committed pre-edit text: %s\n", text);
|
||||
} else debug("committed pre-edit text: (null)\n");
|
||||
screen_draw_overlay_text(screen, NULL);
|
||||
return;
|
||||
case 0:
|
||||
// for macOS, update ime position on every key input
|
||||
|
||||
@@ -86,8 +86,8 @@ newly launched child process.
|
||||
|
||||
--location
|
||||
type=choices
|
||||
default=last
|
||||
choices=first,after,before,neighbor,last,vsplit,hsplit
|
||||
default=default
|
||||
choices=first,after,before,neighbor,last,vsplit,hsplit,default
|
||||
Where to place the newly created window when it is added to a tab which
|
||||
already has existing windows in it. :code:`after` and :code:`before` place the new
|
||||
window before or after the active window. :code:`neighbor` is a synonym for :code:`after`.
|
||||
@@ -95,7 +95,8 @@ Also applies to creating a new tab, where the value of :code:`after`
|
||||
will cause the new tab to be placed next to the current tab instead of at the end.
|
||||
The values of :code:`vsplit` and :code:`hsplit` are only used by the :code:`splits`
|
||||
layout and control if the new window is placed in a vertical or horizontal split
|
||||
with the currently active window.
|
||||
with the currently active window. The default is to place the window in a
|
||||
layout dependent manner, typically, after the currently active window.
|
||||
|
||||
|
||||
--allow-remote-control
|
||||
@@ -254,7 +255,7 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
|
||||
kw['cwd_from'] = active_child.pid_for_cwd
|
||||
else:
|
||||
kw['cwd'] = opts.cwd
|
||||
if opts.location != 'last':
|
||||
if opts.location != 'default':
|
||||
kw['location'] = opts.location
|
||||
if opts.copy_colors and active:
|
||||
kw['copy_colors_from'] = active
|
||||
|
||||
@@ -6,7 +6,7 @@ from functools import partial
|
||||
from itertools import repeat
|
||||
from typing import (
|
||||
Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional, Sequence,
|
||||
Tuple, Union, cast
|
||||
Tuple
|
||||
)
|
||||
|
||||
from kitty.constants import Edges, WindowGeometry
|
||||
@@ -261,24 +261,14 @@ class Layout:
|
||||
assert w is not None
|
||||
return self.neighbors_for_window(w, all_windows)
|
||||
|
||||
def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> bool:
|
||||
# delta can be either a number or a string such as 'left', 'top', etc
|
||||
# for neighborhood moves
|
||||
def move_window(self, all_windows: WindowList, delta: int = 1) -> bool:
|
||||
if all_windows.num_groups < 2 or not delta:
|
||||
return False
|
||||
|
||||
if isinstance(delta, int):
|
||||
return all_windows.move_window_group(by=delta)
|
||||
which = delta.lower()
|
||||
which = {'up': 'top', 'down': 'bottom'}.get(which, which)
|
||||
w = all_windows.active_window
|
||||
if w is None:
|
||||
return False
|
||||
neighbors = self.neighbors_for_window(w, all_windows)
|
||||
q: List[int] = cast(List[int], neighbors.get(which, []))
|
||||
if not q:
|
||||
return False
|
||||
return all_windows.move_window_group(to_group=q[0])
|
||||
return all_windows.move_window_group(by=delta)
|
||||
|
||||
def move_window_to_group(self, all_windows: WindowList, group: int) -> bool:
|
||||
return all_windows.move_window_group(to_group=group)
|
||||
|
||||
def add_window(self, all_windows: WindowList, window: WindowType, location: Optional[str] = None, overlay_for: Optional[int] = None) -> None:
|
||||
if overlay_for is not None and overlay_for in all_windows:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
from functools import lru_cache
|
||||
from itertools import repeat
|
||||
from math import ceil, floor
|
||||
from typing import Callable, Dict, Generator, List, Optional, Sequence, Tuple
|
||||
|
||||
from kitty.constants import Edges
|
||||
@@ -235,9 +236,17 @@ class Grid(Layout):
|
||||
|
||||
def side(row: int, col: int, delta: int) -> List[int]:
|
||||
neighbor_col = col + delta
|
||||
if col_counts[neighbor_col] == col_counts[col]:
|
||||
neighbor_nrows = col_counts[neighbor_col]
|
||||
nrows = col_counts[col]
|
||||
if neighbor_nrows == nrows:
|
||||
return neighbors(row, neighbor_col)
|
||||
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
|
||||
|
||||
start_row = floor(neighbor_nrows * row / nrows)
|
||||
end_row = ceil(neighbor_nrows * (row + 1) / nrows)
|
||||
xs = []
|
||||
for neighbor_row in range(start_row, end_row):
|
||||
xs.extend(neighbors(neighbor_row, neighbor_col))
|
||||
return xs
|
||||
|
||||
return {
|
||||
'top': neighbors(row-1, col) if row else [],
|
||||
|
||||
@@ -244,7 +244,7 @@ class Pair:
|
||||
return parent.modify_size_of_child(which, increment, is_horizontal, layout_object)
|
||||
return False
|
||||
|
||||
def neighbors_for_window(self, window_id: int, ans: NeighborsMap, layout_object: 'Splits') -> None:
|
||||
def neighbors_for_window(self, window_id: int, ans: NeighborsMap, layout_object: 'Splits', all_windows: WindowList) -> None:
|
||||
|
||||
def quadrant(is_horizontal: bool, is_first: bool) -> Tuple[EdgeLiteral, EdgeLiteral]:
|
||||
if is_horizontal:
|
||||
@@ -255,13 +255,27 @@ class Pair:
|
||||
return 'top', 'bottom'
|
||||
return 'bottom', 'top'
|
||||
|
||||
geometries = dict((group.id, group.geometry) for group in all_windows.groups if group.geometry)
|
||||
|
||||
def extend(other: Union[int, 'Pair', None], edge: EdgeLiteral, which: EdgeLiteral) -> None:
|
||||
if not ans[which] and other:
|
||||
if isinstance(other, Pair):
|
||||
ans[which].extend(other.edge_windows(edge))
|
||||
neighbors = (
|
||||
w for w in other.edge_windows(edge)
|
||||
if is_neighbouring_geometry(geometries[w], geometries[window_id], which))
|
||||
ans[which].extend(neighbors)
|
||||
else:
|
||||
ans[which].append(other)
|
||||
|
||||
def is_neighbouring_geometry(a: WindowGeometry, b: WindowGeometry, direction: str) -> bool:
|
||||
def edges(g: WindowGeometry) -> Tuple[int, int]:
|
||||
return (g.top, g.bottom) if direction in ['left', 'right'] else (g.left, g.right)
|
||||
|
||||
a1, a2 = edges(a)
|
||||
b1, b2 = edges(b)
|
||||
|
||||
return a1 < b2 and a2 > b1
|
||||
|
||||
other = self.two if self.one == window_id else self.one
|
||||
extend(other, *quadrant(self.horizontal, self.one == window_id))
|
||||
|
||||
@@ -418,15 +432,15 @@ class Splits(Layout):
|
||||
pair = self.pairs_root.pair_for_window(wg.id)
|
||||
ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}
|
||||
if pair is not None:
|
||||
pair.neighbors_for_window(wg.id, ans, self)
|
||||
pair.neighbors_for_window(wg.id, ans, self, all_windows)
|
||||
return ans
|
||||
|
||||
def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> bool:
|
||||
def move_window_to_group(self, all_windows: WindowList, group: int) -> bool:
|
||||
before = all_windows.active_group
|
||||
if before is None:
|
||||
return False
|
||||
before_idx = all_windows.active_group_idx
|
||||
moved = super().move_window(all_windows, delta)
|
||||
moved = super().move_window_to_group(all_windows, group)
|
||||
after = all_windows.groups[before_idx]
|
||||
if moved and before.id != after.id:
|
||||
self.pairs_root.swap_windows(before.id, after.id)
|
||||
|
||||
@@ -51,6 +51,12 @@ linebuf_mark_line_clean(LineBuf *self, index_type y) {
|
||||
self->line_attrs[y] &= ~TEXT_DIRTY_MASK;
|
||||
}
|
||||
|
||||
void
|
||||
linebuf_mark_line_as_not_continued(LineBuf *self, index_type y) {
|
||||
self->line_attrs[y] &= ~CONTINUED_MASK;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
clear(LineBuf *self, PyObject *a UNUSED) {
|
||||
#define clear_doc "Clear all lines in this LineBuf"
|
||||
@@ -530,8 +536,8 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo
|
||||
if (other->xnum == self->xnum && other->ynum == self->ynum) {
|
||||
memcpy(other->line_map, self->line_map, sizeof(index_type) * self->ynum);
|
||||
memcpy(other->line_attrs, self->line_attrs, sizeof(bool) * self->ynum);
|
||||
memcpy(other->cpu_cell_buf, self->cpu_cell_buf, self->xnum * self->ynum * sizeof(CPUCell));
|
||||
memcpy(other->gpu_cell_buf, self->gpu_cell_buf, self->xnum * self->ynum * sizeof(GPUCell));
|
||||
memcpy(other->cpu_cell_buf, self->cpu_cell_buf, (size_t)self->xnum * self->ynum * sizeof(CPUCell));
|
||||
memcpy(other->gpu_cell_buf, self->gpu_cell_buf, (size_t)self->xnum * self->ynum * sizeof(GPUCell));
|
||||
*num_content_lines_before = self->ynum; *num_content_lines_after = self->ynum;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int );
|
||||
void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *);
|
||||
void linebuf_mark_line_dirty(LineBuf *self, index_type y);
|
||||
void linebuf_mark_line_clean(LineBuf *self, index_type y);
|
||||
void linebuf_mark_line_as_not_continued(LineBuf *self, index_type y);
|
||||
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
|
||||
void linebuf_refresh_sprite_positions(LineBuf *self);
|
||||
void historybuf_add_line(HistoryBuf *self, const Line *line);
|
||||
|
||||
@@ -31,7 +31,8 @@ log_error(const char *fmt, ...) {
|
||||
#define bufprint(func, ...) { if ((size_t)(p - logbuf) < sizeof(logbuf) - 2) { p += func(p, sizeof(logbuf) - (p - logbuf), __VA_ARGS__); } }
|
||||
if (!use_os_log) { // Apple's os_log already records timestamps
|
||||
gettimeofday(&tv, NULL);
|
||||
struct tm *tmp = localtime(&tv.tv_sec);
|
||||
struct tm stack_tmp;
|
||||
struct tm *tmp = localtime_r(&tv.tv_sec, &stack_tmp);
|
||||
if (tmp) {
|
||||
char tbuf[256] = {0}, buf[256] = {0};
|
||||
if (strftime(buf, sizeof(buf), "%j %H:%M:%S.%%06u", tmp) != 0) {
|
||||
|
||||
@@ -48,7 +48,7 @@ handle_signal(int sig_num) {
|
||||
|
||||
void
|
||||
free_loop_data(LoopData *ld) {
|
||||
#define CLOSE(which, idx) if (ld->which[idx] > -1) safe_close(ld->which[idx]); ld->which[idx] = -1;
|
||||
#define CLOSE(which, idx) if (ld->which[idx] > -1) safe_close(ld->which[idx], __FILE__, __LINE__); ld->which[idx] = -1;
|
||||
#ifndef HAS_EVENT_FD
|
||||
CLOSE(wakeup_fds, 0); CLOSE(wakeup_fds, 1);
|
||||
#endif
|
||||
@@ -56,9 +56,9 @@ free_loop_data(LoopData *ld) {
|
||||
CLOSE(signal_fds, 0); CLOSE(signal_fds, 1);
|
||||
#endif
|
||||
#undef CLOSE
|
||||
if (ld->signal_read_fd) {
|
||||
if (ld->signal_read_fd > -1) {
|
||||
#ifdef HAS_SIGNAL_FD
|
||||
safe_close(ld->signal_read_fd);
|
||||
safe_close(ld->signal_read_fd, __FILE__, __LINE__);
|
||||
SIGNAL_SET
|
||||
sigprocmask(SIG_UNBLOCK, &signals, NULL);
|
||||
#else
|
||||
@@ -69,7 +69,7 @@ free_loop_data(LoopData *ld) {
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
}
|
||||
#ifdef HAS_EVENT_FD
|
||||
safe_close(ld->wakeup_read_fd);
|
||||
safe_close(ld->wakeup_read_fd, __FILE__, __LINE__);
|
||||
#endif
|
||||
ld->signal_read_fd = -1; ld->wakeup_read_fd = -1;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,12 @@
|
||||
#define MOUSE_SGR_MODE (1006 << 5)
|
||||
#define MOUSE_URXVT_MODE (1015 << 5)
|
||||
|
||||
// Save cursor (DECSC)
|
||||
#define SAVE_CURSOR (1048 << 5)
|
||||
|
||||
// Alternate screen buffer
|
||||
#define TOGGLE_ALT_SCREEN_1 (47 << 5)
|
||||
#define TOGGLE_ALT_SCREEN_2 (1047 << 5)
|
||||
#define ALTERNATE_SCREEN (1049 << 5)
|
||||
|
||||
// Bracketed paste mode
|
||||
|
||||
@@ -225,9 +225,9 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
|
||||
CALL_ED(screen_save_cursor); break;
|
||||
case ESC_DECRC:
|
||||
CALL_ED(screen_restore_cursor); break;
|
||||
case ESC_DECPNM:
|
||||
case ESC_DECKPNM:
|
||||
CALL_ED(screen_normal_keypad_mode); break;
|
||||
case ESC_DECPAM:
|
||||
case ESC_DECKPAM:
|
||||
CALL_ED(screen_alternate_keypad_mode); break;
|
||||
case '%':
|
||||
case '(':
|
||||
@@ -387,10 +387,10 @@ static inline const char*
|
||||
repr_csi_params(unsigned int *params, unsigned int num_params) {
|
||||
if (!num_params) return "";
|
||||
static char buf[256];
|
||||
unsigned int pos = 0;
|
||||
while (pos < 200 && num_params && sizeof(buf) > pos + 1) {
|
||||
const char *fmt = num_params > 1 ? "%u " : "%u";
|
||||
int ret = snprintf(buf + pos, sizeof(buf) - pos - 1, fmt, params[num_params--]);
|
||||
unsigned int pos = 0, i = 0;
|
||||
while (pos < 200 && i++ < num_params && sizeof(buf) > pos + 1) {
|
||||
const char *fmt = i < num_params ? "%u, " : "%u";
|
||||
int ret = snprintf(buf + pos, sizeof(buf) - pos - 1, fmt, params[i-1]);
|
||||
if (ret < 0) return "An error occurred formatting the params array";
|
||||
pos += ret;
|
||||
}
|
||||
@@ -783,6 +783,12 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
REPORT_ERROR("Unknown DECSTR CSI sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (start_modifier == '>' && (!end_modifier || end_modifier == ';')) {
|
||||
REPORT_ERROR("Ignoring xterm specific key modifier resource options (CSI > m)");
|
||||
break;
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
REPORT_ERROR("Unknown CSI code: '%s' with start_modifier: '%c' and end_modifier: '%c' and parameters: '%s'", utf8(code), start_modifier, end_modifier, repr_csi_params(params, num_params));
|
||||
}
|
||||
@@ -847,7 +853,8 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
#undef CMD_PREFIX
|
||||
#define PRINT_PREFIX "kitty-print|"
|
||||
} else if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 1, PRINT_PREFIX)) {
|
||||
PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + sizeof(PRINT_PREFIX), screen->parser_buf_pos - sizeof(PRINT_PREFIX));
|
||||
const size_t pp_size = sizeof(PRINT_PREFIX);
|
||||
PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + pp_size, screen->parser_buf_pos - pp_size);
|
||||
if (msg != NULL) {
|
||||
REPORT_OSC2(screen_handle_print, (char)screen->parser_buf[0], msg);
|
||||
screen_handle_print(screen, msg);
|
||||
|
||||
@@ -77,7 +77,7 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) {
|
||||
png_read_update_info(png, info);
|
||||
|
||||
int rowbytes = png_get_rowbytes(png, info);
|
||||
d->sz = rowbytes * d->height * sizeof(png_byte);
|
||||
d->sz = sizeof(png_byte) * rowbytes * d->height;
|
||||
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));
|
||||
|
||||
@@ -36,7 +36,7 @@ If specified close the tab this command is run in, rather than the active tab.
|
||||
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
||||
if window:
|
||||
if tab:
|
||||
boss.close_tab(tab)
|
||||
boss.close_tab_no_confirm(tab)
|
||||
|
||||
|
||||
close_tab = CloseTab()
|
||||
|
||||
@@ -123,7 +123,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
) {
|
||||
Py_CLEAR(self); return NULL;
|
||||
}
|
||||
self->alt_tabstops = self->main_tabstops + self->columns * sizeof(bool);
|
||||
self->alt_tabstops = self->main_tabstops + self->columns;
|
||||
self->tabstops = self->main_tabstops;
|
||||
init_tabstops(self->main_tabstops, self->columns);
|
||||
init_tabstops(self->alt_tabstops, self->columns);
|
||||
@@ -137,7 +137,7 @@ static inline Line* range_line_(Screen *self, int y);
|
||||
|
||||
void
|
||||
screen_reset(Screen *self) {
|
||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
|
||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self);
|
||||
linebuf_clear(self->linebuf, BLANK_CHAR);
|
||||
historybuf_clear(self->historybuf);
|
||||
@@ -236,7 +236,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
|
||||
PyMem_Free(self->main_tabstops);
|
||||
self->main_tabstops = PyMem_Calloc(2*self->columns, sizeof(bool));
|
||||
if (self->main_tabstops == NULL) { PyErr_NoMemory(); return false; }
|
||||
self->alt_tabstops = self->main_tabstops + self->columns * sizeof(bool);
|
||||
self->alt_tabstops = self->main_tabstops + self->columns;
|
||||
self->tabstops = self->main_tabstops;
|
||||
init_tabstops(self->main_tabstops, self->columns);
|
||||
init_tabstops(self->alt_tabstops, self->columns);
|
||||
@@ -641,12 +641,12 @@ screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const u
|
||||
|
||||
|
||||
void
|
||||
screen_toggle_screen_buffer(Screen *self) {
|
||||
screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_screen) {
|
||||
bool to_alt = self->linebuf == self->main_linebuf;
|
||||
grman_clear(self->alt_grman, true, self->cell_size); // always clear the alt buffer graphics to free up resources, since it has to be cleared when switching back to it anyway
|
||||
if (to_alt) {
|
||||
linebuf_clear(self->alt_linebuf, BLANK_CHAR);
|
||||
screen_save_cursor(self);
|
||||
if (clear_alt_screen) linebuf_clear(self->alt_linebuf, BLANK_CHAR);
|
||||
if (save_cursor) screen_save_cursor(self);
|
||||
self->linebuf = self->alt_linebuf;
|
||||
self->tabstops = self->alt_tabstops;
|
||||
self->grman = self->alt_grman;
|
||||
@@ -655,7 +655,7 @@ screen_toggle_screen_buffer(Screen *self) {
|
||||
} else {
|
||||
self->linebuf = self->main_linebuf;
|
||||
self->tabstops = self->main_tabstops;
|
||||
screen_restore_cursor(self);
|
||||
if (save_cursor) screen_restore_cursor(self);
|
||||
self->grman = self->main_grman;
|
||||
}
|
||||
screen_history_scroll(self, SCROLL_FULL, false);
|
||||
@@ -727,9 +727,14 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
|
||||
case CONTROL_CURSOR_BLINK:
|
||||
self->cursor->blink = val;
|
||||
break;
|
||||
case SAVE_CURSOR:
|
||||
screen_save_cursor(self);
|
||||
break;
|
||||
case TOGGLE_ALT_SCREEN_1:
|
||||
case TOGGLE_ALT_SCREEN_2:
|
||||
case ALTERNATE_SCREEN:
|
||||
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self);
|
||||
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self);
|
||||
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
|
||||
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
|
||||
break;
|
||||
default:
|
||||
private = mode >= 1 << 5;
|
||||
@@ -1188,12 +1193,14 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
|
||||
line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true);
|
||||
}
|
||||
linebuf_mark_line_dirty(self->linebuf, i);
|
||||
linebuf_mark_line_as_not_continued(self->linebuf, i);
|
||||
}
|
||||
self->is_dirty = true;
|
||||
self->selection = EMPTY_SELECTION;
|
||||
}
|
||||
if (how != 2) {
|
||||
screen_erase_in_line(self, how, private);
|
||||
if (how == 1) linebuf_mark_line_as_not_continued(self->linebuf, self->cursor->y);
|
||||
}
|
||||
if (how == 3 && self->linebuf == self->main_linebuf) {
|
||||
historybuf_clear(self->historybuf);
|
||||
@@ -1574,7 +1581,7 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
|
||||
// Rendering {{{
|
||||
static inline void
|
||||
update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
|
||||
size_t base = dest_y * line->xnum * sizeof(GPUCell);
|
||||
size_t base = sizeof(GPUCell) * dest_y * line->xnum;
|
||||
memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
|
||||
}
|
||||
|
||||
@@ -2392,7 +2399,7 @@ is_main_linebuf(Screen *self, PyObject *a UNUSED) {
|
||||
|
||||
static PyObject*
|
||||
toggle_alt_screen(Screen *self, PyObject *a UNUSED) {
|
||||
screen_toggle_screen_buffer(self);
|
||||
screen_toggle_screen_buffer(self, true, true);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ void screen_erase_in_line(Screen *, unsigned int, bool);
|
||||
void screen_erase_in_display(Screen *, unsigned int, bool);
|
||||
void screen_draw(Screen *screen, uint32_t codepoint);
|
||||
void screen_ensure_bounds(Screen *self, bool use_margins, bool cursor_was_within_margins);
|
||||
void screen_toggle_screen_buffer(Screen *self);
|
||||
void screen_toggle_screen_buffer(Screen *self, bool, bool);
|
||||
void screen_normal_keypad_mode(Screen *self);
|
||||
void screen_alternate_keypad_mode(Screen *self);
|
||||
void screen_change_default_color(Screen *self, unsigned int which, uint32_t col);
|
||||
|
||||
@@ -43,6 +43,7 @@ class Session:
|
||||
self.active_tab_idx = 0
|
||||
self.default_title = default_title
|
||||
self.os_window_size: Optional[WindowSizeOpts] = None
|
||||
self.os_window_class: Optional[str] = None
|
||||
|
||||
def add_tab(self, opts: Options, name: str = '') -> None:
|
||||
if self.tabs and not self.tabs[-1].windows:
|
||||
@@ -124,6 +125,8 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
|
||||
from kitty.config_data import window_size
|
||||
w, h = map(window_size, rest.split(maxsplit=1))
|
||||
ans.os_window_size = WindowSizeOpts(w, h, opts.window_margin_width, opts.window_padding_width, False)
|
||||
elif cmd == 'os_window_class':
|
||||
ans.os_window_class = rest
|
||||
else:
|
||||
raise ValueError('Unknown command in session file: {}'.format(cmd))
|
||||
yield finalize_session(ans)
|
||||
|
||||
@@ -65,7 +65,7 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int
|
||||
copy_image_warned = true;
|
||||
log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
|
||||
}
|
||||
size_t sz = width * height * num_levels;
|
||||
size_t sz = (size_t)width * height * num_levels;
|
||||
pixel *src = malloc(sz * sizeof(pixel));
|
||||
if (src == NULL) { fatal("Out of memory."); }
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id);
|
||||
@@ -334,7 +334,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
|
||||
}
|
||||
|
||||
if (screen->reload_all_gpu_data || screen_resized || screen_is_selection_dirty(screen)) {
|
||||
sz = screen->lines * screen->columns;
|
||||
sz = (size_t)screen->lines * screen->columns;
|
||||
address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
|
||||
screen_apply_selection(screen, address, sz);
|
||||
unmap_vao_buffer(vao_idx, selection_buffer); address = NULL;
|
||||
|
||||
@@ -261,7 +261,7 @@ class TabBar:
|
||||
return
|
||||
self.cell_width = cell_width
|
||||
s = self.screen
|
||||
viewport_width = tab_bar.width - 2 * self.margin_width
|
||||
viewport_width = max(4 * cell_width, tab_bar.width - 2 * self.margin_width)
|
||||
ncells = viewport_width // cell_width
|
||||
s.resize(1, ncells)
|
||||
s.reset_mode(DECAWM)
|
||||
|
||||
@@ -15,18 +15,18 @@ from typing import (
|
||||
from .borders import Borders
|
||||
from .child import Child
|
||||
from .cli_stub import CLIOptions
|
||||
from .constants import appname, is_macos, is_wayland
|
||||
from .constants import appname
|
||||
from .fast_data_types import (
|
||||
add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty,
|
||||
next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
|
||||
set_active_window, swap_tabs, sync_os_window_title, x11_window_id
|
||||
set_active_window, swap_tabs, sync_os_window_title
|
||||
)
|
||||
from .layout.base import Layout, Rect
|
||||
from .layout.interface import create_layout_object_for, evict_cached_layouts
|
||||
from .options_stub import Options
|
||||
from .tab_bar import TabBar, TabBarData
|
||||
from .typing import SessionTab, SessionType, TypedDict
|
||||
from .utils import log_error, resolved_shell
|
||||
from .typing import EdgeLiteral, SessionTab, SessionType, TypedDict
|
||||
from .utils import log_error, platform_window_id, resolved_shell
|
||||
from .window import Watchers, Window, WindowDict
|
||||
from .window_list import WindowList
|
||||
|
||||
@@ -283,12 +283,9 @@ class Tab: # {{{
|
||||
if env:
|
||||
fenv.update(env)
|
||||
fenv['KITTY_WINDOW_ID'] = str(next_window_id())
|
||||
if not is_macos and not is_wayland():
|
||||
try:
|
||||
fenv['WINDOWID'] = str(x11_window_id(self.os_window_id))
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
pwid = platform_window_id(self.os_window_id)
|
||||
if pwid is not None:
|
||||
fenv['WINDOWID'] = str(pwid)
|
||||
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from, allow_remote_control=allow_remote_control)
|
||||
ans.fork()
|
||||
return ans
|
||||
@@ -410,15 +407,37 @@ class Tab: # {{{
|
||||
|
||||
prev_window = previous_window
|
||||
|
||||
def neighboring_window(self, which: str) -> None:
|
||||
neighbors = self.current_layout.neighbors(self.windows)
|
||||
candidates = cast(Optional[Tuple[int, ...]], neighbors.get(which))
|
||||
if candidates:
|
||||
self.windows.set_active_group(candidates[0])
|
||||
def most_recent_group(self, groups: Sequence[int]) -> Optional[int]:
|
||||
groups_set = frozenset(groups)
|
||||
|
||||
def move_window(self, delta: Union[str, int] = 1) -> None:
|
||||
if self.current_layout.move_window(self.windows, delta):
|
||||
self.relayout()
|
||||
for window_id in reversed(self.windows.active_window_history):
|
||||
group = self.windows.group_for_window(window_id)
|
||||
if group and group.id in groups_set:
|
||||
return group.id
|
||||
|
||||
if groups:
|
||||
return groups[0]
|
||||
|
||||
def neighboring_group_id(self, which: EdgeLiteral) -> Optional[int]:
|
||||
neighbors = self.current_layout.neighbors(self.windows)
|
||||
candidates = neighbors.get(which)
|
||||
if candidates:
|
||||
return self.most_recent_group(candidates)
|
||||
|
||||
def neighboring_window(self, which: EdgeLiteral) -> None:
|
||||
neighbor = self.neighboring_group_id(which)
|
||||
if neighbor:
|
||||
self.windows.set_active_group(neighbor)
|
||||
|
||||
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
|
||||
if isinstance(delta, int):
|
||||
if self.current_layout.move_window(self.windows, delta):
|
||||
self.relayout()
|
||||
elif isinstance(delta, str):
|
||||
neighbor = self.neighboring_group_id(delta)
|
||||
if neighbor:
|
||||
if self.current_layout.move_window_to_group(self.windows, neighbor):
|
||||
self.relayout()
|
||||
|
||||
def move_window_to_top(self) -> None:
|
||||
n = self.windows.num_groups
|
||||
|
||||
@@ -42,6 +42,17 @@ def expandvars(val: str, env: Dict[str, str] = {}) -> str:
|
||||
return re.sub(r'\$\{(\S+?)\}', sub, val)
|
||||
|
||||
|
||||
def platform_window_id(os_window_id: int) -> Optional[int]:
|
||||
if is_macos:
|
||||
from .fast_data_types import cocoa_window_id
|
||||
with suppress(Exception):
|
||||
return cocoa_window_id(os_window_id)
|
||||
if not is_wayland():
|
||||
from .fast_data_types import x11_window_id
|
||||
with suppress(Exception):
|
||||
return x11_window_id(os_window_id)
|
||||
|
||||
|
||||
def load_shaders(name: str) -> Tuple[str, str]:
|
||||
from .fast_data_types import GLSL_VERSION
|
||||
with open(os.path.join(BASE, '{}_vertex.glsl'.format(name))) as f:
|
||||
|
||||
@@ -222,6 +222,7 @@ class Window:
|
||||
self.action_on_removal: Optional[Callable] = None
|
||||
self.current_marker_spec: Optional[Tuple[str, Union[str, Tuple[Tuple[int, str], ...]]]] = None
|
||||
self.pty_resized_once = False
|
||||
self.last_reported_pty_size = (-1, -1, -1, -1)
|
||||
self.needs_attention = False
|
||||
self.override_title = override_title
|
||||
self.default_title = os.path.basename(child.argv[0] or appname)
|
||||
@@ -381,18 +382,21 @@ class Window:
|
||||
if self.needs_layout or new_geometry.xnum != self.screen.columns or new_geometry.ynum != self.screen.lines:
|
||||
boss = get_boss()
|
||||
self.screen.resize(new_geometry.ynum, new_geometry.xnum)
|
||||
current_pty_size = (
|
||||
self.screen.lines, self.screen.columns,
|
||||
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
|
||||
sg = self.update_position(new_geometry)
|
||||
self.needs_layout = False
|
||||
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
|
||||
else:
|
||||
sg = self.update_position(new_geometry)
|
||||
current_pty_size = (
|
||||
self.screen.lines, self.screen.columns,
|
||||
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
|
||||
if current_pty_size != self.last_reported_pty_size:
|
||||
boss.child_monitor.resize_pty(self.id, *current_pty_size)
|
||||
if not self.pty_resized_once:
|
||||
self.pty_resized_once = True
|
||||
self.child.mark_terminal_ready()
|
||||
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
|
||||
else:
|
||||
sg = self.update_position(new_geometry)
|
||||
self.last_reported_pty_size = current_pty_size
|
||||
|
||||
self.geometry = g = new_geometry
|
||||
set_window_render_data(self.os_window_id, self.tab_id, self.id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4])
|
||||
self.update_effective_padding()
|
||||
|
||||
@@ -211,7 +211,7 @@ class WindowList:
|
||||
def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]:
|
||||
return iter(g for g in self.groups if g.is_visible_in_layout) if only_visible else iter(self.groups)
|
||||
|
||||
def make_previous_group_active(self, which: int = 1, notify: bool = False) -> None:
|
||||
def make_previous_group_active(self, which: int = 1, notify: bool = True) -> None:
|
||||
which = max(1, which)
|
||||
gid_map = {g.id: i for i, g in enumerate(self.groups)}
|
||||
num = len(self.active_group_history)
|
||||
|
||||
@@ -250,6 +250,10 @@ class TestLayout(BaseTest):
|
||||
self.ae(q.pairs_root.pair_for_window(2).horizontal, False)
|
||||
q.add_window(all_windows, Window(4), location='vsplit')
|
||||
windows = list(all_windows)
|
||||
windows[0].set_geometry(WindowGeometry(0, 0, 10, 20, 0, 0))
|
||||
windows[1].set_geometry(WindowGeometry(11, 0, 20, 10, 0, 0))
|
||||
windows[2].set_geometry(WindowGeometry(11, 11, 15, 20, 0, 0))
|
||||
windows[3].set_geometry(WindowGeometry(16, 11, 20, 20, 0, 0))
|
||||
self.ae(q.neighbors_for_window(windows[0], all_windows), {'left': [], 'right': [2, 3], 'top': [], 'bottom': []})
|
||||
self.ae(q.neighbors_for_window(windows[1], all_windows), {'left': [1], 'right': [], 'top': [], 'bottom': [3, 4]})
|
||||
self.ae(q.neighbors_for_window(windows[2], all_windows), {'left': [1], 'right': [4], 'top': [2], 'bottom': []})
|
||||
|
||||
@@ -176,21 +176,29 @@ class TestScreen(BaseTest):
|
||||
def all_lines(s):
|
||||
return tuple(str(s.line(i)) for i in range(s.lines))
|
||||
|
||||
def continuations(s):
|
||||
return tuple(s.line(i).is_continued() for i in range(s.lines))
|
||||
|
||||
init()
|
||||
s.erase_in_display()
|
||||
s.erase_in_display(0)
|
||||
self.ae(all_lines(s), ('12345', '12', '', '', ''))
|
||||
self.ae(continuations(s), (False, True, False, False, False))
|
||||
|
||||
init()
|
||||
s.erase_in_display(1)
|
||||
self.ae(all_lines(s), ('', ' 45', '12345', '12345', '12345'))
|
||||
self.ae(continuations(s), (False, False, True, True, True))
|
||||
|
||||
init()
|
||||
s.erase_in_display(2)
|
||||
self.ae(all_lines(s), ('', '', '', '', ''))
|
||||
self.assertTrue(s.line(0).cursor_from(1).bold)
|
||||
self.ae(continuations(s), (False, False, False, False, False))
|
||||
|
||||
init()
|
||||
s.erase_in_display(2, True)
|
||||
self.ae(all_lines(s), ('', '', '', '', ''))
|
||||
self.ae(continuations(s), (False, False, False, False, False))
|
||||
self.assertFalse(s.line(0).cursor_from(1).bold)
|
||||
|
||||
def test_cursor_movement(self):
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from . import BaseTest
|
||||
|
||||
is32bit = sys.maxsize <= (1 << 32)
|
||||
|
||||
|
||||
class TestTUI(BaseTest):
|
||||
|
||||
@@ -43,7 +48,7 @@ class TestTUI(BaseTest):
|
||||
le.backspace()
|
||||
self.assertTrue(le.pending_bell)
|
||||
|
||||
@unittest.skipIf(is32bit, 'Fails for some unknown reason on 32bit builds')
|
||||
def test_multiprocessing_spawn(self):
|
||||
return # temporarily disable this test till I can figure out why its failing in the build env
|
||||
from kitty.multiprocessing import test_spawn
|
||||
test_spawn()
|
||||
|
||||
22
launcher.c
22
launcher.c
@@ -42,14 +42,18 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
set_bundle_exe_dir(const wchar_t *exe_dir) {
|
||||
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype) {
|
||||
wchar_t buf[PATH_MAX+1] = {0};
|
||||
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
|
||||
PySys_AddXOption(buf);
|
||||
if (lc_ctype) {
|
||||
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
|
||||
PySys_AddXOption(buf);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FOR_BUNDLE
|
||||
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv) {
|
||||
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv, const char *lc_ctype) {
|
||||
int num;
|
||||
Py_NoSiteFlag = 1;
|
||||
Py_FrozenFlag = 1;
|
||||
@@ -62,7 +66,7 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
||||
int ret = 1;
|
||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
|
||||
set_bundle_exe_dir(exe_dir);
|
||||
set_xoptions(exe_dir, lc_ctype);
|
||||
wchar_t stdlib[PATH_MAX+1] = {0};
|
||||
#ifdef __APPLE__
|
||||
const char *python_relpath = "../Resources/Python/lib";
|
||||
@@ -96,11 +100,11 @@ end:
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv) {
|
||||
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv, const char *lc_ctype) {
|
||||
(void)libpath;
|
||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
|
||||
set_bundle_exe_dir(exe_dir);
|
||||
set_xoptions(exe_dir, lc_ctype);
|
||||
#ifdef FROM_SOURCE
|
||||
PySys_AddXOption(L"kitty_from_source=1");
|
||||
#endif
|
||||
@@ -155,6 +159,10 @@ read_exe_path(char *exe, size_t buf_sz) {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char exe[PATH_MAX+1] = {0};
|
||||
const char *lc_ctype = NULL;
|
||||
#ifdef __APPLE__
|
||||
lc_ctype = getenv("LC_CTYPE");
|
||||
#endif
|
||||
if (!read_exe_path(exe, sizeof(exe))) return 1;
|
||||
|
||||
char *exe_dir = dirname(exe);
|
||||
@@ -182,7 +190,9 @@ int main(int argc, char *argv[]) {
|
||||
ret = 1; goto end;
|
||||
}
|
||||
}
|
||||
ret = run_embedded(exe_dir, lib, num_args, argvw);
|
||||
if (lc_ctype) lc_ctype = strdup(lc_ctype);
|
||||
ret = run_embedded(exe_dir, lib, num_args, argvw, lc_ctype);
|
||||
if (lc_ctype) free((void*)lc_ctype);
|
||||
end:
|
||||
for (i = 0; i < num_args; i++) { if(argvw[i]) PyMem_RawFree(argvw[i]); }
|
||||
return ret;
|
||||
|
||||
@@ -8,7 +8,7 @@ set wildignore+=tags
|
||||
set expandtab
|
||||
set tabstop=4
|
||||
set shiftwidth=4
|
||||
set softtabstop=0
|
||||
set softtabstop=0
|
||||
set smarttab
|
||||
python3 <<endpython
|
||||
import sys
|
||||
|
||||
Reference in New Issue
Block a user