Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7438e0e997 | ||
|
|
cca672e536 | ||
|
|
0fa12a687d | ||
|
|
d2b900c77f | ||
|
|
21242d9144 | ||
|
|
03fd138b63 | ||
|
|
1eeaef6dbb | ||
|
|
b9a93b68c4 | ||
|
|
e3728942dc | ||
|
|
7f0674ac27 | ||
|
|
60fcf7ab3d | ||
|
|
ee82a6ff18 | ||
|
|
bac5e402b7 | ||
|
|
732108109e | ||
|
|
ab3b86d65b | ||
|
|
d4d63506e1 | ||
|
|
3ad69f8ba3 | ||
|
|
9fd50a6ac0 | ||
|
|
9b5c8a18d4 | ||
|
|
9f3112ca56 | ||
|
|
aa9de47da3 | ||
|
|
5eaeda4402 | ||
|
|
ecb03d7dba | ||
|
|
88612d3691 | ||
|
|
2c0e70a423 | ||
|
|
d6b70f10d3 | ||
|
|
40b01fb702 | ||
|
|
89222d93f4 | ||
|
|
b3db3e5732 | ||
|
|
b45161ecc3 | ||
|
|
d4fc0af7de | ||
|
|
5ef8cce1d7 | ||
|
|
c98a147f94 | ||
|
|
e9975cb7eb | ||
|
|
33f0ac83c1 | ||
|
|
3bd45966c6 | ||
|
|
1b7244f3c8 | ||
|
|
1949f3c3d5 | ||
|
|
6a51ce5dc4 | ||
|
|
c0c8e1ce5e | ||
|
|
6a3d6191bd | ||
|
|
b2aa07d29f | ||
|
|
c84fb68bf8 | ||
|
|
128b9e1cd0 | ||
|
|
bbe8bcb28d | ||
|
|
6dc9c22543 | ||
|
|
59f5781f19 | ||
|
|
d61fb9cc42 | ||
|
|
ace052ee70 | ||
|
|
1451a8ccc0 | ||
|
|
547959c1ef | ||
|
|
b1a5e98630 | ||
|
|
f3a1ee7b9f | ||
|
|
b0a39b127d | ||
|
|
e36f1157cc | ||
|
|
053b9a786c | ||
|
|
2561d937da | ||
|
|
55f457caf4 | ||
|
|
f5cc2fd8ad | ||
|
|
e630b76fac | ||
|
|
0b6948cb4f | ||
|
|
b0a2dea747 | ||
|
|
f8395e36a1 | ||
|
|
6ec58f58ce | ||
|
|
96cbfe946c | ||
|
|
ac686a47c1 | ||
|
|
e05aaf77ce | ||
|
|
6b371eb8a8 | ||
|
|
f6660a0d55 | ||
|
|
3b9f213ea2 |
13
__main__.py
13
__main__.py
@@ -7,8 +7,9 @@ import os
|
||||
|
||||
|
||||
def icat(args):
|
||||
from kittens.icat.main import main
|
||||
main(args)
|
||||
from kittens.runner import run_kitten
|
||||
sys.argv = args
|
||||
run_kitten('icat')
|
||||
|
||||
|
||||
def list_fonts(args):
|
||||
@@ -41,7 +42,13 @@ def complete(args):
|
||||
def launch(args):
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
runpy.run_path(args[1], run_name='__main__')
|
||||
exe = args[1]
|
||||
if exe.startswith(':'):
|
||||
import shutil
|
||||
exe = shutil.which(exe[1:])
|
||||
if not exe:
|
||||
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
|
||||
runpy.run_path(exe, run_name='__main__')
|
||||
|
||||
|
||||
def run_kitten(args):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS = -T $(FAIL_WARN)
|
||||
SPHINXBUILD = python3 .. +launch $(shell which sphinx-build)
|
||||
SPHINXBUILD = python3 .. +launch :sphinx-build
|
||||
SPHINXPROJ = kitty
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
@@ -58,6 +58,13 @@ Customizing the installation
|
||||
|ins| \\
|
||||
installer=/path/to/dmg or tarball
|
||||
|
||||
Uninstalling
|
||||
----------------
|
||||
|
||||
All the installer does is copy the kitty files into the install directory
|
||||
(defaults to :file:`~/.local/kitty.app`). To uninstall, simply delete that
|
||||
directory.
|
||||
|
||||
|
||||
Building from source
|
||||
------------------------
|
||||
|
||||
@@ -3,6 +3,49 @@ Changelog
|
||||
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
0.12.1 [2018-09-08]
|
||||
------------------------------
|
||||
|
||||
- Add a new :option:`kitty --start-in-fullscreen` command line flag to start
|
||||
kitty in full screen mode (:iss:`856`)
|
||||
|
||||
- macOS: Fix a character that cannot be rendered in any font causing
|
||||
font fallback for all subsequent characters that cannot be rendered in the
|
||||
main font to fail (:iss:`799`)
|
||||
|
||||
- Linux: Do not enable IME input via ibus unless the ``GLFW_IM_MODULE=ibus``
|
||||
environment variable is set. IME causes key processing latency and even
|
||||
missed keystrokes for many people, so it is now off by default.
|
||||
|
||||
- Fix backspacing of wide characters in wide-character unaware programs not working (:iss:`875`)
|
||||
|
||||
- Linux: Fix number pad arrow keys not working when Numlock is off (:iss:`857`)
|
||||
|
||||
- Wayland: Implement support for clipboard copy/paste (:iss:`855`)
|
||||
|
||||
- Allow mapping shortcuts using the raw key code from the OS (:iss:`848`)
|
||||
|
||||
- Allow mapping of individual key-presses without modifiers as shortcuts
|
||||
|
||||
- Fix legacy invocation of icat as `kitty icat` not working (:iss:`850`)
|
||||
|
||||
- Improve rendering of wavy underline at small font sizes (:iss:`853`)
|
||||
|
||||
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
|
||||
|
||||
- Wayland: Allow using the :option:`kitty --class` command line flag
|
||||
to set the app id (:iss:`862`)
|
||||
|
||||
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
||||
|
||||
- Linux: Fix XCompose rules with no defined symbol not working (:iss:`880`)
|
||||
|
||||
- Linux: Fix crash with some Nvidia drivers when creating tabs in the first
|
||||
top level-window after creating a second top-level window. (:iss:`873`)
|
||||
|
||||
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
||||
a bug in the 0.12.0 macOS package
|
||||
|
||||
0.12.0 [2018-09-01]
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ For some discussion regarding the design choices, see `#33
|
||||
|
||||
To see a quick demo, inside a |kitty| terminal run::
|
||||
|
||||
kitty icat path/to/some/image.png
|
||||
kitty +kitten icat path/to/some/image.png
|
||||
|
||||
You can also see a screenshot with more sophisticated features such as
|
||||
alpha-blending and text over graphics.
|
||||
|
||||
@@ -51,7 +51,8 @@ Quickstart
|
||||
--------------
|
||||
|
||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||
See the :doc:`binary install instructions </binary>`.
|
||||
See the :doc:`binary install instructions </binary>`. You can also
|
||||
:doc:`build from source </build>`.
|
||||
|
||||
If you are on Linux, you can also use your distribution's |kitty| package.
|
||||
|kitty| packages are available for:
|
||||
@@ -60,7 +61,8 @@ If you are on Linux, you can also use your distribution's |kitty| package.
|
||||
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
||||
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
|
||||
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
||||
`Fedora <https://copr.fedorainfracloud.org/coprs/oleastre/kitty-terminal/>`_.
|
||||
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
|
||||
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_.
|
||||
|
||||
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
||||
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
||||
@@ -123,7 +125,7 @@ Action Shortcut
|
||||
New tab :sc:`new_tab`
|
||||
Close tab :sc:`close_tab`
|
||||
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
||||
Previous tab :sc:`previous_tab`
|
||||
Previous tab :sc:`previous_tab` (also :kbd:`control+shift+tab` on macOS)
|
||||
Next layout :sc:`next_layout`
|
||||
Move tab forward :sc:`move_tab_forward`
|
||||
Move tab backward :sc:`move_tab_backward`
|
||||
@@ -181,6 +183,9 @@ Reset background opacity :sc:`reset_background_opacity`
|
||||
Layouts
|
||||
----------
|
||||
|
||||
A layout is an arrangement of multiple *windows*. You can create a new window
|
||||
using the :sc:`new_window` key combination.
|
||||
|
||||
Currently, there are five layouts available,
|
||||
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
@@ -384,6 +389,16 @@ The important thing above is to make sure the call to |kitty| to load the zsh
|
||||
completions happens after the call to :file:`compinit`.
|
||||
|
||||
|
||||
fish
|
||||
~~~~~~~~
|
||||
|
||||
Add the following to your :file:`~/.config/fish/config.fish`
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty + complete setup fish | source
|
||||
|
||||
|
||||
Changelog
|
||||
------------------
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ It supports all image types supported by `ImageMagick
|
||||
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
||||
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
||||
|
||||
You might want to create an alias in your shell's configuration files::
|
||||
|
||||
alias icat="kitty +kitten icat"
|
||||
|
||||
Then you can simply use ``icat image.png`` to view images.
|
||||
|
||||
.. note::
|
||||
|
||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||
|
||||
@@ -8,10 +8,10 @@ glyph in video RAM so that font rendering is not a bottleneck. Interaction
|
||||
with child programs takes place in a separate thread from rendering, to improve
|
||||
smoothness.
|
||||
|
||||
There are two parameters you can tune to adjust the performance. ``repaint_delay``
|
||||
and ``input_delay``. These control the artificial delays introduced into the
|
||||
There are two parameters you can tune to adjust the performance. :opt:`repaint_delay`
|
||||
and :opt:`input_delay`. These control the artificial delays introduced into the
|
||||
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
||||
See also the ``sync_to_monitor`` option to further decrease latency at the cost
|
||||
See also the :opt:`sync_to_monitor` option to further decrease latency at the cost
|
||||
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
||||
|
||||
You can generate detailed per-function performance data using
|
||||
|
||||
51
glfw/backend_utils.c
vendored
51
glfw/backend_utils.c
vendored
@@ -261,3 +261,54 @@ closeFds(int *fds, size_t count) {
|
||||
fds++;
|
||||
}
|
||||
}
|
||||
|
||||
// Splits and translates a text/uri-list into separate file paths
|
||||
// NOTE: This function destroys the provided string
|
||||
//
|
||||
char** parseUriList(char* text, int* count)
|
||||
{
|
||||
const char* prefix = "file://";
|
||||
char** paths = NULL;
|
||||
char* line;
|
||||
|
||||
*count = 0;
|
||||
|
||||
while ((line = strtok(text, "\r\n")))
|
||||
{
|
||||
text = NULL;
|
||||
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||
{
|
||||
line += strlen(prefix);
|
||||
// TODO: Validate hostname
|
||||
while (*line != '/')
|
||||
line++;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
|
||||
char* path = calloc(strlen(line) + 1, 1);
|
||||
paths = realloc(paths, *count * sizeof(char*));
|
||||
paths[*count - 1] = path;
|
||||
|
||||
while (*line)
|
||||
{
|
||||
if (line[0] == '%' && line[1] && line[2])
|
||||
{
|
||||
const char digits[3] = { line[1], line[2], '\0' };
|
||||
*path = strtol(digits, NULL, 16);
|
||||
line += 2;
|
||||
}
|
||||
else
|
||||
*path = *line;
|
||||
|
||||
path++;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
1
glfw/backend_utils.h
vendored
1
glfw/backend_utils.h
vendored
@@ -71,3 +71,4 @@ int pollForEvents(EventLoopData *eld, double timeout);
|
||||
unsigned dispatchTimers(EventLoopData *eld);
|
||||
void closeFds(int *fds, size_t count);
|
||||
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
|
||||
char** parseUriList(char* text, int* count);
|
||||
|
||||
12
glfw/glfw.py
12
glfw/glfw.py
@@ -128,16 +128,6 @@ def collect_source_information():
|
||||
return ans
|
||||
|
||||
|
||||
def patch_in_file(path, pfunc):
|
||||
with open(path, 'r+') as f:
|
||||
raw = f.read()
|
||||
nraw = pfunc(raw)
|
||||
if raw == nraw:
|
||||
raise SystemExit('Patching of {} failed'.format(path))
|
||||
f.seek(0), f.truncate()
|
||||
f.write(nraw)
|
||||
|
||||
|
||||
class Arg:
|
||||
|
||||
def __init__(self, decl):
|
||||
@@ -283,8 +273,6 @@ def from_glfw(glfw_dir):
|
||||
for src in files_to_copy:
|
||||
shutil.copy2(src, '.')
|
||||
shutil.copy2(glfw_header, '.')
|
||||
patch_in_file('cocoa_window.m', lambda x: re.sub(
|
||||
r'[(]void[)]loadMainMenu.+?}', '(void)loadMainMenu\n{ // removed by Kovid as it generated compiler warnings \n}\n', x, flags=re.DOTALL))
|
||||
json.dump(
|
||||
sinfo,
|
||||
open('source-info.json', 'w'),
|
||||
|
||||
6
glfw/glfw3.h
vendored
6
glfw/glfw3.h
vendored
@@ -973,6 +973,10 @@ extern "C" {
|
||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||
*/
|
||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||
/*! @brief Window content area scaling window
|
||||
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||
*/
|
||||
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||
|
||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||
@@ -980,6 +984,8 @@ extern "C" {
|
||||
|
||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||
|
||||
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_NO_API 0
|
||||
|
||||
3
glfw/ibus_glfw.c
vendored
3
glfw/ibus_glfw.c
vendored
@@ -280,8 +280,7 @@ setup_connection(_GLFWIBUSData *ibus) {
|
||||
void
|
||||
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
||||
if (ibus->inited) return;
|
||||
if (!test_env_var("XMODIFIERS", "@im=ibus") && !test_env_var("GTK_IM_MODULE", "ibus") && !test_env_var("QT_IM_MODULE", "ibus") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
if (getenv("GLFW_IM_MODULE") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
ibus->inited = GLFW_TRUE;
|
||||
setup_connection(ibus);
|
||||
}
|
||||
|
||||
6
glfw/internal.h
vendored
6
glfw/internal.h
vendored
@@ -31,6 +31,8 @@
|
||||
#include "glfw_config.h"
|
||||
#endif
|
||||
|
||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
||||
defined(GLFW_INCLUDE_ES1) || \
|
||||
defined(GLFW_INCLUDE_ES2) || \
|
||||
@@ -271,6 +273,7 @@ struct _GLFWwndconfig
|
||||
GLFWbool maximized;
|
||||
GLFWbool centerCursor;
|
||||
GLFWbool focusOnShow;
|
||||
GLFWbool scaleToMonitor;
|
||||
struct {
|
||||
GLFWbool retina;
|
||||
char frameName[256];
|
||||
@@ -279,6 +282,9 @@ struct _GLFWwndconfig
|
||||
char className[256];
|
||||
char instanceName[256];
|
||||
} x11;
|
||||
struct {
|
||||
char appId[256];
|
||||
} wl;
|
||||
};
|
||||
|
||||
// Context configuration
|
||||
|
||||
34
glfw/wgl_context.c
vendored
34
glfw/wgl_context.c
vendored
@@ -256,12 +256,20 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
|
||||
|
||||
static void swapBuffersWGL(_GLFWwindow* window)
|
||||
{
|
||||
// HACK: Use DwmFlush when desktop composition is enabled
|
||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
||||
if (!window->monitor)
|
||||
{
|
||||
int count = abs(window->context.wgl.interval);
|
||||
while (count--)
|
||||
DwmFlush();
|
||||
if (IsWindowsVistaOrGreater())
|
||||
{
|
||||
BOOL enabled;
|
||||
|
||||
// HACK: Use DwmFlush when desktop composition is enabled
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||
{
|
||||
int count = abs(window->context.wgl.interval);
|
||||
while (count--)
|
||||
DwmFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwapBuffers(window->context.wgl.dc);
|
||||
@@ -273,10 +281,18 @@ static void swapIntervalWGL(int interval)
|
||||
|
||||
window->context.wgl.interval = interval;
|
||||
|
||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||
// avoid interfering with DWM vsync
|
||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
||||
interval = 0;
|
||||
if (!window->monitor)
|
||||
{
|
||||
if (IsWindowsVistaOrGreater())
|
||||
{
|
||||
BOOL enabled;
|
||||
|
||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||
// avoid interfering with DWM vsync
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||
interval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_glfw.wgl.EXT_swap_control)
|
||||
_glfw.wgl.SwapIntervalEXT(interval);
|
||||
|
||||
68
glfw/win32_init.c
vendored
68
glfw/win32_init.c
vendored
@@ -62,17 +62,6 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
|
||||
|
||||
#endif // _GLFW_BUILD_DLL
|
||||
|
||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||
return VerifyVersionInfoW(&osvi, mask, cond);
|
||||
}
|
||||
|
||||
// Load necessary libraries (DLLs)
|
||||
//
|
||||
static GLFWbool loadLibraries(void)
|
||||
@@ -100,6 +89,14 @@ static GLFWbool loadLibraries(void)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
|
||||
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
|
||||
_glfw.win32.user32.EnableNonClientDpiScaling_ = (PFN_EnableNonClientDpiScaling)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "EnableNonClientDpiScaling");
|
||||
_glfw.win32.user32.SetProcessDpiAwarenessContext_ = (PFN_SetProcessDpiAwarenessContext)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDpiAwarenessContext");
|
||||
_glfw.win32.user32.GetDpiForWindow_ = (PFN_GetDpiForWindow)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "GetDpiForWindow");
|
||||
_glfw.win32.user32.AdjustWindowRectExForDpi_ = (PFN_AdjustWindowRectExForDpi)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "AdjustWindowRectExForDpi");
|
||||
|
||||
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
|
||||
if (_glfw.win32.dinput8.instance)
|
||||
@@ -155,6 +152,13 @@ static GLFWbool loadLibraries(void)
|
||||
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
|
||||
}
|
||||
|
||||
_glfw.win32.ntdll.instance = LoadLibraryA("ntdll.dll");
|
||||
if (_glfw.win32.ntdll.instance)
|
||||
{
|
||||
_glfw.win32.ntdll.RtlVerifyVersionInfo_ = (PFN_RtlVerifyVersionInfo)
|
||||
GetProcAddress(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo");
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -179,6 +183,9 @@ static void freeLibraries(void)
|
||||
|
||||
if (_glfw.win32.shcore.instance)
|
||||
FreeLibrary(_glfw.win32.shcore.instance);
|
||||
|
||||
if (_glfw.win32.ntdll.instance)
|
||||
FreeLibrary(_glfw.win32.ntdll.instance);
|
||||
}
|
||||
|
||||
// Create key code translation tables
|
||||
@@ -309,6 +316,7 @@ static void createKeyTables(void)
|
||||
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
|
||||
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
|
||||
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
|
||||
_glfw.win32.keycodes[0x059] = GLFW_KEY_KP_EQUAL;
|
||||
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
|
||||
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
||||
|
||||
@@ -339,8 +347,8 @@ static HWND createHelperWindow(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// HACK: The first call to ShowWindow is ignored if the parent process
|
||||
// passed along a STARTUPINFO, so clear that flag with a no-op call
|
||||
// HACK: The command to the first ShowWindow call is ignored if the parent
|
||||
// process passed along a STARTUPINFO, so clear that with a no-op call
|
||||
ShowWindow(window, SW_HIDE);
|
||||
|
||||
// Register for HID device notifications
|
||||
@@ -502,6 +510,36 @@ void _glfwUpdateKeyNamesWin32(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Replacement for IsWindowsVersionOrGreater as MinGW lacks versionhelpers.h
|
||||
//
|
||||
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||
// latter lies unless the user knew to embedd a non-default manifest
|
||||
// announcing support for Windows 10 via supportedOS GUID
|
||||
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||
}
|
||||
|
||||
// Checks whether we are on at least the specified build of Windows 10
|
||||
//
|
||||
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), 10, 0, build };
|
||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER;
|
||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||
// latter lies unless the user knew to embedd a non-default manifest
|
||||
// announcing support for Windows 10 via supportedOS GUID
|
||||
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
@@ -523,7 +561,9 @@ int _glfwPlatformInit(void)
|
||||
createKeyTables();
|
||||
_glfwUpdateKeyNamesWin32();
|
||||
|
||||
if (IsWindows8Point1OrGreater())
|
||||
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
else if (IsWindows8Point1OrGreater())
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
else if (IsWindowsVistaOrGreater())
|
||||
SetProcessDPIAware();
|
||||
|
||||
4
glfw/win32_monitor.c
vendored
4
glfw/win32_monitor.c
vendored
@@ -324,9 +324,9 @@ void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* ysc
|
||||
}
|
||||
|
||||
if (xscale)
|
||||
*xscale = xdpi / 96.f;
|
||||
*xscale = xdpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
if (yscale)
|
||||
*yscale = ydpi / 96.f;
|
||||
*yscale = ydpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
}
|
||||
|
||||
|
||||
|
||||
70
glfw/win32_platform.h
vendored
70
glfw/win32_platform.h
vendored
@@ -98,6 +98,12 @@
|
||||
#ifndef _WIN32_WINNT_WINBLUE
|
||||
#define _WIN32_WINNT_WINBLUE 0x0602
|
||||
#endif
|
||||
#ifndef WM_GETDPISCALEDSIZE
|
||||
#define WM_GETDPISCALEDSIZE 0x02e4
|
||||
#endif
|
||||
#ifndef USER_DEFAULT_SCREEN_DPI
|
||||
#define USER_DEFAULT_SCREEN_DPI 96
|
||||
#endif
|
||||
|
||||
#if WINVER < 0x0601
|
||||
typedef struct
|
||||
@@ -140,23 +146,32 @@ typedef enum
|
||||
} MONITOR_DPI_TYPE;
|
||||
#endif /*DPI_ENUMS_DECLARED*/
|
||||
|
||||
#ifndef _DPI_AWARENESS_CONTEXTS_
|
||||
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) -4)
|
||||
#endif /*_DPI_AWARENESS_CONTEXTS_*/
|
||||
|
||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp);
|
||||
#define IsWindowsXPOrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||
#define IsWindowsVistaOrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||
#define IsWindows7OrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), \
|
||||
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
||||
#define IsWindows8OrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), \
|
||||
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
||||
#define IsWindows8Point1OrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
||||
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||
#define IsWindowsVistaOrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||
#define IsWindows7OrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN7), \
|
||||
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
||||
#define IsWindows8OrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN8), \
|
||||
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
||||
#define IsWindows8Point1OrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
||||
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
||||
|
||||
#define _glfwIsWindows10AnniversaryUpdateOrGreaterWin32() \
|
||||
_glfwIsWindows10BuildOrGreaterWin32(14393)
|
||||
#define _glfwIsWindows10CreatorsUpdateOrGreaterWin32() \
|
||||
_glfwIsWindows10BuildOrGreaterWin32(15063)
|
||||
|
||||
// HACK: Define macros that some xinput.h variants don't
|
||||
#ifndef XINPUT_CAPS_WIRELESS
|
||||
@@ -209,8 +224,16 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
|
||||
// user32.dll function pointer typedefs
|
||||
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
||||
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
||||
typedef BOOL (WINAPI * PFN_EnableNonClientDpiScaling)(HWND);
|
||||
typedef BOOL (WINAPI * PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
|
||||
typedef UINT (WINAPI * PFN_GetDpiForWindow)(HWND);
|
||||
typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
|
||||
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
||||
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
||||
#define EnableNonClientDpiScaling _glfw.win32.user32.EnableNonClientDpiScaling_
|
||||
#define SetProcessDpiAwarenessContext _glfw.win32.user32.SetProcessDpiAwarenessContext_
|
||||
#define GetDpiForWindow _glfw.win32.user32.GetDpiForWindow_
|
||||
#define AdjustWindowRectExForDpi _glfw.win32.user32.AdjustWindowRectExForDpi_
|
||||
|
||||
// dwmapi.dll function pointer typedefs
|
||||
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
|
||||
@@ -226,6 +249,10 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,
|
||||
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
|
||||
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
|
||||
|
||||
// ntdll.dll function pointer typedefs
|
||||
typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG);
|
||||
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
|
||||
|
||||
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
|
||||
|
||||
typedef struct VkWin32SurfaceCreateInfoKHR
|
||||
@@ -279,6 +306,7 @@ typedef struct _GLFWwindowWin32
|
||||
GLFWbool maximized;
|
||||
// Whether to enable framebuffer transparency on DWM
|
||||
GLFWbool transparent;
|
||||
GLFWbool scaleToMonitor;
|
||||
|
||||
// The last received cursor position, regardless of source
|
||||
int lastCursorPosX, lastCursorPosY;
|
||||
@@ -326,6 +354,10 @@ typedef struct _GLFWlibraryWin32
|
||||
HINSTANCE instance;
|
||||
PFN_SetProcessDPIAware SetProcessDPIAware_;
|
||||
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
|
||||
PFN_EnableNonClientDpiScaling EnableNonClientDpiScaling_;
|
||||
PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContext_;
|
||||
PFN_GetDpiForWindow GetDpiForWindow_;
|
||||
PFN_AdjustWindowRectExForDpi AdjustWindowRectExForDpi_;
|
||||
} user32;
|
||||
|
||||
struct {
|
||||
@@ -341,6 +373,11 @@ typedef struct _GLFWlibraryWin32
|
||||
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
||||
} shcore;
|
||||
|
||||
struct {
|
||||
HINSTANCE instance;
|
||||
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
|
||||
} ntdll;
|
||||
|
||||
} _GLFWlibraryWin32;
|
||||
|
||||
// Win32-specific per-monitor data
|
||||
@@ -396,10 +433,11 @@ typedef struct _GLFWmutexWin32
|
||||
|
||||
GLFWbool _glfwRegisterWindowClassWin32(void);
|
||||
void _glfwUnregisterWindowClassWin32(void);
|
||||
GLFWbool _glfwIsCompositionEnabledWin32(void);
|
||||
|
||||
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
|
||||
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
|
||||
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp);
|
||||
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build);
|
||||
void _glfwInputErrorWin32(int error, const char* description);
|
||||
void _glfwUpdateKeyNamesWin32(void);
|
||||
|
||||
|
||||
265
glfw/win32_window.c
vendored
265
glfw/win32_window.c
vendored
@@ -186,14 +186,20 @@ static HICON createIcon(const GLFWimage* image,
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Translate client window size to full window size according to styles
|
||||
// Translate client window size to full window size according to styles and DPI
|
||||
//
|
||||
static void getFullWindowSize(DWORD style, DWORD exStyle,
|
||||
int clientWidth, int clientHeight,
|
||||
int* fullWidth, int* fullHeight)
|
||||
int* fullWidth, int* fullHeight,
|
||||
UINT dpi)
|
||||
{
|
||||
RECT rect = { 0, 0, clientWidth, clientHeight };
|
||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, dpi);
|
||||
else
|
||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||
|
||||
*fullWidth = rect.right - rect.left;
|
||||
*fullHeight = rect.bottom - rect.top;
|
||||
}
|
||||
@@ -206,7 +212,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area)
|
||||
const float ratio = (float) window->numer / (float) window->denom;
|
||||
|
||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||
0, 0, &xoff, &yoff);
|
||||
0, 0, &xoff, &yoff,
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
|
||||
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
|
||||
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
|
||||
@@ -337,7 +344,16 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
||||
style |= getWindowStyle(window);
|
||||
|
||||
GetClientRect(window->win32.handle, &rect);
|
||||
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE,
|
||||
getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
||||
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
||||
@@ -351,10 +367,12 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
||||
//
|
||||
static void updateFramebufferTransparency(const _GLFWwindow* window)
|
||||
{
|
||||
BOOL enabled;
|
||||
|
||||
if (!IsWindowsVistaOrGreater())
|
||||
return;
|
||||
|
||||
if (_glfwIsCompositionEnabledWin32())
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||
{
|
||||
HRGN region = CreateRectRgn(0, 0, -1, -1);
|
||||
DWM_BLURBEHIND bb = {0};
|
||||
@@ -396,29 +414,6 @@ static void updateFramebufferTransparency(const _GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a resource ID
|
||||
//
|
||||
static LPWSTR translateCursorShape(int shape)
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case GLFW_ARROW_CURSOR:
|
||||
return IDC_ARROW;
|
||||
case GLFW_IBEAM_CURSOR:
|
||||
return IDC_IBEAM;
|
||||
case GLFW_CROSSHAIR_CURSOR:
|
||||
return IDC_CROSS;
|
||||
case GLFW_HAND_CURSOR:
|
||||
return IDC_HAND;
|
||||
case GLFW_HRESIZE_CURSOR:
|
||||
return IDC_SIZEWE;
|
||||
case GLFW_VRESIZE_CURSOR:
|
||||
return IDC_SIZENS;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Retrieves and translates modifier keys
|
||||
//
|
||||
static int getKeyMods(void)
|
||||
@@ -578,9 +573,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
if (!window)
|
||||
{
|
||||
// This is the message handling for the hidden helper window
|
||||
// and for a regular window during its initial creation
|
||||
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_NCCREATE:
|
||||
{
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
EnableNonClientDpiScaling(hWnd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DISPLAYCHANGE:
|
||||
_glfwPollMonitorsWin32();
|
||||
break;
|
||||
@@ -1003,7 +1007,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
break;
|
||||
|
||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||
0, 0, &xoff, &yoff);
|
||||
0, 0, &xoff, &yoff,
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
|
||||
if (window->minwidth != GLFW_DONT_CARE &&
|
||||
window->minheight != GLFW_DONT_CARE)
|
||||
@@ -1056,10 +1061,52 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_GETDPISCALEDSIZE:
|
||||
{
|
||||
if (window->win32.scaleToMonitor)
|
||||
break;
|
||||
|
||||
// Adjust the window size to keep the client area size constant
|
||||
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||
{
|
||||
RECT source = {0}, target = {0};
|
||||
SIZE* size = (SIZE*) lParam;
|
||||
|
||||
AdjustWindowRectExForDpi(&source, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
AdjustWindowRectExForDpi(&target, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
LOWORD(wParam));
|
||||
|
||||
size->cx += (target.right - target.left) -
|
||||
(source.right - source.left);
|
||||
size->cy += (target.bottom - target.top) -
|
||||
(source.bottom - source.top);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DPICHANGED:
|
||||
{
|
||||
const float xscale = HIWORD(wParam) / 96.f;
|
||||
const float yscale = LOWORD(wParam) / 96.f;
|
||||
const float xscale = HIWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
const float yscale = LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
|
||||
// Only apply the suggested size if the OS is new enough to have
|
||||
// sent a WM_GETDPISCALEDSIZE before this
|
||||
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||
{
|
||||
RECT* suggested = (RECT*) lParam;
|
||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||
suggested->left,
|
||||
suggested->top,
|
||||
suggested->right - suggested->left,
|
||||
suggested->bottom - suggested->top,
|
||||
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||
break;
|
||||
}
|
||||
@@ -1146,7 +1193,8 @@ static int createNativeWindow(_GLFWwindow* window,
|
||||
|
||||
getFullWindowSize(style, exStyle,
|
||||
wndconfig->width, wndconfig->height,
|
||||
&fullWidth, &fullHeight);
|
||||
&fullWidth, &fullHeight,
|
||||
USER_DEFAULT_SCREEN_DPI);
|
||||
}
|
||||
|
||||
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
|
||||
@@ -1185,6 +1233,40 @@ static int createNativeWindow(_GLFWwindow* window,
|
||||
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
|
||||
}
|
||||
|
||||
window->win32.scaleToMonitor = wndconfig->scaleToMonitor;
|
||||
|
||||
// Adjust window size to account for DPI scaling of the window frame and
|
||||
// optionally DPI scaling of the client area
|
||||
// This cannot be done until we know what monitor it was placed on
|
||||
if (!window->monitor)
|
||||
{
|
||||
RECT rect = { 0, 0, wndconfig->width, wndconfig->height };
|
||||
|
||||
if (wndconfig->scaleToMonitor)
|
||||
{
|
||||
float xscale, yscale;
|
||||
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||
rect.right = (int) (rect.right * xscale);
|
||||
rect.bottom = (int) (rect.bottom * yscale);
|
||||
}
|
||||
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle,
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||
|
||||
SetWindowPos(window->win32.handle, NULL,
|
||||
rect.left, rect.top,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
DragAcceptFiles(window->win32.handle, TRUE);
|
||||
|
||||
if (fbconfig->transparent)
|
||||
@@ -1244,20 +1326,6 @@ void _glfwUnregisterWindowClassWin32(void)
|
||||
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
|
||||
}
|
||||
|
||||
// Returns whether desktop compositing is enabled
|
||||
//
|
||||
GLFWbool _glfwIsCompositionEnabledWin32(void)
|
||||
{
|
||||
if (IsWindowsVistaOrGreater())
|
||||
{
|
||||
BOOL enabled;
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)))
|
||||
return enabled;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
@@ -1395,8 +1463,19 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
||||
{
|
||||
RECT rect = { xpos, ypos, xpos, ypos };
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
|
||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
|
||||
}
|
||||
@@ -1425,8 +1504,19 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
else
|
||||
{
|
||||
RECT rect = { 0, 0, width, height };
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||
0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
|
||||
@@ -1481,8 +1571,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
|
||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||
SetRect(&rect, 0, 0, width, height);
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
if (left)
|
||||
*left = -rect.left;
|
||||
@@ -1563,8 +1663,19 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
else
|
||||
{
|
||||
RECT rect = { xpos, ypos, xpos + width, ypos + height };
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||
rect.left, rect.top,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
@@ -1624,8 +1735,18 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
else
|
||||
after = HWND_NOTOPMOST;
|
||||
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, after,
|
||||
rect.left, rect.top,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
@@ -1660,7 +1781,15 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return window->win32.transparent && _glfwIsCompositionEnabledWin32();
|
||||
BOOL enabled;
|
||||
|
||||
if (!window->win32.transparent)
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (!IsWindowsVistaOrGreater())
|
||||
return GLFW_FALSE;
|
||||
|
||||
return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
||||
@@ -1867,8 +1996,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
{
|
||||
cursor->win32.handle =
|
||||
CopyCursor(LoadCursorW(NULL, translateCursorShape(shape)));
|
||||
LPCWSTR name = NULL;
|
||||
|
||||
if (shape == GLFW_ARROW_CURSOR)
|
||||
name = IDC_ARROW;
|
||||
else if (shape == GLFW_IBEAM_CURSOR)
|
||||
name = IDC_IBEAM;
|
||||
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||
name = IDC_CROSS;
|
||||
else if (shape == GLFW_HAND_CURSOR)
|
||||
name = IDC_HAND;
|
||||
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||
name = IDC_SIZEWE;
|
||||
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||
name = IDC_SIZENS;
|
||||
else
|
||||
return GLFW_FALSE;
|
||||
|
||||
cursor->win32.handle = CopyCursor(LoadCursorW(NULL, name));
|
||||
if (!cursor->win32.handle)
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
|
||||
7
glfw/window.c
vendored
7
glfw/window.c
vendored
@@ -393,6 +393,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
||||
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
case GLFW_SCALE_TO_MONITOR:
|
||||
_glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
case GLFW_CENTER_CURSOR:
|
||||
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
@@ -457,6 +460,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value)
|
||||
strncpy(_glfw.hints.window.x11.instanceName, value,
|
||||
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
||||
return;
|
||||
case GLFW_WAYLAND_APP_ID:
|
||||
strncpy(_glfw.hints.window.wl.appId, value,
|
||||
sizeof(_glfw.hints.window.wl.appId) - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
||||
|
||||
28
glfw/wl_init.c
vendored
28
glfw/wl_init.c
vendored
@@ -560,6 +560,9 @@ static void registryHandleGlobal(void* data,
|
||||
_glfw.wl.seatVersion);
|
||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||
}
|
||||
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||
_glfwSetupWaylandDataDevice();
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||
{
|
||||
@@ -593,6 +596,17 @@ static void registryHandleGlobal(void* data,
|
||||
&zwp_idle_inhibit_manager_v1_interface,
|
||||
1);
|
||||
}
|
||||
else if (strcmp(interface, "wl_data_device_manager") == 0)
|
||||
{
|
||||
_glfw.wl.dataDeviceManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&wl_data_device_manager_interface,
|
||||
1);
|
||||
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||
_glfwSetupWaylandDataDevice();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void registryHandleGlobalRemove(void *data,
|
||||
@@ -761,6 +775,17 @@ void _glfwPlatformTerminate(void)
|
||||
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
|
||||
if (_glfw.wl.idleInhibitManager)
|
||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
||||
if (_glfw.wl.dataSourceForClipboard)
|
||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
||||
if (_glfw.wl.dataOffers[doi].id) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.dataDevice)
|
||||
wl_data_device_destroy(_glfw.wl.dataDevice);
|
||||
if (_glfw.wl.dataDeviceManager)
|
||||
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
|
||||
if (_glfw.wl.registry)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
if (_glfw.wl.display)
|
||||
@@ -769,6 +794,9 @@ void _glfwPlatformTerminate(void)
|
||||
wl_display_disconnect(_glfw.wl.display);
|
||||
}
|
||||
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
||||
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
|
||||
free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL;
|
||||
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetVersionString(void)
|
||||
|
||||
24
glfw/wl_platform.h
vendored
24
glfw/wl_platform.h
vendored
@@ -149,6 +149,7 @@ typedef struct _GLFWwindowWayland
|
||||
double cursorPosX, cursorPosY;
|
||||
|
||||
char* title;
|
||||
char appId[256];
|
||||
|
||||
// We need to track the monitors the window spans on to calculate the
|
||||
// optimal scaling factor.
|
||||
@@ -175,6 +176,19 @@ typedef struct _GLFWwindowWayland
|
||||
|
||||
} _GLFWwindowWayland;
|
||||
|
||||
typedef struct _GLFWWaylandDataOffer
|
||||
{
|
||||
struct wl_data_offer *id;
|
||||
const char *mime;
|
||||
int offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
uint32_t source_actions;
|
||||
uint32_t dnd_action;
|
||||
struct wl_surface *surface;
|
||||
} _GLFWWaylandDataOffer;
|
||||
|
||||
// Wayland-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryWayland
|
||||
@@ -193,6 +207,9 @@ typedef struct _GLFWlibraryWayland
|
||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||
struct wl_data_device_manager* dataDeviceManager;
|
||||
struct wl_data_device* dataDevice;
|
||||
struct wl_data_source* dataSourceForClipboard;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
@@ -232,7 +249,11 @@ typedef struct _GLFWlibraryWayland
|
||||
} egl;
|
||||
|
||||
EventLoopData eventLoopData;
|
||||
|
||||
char* clipboardString;
|
||||
char* clipboardSourceString;
|
||||
struct wl_data_offer* clipboardSourceOffer;
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -261,3 +282,4 @@ typedef struct _GLFWcursorWayland
|
||||
|
||||
|
||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
void _glfwSetupWaylandDataDevice();
|
||||
|
||||
339
glfw/wl_window.c
vendored
339
glfw/wl_window.c
vendored
@@ -37,6 +37,18 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
||||
#define URI_LIST_MIME "text/uri-list"
|
||||
|
||||
|
||||
static const char*
|
||||
clipboard_mime() {
|
||||
static char buf[128] = {0};
|
||||
if (buf[0] == 0) {
|
||||
snprintf(buf, sizeof(buf), "application/glfw+clipboard-%d", getpid());
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void handlePing(void* data,
|
||||
struct wl_shell_surface* shellSurface,
|
||||
uint32_t serial)
|
||||
@@ -681,6 +693,8 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
}
|
||||
if (strlen(window->wl.appId))
|
||||
xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId);
|
||||
|
||||
wl_surface_commit(window->wl.surface);
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
@@ -757,6 +771,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
{
|
||||
window->wl.justCreated = GLFW_TRUE;
|
||||
window->wl.transparent = fbconfig->transparent;
|
||||
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
|
||||
|
||||
if (!createSurface(window, wndconfig))
|
||||
return GLFW_FALSE;
|
||||
@@ -1435,18 +1450,330 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
}
|
||||
}
|
||||
|
||||
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd)
|
||||
{
|
||||
if (_glfw.wl.clipboardString) {
|
||||
size_t len = strlen(_glfw.wl.clipboardString), pos = 0;
|
||||
double start = glfwGetTime();
|
||||
while (pos < len && glfwGetTime() - start < 2.0) {
|
||||
ssize_t ret = write(fd, _glfw.wl.clipboardString + pos, len - pos);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
if (ret > 0) {
|
||||
start = glfwGetTime();
|
||||
pos += ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
wl_display_flush(_glfw.wl.display);
|
||||
size_t sz = 0, capacity = 0;
|
||||
char *buf = NULL;
|
||||
struct pollfd fds;
|
||||
fds.fd = pipefd[0];
|
||||
fds.events = POLLIN;
|
||||
double start = glfwGetTime();
|
||||
#define bail(...) { \
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||
free(buf); buf = NULL; \
|
||||
close(pipefd[0]); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
while (glfwGetTime() - start < 2) {
|
||||
int ret = poll(&fds, 1, 2000);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
bail("Wayland: Failed to poll clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (!ret) {
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
}
|
||||
if (capacity <= sz || capacity - sz <= 64) {
|
||||
capacity += 4096;
|
||||
buf = realloc(buf, capacity);
|
||||
if (!buf) {
|
||||
bail("Wayland: Failed to allocate memory to read clipboard data");
|
||||
}
|
||||
}
|
||||
ret = read(pipefd[0], buf + sz, capacity - sz - 1);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (ret == 0) { close(pipefd[0]); buf[sz] = 0; return buf; }
|
||||
sz += ret;
|
||||
start = glfwGetTime();
|
||||
}
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
#undef bail
|
||||
|
||||
}
|
||||
|
||||
static const char* _glfwReceiveClipboardText(struct wl_data_offer *data_offer, const char *mime)
|
||||
{
|
||||
if (_glfw.wl.clipboardSourceOffer == data_offer && _glfw.wl.clipboardSourceString)
|
||||
return _glfw.wl.clipboardSourceString;
|
||||
free(_glfw.wl.clipboardSourceString);
|
||||
_glfw.wl.clipboardSourceString = read_data_offer(data_offer, mime);
|
||||
return _glfw.wl.clipboardSourceString;
|
||||
}
|
||||
|
||||
static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) {
|
||||
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
|
||||
_glfw.wl.dataSourceForClipboard = NULL;
|
||||
wl_data_source_destroy(wl_data_source);
|
||||
}
|
||||
|
||||
static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) {
|
||||
}
|
||||
|
||||
const static struct wl_data_source_listener data_source_listener = {
|
||||
.send = _glfwSendClipboardText,
|
||||
.cancelled = data_source_canceled,
|
||||
.target = data_source_target,
|
||||
};
|
||||
|
||||
static void prune_unclaimed_data_offers() {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer)
|
||||
{
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == data_offer) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 1;
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == 1) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 0; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
static void handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||
_glfw.wl.dataOffers[i].mime = "text/plain;charset=utf-8";
|
||||
else if (!_glfw.wl.dataOffers[i].mime && strcmp(mime, "text/plain") == 0)
|
||||
_glfw.wl.dataOffers[i].mime = "text/plain";
|
||||
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||
_glfw.wl.dataOffers[i].is_self_offer = 1;
|
||||
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||
_glfw.wl.dataOffers[i].has_uri_list = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void data_offer_source_actions(void *data, struct wl_data_offer* id, uint32_t actions) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].source_actions = actions;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void data_offer_action(void *data, struct wl_data_offer* id, uint32_t action) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].dnd_action = action;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const struct wl_data_offer_listener data_offer_listener = {
|
||||
.offer = handle_offer_mimetype,
|
||||
.source_actions = data_offer_source_actions,
|
||||
.action = data_offer_action,
|
||||
};
|
||||
|
||||
static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) {
|
||||
smallest_idx = _glfw.wl.dataOffers[i].idx;
|
||||
pos = i;
|
||||
}
|
||||
if (_glfw.wl.dataOffers[i].id == NULL) {
|
||||
_glfw.wl.dataOffers[i].id = id;
|
||||
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id);
|
||||
memset(_glfw.wl.dataOffers + pos, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
_glfw.wl.dataOffers[pos].id = id;
|
||||
_glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter;
|
||||
end:
|
||||
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 2;
|
||||
_glfw.wl.dataOffers[i].surface = surface;
|
||||
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
|
||||
wl_data_offer_accept(id, serial, mime);
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 0; // previous drag offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
static void drag_leave(void *data, struct wl_data_device *wl_data_device) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void drop(void *data, struct wl_data_device *wl_data_device) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
|
||||
if (uri_list) {
|
||||
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
|
||||
int count;
|
||||
char** paths = parseUriList(data, &count);
|
||||
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) {
|
||||
_glfwInputDrop(window, count, (const char**) paths);
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
}
|
||||
|
||||
|
||||
for (int k = 0; k < count; k++)
|
||||
free(paths[k]);
|
||||
free(paths);
|
||||
free(uri_list);
|
||||
}
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
|
||||
}
|
||||
|
||||
const static struct wl_data_device_listener data_device_listener = {
|
||||
.data_offer = handle_data_offer,
|
||||
.selection = mark_selection_offer,
|
||||
.enter = drag_enter,
|
||||
.motion = motion,
|
||||
.drop = drop,
|
||||
.leave = drag_leave,
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (!_glfw.wl.dataDevice) return;
|
||||
if (data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
|
||||
}
|
||||
}
|
||||
|
||||
const static struct wl_callback_listener copy_callback_listener = {
|
||||
.done = copy_callback_done
|
||||
};
|
||||
|
||||
void _glfwSetupWaylandDataDevice() {
|
||||
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
|
||||
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
|
||||
}
|
||||
|
||||
static inline GLFWbool _glfwEnsureDataDevice() {
|
||||
if (!_glfw.wl.dataDeviceManager)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot use clipboard, data device manager is not ready");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (!_glfw.wl.dataDevice)
|
||||
{
|
||||
if (!_glfw.wl.seat)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot use clipboard, seat is not ready");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (!_glfw.wl.dataDevice)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot use clipboard, failed to create data device");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Clipboard setting not implemented yet");
|
||||
if (!_glfwEnsureDataDevice()) return;
|
||||
free(_glfw.wl.clipboardString);
|
||||
_glfw.wl.clipboardString = _glfw_strdup(string);
|
||||
if (_glfw.wl.dataSourceForClipboard)
|
||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
|
||||
if (!_glfw.wl.dataSourceForClipboard)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot copy failed to create data source");
|
||||
return;
|
||||
}
|
||||
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
|
||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||
wl_callback_add_listener(callback, ©_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Clipboard getting not implemented yet");
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == 1) {
|
||||
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
|
||||
return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
129
glfw/x11_window.c
vendored
129
glfw/x11_window.c
vendored
@@ -161,29 +161,6 @@ static Bool isSelPropNewValueNotify(Display* display, XEvent* event, XPointer po
|
||||
event->xproperty.atom == notification->xselection.property;
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a font cursor shape
|
||||
//
|
||||
static int translateCursorShape(int shape)
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case GLFW_ARROW_CURSOR:
|
||||
return XC_left_ptr;
|
||||
case GLFW_IBEAM_CURSOR:
|
||||
return XC_xterm;
|
||||
case GLFW_CROSSHAIR_CURSOR:
|
||||
return XC_crosshair;
|
||||
case GLFW_HAND_CURSOR:
|
||||
return XC_hand1;
|
||||
case GLFW_HRESIZE_CURSOR:
|
||||
return XC_sb_h_double_arrow;
|
||||
case GLFW_VRESIZE_CURSOR:
|
||||
return XC_sb_v_double_arrow;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Translates an X event modifier state mask
|
||||
//
|
||||
static int translateState(int state)
|
||||
@@ -206,23 +183,6 @@ static int translateState(int state)
|
||||
return mods;
|
||||
}
|
||||
|
||||
// Return the GLFW window corresponding to the specified X11 window
|
||||
//
|
||||
static _GLFWwindow* findWindowByHandle(Window handle)
|
||||
{
|
||||
_GLFWwindow* window;
|
||||
|
||||
if (XFindContext(_glfw.x11.display,
|
||||
handle,
|
||||
_glfw.x11.context,
|
||||
(XPointer*) &window) != 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
// Sends an EWMH or ICCCM event to the window manager
|
||||
//
|
||||
static void sendEventToWM(_GLFWwindow* window, Atom type,
|
||||
@@ -390,56 +350,6 @@ static void updateWindowMode(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Splits and translates a text/uri-list into separate file paths
|
||||
// NOTE: This function destroys the provided string
|
||||
//
|
||||
static char** parseUriList(char* text, int* count)
|
||||
{
|
||||
const char* prefix = "file://";
|
||||
char** paths = NULL;
|
||||
char* line;
|
||||
|
||||
*count = 0;
|
||||
|
||||
while ((line = strtok(text, "\r\n")))
|
||||
{
|
||||
text = NULL;
|
||||
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||
{
|
||||
line += strlen(prefix);
|
||||
// TODO: Validate hostname
|
||||
while (*line != '/')
|
||||
line++;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
|
||||
char* path = calloc(strlen(line) + 1, 1);
|
||||
paths = realloc(paths, *count * sizeof(char*));
|
||||
paths[*count - 1] = path;
|
||||
|
||||
while (*line)
|
||||
{
|
||||
if (line[0] == '%' && line[1] && line[2])
|
||||
{
|
||||
const char digits[3] = { line[1], line[2], '\0' };
|
||||
*path = strtol(digits, NULL, 16);
|
||||
line += 2;
|
||||
}
|
||||
else
|
||||
*path = *line;
|
||||
|
||||
path++;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
// Encode a Unicode code point to a UTF-8 stream
|
||||
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
||||
@@ -582,6 +492,15 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
const _GLFWwndconfig* wndconfig,
|
||||
Visual* visual, int depth)
|
||||
{
|
||||
int width = wndconfig->width;
|
||||
int height = wndconfig->height;
|
||||
|
||||
if (wndconfig->scaleToMonitor)
|
||||
{
|
||||
width *= _glfw.x11.contentScaleX;
|
||||
height *= _glfw.x11.contentScaleY;
|
||||
}
|
||||
|
||||
// Create a colormap based on the visual used by the current context
|
||||
window->x11.colormap = XCreateColormap(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
@@ -607,7 +526,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
0, 0,
|
||||
wndconfig->width, wndconfig->height,
|
||||
width, height,
|
||||
0, // Border width
|
||||
depth, // Color depth
|
||||
InputOutput,
|
||||
@@ -710,7 +629,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
XFree(hints);
|
||||
}
|
||||
|
||||
updateNormalHints(window, wndconfig->width, wndconfig->height);
|
||||
updateNormalHints(window, width, height);
|
||||
|
||||
// Set ICCCM WM_CLASS property
|
||||
{
|
||||
@@ -1243,8 +1162,10 @@ static void processEvent(XEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
window = findWindowByHandle(event->xany.window);
|
||||
if (window == NULL)
|
||||
if (XFindContext(_glfw.x11.display,
|
||||
event->xany.window,
|
||||
_glfw.x11.context,
|
||||
(XPointer*) &window) != 0)
|
||||
{
|
||||
// This is an event for a window that has already been destroyed
|
||||
return;
|
||||
@@ -2680,8 +2601,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
{
|
||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display,
|
||||
translateCursorShape(shape));
|
||||
int native = 0;
|
||||
|
||||
if (shape == GLFW_ARROW_CURSOR)
|
||||
native = XC_left_ptr;
|
||||
else if (shape == GLFW_IBEAM_CURSOR)
|
||||
native = XC_xterm;
|
||||
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||
native = XC_crosshair;
|
||||
else if (shape == GLFW_HAND_CURSOR)
|
||||
native = XC_hand1;
|
||||
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||
native = XC_sb_h_double_arrow;
|
||||
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||
native = XC_sb_v_double_arrow;
|
||||
else
|
||||
return GLFW_FALSE;
|
||||
|
||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
|
||||
if (!cursor->x11.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
|
||||
25
glfw/xkb_glfw.c
vendored
25
glfw/xkb_glfw.c
vendored
@@ -33,7 +33,7 @@
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
|
||||
|
||||
#define map_key(key) { \
|
||||
#define map_key(key) \
|
||||
switch(key) { \
|
||||
S(space, SPACE); \
|
||||
S(apostrophe, APOSTROPHE); \
|
||||
@@ -93,10 +93,6 @@
|
||||
D(A, Z, A, Z); \
|
||||
R(F1, F25, F1, F25); \
|
||||
R(KP_0, KP_9, KP_0, KP_9); \
|
||||
default: \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
static int
|
||||
glfw_key_for_sym(xkb_keysym_t key) {
|
||||
@@ -105,6 +101,13 @@ glfw_key_for_sym(xkb_keysym_t key) {
|
||||
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
|
||||
#define D(s, e, gs, ...) R(s, e, gs)
|
||||
map_key(key)
|
||||
S(KP_Up, UP);
|
||||
S(KP_Down, DOWN);
|
||||
S(KP_Left, LEFT);
|
||||
S(KP_Right, RIGHT);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
#undef F
|
||||
#undef D
|
||||
@@ -119,6 +122,9 @@ glfw_xkb_sym_for_key(int key) {
|
||||
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
|
||||
#define D(...)
|
||||
map_key(key)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
#undef F
|
||||
#undef D
|
||||
@@ -329,12 +335,14 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
||||
static KeyEvent key_event = {};
|
||||
|
||||
static inline xkb_keysym_t
|
||||
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym) {
|
||||
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) {
|
||||
*compose_completed = 0;
|
||||
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
|
||||
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
||||
switch (xkb_compose_state_get_status(composeState)) {
|
||||
case XKB_COMPOSE_COMPOSED:
|
||||
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
|
||||
*compose_completed = 1;
|
||||
return xkb_compose_state_get_one_sym(composeState);
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
case XKB_COMPOSE_CANCELLED:
|
||||
@@ -465,8 +473,9 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
|
||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||
const char *text_type = "composed_text";
|
||||
glfw_sym = compose_symbol(sg->composeState, syms[0]);
|
||||
if (glfw_sym == XKB_KEY_NoSymbol) {
|
||||
int compose_completed;
|
||||
glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed);
|
||||
if (glfw_sym == XKB_KEY_NoSymbol && !compose_completed) {
|
||||
debug("compose not complete, ignoring.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ from .search import BadRegex, Search
|
||||
try:
|
||||
from .highlight import initialize_highlighter, highlight_collection
|
||||
except ImportError:
|
||||
initialize_highlighter = None
|
||||
initialize_highlighter = highlight_collection = None
|
||||
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||
|
||||
@@ -34,9 +34,9 @@ Horizontal alignment for the displayed image.
|
||||
--place
|
||||
Choose where on the screen to display the image. The image will
|
||||
be scaled to fit into the specified rectangle. The syntax for
|
||||
specifying rectanges is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>. All measurements
|
||||
are in cells (i.e. cursor positions) with the origin :italic:`(0, 0)` at
|
||||
the top-left corner of the screen.
|
||||
specifying rectangles is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
||||
All measurements are in cells (i.e. cursor positions) with the
|
||||
origin :italic:`(0, 0)` at the top-left corner of the screen.
|
||||
|
||||
|
||||
--scale-up
|
||||
|
||||
@@ -43,7 +43,7 @@ class Resize(Handler):
|
||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||
payload = resize_window(global_opts, opts, items)
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload}
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
|
||||
self.write(encode_send(send))
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
|
||||
@@ -103,6 +103,8 @@ class Boss:
|
||||
if new_os_window_trigger is not None:
|
||||
self.keymap.pop(new_os_window_trigger, None)
|
||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||
if args.start_in_fullscreen:
|
||||
self.toggle_fullscreen()
|
||||
|
||||
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
|
||||
if os_window_id is None:
|
||||
|
||||
@@ -64,7 +64,7 @@ vec4 calculate_foreground() {
|
||||
// Since strike and text are the same color, we simply add the alpha values
|
||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||
// Underline color might be different, so alpha blend
|
||||
vec4 ans = alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
||||
vec4 ans = alpha_blend(fg, combined_alpha * effective_text_alpha, decoration_fg, underline_alpha * effective_text_alpha);
|
||||
return mix(ans, cursor_color_vec, cursor_alpha);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
static inline char**
|
||||
serialize_string_tuple(PyObject *src) {
|
||||
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
||||
|
||||
char **ans = calloc(sz + 1, sizeof(char*));
|
||||
if (!ans) fatal("Out of memory");
|
||||
for (Py_ssize_t i = 0; i < sz; i++) {
|
||||
@@ -28,6 +29,13 @@ serialize_string_tuple(PyObject *src) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
free_string_tuple(char** data) {
|
||||
size_t i = 0;
|
||||
while(data[i]) free(data[i++]);
|
||||
free(data);
|
||||
}
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static inline void
|
||||
@@ -125,8 +133,8 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
break;
|
||||
}
|
||||
#undef exit_on_err
|
||||
free(argv);
|
||||
free(env);
|
||||
free_string_tuple(argv);
|
||||
free_string_tuple(env);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
return PyLong_FromLong(pid);
|
||||
}
|
||||
|
||||
@@ -464,7 +464,7 @@ def options_spec():
|
||||
dest=cls
|
||||
default={appname}
|
||||
condition=not is_macos
|
||||
Set the class part of the :italic:`WM_CLASS` window property
|
||||
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
|
||||
|
||||
|
||||
--name
|
||||
@@ -546,6 +546,11 @@ specify this address. This option will be ignored, unless you set
|
||||
option as it is read automatically from the environment.
|
||||
|
||||
|
||||
--start-in-fullscreen
|
||||
type=bool-set
|
||||
Make the initial kitty window fullscreen on startup
|
||||
|
||||
|
||||
# Debugging options
|
||||
|
||||
--version -v
|
||||
|
||||
@@ -32,6 +32,9 @@ class UnknownLayout(ValueError):
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
cmap = {}
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
||||
|
||||
def w(func):
|
||||
@@ -45,6 +48,7 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
||||
func.no_response = no_response
|
||||
func.string_return_is_error = string_return_is_error
|
||||
func.args_count = 0 if not argspec else args_count
|
||||
cmap[func.name] = func
|
||||
return func
|
||||
return w
|
||||
|
||||
@@ -363,7 +367,7 @@ reset the layout to its default configuration.
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the window this command is run in, rather than the active window.
|
||||
If specified resize the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec='',
|
||||
string_return_is_error=True
|
||||
@@ -787,9 +791,6 @@ def kitten(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||
|
||||
|
||||
def cli_params_for(func):
|
||||
return (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ completion_scripts = {
|
||||
'zsh': '''
|
||||
_kitty() {
|
||||
local src
|
||||
# Send all words upto the word the cursor is currently on
|
||||
# Send all words up to the word the cursor is currently on
|
||||
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
|
||||
if [[ $? == 0 ]]; then
|
||||
eval ${src}
|
||||
@@ -56,6 +56,14 @@ kitty_completions() {
|
||||
}
|
||||
|
||||
complete -o nospace -F kitty_completions kitty
|
||||
''',
|
||||
'fish': '''
|
||||
function __kitty_completions
|
||||
# Send all words up to the one before the cursor
|
||||
commandline -cop | kitty +complete fish
|
||||
end
|
||||
|
||||
complete -f -c kitty -a "(__kitty_completions)"
|
||||
''',
|
||||
}
|
||||
|
||||
@@ -86,6 +94,11 @@ def bash_input_parser(data):
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def fish_input_parser(data):
|
||||
return data.rstrip().splitlines(), True
|
||||
|
||||
|
||||
@output_serializer
|
||||
def zsh_output_serializer(ans):
|
||||
lines = []
|
||||
@@ -95,6 +108,10 @@ def zsh_output_serializer(ans):
|
||||
cmd += ['-S', '""']
|
||||
if description in ans.files_groups:
|
||||
cmd.append('-f')
|
||||
common_prefix = os.path.commonprefix(tuple(matches))
|
||||
if common_prefix:
|
||||
cmd.extend(('-p', shlex.quote(common_prefix)))
|
||||
matches = {k[len(common_prefix):]: v for k, v in matches.items()}
|
||||
cmd.append('--')
|
||||
for word, description in matches.items():
|
||||
cmd.append(shlex.quote(word))
|
||||
@@ -117,6 +134,16 @@ def bash_output_serializer(ans):
|
||||
# }}}
|
||||
|
||||
|
||||
@output_serializer
|
||||
def fish_output_serializer(ans):
|
||||
lines = []
|
||||
for matches in ans.match_groups.values():
|
||||
for word in matches:
|
||||
lines.append(shlex.quote(word))
|
||||
# debug('\n'.join(lines))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||
cmds = ['@' + c for c in cmap]
|
||||
ans.match_groups['Entry points'] = {
|
||||
@@ -197,21 +224,40 @@ def complete_remote_command(ans, cmd_name, words, new_word):
|
||||
complete_alias_map(ans, words, new_word, alias_map)
|
||||
|
||||
|
||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=lambda filename: True):
|
||||
def path_completion(prefix=''):
|
||||
prefix = prefix.replace(r'\ ', ' ')
|
||||
dirs, files = [], []
|
||||
base = '.'
|
||||
if prefix.endswith('/'):
|
||||
base = prefix
|
||||
elif '/' in prefix:
|
||||
base = os.path.dirname(prefix)
|
||||
for x in os.scandir(base):
|
||||
q = os.path.relpath(x.path)
|
||||
if x.is_dir():
|
||||
if q.startswith(prefix):
|
||||
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||
src = os.path.expandvars(os.path.expanduser(base))
|
||||
src_prefix = os.path.abspath(os.path.expandvars(os.path.expanduser(prefix))) if prefix else ''
|
||||
try:
|
||||
items = os.scandir(src)
|
||||
except FileNotFoundError:
|
||||
items = ()
|
||||
for x in items:
|
||||
abspath = os.path.abspath(x.path)
|
||||
if prefix and not abspath.startswith(src_prefix):
|
||||
continue
|
||||
if prefix:
|
||||
q = prefix + abspath[len(src_prefix):].lstrip(os.sep)
|
||||
q = os.path.expandvars(os.path.expanduser(q))
|
||||
else:
|
||||
if q.startswith(prefix) and predicate(q):
|
||||
files.append(q)
|
||||
q = os.path.relpath(abspath)
|
||||
if x.is_dir():
|
||||
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||
else:
|
||||
files.append(q)
|
||||
return dirs, files
|
||||
|
||||
|
||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=None):
|
||||
dirs, files = path_completion(prefix or '')
|
||||
files = filter(predicate, files)
|
||||
|
||||
if dirs:
|
||||
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
||||
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
||||
@@ -232,6 +278,17 @@ def complete_icat_args(ans, opt, prefix):
|
||||
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
||||
|
||||
|
||||
def config_file_predicate(filename):
|
||||
return filename.endswith('.conf')
|
||||
|
||||
|
||||
def complete_diff_args(ans, opt, prefix):
|
||||
if opt is None:
|
||||
complete_files_and_dirs(ans, prefix, 'Files')
|
||||
elif opt['dest'] == 'config':
|
||||
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
|
||||
|
||||
|
||||
def complete_kitten(ans, kitten, words, new_word):
|
||||
try:
|
||||
cd = get_kitten_cli_docs(kitten)
|
||||
@@ -247,7 +304,8 @@ def complete_kitten(ans, kitten, words, new_word):
|
||||
for alias in opt['aliases']:
|
||||
option_map[alias] = opt
|
||||
complete_alias_map(ans, words, new_word, option_map, {
|
||||
'icat': complete_icat_args
|
||||
'icat': complete_icat_args,
|
||||
'diff': complete_diff_args,
|
||||
}.get(kitten))
|
||||
|
||||
|
||||
|
||||
@@ -35,14 +35,22 @@ named_keys = {
|
||||
|
||||
def parse_shortcut(sc):
|
||||
parts = sc.split('+')
|
||||
mods = parse_mods(parts[:-1], sc)
|
||||
if mods is None:
|
||||
return None, None, None
|
||||
mods = 0
|
||||
if len(parts) > 1:
|
||||
mods = parse_mods(parts[:-1], sc)
|
||||
if mods is None:
|
||||
return None, None, None
|
||||
key = parts[-1].upper()
|
||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||
is_native = False
|
||||
if key is None:
|
||||
key = defines.key_for_native_key_name(parts[-1])
|
||||
if parts[-1].startswith('0x'):
|
||||
try:
|
||||
key = int(parts[-1], 16)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
key = defines.key_for_native_key_name(parts[-1])
|
||||
is_native = key is not None
|
||||
return mods, is_native, key
|
||||
|
||||
|
||||
@@ -95,6 +95,21 @@ for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
|
||||
prefix. Note that you should only use an XKB key name for keys that are not present
|
||||
in the list of GLFW keys.
|
||||
|
||||
Finally, you can use raw system key codes to map keys. To see the system key code
|
||||
for a key, start kitty with the :option:`kitty --debug-keyboard` option. Then kitty will
|
||||
output some debug text for every key event. In that text look for ``native_code``
|
||||
the value of that becomes the key name in the shortcut. For example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
on_key_input: glfw key: 65 native_code: 0x61 action: PRESS mods: 0x0 text: 'a'
|
||||
|
||||
Here, the key name for the :kbd:`A` key is :kbd:`0x61` and you can use it with::
|
||||
|
||||
map ctrl+0x61 something
|
||||
|
||||
to map :kbd:`ctrl+a` to something.
|
||||
|
||||
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
||||
assigned in the default configuration.
|
||||
|
||||
@@ -290,9 +305,19 @@ o('cursor_stop_blinking_after', 15.0, option_type=positive_float)
|
||||
|
||||
g('scrollback') # {{{
|
||||
|
||||
o('scrollback_lines', 2000, option_type=positive_int, long_text=_('''
|
||||
|
||||
def scrollback_lines(x):
|
||||
x = int(x)
|
||||
if x < 0:
|
||||
x = 2 ** 32 - 1
|
||||
return x
|
||||
|
||||
|
||||
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
||||
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
||||
on demand.'''))
|
||||
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
||||
very large scrollback is not recommended a it can slow down resizing of the terminal
|
||||
and also use large amounts of RAM.'''))
|
||||
|
||||
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
||||
Program with which to view scrollback in a new window. The scrollback buffer is
|
||||
@@ -850,6 +875,8 @@ g('shortcuts.tab') # {{{
|
||||
if is_macos:
|
||||
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'))
|
||||
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
||||
if is_macos:
|
||||
k('previous_tab', 'ctrl+shift+tab', 'previous_tab', _('Previous tab'))
|
||||
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
||||
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
||||
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
|
||||
|
||||
@@ -9,7 +9,7 @@ from collections import namedtuple
|
||||
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 12, 0)
|
||||
version = (0, 12, 1)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// *Bell*: Beeps.
|
||||
#define BEL 0x07
|
||||
|
||||
// *Backspace*: Backspace one column, but not past the begining of the
|
||||
// *Backspace*: Backspace one column, but not past the beginning of the
|
||||
// line.
|
||||
#define BS 0x08
|
||||
|
||||
|
||||
@@ -171,6 +171,14 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
|
||||
else start++;
|
||||
if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); return NULL; }
|
||||
if (new_font == old_font) { CFRelease(new_font); continue; }
|
||||
CFStringRef name = CTFontCopyPostScriptName(new_font);
|
||||
CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0);
|
||||
CFRelease(name);
|
||||
if (cr == kCFCompareEqualTo) {
|
||||
CFRelease(new_font);
|
||||
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
|
||||
return NULL;
|
||||
}
|
||||
return new_font;
|
||||
}
|
||||
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
|
||||
|
||||
@@ -126,6 +126,7 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||
static char ctty[L_ctermid+1];
|
||||
int fd = open(ctermid(ctty), flags);
|
||||
if (fd == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
||||
if (!termios_p) return PyErr_NoMemory();
|
||||
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
|
||||
@@ -1077,10 +1077,10 @@ send_prerendered_sprites(FontGroup *fg) {
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); }
|
||||
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
|
||||
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
|
||||
if (y > 0) { fatal("too many prerendered sprites for your GPU"); }
|
||||
if (y > 0) { fatal("too many pre-rendered sprites for your GPU or the font size is too large"); }
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
||||
@@ -1280,6 +1280,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT;
|
||||
FontGroup *fg = font_groups;
|
||||
ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell);
|
||||
if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; }
|
||||
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
|
||||
return fg->fonts[ans].face;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import ctypes
|
||||
import sys
|
||||
from functools import partial
|
||||
from math import ceil, floor, pi, sin, sqrt
|
||||
from math import ceil, pi, cos, sqrt
|
||||
|
||||
from kitty.config import defaults
|
||||
from kitty.constants import is_macos
|
||||
@@ -116,7 +116,7 @@ def add_dline(buf, cell_width, position, thickness, cell_height):
|
||||
|
||||
def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||
xfactor = 2.0 * pi / cell_width
|
||||
yfactor = thickness
|
||||
yfactor = max(thickness, 2)
|
||||
|
||||
def clamp_y(y):
|
||||
return max(0, min(int(y), cell_height - 1))
|
||||
@@ -125,19 +125,18 @@ def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||
return max(0, min(int(x), cell_width - 1))
|
||||
|
||||
def add_intensity(x, y, distance):
|
||||
buf[cell_width * y + x] = min(
|
||||
255, buf[cell_width * y + x] + int(255 * (1 - distance))
|
||||
idx = cell_width * y + x
|
||||
buf[idx] = min(
|
||||
255, buf[idx] + int(255 * (1 - distance))
|
||||
)
|
||||
|
||||
for x_exact in range(cell_width):
|
||||
y_exact = yfactor * sin(x_exact * xfactor) + position
|
||||
y_below = clamp_y(floor(y_exact))
|
||||
y_above = clamp_y(ceil(y_exact))
|
||||
y_exact = yfactor * cos(x_exact * xfactor) + position
|
||||
y = clamp_y(ceil(y_exact))
|
||||
x_before, x_after = map(clamp_x, (x_exact - 1, x_exact + 1))
|
||||
for x in {x_before, x_exact, x_after}:
|
||||
for y in {y_below, y_above}:
|
||||
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
||||
add_intensity(x, y, dist)
|
||||
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
||||
add_intensity(x, y, dist)
|
||||
|
||||
|
||||
def render_special(
|
||||
|
||||
6
kitty/glfw-wrapper.h
generated
6
kitty/glfw-wrapper.h
generated
@@ -734,6 +734,10 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||
*/
|
||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||
/*! @brief Window content area scaling window
|
||||
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||
*/
|
||||
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||
|
||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||
@@ -741,6 +745,8 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
|
||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||
|
||||
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_NO_API 0
|
||||
|
||||
@@ -278,12 +278,11 @@ set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static GLFWwindow *current_os_window_ctx = NULL;
|
||||
|
||||
void
|
||||
make_os_window_context_current(OSWindow *w) {
|
||||
if (w->handle != current_os_window_ctx) {
|
||||
current_os_window_ctx = w->handle;
|
||||
GLFWwindow *current_context = glfwGetCurrentContext();
|
||||
if (w->handle != current_context) {
|
||||
glfwMakeContextCurrent(w->handle);
|
||||
}
|
||||
}
|
||||
@@ -409,6 +408,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#ifndef __APPLE__
|
||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
|
||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
|
||||
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
|
||||
if (OPT(x11_hide_window_decorations)) {
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||
}
|
||||
@@ -488,7 +488,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
w->logical_dpi_x = dpi_x; w->logical_dpi_y = dpi_y;
|
||||
w->fonts_data = fonts_data;
|
||||
current_os_window_ctx = glfw_window;
|
||||
w->shown_once = true;
|
||||
push_focus_history(w);
|
||||
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
|
||||
@@ -535,7 +534,6 @@ destroy_os_window(OSWindow *w) {
|
||||
show_mouse_cursor(w->handle);
|
||||
glfwSetCursor(w->handle, NULL);
|
||||
glfwDestroyWindow(w->handle);
|
||||
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
|
||||
}
|
||||
w->handle = NULL;
|
||||
#ifdef __APPLE__
|
||||
|
||||
@@ -61,7 +61,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
|
||||
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
||||
|
||||
if (xnum * ynum == 0) {
|
||||
if (xnum == 0 || ynum == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
|
||||
return NULL;
|
||||
}
|
||||
@@ -74,9 +74,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
add_segment(self);
|
||||
self->line = alloc_line();
|
||||
self->line->xnum = xnum;
|
||||
for(index_type y = 0; y < self->ynum; y++) {
|
||||
clear_chars_in_line(cpu_lineptr(self, y), gpu_lineptr(self, y), self->xnum, BLANK_CHAR);
|
||||
}
|
||||
}
|
||||
|
||||
return (PyObject*)self;
|
||||
@@ -164,7 +161,7 @@ line(HistoryBuf *self, PyObject *val) {
|
||||
|
||||
static PyObject*
|
||||
__str__(HistoryBuf *self) {
|
||||
PyObject *lines = PyTuple_New(self->ynum);
|
||||
PyObject *lines = PyTuple_New(self->count);
|
||||
if (lines == NULL) return PyErr_NoMemory();
|
||||
for (index_type i = 0; i < self->count; i++) {
|
||||
init_line(self, index_of(self, i), self->line);
|
||||
@@ -225,7 +222,7 @@ static PyObject*
|
||||
dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||
PyObject *ans = PyList_New(0);
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
for (index_type i = 0; i < self->count; i++) {
|
||||
if (*attrptr(self, i) & TEXT_DIRTY_MASK) {
|
||||
PyList_Append(ans, PyLong_FromUnsignedLong(i));
|
||||
}
|
||||
|
||||
18
kitty/keys.c
18
kitty/keys.c
@@ -103,13 +103,9 @@ check_if_special(int key, int mods, int scancode) {
|
||||
qkey = SPECIAL_INDEX(qkey);
|
||||
special = needs_special_handling[qkey];
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
(void)scancode;
|
||||
#else
|
||||
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
||||
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
||||
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
||||
}
|
||||
return special;
|
||||
}
|
||||
|
||||
@@ -127,7 +123,7 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
|
||||
void
|
||||
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
|
||||
Window *w = active_window();
|
||||
debug("on_key_input: glfw key: %d native_code: %d action: %s mods: 0x%x text: '%s' state: %d ",
|
||||
debug("on_key_input: glfw key: %d native_code: 0x%x action: %s mods: 0x%x text: '%s' state: %d ",
|
||||
key, scancode,
|
||||
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
||||
mods, text, state);
|
||||
@@ -222,15 +218,13 @@ PYWRAP1(key_for_native_key_name) {
|
||||
const char *name;
|
||||
int case_sensitive = 0;
|
||||
PA("s|p", &name, case_sensitive);
|
||||
#ifdef __APPLE__
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
#ifndef __APPLE__
|
||||
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
|
||||
int scancode = glfwGetXKBScancode(name, case_sensitive);
|
||||
if (scancode) return Py_BuildValue("i", scancode);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
#endif
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
|
||||
@@ -8,7 +8,6 @@ from . import fast_data_types as defines
|
||||
from .key_encoding import KEY_MAP
|
||||
from .terminfo import key_as_bytes
|
||||
from .utils import base64_encode
|
||||
from .constants import is_macos
|
||||
|
||||
|
||||
def modify_key_bytes(keybytes, amt):
|
||||
@@ -276,7 +275,7 @@ def interpret_key_event(key, scancode, mods, window, action):
|
||||
def get_shortcut(keymap, mods, key, scancode):
|
||||
mods &= 0b1111
|
||||
ans = keymap.get((mods, False, key))
|
||||
if ans is None and not is_macos:
|
||||
if ans is None:
|
||||
ans = keymap.get((mods, True, scancode))
|
||||
return ans
|
||||
|
||||
|
||||
@@ -191,12 +191,12 @@ def setup_profiling(args):
|
||||
print('To view the graphical call data, use: kcachegrind', cg)
|
||||
|
||||
|
||||
def macos_cmdline():
|
||||
def macos_cmdline(argv_args):
|
||||
try:
|
||||
with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f:
|
||||
raw = f.read()
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
return argv_args
|
||||
import shlex
|
||||
raw = raw.strip()
|
||||
ans = shlex.split(raw)
|
||||
@@ -242,7 +242,7 @@ def _main():
|
||||
args = sys.argv[1:]
|
||||
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
|
||||
os.chdir(os.path.expanduser('~'))
|
||||
args = macos_cmdline()
|
||||
args = macos_cmdline(args)
|
||||
try:
|
||||
cwd_ok = os.path.isdir(os.getcwd())
|
||||
except Exception:
|
||||
|
||||
@@ -708,7 +708,15 @@ screen_is_cursor_visible(Screen *self) {
|
||||
|
||||
void
|
||||
screen_backspace(Screen *self) {
|
||||
screen_cursor_back(self, 1, -1);
|
||||
unsigned int amount = 1;
|
||||
if (self->cursor->x < self->columns && self->cursor->x > 1) {
|
||||
// check if previous character is a wide character
|
||||
linebuf_init_line(self->linebuf, self->cursor->y);
|
||||
unsigned int xpos = self->cursor->x - 2;
|
||||
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
|
||||
if ((gpu_cell->attrs & WIDTH_MASK) == 2) amount = 2;
|
||||
}
|
||||
screen_cursor_back(self, amount, -1);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -90,6 +90,7 @@ add_os_window() {
|
||||
static inline id_type
|
||||
add_tab(id_type os_window_id) {
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
make_os_window_context_current(os_window);
|
||||
ensure_space_for(os_window, tabs, Tab, os_window->num_tabs + 1, capacity, 1, true);
|
||||
memset(os_window->tabs + os_window->num_tabs, 0, sizeof(Tab));
|
||||
os_window->tabs[os_window->num_tabs].id = ++global_state.tab_id_counter;
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import (
|
||||
DECAWM, sprite_map_set_layout, sprite_map_set_limits, test_render_line,
|
||||
test_sprite_position_for, wcwidth
|
||||
DECAWM, get_fallback_font, sprite_map_set_layout, sprite_map_set_limits,
|
||||
test_render_line, test_sprite_position_for, wcwidth
|
||||
)
|
||||
from kitty.fonts.box_drawing import box_chars
|
||||
from kitty.fonts.render import render_string, setup_for_testing, shape_string
|
||||
@@ -106,3 +109,16 @@ class Rendering(BaseTest):
|
||||
s.draw('\ufe0f')
|
||||
self.ae((s.cursor.x, s.cursor.y), (2, 4))
|
||||
self.ae(str(s.line(s.cursor.y)), '\u2716\ufe0f')
|
||||
|
||||
@unittest.skipUnless(is_macos, 'Only macOS has a Last Resort font')
|
||||
def test_fallback_font_not_last_resort(self):
|
||||
# Ensure that the LastResort font is not reported as a fallback font on
|
||||
# macOS. See https://github.com/kovidgoyal/kitty/issues/799
|
||||
from io import StringIO
|
||||
orig, buf = sys.stderr, StringIO()
|
||||
sys.stderr = buf
|
||||
try:
|
||||
self.assertRaises(ValueError, get_fallback_font, '\U0010FFFF', False, False)
|
||||
finally:
|
||||
sys.stderr = orig
|
||||
self.assertIn('LastResort', buf.getvalue())
|
||||
|
||||
@@ -222,6 +222,15 @@ class TestScreen(BaseTest):
|
||||
for i in range(1, 5):
|
||||
self.ae(str(s.line(i)), '12345')
|
||||
|
||||
def test_backspace_wide_characters(self):
|
||||
s = self.create_screen()
|
||||
s.draw('⛅')
|
||||
self.ae(s.cursor.x, 2)
|
||||
s.backspace()
|
||||
s.draw(' ')
|
||||
s.backspace()
|
||||
self.ae(s.cursor.x, 0)
|
||||
|
||||
def test_resize(self):
|
||||
s = self.create_screen(scrollback=6)
|
||||
s.draw(''.join([str(i) * s.columns for i in range(s.lines)]))
|
||||
|
||||
@@ -331,8 +331,14 @@ def require_git_master(branch='master'):
|
||||
raise SystemExit('You must be in the {} git branch'.format(branch))
|
||||
|
||||
|
||||
def require_penv():
|
||||
if 'PENV' not in os.environ:
|
||||
raise SystemExit('The PENV env var is not present, required for uploading releases')
|
||||
|
||||
|
||||
def main():
|
||||
require_git_master()
|
||||
require_penv()
|
||||
parser = argparse.ArgumentParser(description='Publish kitty')
|
||||
parser.add_argument(
|
||||
'--only',
|
||||
|
||||
Reference in New Issue
Block a user