Compare commits

...

70 Commits

Author SHA1 Message Date
Kovid Goyal
7438e0e997 version 0.12.1 2018-09-08 05:22:02 +05:30
Kovid Goyal
cca672e536 typo 2018-09-07 16:24:29 +05:30
Kovid Goyal
0fa12a687d Linux: Turn off IME unless the GLFW_IM_MODULE=ibus env var is set 2018-09-07 16:07:01 +05:30
Kovid Goyal
d2b900c77f Changelog cleanups 2018-09-07 15:38:04 +05:30
Kovid Goyal
21242d9144 ... 2018-09-07 15:30:36 +05:30
Kovid Goyal
03fd138b63 Linux: Fix crash with some nvidia drivers when creating tabs in the first top level-window after creating a second top-level window.
Fixes #873
The wrong window's OpenGL context was current when creating the tabs.
2018-09-07 15:23:10 +05:30
Kovid Goyal
1eeaef6dbb Get rid of extra global variable 2018-09-07 14:53:29 +05:30
Kovid Goyal
b9a93b68c4 ... 2018-09-07 11:37:33 +05:30
Kovid Goyal
e3728942dc Fix a small memory leak when creating a new child process 2018-09-07 11:35:00 +05:30
Kovid Goyal
7f0674ac27 Fix backspacing of wide characters in wide-character unaware programs not working
Fixes #875
2018-09-07 10:08:25 +05:30
Kovid Goyal
60fcf7ab3d Merge branch 'prev_tab_default_macos' of https://github.com/Luflosi/kitty 2018-09-07 06:53:24 +05:30
Luflosi
ee82a6ff18 Make ctrl+shift+tab previous_tab by default on macOS 2018-09-06 22:43:36 +02:00
Kovid Goyal
bac5e402b7 Fix #890 2018-09-06 21:25:29 +05:30
Kovid Goyal
732108109e Nicer error message when sphinx-build is not in PATH 2018-09-06 21:23:03 +05:30
Kovid Goyal
ab3b86d65b Merge branch 'void' of https://github.com/SolitudeSF/kitty 2018-09-06 21:11:03 +05:30
Kovid Goyal
d4d63506e1 Fix pre-allocation of the full history buffer
Also map negative scrollback numbers to (effectively) infinite
scrollback.
2018-09-06 21:06:31 +05:30
SolitudeSF
3ad69f8ba3 Add link for the Void Linux package 2018-09-06 18:36:29 +03:00
Kovid Goyal
9fd50a6ac0 ... 2018-09-06 20:26:51 +05:30
Kovid Goyal
9b5c8a18d4 Merge branch 'master' of https://github.com/eigengrau/kitty 2018-09-06 18:29:06 +05:30
Kovid Goyal
9f3112ca56 Merge branch 'spelling' of https://github.com/EdwardBetts/kitty 2018-09-06 18:27:02 +05:30
Kovid Goyal
aa9de47da3 Support compose sequences with no symbol
Compose sequences can be defined without an associated symbol. To test for
completeness, we now also rely on XKB’s reported compose state.

