diff --git a/builder/bxpkg/__init__.py b/builder/bxpkg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/builder/bxpkg/configure.py b/builder/bxpkg/configure.py new file mode 100644 index 0000000..65fa3fd --- /dev/null +++ b/builder/bxpkg/configure.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from typing import Any, Dict, Tuple, TypeVar + +from clickgen.util import PNGProvider +from bxpkg.constants import WIN_CURSORS_CFG, WIN_DELAY, X_CURSORS_CFG, X_DELAY + +X = TypeVar("X") + + +def to_tuple(x: X) -> Tuple[X, X]: + return (x, x) + + +def get_config(bitmaps_dir, **kwargs) -> Dict[str, Any]: + """Return configuration of `BreezeX` pointers. + + + :param bitmaps_dir: Path to .png file's directory. + :type bitmaps_dir: Union[str, Path] + + :param \**kwargs: + See below + + :Keyword Arguments: + * *x_sizes* (``List[int]``) -- + List of pixel-sizes for xcursors. + * *win_canvas_size* (``int``) -- + Windows cursor's canvas pixel-size. + * *win_size* (``int``) -- + Pixel-size for Windows cursor. + + Example: + + ```python + get_config("./bitmaps", x_sizes=[24, 32], win_canvas_size=32, win_size=24) + ``` + """ + + w_size = to_tuple(kwargs.pop("win_size")) + w_canvas_size = to_tuple(kwargs.pop("win_canvas_size")) + raw_x_sizes = kwargs.pop("x_sizes") + + x_sizes = [] + for size in raw_x_sizes: + x_sizes.append(to_tuple(size)) + + png_provider = PNGProvider(bitmaps_dir) + config: Dict[str, Any] = {} + + for key, item in X_CURSORS_CFG.items(): + x_hot: int = int(item.get("xhot", 0)) + y_hot: int = int(item.get("yhot", 0)) + hotspot: Tuple[int, int] = (x_hot, y_hot) + + delay: int = int(item.get("delay", X_DELAY)) + png = png_provider.get(key) + if not png: + raise FileNotFoundError(f"{key} not found") + + data = { + "png": png, + "x_sizes": x_sizes, + "hotspot": hotspot, + "delay": delay, + } + + win_data = WIN_CURSORS_CFG.get(key) + + if win_data: + win_key: str = str(win_data.get("to")) + + position: str = str(win_data.get("position", "center")) + win_delay: int = int(win_data.get("delay", WIN_DELAY)) + + canvas_size = win_data.get("canvas_size", w_canvas_size) + win_size = win_data.get("size", w_size) + + # Because provided cursor size is bigger than cursor's canvas. + # Also, "position" settings will not effect on cursor because the + # cursor's canvas and cursor sizes are equals. + if (win_size[0] > canvas_size[0]) | (win_size[1] > canvas_size[1]): + canvas_size = win_size + + config[key] = { + **data, + "win_key": win_key, + "position": position, + "canvas_size": canvas_size, + "win_size": win_size, + "win_delay": win_delay, + } + else: + config[key] = data + + return config diff --git a/builder/bxpkg/constants.py b/builder/bxpkg/constants.py new file mode 100644 index 0000000..a402071 --- /dev/null +++ b/builder/bxpkg/constants.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from typing import Dict + +# Info +AUTHOR = "Kaiz Khatri" +URL = "https://github.com/ful1e5/BreezeX_Cursor" + +# XCursor +X_DELAY: int = 10 + + +# Windows Cursor +WIN_DELAY = 1 + +X_CURSORS_CFG: Dict[str, Dict[str, int]] = { + ########## + # Static # + ########## + "all-scroll.png": {"xhot": 100, "yhot": 100}, + "bottom_left_corner.png": {"xhot": 100, "yhot": 100}, + "bottom_right_corner.png": {"xhot": 100, "yhot": 100}, + "bottom_tee.png": {"xhot": 100, "yhot": 100}, + "context-menu.png": {"xhot": 100, "yhot": 100}, + "copy.png": {"xhot": 100, "yhot": 100}, + "cross.png": {"xhot": 100, "yhot": 100}, + "dnd_no_drop.png": {"xhot": 100, "yhot": 100}, + "dotbox.png": {"xhot": 100, "yhot": 100}, + "hand1.png": {"xhot": 100, "yhot": 100}, + "hand2.png": {"xhot": 100, "yhot": 100}, + "left_ptr.png": {"xhot": 100, "yhot": 100}, + "left_tee.png": {"xhot": 100, "yhot": 100}, + "link.png": {"xhot": 100, "yhot": 100}, + "ll_angle.png": {"xhot": 100, "yhot": 100}, + "lr_angle.png": {"xhot": 100, "yhot": 100}, + "move.png": {"xhot": 100, "yhot": 100}, + "pencil.png": {"xhot": 100, "yhot": 100}, + "plus.png": {"xhot": 100, "yhot": 100}, + "question_arrow.png": {"xhot": 100, "yhot": 100}, + "right_ptr.png": {"xhot": 100, "yhot": 100}, + "right_tee.png": {"xhot": 100, "yhot": 100}, + "sb_down_arrow.png": {"xhot": 100, "yhot": 100}, + "sb_h_double_arrow.png": {"xhot": 100, "yhot": 100}, + "sb_left_arrow.png": {"xhot": 100, "yhot": 100}, + "sb_right_arrow.png": {"xhot": 100, "yhot": 100}, + "sb_up_arrow.png": {"xhot": 100, "yhot": 100}, + "sb_v_double_arrow.png": {"xhot": 100, "yhot": 100}, + "top_tee.png": {"xhot": 100, "yhot": 100}, + "ul_angle.png": {"xhot": 100, "yhot": 100}, + "ur_angle.png": {"xhot": 100, "yhot": 100}, + "vertical-text.png": {"xhot": 100, "yhot": 100}, + "wayland-cursor.png": {"xhot": 100, "yhot": 100}, + "X_cursor.png": {"xhot": 100, "yhot": 100}, + "xterm.png": {"xhot": 100, "yhot": 89}, + "zoom-in.png": {"xhot": 100, "yhot": 100}, + "zoom-out.png": {"xhot": 100, "yhot": 100}, + ############ + # Animated # + ############ + # Note: Animated cursors don't need an extension and frame numbers. + "left_ptr_watch": {"xhot": 100, "yhot": 100}, + "wait": {"xhot": 100, "yhot": 100}, +} + +WIN_CURSORS_CFG: Dict[str, Dict[str, str]] = { + ########## + # Static # + ########## + "right_ptr.png": {"to": "Alternate", "position": "top_right"}, + "cross.png": {"to": "Cross"}, + "left_ptr.png": {"to": "Default", "position": "top_left"}, + "bottom_right_corner.png": {"to": "Diagonal_1"}, + "bottom_left_corner.png": {"to": "Diagonal_2"}, + "pencil.png": {"to": "Handwriting"}, + "question_arrow.png": {"to": "Help", "position.png": "top_left"}, + "sb_h_double_arrow.png": {"to": "Horizontal"}, + "xterm.png": {"to": "IBeam", "position": "top_left"}, + "hand2.png": {"to": "Link", "position": "top_left"}, + "hand1.png": {"to": "Move"}, + "dnd_no_drop.png": {"to": "Unavailiable", "position": "top_left"}, + "sb_v_double_arrow.png": {"to": "Vertical"}, + ############ + # Animated # + ############ + # Note: Animated cursors don't need frame numbers. + "left_ptr_watch": {"to": "Work", "position": "top_left"}, + "wait": {"to": "Busy"}, +} diff --git a/builder/bxpkg/generator.py b/builder/bxpkg/generator.py new file mode 100644 index 0000000..4789a29 --- /dev/null +++ b/builder/bxpkg/generator.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pathlib import Path +from typing import Any, Dict, NamedTuple + +from clickgen.builders import WindowsCursor, XCursor +from clickgen.core import CursorAlias +from clickgen.packagers import WindowsPackager, XPackager +from bxpkg.constants import AUTHOR, URL +from bxpkg.symlinks import add_missing_xcursor + + +class Info(NamedTuple): + name: str + comment: str + + +def xbuild(config: Dict[str, Dict[str, Any]], x_out_dir: Path, info: Info) -> None: + """Build `BreezeX` cursor theme for only `X11`(UNIX) platform. + + :param config: `BreezeX` configuration. + :type config: Dict + + :param x_out_dir: Path to the output directory,\ + Where the `X11` cursor theme package will generate.\ + It also creates a directory if not exists. + :type x_out_dir: Path + + :param info: Content theme name & comment + :type info: Info + """ + + for _, item in config.items(): + png = item["png"] + hotspot = item["hotspot"] + x_sizes = item["x_sizes"] + delay = item["delay"] + + with CursorAlias.from_bitmap(png, hotspot) as alias: + x_cfg = alias.create(x_sizes, delay) + print(f"Building '{x_cfg.stem}' XCursor...") + XCursor.create(x_cfg, x_out_dir) + + add_missing_xcursor(x_out_dir / "cursors") + XPackager(x_out_dir, info.name, info.comment) + + +def wbuild(config: Dict[str, Dict[str, Any]], win_out_dir: Path, info: Info) -> None: + """Build `BreezeX` cursor theme for only `Windows` platforms. + + :param config: `BreezeX` configuration. + :type config: Dict + + :param win_out_dir: Path to the output directory,\ + Where the `Windows` cursor theme package will generate.\ + It also creates a directory if not exists. + :type win_out_dir: Path + + :param info: Content theme name & comment + :type info: Info + """ + + for _, item in config.items(): + png = item["png"] + hotspot = item["hotspot"] + x_sizes = item["x_sizes"] + delay = item["delay"] + + with CursorAlias.from_bitmap(png, hotspot) as alias: + alias.create(x_sizes, delay) + + if item.get("win_key"): + position = item["position"] + win_size = item["win_size"] + win_key = item["win_key"] + canvas_size = item["canvas_size"] + win_delay = item["win_delay"] + + win_cfg = alias.reproduce( + win_size, canvas_size, position, delay=win_delay + ).rename(win_key) + print(f"Building '{win_cfg.stem}' Windows Cursor...") + WindowsCursor.create(win_cfg, win_out_dir) + + WindowsPackager(win_out_dir, info.name, info.comment, AUTHOR, URL) + + +def build( + config: Dict[str, Dict[str, Any]], x_out_dir: Path, win_out_dir: Path, info: Info +) -> None: + """Build `BreezeX` cursor theme for `X11` & `Windows` platforms. + + :param config: `BreezeX` configuration. + :type config: Dict + + :param x_out_dir: Path to the output directory,\ + Where the `X11` cursor theme package will generate.\ + It also creates a directory if not exists. + :type x_out_dir: Path + + :param win_out_dir: Path to the output directory,\ + Where the `Windows` cursor theme package will generate.\ + It also creates a directory if not exists. + :type win_out_dir: Path + + :param info: Content theme name & comment + :type info: Info + """ + + def win_build(item: Dict[str, Any], alias: CursorAlias) -> None: + position = item["position"] + win_size = item["win_size"] + win_key = item["win_key"] + canvas_size = item["canvas_size"] + win_delay = item["win_delay"] + + win_cfg = alias.reproduce( + win_size, canvas_size, position, delay=win_delay + ).rename(win_key) + print(f"Building '{win_cfg.stem}' Windows Cursor...") + WindowsCursor.create(win_cfg, win_out_dir) + + for _, item in config.items(): + png = item["png"] + hotspot = item["hotspot"] + x_sizes = item["x_sizes"] + delay = item["delay"] + + with CursorAlias.from_bitmap(png, hotspot) as alias: + x_cfg = alias.create(x_sizes, delay) + print(f"Building '{x_cfg.stem}' XCursor...") + XCursor.create(x_cfg, x_out_dir) + + if item.get("win_key"): + win_build(item, alias) + + add_missing_xcursor(x_out_dir / "cursors") + XPackager(x_out_dir, info.name, info.comment) + + WindowsPackager(win_out_dir, info.name, info.comment, AUTHOR, URL) diff --git a/builder/bxpkg/symlinks.py b/builder/bxpkg/symlinks.py new file mode 100644 index 0000000..d80480d --- /dev/null +++ b/builder/bxpkg/symlinks.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +from typing import Dict, List, Union + +from clickgen.util import chdir + + +def add_missing_xcursor(directory) -> None: + """Add missing `XCursor` to the Unix cursor package. + + :param directory: directory where XCursors are available. + :type directory: Union[str, Path] + """ + + symlinks: List[Dict[str, Union[str, List[str]]]] = [ + {"src": "all-scroll", "links": ["fleur", "size_all"]}, + { + "src": "bottom_left_corner", + "links": [ + "fcf1c3c7cd4491d801f1e1c78f100000", + "sw-resize", + "ne-resize", + "size_bdiag", + "nesw-resize", + "top_right_corner", + "fd_double_arrow", + ], + }, + { + "src": "bottom_right_corner", + "links": [ + "c7088f0f3e6c8088236ef8e1e3e70000", + "top_left_corner", + "se-resize", + "nw-resize", + "size_fdiag", + "nwse-resize", + "bd_double_arrow", + ], + }, + { + "src": "copy", + "links": [ + "1081e37283d90000800003c07f3ef6bf", + "6407b0e94181790501fd1e167b474872", + "b66166c04f8c3109214a4fbd64a50fc8", + "dnd-copy", + ], + }, + { + "src": "cross", + "links": [ + "cross_reverse", + "diamond_cross", + "tcross", + "color-picker", + "crosshair", + ], + }, + { + "src": "dnd_no_drop", + "links": [ + "no-drop", + # crossed_circle symlinks + "crossed_circle", + "03b6e0fcb3499374a867c041f52298f0", + "not-allowed", + "forbidden", + "circle", + ], + }, + {"src": "dotbox", "links": ["dot_box_mask", "draped_box", "icon", "target"]}, + {"src": "hand1", "links": ["grab", "openhand"]}, + { + "src": "hand2", + "links": [ + "9d800788f1b08800ae810202380a0822", + "e29285e634086352946a0e7090d73106", + "pointer", + "pointing_hand", + ], + }, + { + "src": "left_ptr", + "links": [ + "arrow", + "default", + "center_ptr", + ], + }, + { + "src": "left_ptr_watch", + "links": [ + "00000000000000020006000e7e9ffc3f", + "08e8e1c95fe2fc01f976f1e063a24ccd", + "3ecb610c1bf2410f44200f48c40d3599", + "progress", + ], + }, + { + "src": "link", + "links": [ + "3085a0e285430894940527032f8b26df", + "640fb0e74195791501fd1ed57b41487f", + "a2a266d0498c3104214a47bd64ab0fc8", + "alias", + "dnd-link", + ], + }, + { + "src": "move", + "links": [ + "4498f0e0c1937ffe01fd06f973665830", + "9081237383d90e509aa00f00170e968f", + "fcf21c00b30f7e3f83fe0dfd12e71cff", + "grabbing", + "pointer_move", + "dnd-move", + "closedhand", + "dnd-none", + ], + }, + {"src": "pencil", "links": ["draft"]}, + {"src": "plus", "links": ["cell"]}, + { + "src": "question_arrow", + "links": [ + "5c6cd98b3f3ebcb1f9c7f1c204630408", + "d9ce0ab605698f320427677b458ad60b", + "help", + "left_ptr_help", + "whats_this", + "dnd-ask", + ], + }, + {"src": "right_ptr", "links": ["draft_large", "draft_small"]}, # required + {"src": "sb_down_arrow", "links": ["down-arrow"]}, + { + "src": "sb_h_double_arrow", + "links": [ + "028006030e0e7ebffc7f7070c0600140", + "14fef782d02440884392942c1120523", + "col-resize", + "ew-resize", + "h_double_arrow", + "size-hor", + "size_hor", + "split_h", + "left_side", + "w-resize", + "right_side", + "e-resize", + ], + }, + {"src": "sb_left_arrow", "links": ["left-arrow"]}, + {"src": "sb_right_arrow", "links": ["right-arrow"]}, + {"src": "sb_up_arrow", "links": ["up-arrow"]}, + { + "src": "sb_v_double_arrow", + "links": [ + "00008160000006810000408080010102", + "2870a09082c103050810ffdffffe0204", + "double_arrow", + "ns-resize", + "row-resize", + "size-ver", + "size_ver", + "split_v", + "v_double_arrow", + "top_side", + "s-resize", + "n-resize", + "bottom_side", + ], + }, + {"src": "wait", "links": ["watch"]}, + {"src": "X_cursor", "links": ["pirate", "x-cursor"]}, + {"src": "xterm", "links": ["ibeam", "text"]}, + ] + + with chdir(directory): + for item in symlinks: + src = str(item["src"]) + for link in item.get("links"): + print(f"Creating symlink {src} -> {link}") + os.symlink(src, link)