diff --git a/builder/bbpkg/__init__.py b/builder/bbpkg/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/builder/bbpkg/configure.py b/builder/bbpkg/configure.py new file mode 100644 index 00000000..d951c858 --- /dev/null +++ b/builder/bbpkg/configure.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from typing import Any, Dict, Tuple, TypeVar + +from clickgen.util import PNGProvider + +from bbpkg.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 `GoogleDot` pointers. + + ``` + Args: + + :bitmaps_dir: (str | Path) Path to .png file's directory. + + + Keywords Args: + + :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, 24), (32, 32)], + win_canvas_size=(32, 32), + win_size=(24, 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 in {bitmaps_dir}") + + 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/bbpkg/constants.py b/builder/bbpkg/constants.py new file mode 100644 index 00000000..e9240d44 --- /dev/null +++ b/builder/bbpkg/constants.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from typing import Dict + +# Info +AUTHOR = "Kaiz Khatri" +URL = "https://github.com/ful1e5/Google_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}, + "bd_double_arrow.png": {"xhot": 98, "yhot": 100}, + "bottom_left_corner.png": {"xhot": 31, "yhot": 172}, + "bottom_right_corner.png": {"xhot": 170, "yhot": 172}, + "bottom_side.png": {"xhot": 100, "yhot": 164}, + "bottom_tee.png": {"xhot": 100, "yhot": 164}, + "center_ptr.png": {"xhot": 98, "yhot": 131}, + "circle.png": {"xhot": 48, "yhot": 25}, + "context-menu.png": {"xhot": 48, "yhot": 25}, + "copy.png": {"xhot": 48, "yhot": 25}, + "cross.png": {"xhot": 98, "yhot": 96}, + "crossed_circle.png": {"xhot": 100, "yhot": 100}, + "crosshair.png": {"xhot": 99, "yhot": 99}, + "dnd_no_drop.png": {"xhot": 86, "yhot": 79}, + "dnd-ask.png": {"xhot": 86, "yhot": 79}, + "dnd-copy.png": {"xhot": 86, "yhot": 79}, + "dnd-link.png": {"xhot": 86, "yhot": 79}, + "dnd-move.png": {"xhot": 86, "yhot": 79}, + "dnd-none.png": {"xhot": 99, "yhot": 98}, + "dotbox.png": {"xhot": 100, "yhot": 100}, + "fd_double_arrow.png": {"xhot": 98, "yhot": 100}, + "grabbing.png": {"xhot": 106, "yhot": 79}, + "hand1.png": {"xhot": 113, "yhot": 95}, + "hand2.png": {"xhot": 88, "yhot": 32}, + "left_ptr.png": {"xhot": 53, "yhot": 36}, + "left_side.png": {"xhot": 35, "yhot": 100}, + "left_tee.png": {"xhot": 165, "yhot": 95}, + "link.png": {"xhot": 48, "yhot": 25}, + "ll_angle.png": {"xhot": 34, "yhot": 165}, + "lr_angle.png": {"xhot": 167, "yhot": 164}, + "move.png": {"xhot": 100, "yhot": 100}, + "pencil.png": {"xhot": 37, "yhot": 161}, + "plus.png": {"xhot": 100, "yhot": 100}, + "pointer-move.png": {"xhot": 48, "yhot": 25}, + "question_arrow.png": {"xhot": 102, "yhot": 102}, + "right_ptr.png": {"xhot": 150, "yhot": 29}, + "right_side.png": {"xhot": 163, "yhot": 98}, + "right_tee.png": {"xhot": 30, "yhot": 96}, + "sb_down_arrow.png": {"xhot": 100, "yhot": 126}, + "sb_h_double_arrow.png": {"xhot": 100, "yhot": 100}, + "sb_left_arrow.png": {"xhot": 86, "yhot": 100}, + "sb_right_arrow.png": {"xhot": 113, "yhot": 100}, + "sb_up_arrow.png": {"xhot": 99, "yhot": 86}, + "sb_v_double_arrow.png": {"xhot": 100, "yhot": 100}, + "tcross.png": {"xhot": 98, "yhot": 100}, + "top_left_corner.png": {"xhot": 29, "yhot": 27}, + "top_right_corner.png": {"xhot": 170, "yhot": 28}, + "top_side.png": {"xhot": 98, "yhot": 34}, + "top_tee.png": {"xhot": 98, "yhot": 29}, + "ul_angle.png": {"xhot": 34, "yhot": 35}, + "ur_angle.png": {"xhot": 164, "yhot": 34}, + "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": 100}, + "zoom-in.png": {"xhot": 90, "yhot": 89}, + "zoom-out.png": {"xhot": 93, "yhot": 90}, + ############ + # Animated # + ############ + # Note: Animated cursors don't need an extension and frame numbers. + "left_ptr_watch": {"xhot": 50, "yhot": 28}, + "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/bbpkg/generator.py b/builder/bbpkg/generator.py new file mode 100644 index 00000000..26991ad1 --- /dev/null +++ b/builder/bbpkg/generator.py @@ -0,0 +1,135 @@ +#!/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 bbpkg.constants import AUTHOR, URL +from bbpkg.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 `Bibata` cursor theme for only `X11`(UNIX) platform. + + ``` + :config: (Dict) `Bibata` configuration. + + :x_out_dir: (Path) Path to the output directory, + Where the `X11` cursor theme package will generate. + It also creates a directory if not exists. + :info: (Dict) Content theme name & comment + ``` + """ + + 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 `Bibata` cursor theme for only `Windows` platforms. + + ``` + :config: (Dict) `Bibata` configuration. + + :win_out_dir: (Path) Path to the output directory, + Where the `Windows` cursor theme package will generate. + It also creates a directory if not exists. + :info: (Dict) Content theme name & comment + ``` + """ + + 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 `Bibata` cursor theme for `X11` & `Windows` platforms. + + ``` + :config: (Dict) `Bibata` configuration. + + :x_out_dir: (Path) Path to the output directory, + Where the `X11` cursor theme package will generate. + It also creates a directory if not exists. + + :win_out_dir: (Path) Path to the output directory, + Where the `Windows` cursor theme package will generate. + It also creates a directory if not exists. + :info: (Dict) Content theme name & comment + ``` + """ + + 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/bbpkg/symlinks.py b/builder/bbpkg/symlinks.py new file mode 100644 index 00000000..e00a3401 --- /dev/null +++ b/builder/bbpkg/symlinks.py @@ -0,0 +1,202 @@ +#!/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. + + :directory: (Path|str) directory where XCursors are available. + """ + + 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 + "crosshair", + ], + }, + # { + # "src": "crossed_circle", + # "links": [ + # "03b6e0fcb3499374a867c041f52298f0", + # "not-allowed", + # "forbidden", + # "circle", + # ], + # }, + { + "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 symlinks + "center_ptr", + ], + }, + { + "src": "left_ptr_watch", + "links": [ + "00000000000000020006000e7e9ffc3f", + "08e8e1c95fe2fc01f976f1e063a24ccd", + "3ecb610c1bf2410f44200f48c40d3599", + "progress", + ], + }, + # {"src": "left_side", "links": ["w-resize", "right_side", "e-resize"]}, + { + "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 symlinks + "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 symlinks + "top_side", + "s-resize", + "n-resize", + "bottom_side", + ], + }, + # {"src": "top_side", "links": ["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 = item.get("src") + for link in item.get("links"): + print(f"Creating symlink {src} -> {link}") + os.symlink(src, link) diff --git a/builder/setup.py b/builder/setup.py new file mode 100644 index 00000000..e457421a --- /dev/null +++ b/builder/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from setuptools import setup + +setup( + name="bbpkg", + version="1.0.4", + description="Generate 'Bibata' cursor theme from PNGs file", + url="https://github.com/ful1e5/Bibata_Cursor", + packages=["bbpkg"], + package_dir={"bbpkg": "bbpkg"}, + author="Kaiz Khatri", + author_email="kaizmandhu@gamil.com", + install_requires=["clickgen==1.1.8"], + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + ], + python_requires=">=3.8", + zip_safe=True, +)