Fixes #880.
2018-09-06 14:53:16 +02:00
Edward Betts
5eaeda4402 Correct spelling mistakes. 2018-09-06 12:07:09 +01:00
Kovid Goyal
ecb03d7dba ... 2018-09-06 11:58:43 +05:30
Kovid Goyal
88612d3691 Better error message when font size is too large 2018-09-06 11:52:48 +05:30
Kovid Goyal
2c0e70a423 Fix #868 2018-09-06 11:49:30 +05:30
Kovid Goyal
d6b70f10d3 Cross-linking options in the docs 2018-09-06 11:02:53 +05:30
Kovid Goyal
40b01fb702 Add a note about using an alias for icat 2018-09-06 10:27:13 +05:30
Kovid Goyal
89222d93f4 Change legacy kitty icat reference 2018-09-06 10:20:23 +05:30
Kovid Goyal
b3db3e5732 Fix completion of paths with spaces 2018-09-06 09:47:36 +05:30
Kovid Goyal
b45161ecc3 zsh completion: Eliminate common prefix when completing file names 2018-09-06 09:46:04 +05:30
Kovid Goyal
d4fc0af7de Cleanup up path completions 2018-09-06 09:34:25 +05:30
Kovid Goyal
5ef8cce1d7 Fix filenames not being completed for the diff kitten 2018-09-06 07:50:07 +05:30
Kovid Goyal
c98a147f94 Add changelog entry 2018-09-06 07:05:41 +05:30
Kovid Goyal
e9975cb7eb Merge branch 'fish-completion' of https://github.com/scorphus/kitty 2018-09-06 07:04:54 +05:30
Kovid Goyal
33f0ac83c1 Wayland: Allow using the --class to set the app id
Fixes #862
2018-09-06 06:54:38 +05:30
Kovid Goyal
3bd45966c6 diff kitten: Fix spurious traceback on exit when pygments is missing
Fixes #863
2018-09-06 06:05:08 +05:30
Pablo S. Blum de Aguiar
1b7244f3c8 Completion for fish 2018-09-05 22:33:39 +02:00
Kovid Goyal
1949f3c3d5 Wayland fix self copy detection across multiple processes 2018-09-05 21:45:32 +05:30
Kovid Goyal
6a51ce5dc4 Wayland: Work on supporting drop of file paths 2018-09-05 21:41:47 +05:30
Kovid Goyal
c0c8e1ce5e Fill up the rest of the v1 data_source listener struct 2018-09-05 20:04:38 +05:30
Kovid Goyal
6a3d6191bd Implement the rest of the the data_offer listener callbacks 2018-09-05 19:58:55 +05:30
Kovid Goyal
b2aa07d29f Wayland: Fix missing data source canceled listener 2018-09-05 19:37:55 +05:30
Kovid Goyal
c84fb68bf8 Wayland: fix copy/pasting into self 2018-09-05 19:23:42 +05:30
Kovid Goyal
128b9e1cd0 Wayland: Implement support for clipboard copy/paste
Fix #855
2018-09-05 17:54:26 +05:30
Kovid Goyal
bbe8bcb28d Fix a regression that broke layout resizing
Fixes #860
2018-09-05 10:51:08 +05:30
Kovid Goyal
6dc9c22543 Remove unnecessary use of globals 2018-09-05 10:48:23 +05:30
Kovid Goyal
59f5781f19 Merge branch 'patch-1' of https://github.com/skorokithakis/kitty 2018-09-05 10:41:42 +05:30
Stavros Korokithakis
d61fb9cc42 Add sentence about layouts using windows 2018-09-05 08:08:31 +03:00
Kovid Goyal
ace052ee70 Move data device initialization into its own function 2018-09-05 07:51:15 +05:30
Kovid Goyal
1451a8ccc0 Add a new --start-in-fullscreen command line flag
Fixes #856
2018-09-05 07:36:13 +05:30
Kovid Goyal
547959c1ef Linux: Fix numpad arrow keys not working when num lock is off
Fixes #857
2018-09-05 07:31:55 +05:30
Kovid Goyal
b1a5e98630 Update link to Fedora package to a newer one 2018-09-04 22:28:52 +05:30
Kovid Goyal
f3a1ee7b9f Wayland backend: Add support for copy to clipboard 2018-09-04 22:22:43 +05:30
Kovid Goyal
b0a39b127d Remove unused import 2018-09-04 17:54:33 +05:30
Kovid Goyal
e36f1157cc Improve anti-aliasing at small sizes for wavy underlines 2018-09-04 15:48:01 +05:30
Kovid Goyal
053b9a786c Use a cosine instead of sine wave for wavy underlines
Matches other terminals
2018-09-04 15:35:48 +05:30
Kovid Goyal
2561d937da Forgot to remove mac os filter for native shortcut dispatching in one place 2018-09-04 15:00:09 +05:30
Kovid Goyal
55f457caf4 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
Fixes #799
2018-09-04 14:21:36 +05:30
Kovid Goyal
f5cc2fd8ad Update GLFW from upstream 2018-09-04 09:49:18 +05:30
Kovid Goyal
e630b76fac Don't patch glfw when importing since the patch has moved upstream 2018-09-04 09:46:09 +05:30
Kovid Goyal
0b6948cb4f macOS: Fix using open --args not working
Fixes #854
2018-09-03 20:58:31 +05:30
Kovid Goyal
b0a2dea747 Improve rendering of wavy underline at small font sizes
See #853
2018-09-03 10:15:53 +05:30
Kovid Goyal
f8395e36a1 Draw underlines under the text instead of over it 2018-09-03 09:48:53 +05:30
Kovid Goyal
6ec58f58ce Allow mapping shortcuts using the raw key code from the OS
Fixes #848
2018-09-02 18:46:10 +05:30
Kovid Goyal
96cbfe946c Fix legacy form of invoking icat broken in 0.12.0
Fixes #850
2018-09-02 15:59:18 +05:30
Kovid Goyal
ac686a47c1 Fail early when opening tty fails 2018-09-02 14:29:10 +05:30
Kovid Goyal
e05aaf77ce spelling typo 2018-09-01 10:41:35 +05:30
Kovid Goyal
6b371eb8a8 Merge branch 'master' of github.com:kovidgoyal/kitty 2018-09-01 10:32:44 +05:30
Kovid Goyal
f6660a0d55 Require PENV when publishing 2018-09-01 10:32:08 +05:30
Kovid Goyal
3b9f213ea2 ... 2018-09-01 10:00:35 +05:30
53 changed files with 1161 additions and 311 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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
------------------------

View File

@@ -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]
------------------------------

View File

@@ -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.

View File

@@ -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
------------------

View File

@@ -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

View File

@@ -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
View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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();

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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, &copy_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
View File

@@ -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
View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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:

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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

View File

@@ -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'))

View File

@@ -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

View File

@@ -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

View File

@@ -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");

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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
View File

@@ -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

View File

@@ -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__

View File

@@ -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));
}

View File

@@ -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[] = {

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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;

View File

@@ -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())

View File

@@ -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)]))

View File

@@ -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',