macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
Fixes #359. Also, update GLFW from upstream for retina cursor setting support.
This commit is contained in:
parent
1faddeb402
commit
4313531432
@ -15,6 +15,9 @@ Changelog
|
|||||||
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
|
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
|
||||||
top-level windows are resizable using the mouse or not (:iss:`698`)
|
top-level windows are resizable using the mouse or not (:iss:`698`)
|
||||||
|
|
||||||
|
- macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
|
||||||
|
(:iss:`359`)
|
||||||
|
|
||||||
- Fix triple-click to select line not working when the entire line is filled
|
- Fix triple-click to select line not working when the entire line is filled
|
||||||
(:iss:`703`)
|
(:iss:`703`)
|
||||||
|
|
||||||
|
|||||||
@ -1886,44 +1886,44 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
NSImage* native;
|
NSImage* native;
|
||||||
NSBitmapImageRep* rep;
|
NSBitmapImageRep* rep;
|
||||||
|
|
||||||
if (!initializeAppKit())
|
if (!initializeAppKit())
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
|
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
||||||
rep = [[NSBitmapImageRep alloc]
|
if (native == nil)
|
||||||
initWithBitmapDataPlanes:NULL
|
|
||||||
pixelsWide:image->width
|
|
||||||
pixelsHigh:image->height
|
|
||||||
bitsPerSample:8
|
|
||||||
samplesPerPixel:4
|
|
||||||
hasAlpha:YES
|
|
||||||
isPlanar:NO
|
|
||||||
colorSpaceName:NSCalibratedRGBColorSpace
|
|
||||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
|
||||||
bytesPerRow:image->width * 4
|
|
||||||
bitsPerPixel:32];
|
|
||||||
|
|
||||||
if (rep == nil)
|
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
|
|
||||||
memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
|
for (int i = 0; i < count; i++) {
|
||||||
|
const GLFWimage *src = image + i;
|
||||||
|
rep = [[NSBitmapImageRep alloc]
|
||||||
|
initWithBitmapDataPlanes:NULL
|
||||||
|
pixelsWide:src->width
|
||||||
|
pixelsHigh:src->height
|
||||||
|
bitsPerSample:8
|
||||||
|
samplesPerPixel:4
|
||||||
|
hasAlpha:YES
|
||||||
|
isPlanar:NO
|
||||||
|
colorSpaceName:NSCalibratedRGBColorSpace
|
||||||
|
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
||||||
|
bytesPerRow:src->width * 4
|
||||||
|
bitsPerPixel:32];
|
||||||
|
if (rep == nil)
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
||||||
[native addRepresentation:rep];
|
[native addRepresentation:rep];
|
||||||
|
[rep release];
|
||||||
|
}
|
||||||
|
|
||||||
cursor->ns.object = [[NSCursor alloc] initWithImage:native
|
cursor->ns.object = [[NSCursor alloc] initWithImage:native
|
||||||
hotSpot:NSMakePoint(xhot, yhot)];
|
hotSpot:NSMakePoint(xhot, yhot)];
|
||||||
|
|
||||||
[native release];
|
[native release];
|
||||||
[rep release];
|
|
||||||
|
|
||||||
if (cursor->ns.object == nil)
|
if (cursor->ns.object == nil)
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
|
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
glfw/glfw3.h
vendored
5
glfw/glfw3.h
vendored
@ -4123,6 +4123,7 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
|
|||||||
* @param[in] image The desired cursor image.
|
* @param[in] image The desired cursor image.
|
||||||
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
|
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
|
||||||
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
|
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
|
||||||
|
* @param[in] count The number of images. Used on Cocoa for retina cursors. The first image should be the 1:1 scale image.
|
||||||
* @return The handle of the created cursor, or `NULL` if an
|
* @return The handle of the created cursor, or `NULL` if an
|
||||||
* [error](@ref error_handling) occurred.
|
* [error](@ref error_handling) occurred.
|
||||||
*
|
*
|
||||||
@ -4138,11 +4139,11 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
|
|||||||
* @sa @ref glfwDestroyCursor
|
* @sa @ref glfwDestroyCursor
|
||||||
* @sa @ref glfwCreateStandardCursor
|
* @sa @ref glfwCreateStandardCursor
|
||||||
*
|
*
|
||||||
* @since Added in version 3.1.
|
* @since Added in version 3.1. Changed in 4.0 to add the count parameter.
|
||||||
*
|
*
|
||||||
* @ingroup input
|
* @ingroup input
|
||||||
*/
|
*/
|
||||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
|
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count);
|
||||||
|
|
||||||
/*! @brief Creates a cursor with a standard shape.
|
/*! @brief Creates a cursor with a standard shape.
|
||||||
*
|
*
|
||||||
|
|||||||
5
glfw/input.c
vendored
5
glfw/input.c
vendored
@ -799,11 +799,12 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
|
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
_GLFWcursor* cursor;
|
_GLFWcursor* cursor;
|
||||||
|
|
||||||
assert(image != NULL);
|
assert(image != NULL);
|
||||||
|
assert(count > 0);
|
||||||
|
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
|
||||||
@ -811,7 +812,7 @@ GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
|
|||||||
cursor->next = _glfw.cursorListHead;
|
cursor->next = _glfw.cursorListHead;
|
||||||
_glfw.cursorListHead = cursor;
|
_glfw.cursorListHead = cursor;
|
||||||
|
|
||||||
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot))
|
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot, count))
|
||||||
{
|
{
|
||||||
glfwDestroyCursor((GLFWcursor*) cursor);
|
glfwDestroyCursor((GLFWcursor*) cursor);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
2
glfw/internal.h
vendored
2
glfw/internal.h
vendored
@ -600,7 +600,7 @@ void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
|
|||||||
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image, int xhot, int yhot);
|
const GLFWimage* image, int xhot, int yhot, int count);
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
|
||||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
|
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
|
||||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
||||||
|
|||||||
2
glfw/null_window.c
vendored
2
glfw/null_window.c
vendored
@ -267,7 +267,7 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
2
glfw/win32_window.c
vendored
2
glfw/win32_window.c
vendored
@ -1839,7 +1839,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
|
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
|
||||||
if (!cursor->win32.handle)
|
if (!cursor->win32.handle)
|
||||||
|
|||||||
2
glfw/wl_window.c
vendored
2
glfw/wl_window.c
vendored
@ -1257,7 +1257,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
cursor->wl.buffer = createShmBuffer(image);
|
cursor->wl.buffer = createShmBuffer(image);
|
||||||
cursor->wl.width = image->width;
|
cursor->wl.width = image->width;
|
||||||
|
|||||||
2
glfw/x11_window.c
vendored
2
glfw/x11_window.c
vendored
@ -2684,7 +2684,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
|
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
|
||||||
if (!cursor->x11.handle)
|
if (!cursor->x11.handle)
|
||||||
|
|||||||
@ -105,6 +105,7 @@ def wakeup():
|
|||||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
||||||
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
||||||
|
beam_cursor_data_file = os.path.join(base_dir, 'logo', 'beam-cursor.png')
|
||||||
try:
|
try:
|
||||||
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
|
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
41
kitty/glfw.c
41
kitty/glfw.c
@ -456,8 +456,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
|||||||
if (OPT(macos_hide_from_tasks)) cocoa_set_hide_from_tasks();
|
if (OPT(macos_hide_from_tasks)) cocoa_set_hide_from_tasks();
|
||||||
#endif
|
#endif
|
||||||
#define CC(dest, shape) {\
|
#define CC(dest, shape) {\
|
||||||
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
|
if (!dest##_cursor) { \
|
||||||
if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); }}
|
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
|
||||||
|
if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); } \
|
||||||
|
}}
|
||||||
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
|
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
|
||||||
#undef CC
|
#undef CC
|
||||||
is_first_window = false;
|
is_first_window = false;
|
||||||
@ -839,6 +841,39 @@ set_smallest_allowed_resize(PyObject *self UNUSED, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
|
||||||
|
int shape;
|
||||||
|
int x=0, y=0;
|
||||||
|
Py_ssize_t sz;
|
||||||
|
PyObject *images;
|
||||||
|
if (!PyArg_ParseTuple(args, "iO!|ii", &shape, &PyTuple_Type, &images, &x, &y)) return NULL;
|
||||||
|
static GLFWimage gimages[16] = {{0}};
|
||||||
|
size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages));
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL;
|
||||||
|
if (gimages[i].width * gimages[i].height * 4 != sz) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#define CASE(which, dest) {\
|
||||||
|
case which: \
|
||||||
|
standard_cursor = glfwCreateCursor(gimages, x, y, count); \
|
||||||
|
if (standard_cursor == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create custom cursor"); return NULL; } \
|
||||||
|
break; \
|
||||||
|
}
|
||||||
|
switch(shape) {
|
||||||
|
CASE(GLFW_IBEAM_CURSOR, standard_cursor);
|
||||||
|
CASE(GLFW_HAND_CURSOR, click_cursor);
|
||||||
|
CASE(GLFW_ARROW_CURSOR, arrow_cursor);
|
||||||
|
default:
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Unknown cursor shape");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
void
|
void
|
||||||
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
|
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
|
||||||
@ -848,6 +883,7 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
|
|||||||
// Boilerplate {{{
|
// Boilerplate {{{
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
|
METHODB(set_custom_cursor, METH_VARARGS),
|
||||||
METHODB(set_smallest_allowed_resize, METH_VARARGS),
|
METHODB(set_smallest_allowed_resize, METH_VARARGS),
|
||||||
METHODB(create_os_window, METH_VARARGS),
|
METHODB(create_os_window, METH_VARARGS),
|
||||||
METHODB(set_default_window_icon, METH_VARARGS),
|
METHODB(set_default_window_icon, METH_VARARGS),
|
||||||
@ -891,6 +927,7 @@ init_glfw(PyObject *m) {
|
|||||||
ADDC(GLFW_PRESS);
|
ADDC(GLFW_PRESS);
|
||||||
ADDC(GLFW_REPEAT);
|
ADDC(GLFW_REPEAT);
|
||||||
ADDC(GLFW_TRUE); ADDC(GLFW_FALSE);
|
ADDC(GLFW_TRUE); ADDC(GLFW_FALSE);
|
||||||
|
ADDC(GLFW_IBEAM_CURSOR); ADDC(GLFW_HAND_CURSOR); ADDC(GLFW_ARROW_CURSOR);
|
||||||
|
|
||||||
// --- Keys --------------------------------------------------------------------
|
// --- Keys --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -12,12 +12,13 @@ from .boss import Boss
|
|||||||
from .cli import create_opts, parse_args
|
from .cli import create_opts, parse_args
|
||||||
from .config import cached_values_for, initial_window_size_func
|
from .config import cached_values_for, initial_window_size_func
|
||||||
from .constants import (
|
from .constants import (
|
||||||
appname, config_dir, glfw_path, is_macos, is_wayland, kitty_exe,
|
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
|
||||||
logo_data_file
|
is_wayland, kitty_exe, logo_data_file
|
||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
GLFW_MOD_SUPER, create_os_window, free_font_data, glfw_init,
|
GLFW_IBEAM_CURSOR, GLFW_MOD_SUPER, create_os_window, free_font_data,
|
||||||
glfw_terminate, set_default_window_icon, set_options
|
glfw_init, glfw_terminate, load_png_data, set_custom_cursor,
|
||||||
|
set_default_window_icon, set_options
|
||||||
)
|
)
|
||||||
from .fonts.box_drawing import set_scale
|
from .fonts.box_drawing import set_scale
|
||||||
from .fonts.render import set_font_family
|
from .fonts.render import set_font_family
|
||||||
@ -28,6 +29,21 @@ from .utils import (
|
|||||||
from .window import load_shader_programs
|
from .window import load_shader_programs
|
||||||
|
|
||||||
|
|
||||||
|
def set_custom_ibeam_cursor():
|
||||||
|
with open(beam_cursor_data_file, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
rgba_data, width, height = load_png_data(data)
|
||||||
|
c2x = os.path.splitext(beam_cursor_data_file)
|
||||||
|
with open(c2x[0] + '@2x' + c2x[1], 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
rgba_data2, width2, height2 = load_png_data(data)
|
||||||
|
images = (rgba_data, width, height), (rgba_data2, width2, height2)
|
||||||
|
try:
|
||||||
|
set_custom_cursor(GLFW_IBEAM_CURSOR, images, 4, 8)
|
||||||
|
except Exception as e:
|
||||||
|
log_error('Failed to set custom beam cursor with error: {}'.format(e))
|
||||||
|
|
||||||
|
|
||||||
def talk_to_instance(args):
|
def talk_to_instance(args):
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
@ -100,6 +116,8 @@ def get_new_os_window_trigger(opts):
|
|||||||
|
|
||||||
def _run_app(opts, args):
|
def _run_app(opts, args):
|
||||||
new_os_window_trigger = get_new_os_window_trigger(opts)
|
new_os_window_trigger = get_new_os_window_trigger(opts)
|
||||||
|
if is_macos:
|
||||||
|
set_custom_ibeam_cursor()
|
||||||
with cached_values_for(run_app.cached_values_name) as cached_values:
|
with cached_values_for(run_app.cached_values_name) as cached_values:
|
||||||
with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback:
|
with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback:
|
||||||
window_id = create_os_window(
|
window_id = create_os_window(
|
||||||
|
|||||||
BIN
logo/beam-cursor.png
Normal file
BIN
logo/beam-cursor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 B |
BIN
logo/beam-cursor@2x.png
Normal file
BIN
logo/beam-cursor@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
2
setup.py
2
setup.py
@ -584,6 +584,8 @@ def package(args, for_bundle=False, sh_launcher=False):
|
|||||||
subprocess.check_call(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'])
|
subprocess.check_call(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'])
|
||||||
shutil.copy2('__main__.py', libdir)
|
shutil.copy2('__main__.py', libdir)
|
||||||
shutil.copy2('logo/kitty.rgba', os.path.join(libdir, 'logo'))
|
shutil.copy2('logo/kitty.rgba', os.path.join(libdir, 'logo'))
|
||||||
|
shutil.copy2('logo/beam-cursor.png', os.path.join(libdir, 'logo'))
|
||||||
|
shutil.copy2('logo/beam-cursor@2x.png', os.path.join(libdir, 'logo'))
|
||||||
|
|
||||||
def src_ignore(parent, entries):
|
def src_ignore(parent, entries):
|
||||||
return [
|
return [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user