Repository: ful1e5/clickgen (language: python)

Issue:
support Windows and macOS

OSError: dlopen(apple_cursor-main/builder/venv/lib/python3.8/site-packages/clickgen-1.1.8-py3.8.egg/clickgen/xcursorgen.so, 6): no suitable image found.  Did find:
	apple_cursor-main/builder/venv/lib/python3.8/site-packages/clickgen-1.1.8-py3.8.egg/clickgen/xcursorgen.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00
	apple_cursor-main/builder/venv/lib/python3.8/site-packages/clickgen-1.1.8-py3.8.egg/clickgen/xcursorgen.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00
support Windows and macOS

OSError: dlopen(apple_cursor-main/builder/venv/lib/python3.8/site-packages/clickgen-1.1.8-py3.8.egg/clickgen/xcursorgen.so, 6): no suitable image found.  Did find:
	apple_cursor-main/builder/venv/lib/python3.8/site-packages/clickgen-1.1.8-py3.8.egg/clickgen/xcursorgen.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00
	apple_cursor-main/builder/venv/lib/python3.8/site-packages/clickgen-1.1.8-py3.8.egg/clickgen/xcursorgen.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7851a72a..70299f20 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,10 +15,11 @@ on:
 
 jobs:
   test:
-    runs-on: ubuntu-18.04
+    runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.8, 3.9, "3.10"]
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: ["3.7", "3.8", "3.9", "3.10"]
 
     steps:
       - uses: actions/checkout@v2
@@ -32,14 +33,10 @@ jobs:
         id: pip-cache # use this to check for `cache-hit` (`steps.pip-cache.outputs.cache-hit != 'true'`)
         with:
           path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
           restore-keys: |
             ${{ runner.os }}-pip-
 
-      - name: Install build dependencies using 'apt'
-        run: sudo apt install libx11-dev libxcursor-dev libpng-dev
-        continue-on-error: false
-
       - name: Install pip dependencies
         run: |
           python -m pip install --upgrade pip
@@ -47,10 +44,6 @@ jobs:
           python -m pip install tox-gh-actions
         continue-on-error: false
 
-      - name: Compiling Xcursorgen C library
-        run: make xcursorgen.so
-        continue-on-error: false
-
       - name: Test with tox
         run: tox
         continue-on-error: false
@@ -65,20 +58,14 @@ jobs:
         with:
           python-version: "3.x"
 
-      - name: Cache pip dependencies
+      - name: Caching pip packages
         uses: actions/cache@v2
+        id: pip-cache # use this to check for `cache-hit` (`steps.pip-cache.outputs.cache-hit != 'true'`)
         with:
-          # This path is specific to Ubuntu
           path: ~/.cache/pip
-          # Look to see if there is a cache hit for the corresponding requirements file
-          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
           restore-keys: |
             ${{ runner.os }}-pip-
-            ${{ runner.os }}-
-
-      - name: Install build dependencies using 'apt'
-        run: sudo apt install libx11-dev libxcursor-dev libpng-dev
-        continue-on-error: false
 
       - name: Install pip dependencies
         run: |
@@ -88,10 +75,6 @@ jobs:
           python -m pip install pytest pytest-cov
         continue-on-error: false
 
-      - name: Compiling Xcursorgen C library
-        run: make xcursorgen.so
-        continue-on-error: false
-
       - name: Building clickgen
         run: python setup.py sdist bdist_wheel
         continue-on-error: false
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 90b7d71c..707ff86e 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -20,14 +20,10 @@ jobs:
         id: pip-cache # use this to check for `cache-hit` (`steps.pip-cache.outputs.cache-hit != 'true'`)
         with:
           path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
           restore-keys: |
             ${{ runner.os }}-pip-
 
-      - name: Install build dependencies using 'apt'
-        run: sudo apt install libx11-dev libxcursor-dev libpng-dev
-        continue-on-error: false
-
       - name: Install pip dependencies
         run: |
           python -m pip install --upgrade pip
@@ -47,10 +43,6 @@ jobs:
       - name: Generating clickgen `stubfile`
         run: make stubgen
 
-      - name: Compiling Xcursorgen C library
-        run: make xcursorgen.so
-        continue-on-error: false
-
       - name: Building
         run: python setup.py sdist bdist_wheel
         continue-on-error: false
diff --git a/.gitignore b/.gitignore
index 0b9ad1c9..b4425135 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ env
 themes
 .vscode
 .vim
+samples/out
 
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b34bf750..8d91acc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,11 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- refactor: removed old source code
+- add: python 3.7 support
+- add: **Windows** and **macOS** support #24
+- init: `cursor`, `configparser`, `packer`, `parser` and `writer` module
 - 'Twitter' and 'Download' links added on PYPI page
+- cli init ( `clickgen -h` )
 
 ### Changed
 
 - Changed docs theme to 'furo'
+- chore: updated template variables inside `packer.windows`
+- make: install all dependencies with `make install_deps` command
+- chore: directory renamed `examples` -> `samples`
 
 ## [1.2.0] - 26 Mar 2022
 
diff --git a/Makefile b/Makefile
index 93e3269e..316b1a5d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,36 +1,26 @@
 py3 = python3
 
 clean:
-	rm -rf .vscode .vim venv coverage.xml
+	rm -rf .vscode .vim venv coverage.xml out samples/out
 	rm -rf .tox build dist src/clickgen.egg-info .mypy_cache .pytest_cache .coverage htmlcov .python-version
-	rm -rf src/clickgen/__pycache__ tests/__pycache__
-	make -C src/xcursorgen clean
+	find . -name "__pycache__" -type d -exec /bin/rm -rf {} + 
 	make -C docs clean
-	$(py3) -m pip uninstall -y clickgen
 
-install_requirements:
+install_deps:
 	$(py3) -m pip install -r requirements.txt
-
-install_dev_requirements:
 	$(py3) -m pip install -r requirements.dev.txt
-
-install_docs_requirements:
 	$(py3) -m pip install -r docs/requirements.txt
 
 install:
-	sudo $(py3) -m pip install --user -e .
+	$(py3) -m pip install -e .
 
 test:
-	$(py3) -m pytest
+	pytest
 
 coverage:
-	$(py3) -m coverage run -m pytest
-	coverage html
-
-xcursorgen.so:
-	@make -C src/xcursorgen
+	pytest --cov=clickgen --cov-report=html
 
-build: clean xcursorgen.so
+build: clean
 	$(py3) setup.py sdist bdist_wheel
 
 stubgen:
@@ -40,8 +30,9 @@ stubgen:
 docsgen: build install
 	make -C docs html
 
-tox: build install
+tox: clean
 	pyenv local 3.7.5 3.8.12 3.9.10 3.10.2
 	tox
+	pyenv local system
 
-dev: clean install_requirements install_dev_requirements install_docs_requirements stubgen install xcursorgen.so test coverage docsgen
+dev: clean install_deps stubgen install test coverage docsgen
diff --git a/docs/requirements.txt b/docs/requirements.txt
index bffa82d6..57d9e50f 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,4 @@
 sphinx==4.5.0
-furo==2022.3.4
+furo==2022.4.7
 readthedocs-sphinx-search==0.1.1
 sphinx-autoapi==1.8.4
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f9aad46c..e0d1bf2a 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -13,6 +13,8 @@
 import os
 import sys
 
+import clickgen
+
 sys.path.insert(0, os.path.abspath("./../.."))
 
 # -- Project information -----------------------------------------------------
@@ -22,7 +24,7 @@
 author = "Kaiz Khatri"
 
 # The full version, including alpha/beta/rc tags
-release = "1.2.0"
+release = clickgen.__version__  # type: ignore
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/source/index.rst b/docs/source/index.rst
index d25d692d..eb27b6b2 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -6,7 +6,7 @@
 clickgen
 ========
 
-clickgen is hussle free cursor building toolbox by `Kaiz Khatri`, Built upon powerful tools 
+clickgen is hassle free cursor building toolbox by `Kaiz Khatri`, Built upon powerful tools 
 `anicursorgen.py <https://github.com/ubuntu/yaru/blob/master/icons/src/cursors/anicursorgen.py>`_ 
 and `xcursorgen <https://linux.die.net/man/1/xcursorgen>`_.
 
@@ -35,14 +35,10 @@ Overview
 
 This library adds cursor building capabilities to your Python interpreter.
 
-``xcursorgen`` & ``anicursorgen.py`` tools are designed to build cursor from *config file* once at 
-a time. They should provide a solid foundation for the general cursor building tool.
-
 .. toctree::
    :maxdepth: 2
 
    installation.rst
-   reference/index.rst
    about.rst
 
 
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 7beea966..f645cb2b 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -2,14 +2,6 @@
 Installation
 ============
 
-Warnings
---------
-
-.. warning:: 
-    clickgen CI & Building is tested on Linux platform only.
-    Check `GitHub Action <https://github.com/ful1e5/clickgen/actions>`_ for
-    more detail.
-
 
 Python Support
 --------------
@@ -18,6 +10,8 @@ clickgen supports these Python versions.
 +---------------------+----------+---------+---------+---------+---------+
 | **Python**          | **3.10** | **3.9** | **3.8** | **3.7** | **3.6** |
 +=====================+==========+=========+=========+=========+=========+
+| clickgen 2.0.0      | Yes      | Yes     | Yes     | Yes     |         |
++---------------------+----------+---------+---------+---------+---------+
 | clickgen 1.2.0      | Yes      | Yes     | Yes     |         |         |
 +---------------------+----------+---------+---------+---------+---------+
 | clickgen 1.1.9      |          | Yes     | Yes     |         |         |
@@ -44,12 +38,6 @@ clickgen supports these Python versions.
 
 Basic Installation
 ------------------
-.. note::
-
-    The following instructions will install clickgen with support for
-    most common image formats. See :ref:`external-libraries` for a
-    full list of external libraries supported.
-
 Install clickgen with :command:`pip`::
 
     python3 -m pip install --upgrade pip
@@ -63,42 +51,9 @@ Download and extract the `compressed archive from PyPI`_.
 
 .. _compressed archive from PyPI: https://pypi.org/project/clickgen/
 
-.. _external-libraries:
-
-External Libraries
-^^^^^^^^^^^^^^^^^^
-.. note::
-    clickgen is using ``xcursorgen`` CLI internally for making xcursors.
-
-xcursorgen require external libraries:
-
-* **libpng** provides PNG functionality.
-  * Starting with clickgen 1.1.0, libpng is required by default.
-
-* **zlib** provides access to compressed PNGs.
-  * Starting with clickgen 1.1.0, zlib is required by default.
-
-* **libXcursor** X Window System Cursor management library.
-  * Starting with clickgen 1.1.0, libXcursor is required by default.
-
-* **libX11** Core X11 protocol client library.
-  * Starting with clickgen 1.1.0, libX11 is required by default.
-
-
-Once you have installed the prerequisites, run::
-
-    python3 -m pip install --upgrade pip
-    python3 -m pip install --upgrade clickgen
-
 Building on macOS
 ^^^^^^^^^^^^^^^^^
-The easiest way to install external libraries is via `Homebrew
-<https://brew.sh/>`_. After you install Homebrew, run::
-
-    brew install gcc libpng
-    brew install --cask xquartz
-
-Now install clickgen with::
+To install clickgen with::
 
     python3 -m pip install --upgrade pip
     python3 -m pip install --upgrade clickgen
@@ -122,26 +77,20 @@ In Fedora, the command is::
 
 .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
 
-Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with::
-
-    sudo apt install libx11-dev libxcursor-dev libpng-dev
-
-Prerequisites are installed on **Arch Linux, Manjaro** with::
-
-    sudo pacman -S libx11 libxcursor libpng
-
-Prerequisites are installed on recent **Red Hat, CentOS** or **Fedora** with::
+To install clickgen with::
 
-    sudo dnf install libx11-devel libxcursor-devel libpng-devel
+    python3 -m pip install --upgrade pip
+    python3 -m pip install --upgrade clickgen
 
-Note that the package manager may be yum or DNF, depending on the exact distribution.
+or from within the uncompressed source directory::
 
+    python3 setup.py install
 
 Platform Support
 ----------------
 Current platform support for clickgen. Binary distributions are contributed for each 
 release on a volunteer basis, but the source should compile and run everywhere platform 
-support is listed. In general, we aim to support all current versions of Linux and macOS.
+support is listed. In general, we aim to support all current versions of Windows, Linux and macOS.
 
 Continuous Integration Targets
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -150,9 +99,12 @@ These platforms are built and tested for every change.
 +---------------------------------+----------------------------+-------------------------+
 | **Operating system**            | **Tested Python versions** | **Tested architecture** |
 +---------------------------------+----------------------------+-------------------------+
-| Ubuntu Linux 18.04 LTS (Bionic) | 3.8, 3.9                   | x86-64                  |
+| Ubuntu Linux 20.04.4 LTS        | 3.7, 3.8, 3.9, 3.10        | x86-64                  |
++---------------------------------+----------------------------+-------------------------+
+| macOS 11.6.5                    | 3.7, 3.8, 3.9, 3.10        | x86-64                  |
++---------------------------------+----------------------------+-------------------------+
+| Windows Server 2022             | 3.7, 3.8, 3.9, 3.10        | x86-64                  |
 +---------------------------------+----------------------------+-------------------------+
-
 
 .. _release history at PyPI: https://pypi.org/project/clickgen/#history
 
diff --git a/docs/source/reference/builders.rst b/docs/source/reference/builders.rst
deleted file mode 100644
index a777c3a3..00000000
--- a/docs/source/reference/builders.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-
-.. py:module:: clickgen.builders
-.. py:currentmodule:: clickgen.builders
-
-:py:mod:`~clickgen.builders` Module
-===================================
-
-Typing
-------
-.. autodata:: clickgen.builders.ConfigFrame
-
-:py:mod:`~clickgen.builders.XCursor` Class
-------------------------------------------
-.. autoclass:: clickgen.builders.XCursor
-.. automethod:: clickgen.builders.XCursor.__init__
-.. automethod:: clickgen.builders.XCursor.gen_argv_ctypes
-.. automethod:: clickgen.builders.XCursor.generate
-.. automethod:: clickgen.builders.XCursor.create
-
-:py:mod:`~clickgen.builders.WindowsCursor` Class
-------------------------------------------------
-.. autoclass:: clickgen.builders.WindowsCursor
-.. automethod:: clickgen.builders.WindowsCursor.__init__
-.. automethod:: clickgen.builders.WindowsCursor.get_frames
-.. automethod:: clickgen.builders.WindowsCursor.make_framesets
-.. automethod:: clickgen.builders.WindowsCursor.copy_to
-.. automethod:: clickgen.builders.WindowsCursor.make_ani
-.. automethod:: clickgen.builders.WindowsCursor.shadowize
-.. automethod:: clickgen.builders.WindowsCursor.create_shadow
-.. automethod:: clickgen.builders.WindowsCursor.write_png
-.. automethod:: clickgen.builders.WindowsCursor.write_cur
-.. automethod:: clickgen.builders.WindowsCursor.make_cur
-.. automethod:: clickgen.builders.WindowsCursor.generate
-.. automethod:: clickgen.builders.WindowsCursor.create
diff --git a/docs/source/reference/core.rst b/docs/source/reference/core.rst
deleted file mode 100644
index 3d8c128c..00000000
--- a/docs/source/reference/core.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-
-.. py:module:: clickgen.core
-.. py:currentmodule:: clickgen.core
-
-:py:mod:`~clickgen.core` Module
-===============================
-
-:py:mod:`~clickgen.core.Bitmap` Class
--------------------------------------
-.. autoclass:: clickgen.core.Bitmap
-.. automethod:: clickgen.core.Bitmap.__init__
-.. automethod:: clickgen.core.Bitmap.resize
-.. automethod:: clickgen.core.Bitmap.rename
-.. automethod:: clickgen.core.Bitmap.reproduce
-.. automethod:: clickgen.core.Bitmap.copy
-.. automethod:: clickgen.core.Bitmap.__str__
-.. automethod:: clickgen.core.Bitmap.__repr__
-
-:py:mod:`~clickgen.core.CursorAlias` Class
-------------------------------------------
-.. autoclass:: clickgen.core.CursorAlias
-.. automethod:: clickgen.core.CursorAlias.__init__
-.. automethod:: clickgen.core.CursorAlias.from_bitmap
-.. automethod:: clickgen.core.CursorAlias.create
-.. automethod:: clickgen.core.CursorAlias.check_alias
-.. automethod:: clickgen.core.CursorAlias.extension
-.. automethod:: clickgen.core.CursorAlias.copy
-.. automethod:: clickgen.core.CursorAlias.rename
-.. automethod:: clickgen.core.CursorAlias.reproduce
diff --git a/docs/source/reference/db.rst b/docs/source/reference/db.rst
deleted file mode 100644
index 40264ddc..00000000
--- a/docs/source/reference/db.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-
-.. py:module:: clickgen.db
-.. py:currentmodule:: clickgen.db
-
-:py:mod:`~clickgen.db` Module
-=============================
-
-This module provides **Cursor** database along symlinks supports.
-
-:py:class:`~clickgen.db.CursorDB` Class
----------------------------------------
-.. autoclass:: clickgen.db.CursorDB
-.. automethod:: clickgen.db.CursorDB.__init__
-.. automethod:: clickgen.db.CursorDB.search_symlinks
-.. automethod:: clickgen.db.CursorDB.rename_file
-
-
-Constants
----------
-.. data:: DATA
-
-    Default dataset of XCursor in ``clickgen``.
-
diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst
deleted file mode 100644
index f2bded50..00000000
--- a/docs/source/reference/index.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-Reference
-=========
-
-.. toctree::
-    :maxdepth: 2
-
-
-    core
-    util
-    db
-    packagers
-    builders
diff --git a/docs/source/reference/packagers.rst b/docs/source/reference/packagers.rst
deleted file mode 100644
index c869621b..00000000
--- a/docs/source/reference/packagers.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-
-.. py:module:: clickgen.packagers
-.. py:currentmodule:: clickgen.packagers
-
-:py:mod:`~clickgen.packagers` Module
-====================================
-Using this module you are able to create ``Windows`` or ``Unix`` cursor packages.
-
-:py:mod:`~clickgen.packagers.XPackager`
----------------------------------------
-.. autofunction:: XPackager
-
-:py:mod:`~clickgen.packagers.WindowsPackager`
----------------------------------------------
-.. autofunction:: WindowsPackager
diff --git a/docs/source/reference/util.rst b/docs/source/reference/util.rst
deleted file mode 100644
index 441c6f2b..00000000
--- a/docs/source/reference/util.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-.. py:module:: clickgen.util
-.. py:currentmodule:: clickgen.util
-
-:py:mod:`~clickgen.util` Module
-===============================
-
-This module provides utility functions and classes, Which makes developer life easier.
-
-:py:class:`~clickgen.util.pngprovider` class
---------------------------------------------
-.. autoclass:: clickgen.util.PNGProvider
-.. automethod:: clickgen.util.PNGProvider.get
-
-:py:mod:`~clickgen.util.add_missing_xcursors` function
-------------------------------------------------------
-.. autofunction:: add_missing_xcursors
-
-Utility
--------
-
-:py:mod:`~clickgen.util.chdir`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. autofunction:: chdir
-
-:py:mod:`~clickgen.util.remove_util`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. autofunction:: remove_util
diff --git a/examples/__init__.py b/examples/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/examples/bitmaps/all_scroll.png b/examples/bitmaps/all_scroll.png
deleted file mode 100644
index 6e6874c2..00000000
Binary files a/examples/bitmaps/all_scroll.png and /dev/null differ
diff --git a/examples/bitmaps/bd_double_arrow.png b/examples/bitmaps/bd_double_arrow.png
deleted file mode 100644
index 37f4d1b6..00000000
Binary files a/examples/bitmaps/bd_double_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/bottom_left_corner.png b/examples/bitmaps/bottom_left_corner.png
deleted file mode 100644
index faed5257..00000000
Binary files a/examples/bitmaps/bottom_left_corner.png and /dev/null differ
diff --git a/examples/bitmaps/bottom_right_corner.png b/examples/bitmaps/bottom_right_corner.png
deleted file mode 100644
index 97a0a4b4..00000000
Binary files a/examples/bitmaps/bottom_right_corner.png and /dev/null differ
diff --git a/examples/bitmaps/bottom_side.png b/examples/bitmaps/bottom_side.png
deleted file mode 100644
index 8764dd7a..00000000
Binary files a/examples/bitmaps/bottom_side.png and /dev/null differ
diff --git a/examples/bitmaps/bottom_tee.png b/examples/bitmaps/bottom_tee.png
deleted file mode 100644
index 105b3a39..00000000
Binary files a/examples/bitmaps/bottom_tee.png and /dev/null differ
diff --git a/examples/bitmaps/center_ptr.png b/examples/bitmaps/center_ptr.png
deleted file mode 100644
index e4766068..00000000
Binary files a/examples/bitmaps/center_ptr.png and /dev/null differ
diff --git a/examples/bitmaps/circle.png b/examples/bitmaps/circle.png
deleted file mode 100644
index 876d5a70..00000000
Binary files a/examples/bitmaps/circle.png and /dev/null differ
diff --git a/examples/bitmaps/context_menu.png b/examples/bitmaps/context_menu.png
deleted file mode 100644
index e8f6d1e8..00000000
Binary files a/examples/bitmaps/context_menu.png and /dev/null differ
diff --git a/examples/bitmaps/copy.png b/examples/bitmaps/copy.png
deleted file mode 100644
index b9da4afd..00000000
Binary files a/examples/bitmaps/copy.png and /dev/null differ
diff --git a/examples/bitmaps/cross.png b/examples/bitmaps/cross.png
deleted file mode 100644
index 6e0adb1d..00000000
Binary files a/examples/bitmaps/cross.png and /dev/null differ
diff --git a/examples/bitmaps/crossed_circle.png b/examples/bitmaps/crossed_circle.png
deleted file mode 100644
index 2d2874f3..00000000
Binary files a/examples/bitmaps/crossed_circle.png and /dev/null differ
diff --git a/examples/bitmaps/crosshair.png b/examples/bitmaps/crosshair.png
deleted file mode 100644
index 32b59913..00000000
Binary files a/examples/bitmaps/crosshair.png and /dev/null differ
diff --git a/examples/bitmaps/dnd_ask.png b/examples/bitmaps/dnd_ask.png
deleted file mode 100644
index b9577a9a..00000000
Binary files a/examples/bitmaps/dnd_ask.png and /dev/null differ
diff --git a/examples/bitmaps/dnd_copy.png b/examples/bitmaps/dnd_copy.png
deleted file mode 100644
index d024ce58..00000000
Binary files a/examples/bitmaps/dnd_copy.png and /dev/null differ
diff --git a/examples/bitmaps/dnd_link.png b/examples/bitmaps/dnd_link.png
deleted file mode 100644
index 0637506f..00000000
Binary files a/examples/bitmaps/dnd_link.png and /dev/null differ
diff --git a/examples/bitmaps/dnd_move.png b/examples/bitmaps/dnd_move.png
deleted file mode 100644
index 18b7dd55..00000000
Binary files a/examples/bitmaps/dnd_move.png and /dev/null differ
diff --git a/examples/bitmaps/dnd_no_drop.png b/examples/bitmaps/dnd_no_drop.png
deleted file mode 100644
index 0dca0e48..00000000
Binary files a/examples/bitmaps/dnd_no_drop.png and /dev/null differ
diff --git a/examples/bitmaps/dnd_none.png b/examples/bitmaps/dnd_none.png
deleted file mode 100644
index 6e6874c2..00000000
Binary files a/examples/bitmaps/dnd_none.png and /dev/null differ
diff --git a/examples/bitmaps/dotbox.png b/examples/bitmaps/dotbox.png
deleted file mode 100644
index 1a9465c1..00000000
Binary files a/examples/bitmaps/dotbox.png and /dev/null differ
diff --git a/examples/bitmaps/fd_double_arrow.png b/examples/bitmaps/fd_double_arrow.png
deleted file mode 100644
index 295558d9..00000000
Binary files a/examples/bitmaps/fd_double_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/grabbing.png b/examples/bitmaps/grabbing.png
deleted file mode 100644
index 6f687ace..00000000
Binary files a/examples/bitmaps/grabbing.png and /dev/null differ
diff --git a/examples/bitmaps/hand1.png b/examples/bitmaps/hand1.png
deleted file mode 100644
index 4844372e..00000000
Binary files a/examples/bitmaps/hand1.png and /dev/null differ
diff --git a/examples/bitmaps/hand2.png b/examples/bitmaps/hand2.png
deleted file mode 100644
index 2b541949..00000000
Binary files a/examples/bitmaps/hand2.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-001.png b/examples/bitmaps/left_ptr_watch-001.png
deleted file mode 100644
index e3b4615c..00000000
Binary files a/examples/bitmaps/left_ptr_watch-001.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-002.png b/examples/bitmaps/left_ptr_watch-002.png
deleted file mode 100644
index 5f8ee001..00000000
Binary files a/examples/bitmaps/left_ptr_watch-002.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-003.png b/examples/bitmaps/left_ptr_watch-003.png
deleted file mode 100644
index 5b1c4309..00000000
Binary files a/examples/bitmaps/left_ptr_watch-003.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-004.png b/examples/bitmaps/left_ptr_watch-004.png
deleted file mode 100644
index 5005efad..00000000
Binary files a/examples/bitmaps/left_ptr_watch-004.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-005.png b/examples/bitmaps/left_ptr_watch-005.png
deleted file mode 100644
index 4eef4ec7..00000000
Binary files a/examples/bitmaps/left_ptr_watch-005.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-006.png b/examples/bitmaps/left_ptr_watch-006.png
deleted file mode 100644
index 9ea6ba69..00000000
Binary files a/examples/bitmaps/left_ptr_watch-006.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-007.png b/examples/bitmaps/left_ptr_watch-007.png
deleted file mode 100644
index a665a5c8..00000000
Binary files a/examples/bitmaps/left_ptr_watch-007.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-008.png b/examples/bitmaps/left_ptr_watch-008.png
deleted file mode 100644
index 57425723..00000000
Binary files a/examples/bitmaps/left_ptr_watch-008.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-009.png b/examples/bitmaps/left_ptr_watch-009.png
deleted file mode 100644
index edad8592..00000000
Binary files a/examples/bitmaps/left_ptr_watch-009.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-010.png b/examples/bitmaps/left_ptr_watch-010.png
deleted file mode 100644
index d615d82c..00000000
Binary files a/examples/bitmaps/left_ptr_watch-010.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-011.png b/examples/bitmaps/left_ptr_watch-011.png
deleted file mode 100644
index bb5edf65..00000000
Binary files a/examples/bitmaps/left_ptr_watch-011.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-012.png b/examples/bitmaps/left_ptr_watch-012.png
deleted file mode 100644
index fffd1813..00000000
Binary files a/examples/bitmaps/left_ptr_watch-012.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-013.png b/examples/bitmaps/left_ptr_watch-013.png
deleted file mode 100644
index b18832f2..00000000
Binary files a/examples/bitmaps/left_ptr_watch-013.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-014.png b/examples/bitmaps/left_ptr_watch-014.png
deleted file mode 100644
index f934f4f0..00000000
Binary files a/examples/bitmaps/left_ptr_watch-014.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-015.png b/examples/bitmaps/left_ptr_watch-015.png
deleted file mode 100644
index 06fec4e3..00000000
Binary files a/examples/bitmaps/left_ptr_watch-015.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-016.png b/examples/bitmaps/left_ptr_watch-016.png
deleted file mode 100644
index dac2e100..00000000
Binary files a/examples/bitmaps/left_ptr_watch-016.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-017.png b/examples/bitmaps/left_ptr_watch-017.png
deleted file mode 100644
index 3dc0761a..00000000
Binary files a/examples/bitmaps/left_ptr_watch-017.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-018.png b/examples/bitmaps/left_ptr_watch-018.png
deleted file mode 100644
index 7b606c25..00000000
Binary files a/examples/bitmaps/left_ptr_watch-018.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-019.png b/examples/bitmaps/left_ptr_watch-019.png
deleted file mode 100644
index 62e5fe62..00000000
Binary files a/examples/bitmaps/left_ptr_watch-019.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-020.png b/examples/bitmaps/left_ptr_watch-020.png
deleted file mode 100644
index 58447092..00000000
Binary files a/examples/bitmaps/left_ptr_watch-020.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-021.png b/examples/bitmaps/left_ptr_watch-021.png
deleted file mode 100644
index e3086e7d..00000000
Binary files a/examples/bitmaps/left_ptr_watch-021.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-022.png b/examples/bitmaps/left_ptr_watch-022.png
deleted file mode 100644
index f0ef3d6e..00000000
Binary files a/examples/bitmaps/left_ptr_watch-022.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-023.png b/examples/bitmaps/left_ptr_watch-023.png
deleted file mode 100644
index da219c5a..00000000
Binary files a/examples/bitmaps/left_ptr_watch-023.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-024.png b/examples/bitmaps/left_ptr_watch-024.png
deleted file mode 100644
index 76a09001..00000000
Binary files a/examples/bitmaps/left_ptr_watch-024.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-025.png b/examples/bitmaps/left_ptr_watch-025.png
deleted file mode 100644
index ec3269a9..00000000
Binary files a/examples/bitmaps/left_ptr_watch-025.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-026.png b/examples/bitmaps/left_ptr_watch-026.png
deleted file mode 100644
index b7d21299..00000000
Binary files a/examples/bitmaps/left_ptr_watch-026.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-027.png b/examples/bitmaps/left_ptr_watch-027.png
deleted file mode 100644
index 3b996f69..00000000
Binary files a/examples/bitmaps/left_ptr_watch-027.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-028.png b/examples/bitmaps/left_ptr_watch-028.png
deleted file mode 100644
index 6632c712..00000000
Binary files a/examples/bitmaps/left_ptr_watch-028.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-029.png b/examples/bitmaps/left_ptr_watch-029.png
deleted file mode 100644
index 15f686f3..00000000
Binary files a/examples/bitmaps/left_ptr_watch-029.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-030.png b/examples/bitmaps/left_ptr_watch-030.png
deleted file mode 100644
index 6b0013a4..00000000
Binary files a/examples/bitmaps/left_ptr_watch-030.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-031.png b/examples/bitmaps/left_ptr_watch-031.png
deleted file mode 100644
index d2b5c875..00000000
Binary files a/examples/bitmaps/left_ptr_watch-031.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-032.png b/examples/bitmaps/left_ptr_watch-032.png
deleted file mode 100644
index 09e4ffb8..00000000
Binary files a/examples/bitmaps/left_ptr_watch-032.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-033.png b/examples/bitmaps/left_ptr_watch-033.png
deleted file mode 100644
index a872cd79..00000000
Binary files a/examples/bitmaps/left_ptr_watch-033.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-034.png b/examples/bitmaps/left_ptr_watch-034.png
deleted file mode 100644
index 587f7517..00000000
Binary files a/examples/bitmaps/left_ptr_watch-034.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-035.png b/examples/bitmaps/left_ptr_watch-035.png
deleted file mode 100644
index 8eaed4ce..00000000
Binary files a/examples/bitmaps/left_ptr_watch-035.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-036.png b/examples/bitmaps/left_ptr_watch-036.png
deleted file mode 100644
index 37a8d25d..00000000
Binary files a/examples/bitmaps/left_ptr_watch-036.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-037.png b/examples/bitmaps/left_ptr_watch-037.png
deleted file mode 100644
index bf96dea9..00000000
Binary files a/examples/bitmaps/left_ptr_watch-037.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-038.png b/examples/bitmaps/left_ptr_watch-038.png
deleted file mode 100644
index 87ca6c16..00000000
Binary files a/examples/bitmaps/left_ptr_watch-038.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-039.png b/examples/bitmaps/left_ptr_watch-039.png
deleted file mode 100644
index 2ce87cd0..00000000
Binary files a/examples/bitmaps/left_ptr_watch-039.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-040.png b/examples/bitmaps/left_ptr_watch-040.png
deleted file mode 100644
index 627df4cc..00000000
Binary files a/examples/bitmaps/left_ptr_watch-040.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-041.png b/examples/bitmaps/left_ptr_watch-041.png
deleted file mode 100644
index 33d36670..00000000
Binary files a/examples/bitmaps/left_ptr_watch-041.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-042.png b/examples/bitmaps/left_ptr_watch-042.png
deleted file mode 100644
index 143bd63a..00000000
Binary files a/examples/bitmaps/left_ptr_watch-042.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-043.png b/examples/bitmaps/left_ptr_watch-043.png
deleted file mode 100644
index 9ea68ccb..00000000
Binary files a/examples/bitmaps/left_ptr_watch-043.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-044.png b/examples/bitmaps/left_ptr_watch-044.png
deleted file mode 100644
index 797945d5..00000000
Binary files a/examples/bitmaps/left_ptr_watch-044.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-045.png b/examples/bitmaps/left_ptr_watch-045.png
deleted file mode 100644
index a0944df4..00000000
Binary files a/examples/bitmaps/left_ptr_watch-045.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-046.png b/examples/bitmaps/left_ptr_watch-046.png
deleted file mode 100644
index b26c7e5f..00000000
Binary files a/examples/bitmaps/left_ptr_watch-046.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-047.png b/examples/bitmaps/left_ptr_watch-047.png
deleted file mode 100644
index 86918d8d..00000000
Binary files a/examples/bitmaps/left_ptr_watch-047.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-048.png b/examples/bitmaps/left_ptr_watch-048.png
deleted file mode 100644
index 92bd811f..00000000
Binary files a/examples/bitmaps/left_ptr_watch-048.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-049.png b/examples/bitmaps/left_ptr_watch-049.png
deleted file mode 100644
index d637f377..00000000
Binary files a/examples/bitmaps/left_ptr_watch-049.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-050.png b/examples/bitmaps/left_ptr_watch-050.png
deleted file mode 100644
index d8fc8dc4..00000000
Binary files a/examples/bitmaps/left_ptr_watch-050.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-051.png b/examples/bitmaps/left_ptr_watch-051.png
deleted file mode 100644
index d632ef58..00000000
Binary files a/examples/bitmaps/left_ptr_watch-051.png and /dev/null differ
diff --git a/examples/bitmaps/left_ptr_watch-052.png b/examples/bitmaps/left_ptr_watch-052.png
deleted file mode 100644
index 372363b2..00000000
Binary files a/examples/bitmaps/left_ptr_watch-052.png and /dev/null differ
diff --git a/examples/bitmaps/left_side.png b/examples/bitmaps/left_side.png
deleted file mode 100644
index fafa2e3d..00000000
Binary files a/examples/bitmaps/left_side.png and /dev/null differ
diff --git a/examples/bitmaps/left_tee.png b/examples/bitmaps/left_tee.png
deleted file mode 100644
index 1a407dee..00000000
Binary files a/examples/bitmaps/left_tee.png and /dev/null differ
diff --git a/examples/bitmaps/link.png b/examples/bitmaps/link.png
deleted file mode 100644
index 58923d0a..00000000
Binary files a/examples/bitmaps/link.png and /dev/null differ
diff --git a/examples/bitmaps/ll_angle.png b/examples/bitmaps/ll_angle.png
deleted file mode 100644
index f4d49afa..00000000
Binary files a/examples/bitmaps/ll_angle.png and /dev/null differ
diff --git a/examples/bitmaps/lr_angle.png b/examples/bitmaps/lr_angle.png
deleted file mode 100644
index 1c2f8791..00000000
Binary files a/examples/bitmaps/lr_angle.png and /dev/null differ
diff --git a/examples/bitmaps/move.png b/examples/bitmaps/move.png
deleted file mode 100644
index 6e6874c2..00000000
Binary files a/examples/bitmaps/move.png and /dev/null differ
diff --git a/examples/bitmaps/pencil.png b/examples/bitmaps/pencil.png
deleted file mode 100644
index e3a66808..00000000
Binary files a/examples/bitmaps/pencil.png and /dev/null differ
diff --git a/examples/bitmaps/plus.png b/examples/bitmaps/plus.png
deleted file mode 100644
index b99e1ebc..00000000
Binary files a/examples/bitmaps/plus.png and /dev/null differ
diff --git a/examples/bitmaps/pointer_move.png b/examples/bitmaps/pointer_move.png
deleted file mode 100644
index 3e2914b9..00000000
Binary files a/examples/bitmaps/pointer_move.png and /dev/null differ
diff --git a/examples/bitmaps/question_arrow.png b/examples/bitmaps/question_arrow.png
deleted file mode 100644
index 17a8f4f5..00000000
Binary files a/examples/bitmaps/question_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/right_ptr.png b/examples/bitmaps/right_ptr.png
deleted file mode 100644
index 88f6f9f2..00000000
Binary files a/examples/bitmaps/right_ptr.png and /dev/null differ
diff --git a/examples/bitmaps/right_side.png b/examples/bitmaps/right_side.png
deleted file mode 100644
index ee8f5e6d..00000000
Binary files a/examples/bitmaps/right_side.png and /dev/null differ
diff --git a/examples/bitmaps/right_tee.png b/examples/bitmaps/right_tee.png
deleted file mode 100644
index b881b1f4..00000000
Binary files a/examples/bitmaps/right_tee.png and /dev/null differ
diff --git a/examples/bitmaps/sb_down_arrow.png b/examples/bitmaps/sb_down_arrow.png
deleted file mode 100644
index 73d4f8dd..00000000
Binary files a/examples/bitmaps/sb_down_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/sb_h_double_arrow.png b/examples/bitmaps/sb_h_double_arrow.png
deleted file mode 100644
index 5e5c95c0..00000000
Binary files a/examples/bitmaps/sb_h_double_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/sb_left_arrow.png b/examples/bitmaps/sb_left_arrow.png
deleted file mode 100644
index 19f3c070..00000000
Binary files a/examples/bitmaps/sb_left_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/sb_right_arrow.png b/examples/bitmaps/sb_right_arrow.png
deleted file mode 100644
index 6031ee4b..00000000
Binary files a/examples/bitmaps/sb_right_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/sb_up_arrow.png b/examples/bitmaps/sb_up_arrow.png
deleted file mode 100644
index 5bc4589e..00000000
Binary files a/examples/bitmaps/sb_up_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/sb_v_double_arrow.png b/examples/bitmaps/sb_v_double_arrow.png
deleted file mode 100644
index f88dce20..00000000
Binary files a/examples/bitmaps/sb_v_double_arrow.png and /dev/null differ
diff --git a/examples/bitmaps/tcross.png b/examples/bitmaps/tcross.png
deleted file mode 100644
index f02f678d..00000000
Binary files a/examples/bitmaps/tcross.png and /dev/null differ
diff --git a/examples/bitmaps/top_left_corner.png b/examples/bitmaps/top_left_corner.png
deleted file mode 100644
index 73f50436..00000000
Binary files a/examples/bitmaps/top_left_corner.png and /dev/null differ
diff --git a/examples/bitmaps/top_right_corner.png b/examples/bitmaps/top_right_corner.png
deleted file mode 100644
index 1dac5edf..00000000
Binary files a/examples/bitmaps/top_right_corner.png and /dev/null differ
diff --git a/examples/bitmaps/top_side.png b/examples/bitmaps/top_side.png
deleted file mode 100644
index ff249bcf..00000000
Binary files a/examples/bitmaps/top_side.png and /dev/null differ
diff --git a/examples/bitmaps/top_tee.png b/examples/bitmaps/top_tee.png
deleted file mode 100644
index f25dab1a..00000000
Binary files a/examples/bitmaps/top_tee.png and /dev/null differ
diff --git a/examples/bitmaps/ul_angle.png b/examples/bitmaps/ul_angle.png
deleted file mode 100644
index 83682eb6..00000000
Binary files a/examples/bitmaps/ul_angle.png and /dev/null differ
diff --git a/examples/bitmaps/ur_angle.png b/examples/bitmaps/ur_angle.png
deleted file mode 100644
index 4367ae7c..00000000
Binary files a/examples/bitmaps/ur_angle.png and /dev/null differ
diff --git a/examples/bitmaps/vertical_text.png b/examples/bitmaps/vertical_text.png
deleted file mode 100644
index 35deec7a..00000000
Binary files a/examples/bitmaps/vertical_text.png and /dev/null differ
diff --git a/examples/bitmaps/wayland_cursor.png b/examples/bitmaps/wayland_cursor.png
deleted file mode 100644
index 2202d030..00000000
Binary files a/examples/bitmaps/wayland_cursor.png and /dev/null differ
diff --git a/examples/bitmaps/x_cursor.png b/examples/bitmaps/x_cursor.png
deleted file mode 100644
index ebbd82c0..00000000
Binary files a/examples/bitmaps/x_cursor.png and /dev/null differ
diff --git a/examples/bitmaps/xterm.png b/examples/bitmaps/xterm.png
deleted file mode 100644
index 9b23c0d4..00000000
Binary files a/examples/bitmaps/xterm.png and /dev/null differ
diff --git a/examples/bitmaps/zoom_in.png b/examples/bitmaps/zoom_in.png
deleted file mode 100644
index f5334418..00000000
Binary files a/examples/bitmaps/zoom_in.png and /dev/null differ
diff --git a/examples/bitmaps/zoom_out.png b/examples/bitmaps/zoom_out.png
deleted file mode 100644
index 08a03736..00000000
Binary files a/examples/bitmaps/zoom_out.png and /dev/null differ
diff --git a/examples/build.py b/examples/build.py
deleted file mode 100644
index 10ea2d24..00000000
--- a/examples/build.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from pathlib import Path
-from typing import Any, List, Tuple
-
-from clickgen.builders import WindowsCursor, XCursor
-from clickgen.core import CursorAlias
-from clickgen.packagers import WindowsPackager, XPackager
-from clickgen.util import add_missing_xcursors
-
-from examples.configure import get_config
-
-X_SIZES: List[Tuple[int, int]] = [(24, 24), (32, 32)]
-
-x_out_dir = Path("themes") / "Xpro"
-win_out_dir = Path("themes") / "Xpro-Windows"
-
-
-def win_build(item: Any, alias: CursorAlias) -> None:
-    position = item["position"]
-    size = item["size"]
-    win_key = item["win_key"]
-    canvas_size = item["canvas_size"]
-
-    win_cfg = alias.reproduce(size, canvas_size, position, delay=3).rename(win_key)
-    WindowsCursor.create(win_cfg, win_out_dir)
-
-
-def build() -> None:
-    config = get_config()
-
-    # Building
-    for _, item in config.items():
-        png = item["png"]
-        hotspot = item["hotspot"]
-        delay = item["delay"]
-
-        with CursorAlias.from_bitmap(png, hotspot) as alias:
-            x_cfg = alias.create(X_SIZES, delay)
-            XCursor.create(x_cfg, x_out_dir)
-
-            if item.get("win_key"):
-                win_build(item, alias)
-
-    theme_name: str = "Example"
-    comment: str = "Example theme generated from clickgen"
-    author: str = "Kaiz Khatri"
-    url: str = "https://github.com/ful1e5/clickgen/tree/main/examples"
-
-    add_missing_xcursors(x_out_dir / "cursors", rename=True, force=True)
-
-    XPackager(x_out_dir, theme_name, comment)
-    WindowsPackager(win_out_dir, theme_name, comment, author, url)
-
-
-build()
diff --git a/examples/configure.py b/examples/configure.py
deleted file mode 100644
index 0035b0eb..00000000
--- a/examples/configure.py
+++ /dev/null
@@ -1,151 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from pathlib import Path
-from typing import Any, Dict, List, Tuple, Union
-
-from clickgen.util import PNGProvider
-
-# XCursor
-X_DELAY: int = 10
-
-# Windows Cursor
-CANVAS_SIZE = (32, 32)
-SIZE = (24, 24)
-WIN_DELAY = 3
-
-X_CURSORS_CFG: Dict[str, Dict[str, int]] = {
-    #
-    # Static
-    #
-    "dotbox.png": {"xhot": 100, "yhot": 100},
-    "move.png": {"xhot": 100, "yhot": 100},
-    "plus.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": 100},
-    "bd_double_arrow.png": {"xhot": 98, "yhot": 100},
-    "fd_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},
-    "link.png": {"xhot": 48, "yhot": 25},
-    "pointer_move.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_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_no_drop.png": {"xhot": 86, "yhot": 79},
-    "dnd_none.png": {"xhot": 99, "yhot": 98},
-    "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},
-    "ll_angle.png": {"xhot": 34, "yhot": 165},
-    "lr_angle.png": {"xhot": 167, "yhot": 164},
-    "pencil.png": {"xhot": 37, "yhot": 161},
-    "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_v_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},
-    "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},
-    "zoom_in.png": {"xhot": 90, "yhot": 89},
-    "zoom_out.png": {"xhot": 93, "yhot": 90},
-    #
-    # Animated
-    #
-    # Note: Animated cursors not need any extension & frames number
-    "left_ptr_watch": {"xhot": 50, "yhot": 28, "delay": 20},
-    "wait": {"xhot": 100, "yhot": 100, "delay": 20},
-}
-
-WIN_CURSORS_CFG: Dict[str, Dict[str, str]] = {
-    #
-    # Static
-    #
-    "right_ptr.png": {"to": "Alternate", "position": "top_left"},
-    "cross.png": {"to": "Cross"},
-    "left_ptr.png": {"to": "Default", "position": "top_left"},
-    "fd_double_arrow.png": {"to": "Diagonal_1"},
-    "bd_double_arrow.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"},
-    "circle.png": {"to": "Unavailiable", "position": "top_left"},
-    "sb_v_double_arrow.png": {"to": "Vertical"},
-    #
-    # Animated
-    #
-    # Note: Animated cursors not need any extension & frames number
-    "wait": {"to": "Busy", "size": (28, 28)},
-    "left_ptr_watch": {"to": "Work", "position": "top_left", "size": (28, 28)},
-}
-
-
-def get_config() -> Dict[str, Any]:
-
-    png = PNGProvider("bitmaps")
-    config: Dict[str, Any] = {}
-
-    for key, item in X_CURSORS_CFG.items():
-        x_hot: int = item.get("x_hot", 0)
-        y_hot: int = item.get("y_hot", 0)
-        hotspot: Tuple[int, int] = (x_hot, y_hot)
-
-        delay: int = item.get("delay", X_DELAY)
-        p: Union[List[Path], Path] = png.get(key)
-
-        data = {
-            "png": p,
-            "hotspot": hotspot,
-            "delay": delay,
-        }
-
-        win_data = WIN_CURSORS_CFG.get(key)
-
-        if win_data:
-            win_key = win_data.get("to")
-
-            position = win_data.get("position", "center")
-            canvas_size: Tuple[int, int] = win_data.get("canvas_size", CANVAS_SIZE)
-            size: Tuple[int, int] = win_data.get("size", SIZE)
-            win_delay: int = win_data.get("delay", WIN_DELAY)
-
-            config[key] = {
-                **data,
-                "win_key": win_key,
-                "position": position,
-                "canvas_size": canvas_size,
-                "size": size,
-                "win_delay": win_delay,
-            }
-        else:
-            config[key] = data
-
-    return config
diff --git a/pyproject.toml b/pyproject.toml
index f584ae2b..cf180809 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,6 +7,7 @@ build-backend = "setuptools.build_meta"
 
 [tool.pytest.ini_options]
 addopts = "-s -vv --cache-clear"
+pythonpath=["src"]
 testpaths = [
     "tests",
 ]
diff --git a/requirements.dev.txt b/requirements.dev.txt
index f2f7421b..a296e4ed 100644
--- a/requirements.dev.txt
+++ b/requirements.dev.txt
@@ -1,6 +1,5 @@
 flake8==4.0.1
-tox==3.24.5
+tox==3.25.0
 pytest==7.1.1
 pytest-cov==3.0.0
 mypy==0.942
-coverage==6.3.2
diff --git a/samples/build.py b/samples/build.py
new file mode 100644
index 00000000..e1d95b0d
--- /dev/null
+++ b/samples/build.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import traceback
+from glob import glob
+from pathlib import Path
+from threading import Lock
+from typing import List
+
+from clickgen.parser import open_blob
+from clickgen.writer import to_win, to_x11
+
+if __name__ == "__main__":
+    print_lock = Lock()
+
+    # mkdir 'out'
+    out = Path("out")
+    out.mkdir(parents=True, exist_ok=True)
+
+    fnames = glob("pngs/wait-*.png")
+    pngs: List[bytes] = []
+    for f in sorted(fnames):
+        with open(f, "rb") as p:
+            pngs.append(p.read())
+
+    try:
+        ani = open_blob(pngs, hotspot=(100, 100))
+
+        # save Windows animated cursor
+        aext, aresult = to_win(ani.frames)
+        with open(out / f"test-ani{aext}", "wb") as o:
+            o.write(aresult)
+
+        # save X11 animated cursor
+        axresult = to_x11(ani.frames)
+        with open(out / "xtest-ani", "wb") as o:
+            o.write(axresult)
+
+        with open("pngs/pointer.png", "rb") as p:
+            cur = open_blob([p.read()], hotspot=(50, 50))
+
+            # save Windows static cursor
+            ext, result = to_win(cur.frames)
+            with open(out / f"test{ext}", "wb") as o:
+                o.write(result)
+
+            # save X11 static cursor
+            xresult = to_x11(cur.frames)
+            with open(out / "xtest", "wb") as o:
+                o.write(xresult)
+    except Exception:
+        with print_lock:
+            print(f"Error occurred while processing ", file=sys.stderr)
+            traceback.print_exc()
+
+    print("Building ... DONE")
diff --git a/examples/bitmaps/left_ptr.png b/samples/pngs/pointer.png
similarity index 100%
rename from examples/bitmaps/left_ptr.png
rename to samples/pngs/pointer.png
diff --git a/examples/bitmaps/wait-001.png b/samples/pngs/wait-001.png
similarity index 100%
rename from examples/bitmaps/wait-001.png
rename to samples/pngs/wait-001.png
diff --git a/examples/bitmaps/wait-002.png b/samples/pngs/wait-002.png
similarity index 100%
rename from examples/bitmaps/wait-002.png
rename to samples/pngs/wait-002.png
diff --git a/examples/bitmaps/wait-003.png b/samples/pngs/wait-003.png
similarity index 100%
rename from examples/bitmaps/wait-003.png
rename to samples/pngs/wait-003.png
diff --git a/examples/bitmaps/wait-004.png b/samples/pngs/wait-004.png
similarity index 100%
rename from examples/bitmaps/wait-004.png
rename to samples/pngs/wait-004.png
diff --git a/examples/bitmaps/wait-005.png b/samples/pngs/wait-005.png
similarity index 100%
rename from examples/bitmaps/wait-005.png
rename to samples/pngs/wait-005.png
diff --git a/examples/bitmaps/wait-006.png b/samples/pngs/wait-006.png
similarity index 100%
rename from examples/bitmaps/wait-006.png
rename to samples/pngs/wait-006.png
diff --git a/examples/bitmaps/wait-007.png b/samples/pngs/wait-007.png
similarity index 100%
rename from examples/bitmaps/wait-007.png
rename to samples/pngs/wait-007.png
diff --git a/examples/bitmaps/wait-008.png b/samples/pngs/wait-008.png
similarity index 100%
rename from examples/bitmaps/wait-008.png
rename to samples/pngs/wait-008.png
diff --git a/examples/bitmaps/wait-009.png b/samples/pngs/wait-009.png
similarity index 100%
rename from examples/bitmaps/wait-009.png
rename to samples/pngs/wait-009.png
diff --git a/examples/bitmaps/wait-010.png b/samples/pngs/wait-010.png
similarity index 100%
rename from examples/bitmaps/wait-010.png
rename to samples/pngs/wait-010.png
diff --git a/examples/bitmaps/wait-011.png b/samples/pngs/wait-011.png
similarity index 100%
rename from examples/bitmaps/wait-011.png
rename to samples/pngs/wait-011.png
diff --git a/examples/bitmaps/wait-012.png b/samples/pngs/wait-012.png
similarity index 100%
rename from examples/bitmaps/wait-012.png
rename to samples/pngs/wait-012.png
diff --git a/examples/bitmaps/wait-013.png b/samples/pngs/wait-013.png
similarity index 100%
rename from examples/bitmaps/wait-013.png
rename to samples/pngs/wait-013.png
diff --git a/examples/bitmaps/wait-014.png b/samples/pngs/wait-014.png
similarity index 100%
rename from examples/bitmaps/wait-014.png
rename to samples/pngs/wait-014.png
diff --git a/examples/bitmaps/wait-015.png b/samples/pngs/wait-015.png
similarity index 100%
rename from examples/bitmaps/wait-015.png
rename to samples/pngs/wait-015.png
diff --git a/examples/bitmaps/wait-016.png b/samples/pngs/wait-016.png
similarity index 100%
rename from examples/bitmaps/wait-016.png
rename to samples/pngs/wait-016.png
diff --git a/examples/bitmaps/wait-017.png b/samples/pngs/wait-017.png
similarity index 100%
rename from examples/bitmaps/wait-017.png
rename to samples/pngs/wait-017.png
diff --git a/examples/bitmaps/wait-018.png b/samples/pngs/wait-018.png
similarity index 100%
rename from examples/bitmaps/wait-018.png
rename to samples/pngs/wait-018.png
diff --git a/examples/bitmaps/wait-019.png b/samples/pngs/wait-019.png
similarity index 100%
rename from examples/bitmaps/wait-019.png
rename to samples/pngs/wait-019.png
diff --git a/examples/bitmaps/wait-020.png b/samples/pngs/wait-020.png
similarity index 100%
rename from examples/bitmaps/wait-020.png
rename to samples/pngs/wait-020.png
diff --git a/examples/bitmaps/wait-021.png b/samples/pngs/wait-021.png
similarity index 100%
rename from examples/bitmaps/wait-021.png
rename to samples/pngs/wait-021.png
diff --git a/examples/bitmaps/wait-022.png b/samples/pngs/wait-022.png
similarity index 100%
rename from examples/bitmaps/wait-022.png
rename to samples/pngs/wait-022.png
diff --git a/examples/bitmaps/wait-023.png b/samples/pngs/wait-023.png
similarity index 100%
rename from examples/bitmaps/wait-023.png
rename to samples/pngs/wait-023.png
diff --git a/examples/bitmaps/wait-024.png b/samples/pngs/wait-024.png
similarity index 100%
rename from examples/bitmaps/wait-024.png
rename to samples/pngs/wait-024.png
diff --git a/examples/bitmaps/wait-025.png b/samples/pngs/wait-025.png
similarity index 100%
rename from examples/bitmaps/wait-025.png
rename to samples/pngs/wait-025.png
diff --git a/examples/bitmaps/wait-026.png b/samples/pngs/wait-026.png
similarity index 100%
rename from examples/bitmaps/wait-026.png
rename to samples/pngs/wait-026.png
diff --git a/examples/bitmaps/wait-027.png b/samples/pngs/wait-027.png
similarity index 100%
rename from examples/bitmaps/wait-027.png
rename to samples/pngs/wait-027.png
diff --git a/examples/bitmaps/wait-028.png b/samples/pngs/wait-028.png
similarity index 100%
rename from examples/bitmaps/wait-028.png
rename to samples/pngs/wait-028.png
diff --git a/examples/bitmaps/wait-029.png b/samples/pngs/wait-029.png
similarity index 100%
rename from examples/bitmaps/wait-029.png
rename to samples/pngs/wait-029.png
diff --git a/examples/bitmaps/wait-030.png b/samples/pngs/wait-030.png
similarity index 100%
rename from examples/bitmaps/wait-030.png
rename to samples/pngs/wait-030.png
diff --git a/examples/bitmaps/wait-031.png b/samples/pngs/wait-031.png
similarity index 100%
rename from examples/bitmaps/wait-031.png
rename to samples/pngs/wait-031.png
diff --git a/examples/bitmaps/wait-032.png b/samples/pngs/wait-032.png
similarity index 100%
rename from examples/bitmaps/wait-032.png
rename to samples/pngs/wait-032.png
diff --git a/examples/bitmaps/wait-033.png b/samples/pngs/wait-033.png
similarity index 100%
rename from examples/bitmaps/wait-033.png
rename to samples/pngs/wait-033.png
diff --git a/examples/bitmaps/wait-034.png b/samples/pngs/wait-034.png
similarity index 100%
rename from examples/bitmaps/wait-034.png
rename to samples/pngs/wait-034.png
diff --git a/examples/bitmaps/wait-035.png b/samples/pngs/wait-035.png
similarity index 100%
rename from examples/bitmaps/wait-035.png
rename to samples/pngs/wait-035.png
diff --git a/examples/bitmaps/wait-036.png b/samples/pngs/wait-036.png
similarity index 100%
rename from examples/bitmaps/wait-036.png
rename to samples/pngs/wait-036.png
diff --git a/examples/bitmaps/wait-037.png b/samples/pngs/wait-037.png
similarity index 100%
rename from examples/bitmaps/wait-037.png
rename to samples/pngs/wait-037.png
diff --git a/examples/bitmaps/wait-038.png b/samples/pngs/wait-038.png
similarity index 100%
rename from examples/bitmaps/wait-038.png
rename to samples/pngs/wait-038.png
diff --git a/examples/bitmaps/wait-039.png b/samples/pngs/wait-039.png
similarity index 100%
rename from examples/bitmaps/wait-039.png
rename to samples/pngs/wait-039.png
diff --git a/examples/bitmaps/wait-040.png b/samples/pngs/wait-040.png
similarity index 100%
rename from examples/bitmaps/wait-040.png
rename to samples/pngs/wait-040.png
diff --git a/examples/bitmaps/wait-041.png b/samples/pngs/wait-041.png
similarity index 100%
rename from examples/bitmaps/wait-041.png
rename to samples/pngs/wait-041.png
diff --git a/examples/bitmaps/wait-042.png b/samples/pngs/wait-042.png
similarity index 100%
rename from examples/bitmaps/wait-042.png
rename to samples/pngs/wait-042.png
diff --git a/examples/bitmaps/wait-043.png b/samples/pngs/wait-043.png
similarity index 100%
rename from examples/bitmaps/wait-043.png
rename to samples/pngs/wait-043.png
diff --git a/examples/bitmaps/wait-044.png b/samples/pngs/wait-044.png
similarity index 100%
rename from examples/bitmaps/wait-044.png
rename to samples/pngs/wait-044.png
diff --git a/examples/bitmaps/wait-045.png b/samples/pngs/wait-045.png
similarity index 100%
rename from examples/bitmaps/wait-045.png
rename to samples/pngs/wait-045.png
diff --git a/examples/bitmaps/wait-046.png b/samples/pngs/wait-046.png
similarity index 100%
rename from examples/bitmaps/wait-046.png
rename to samples/pngs/wait-046.png
diff --git a/samples/sample.cfg b/samples/sample.cfg
new file mode 100644
index 00000000..3d0d364b
--- /dev/null
+++ b/samples/sample.cfg
@@ -0,0 +1,18 @@
+[theme]
+name=Sample
+comment=This is sample cursor theme
+website=https://www.example.com/
+
+[config]
+bitmaps=pngs
+out=out
+sizes=22, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96
+platforms=windows, x11
+
+[config.hotspots]
+a=(10,10)
+b=(10,10)
+
+[config.x11.symlinks]
+a=c,
+b=d, e, f 
diff --git a/setup.cfg b/setup.cfg
index 91057de8..6362ae98 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,13 +1,13 @@
 [metadata]
 name = clickgen
-version = attr: clickgen.VERSION
+version = attr: clickgen.__version__
 author = Kaiz Khatri
 author_email = kaizmandhu@gmail.com
 description = The hassle-free cursor building toolbox.
 long_description = file: README.md
 long_description_content_type = text/markdown
 url = https://github.com/ful1e5/clickgen
-keywords = cursor, xcursor, windows, linux, anicursorgen, xcursorgen
+keywords = cursor, xcursor, windows, linux
 project_urls =
     Source = https://github.com/ful1e5/clickgen
     Download = https://pypi.org/project/clickgen/#files
@@ -21,6 +21,7 @@ classifiers =
     License :: OSI Approved :: MIT License
     Programming Language :: C
     Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: Implementation :: CPython
@@ -31,9 +32,8 @@ classifiers =
     Typing :: Typed
 
 [options]
-python_requires = >=3.8
+python_requires = >=3.7
 include_package_data = True
-zip_safe = True
 packages = clickgen
 install_requires =
     Pillow>=8.1.1
@@ -42,7 +42,7 @@ package_dir =
 
 [options.package_data]
 * = LICENSE, README.md
-clickgen = *.pyi, py.typed, xcursorgen.so
+clickgen = *.pyi, py.typed
 
 [options.entry_points]
 console_scripts =
@@ -51,13 +51,13 @@ console_scripts =
 [options.extras_require]
 test =
     flake8>=4.0.1
-    tox>=3.24.5
+    tox>=3.25.0
     mypy>=0.940
     pytest>=7.0.1
     pytest-cov>=3.0.0
     coverage==6.3.2
 doc =
     sphinx==4.4.0
-    furo==2022.3.4
+    furo==2022.4.7
     readthedocs-sphinx-search==0.1.1
     sphinx-autoapi==1.8.4
diff --git a/src/clickgen/__init__.py b/src/clickgen/__init__.py
index 848258f9..27433f05 100644
--- a/src/clickgen/__init__.py
+++ b/src/clickgen/__init__.py
@@ -1,1 +1,4 @@
-VERSION = "1.2.0"
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__version__ = "2.0.0"
diff --git a/src/clickgen/__init__.pyi b/src/clickgen/__init__.pyi
index 3acee936..e69de29b 100644
--- a/src/clickgen/__init__.pyi
+++ b/src/clickgen/__init__.pyi
@@ -1,1 +0,0 @@
-VERSION: str
diff --git a/src/clickgen/builders.py b/src/clickgen/builders.py
deleted file mode 100644
index 8e152652..00000000
--- a/src/clickgen/builders.py
+++ /dev/null
@@ -1,766 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""Generate Cursors without hassle.
-
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import ctypes
-import io
-import math
-import shlex
-from ctypes import CDLL
-from pathlib import Path
-from struct import pack
-from typing import Any, List, NamedTuple, Tuple, Union
-
-from PIL import Image, ImageFilter
-
-from clickgen import __path__
-from clickgen.core import CursorAlias
-from clickgen.util import remove_util
-
-clickgen_pypi_path = "".join(map(str, __path__))
-
-
-class XCursor:
-    """Build `XCursor` from the ``.in`` config file. \
-    This class call ``xcursorgen`` for generating static \
-    and animated ``xcursor``.
-    """
-
-    config_file: Path
-    prefix: Path
-    out_dir: Path
-    out: Path
-
-    # main function ctypes define
-    _lib_location: Path = Path(clickgen_pypi_path) / "xcursorgen.so"
-    _lib: CDLL = CDLL(str(_lib_location.absolute()))
-    _LP_c_char = ctypes.POINTER(ctypes.c_char)
-    _LP_LP_c_char = ctypes.POINTER(_LP_c_char)
-    _lib.main.argtypes = (ctypes.c_int, _LP_LP_c_char)
-
-    def __init__(self, config_file: Path, out_dir: Path) -> None:
-        """
-        :param config_file: Cursor config file location.
-        :type config_file: ``pathlib.Path``
-
-        :param out_dir: directory path where ``xcursor`` generated.
-        :type out_dir: ``pathlib.Path``
-
-        :raise FileNotFoundError: If ``config_file`` not existed on \
-                filesystem.
-        """
-        if not config_file.exists() or not config_file.is_file():
-            raise FileNotFoundError(
-                f"'{config_file.name}' is not found or not a config file"
-            )
-
-        self.config_file: Path = config_file
-        self.prefix = config_file.parent
-        self.out_dir = out_dir / "cursors"
-        self.out_dir.mkdir(parents=True, exist_ok=True)
-        self.out = self.out_dir / self.config_file.stem
-
-    def gen_argv_ctypes(self, argv: List[str]) -> Any:
-        """Convert `string` arguments to `ctypes` pointer.
-
-        :param argv: ``xcursorgen`` command-line arguments. First argument \
-                must be named of program, Here it's ``xcursorgen``.
-        :type argv: List[str]
-
-        :returns: **Casted** command line arguments for *xcursorgen* with \
-                ``ctype``.
-        :rtype: ctypes.POINTER(ctypes.POINTER(ctypes.c_char))
-        """
-        p = (self._LP_c_char * len(argv))()  # type: ignore
-
-        for i, arg in enumerate(argv):
-            enc_arg: bytes = str(arg).encode("utf-8")
-            p[i] = ctypes.create_string_buffer(enc_arg)
-
-        return ctypes.cast(p, self._LP_LP_c_char)
-
-    def generate(self) -> None:
-        """ Generate the ``xcursor`` at ``out_dir``.
-
-        :raise RuntimeError: unable to generate ``xcursor`` from \
-                ``config_file``.
-        """
-        # remove old cursor file
-        remove_util(self.out)
-
-        argv: List[str] = [
-            "xcursorgen",
-            # prefix args for xcursorgen (do not remove)
-            "-p",
-            str(self.prefix.absolute()),
-            # cursor's config/alias file
-            str(self.config_file.absolute()),
-            # xcursor/output path
-            str(self.out.absolute()),
-        ]
-
-        kwargs: ctypes.pointer[ctypes.c_char] = self.gen_argv_ctypes(argv)
-        args: ctypes.c_int = ctypes.c_int(len(argv))
-
-        exec_with_error: bool = bool(self._lib.main(args, kwargs))
-        if exec_with_error:
-            raise RuntimeError(
-                f"'xcursorgen' failed to generate XCursor from '{self.config_file.name}'"
-            )
-
-    @classmethod
-    def create(cls, alias_file: Path, out_dir: Path) -> Path:
-        """ Class method for generate ``xcursor`` from cursor's \
-        config files.
-
-        This method gives ability to generate ``xcursor`` with initiate the \
-        XCursor object.
-
-        :returns: Generated ``XCursor`` pathlib.Path object.
-        :rtype: ``pathlib.Path``
-
-        """
-        cursor = cls(alias_file, out_dir)
-        cursor.generate()
-        return cursor.out
-
-    @classmethod
-    def from_bitmap(cls, **kwargs) -> Path:
-        if "png" not in kwargs:
-            raise KeyError("argument 'png' required")
-        elif "hotspot" not in kwargs:
-            raise KeyError("argument 'hotspot' required")
-        elif "x_sizes" not in kwargs:
-            raise KeyError("argument 'x_sizes' required")
-        elif "out_dir" not in kwargs:
-            raise KeyError("argument 'out_dir' required")
-
-        with CursorAlias.from_bitmap(kwargs["png"], kwargs["hotspot"]) as alias:
-            x_cfg: Path
-            if alias.bitmap.animated:
-                if "delay" not in kwargs:
-                    raise KeyError("argument 'delay' required")
-                else:
-                    x_cfg = alias.create(kwargs["x_sizes"], kwargs["delay"])
-            else:
-                x_cfg = alias.create(kwargs["x_sizes"])
-            cursor = cls(x_cfg, kwargs["out_dir"])
-            cursor.generate()
-            return cursor.out
-
-
-Color = Tuple[int, int, int, int]
-
-
-class Options(NamedTuple):
-    """Structure `anicursorgen.py` CLI arguments.
-
-    :param add_shadow: Do not generate shadows for cursors \
-            (assign False to cancel its effect).
-    :type add_shadow: bool
-
-    :param blur: Blur radius, in percentage of the canvas size \
-            (default is 3.125, set to 0 to disable blurring).
-    :type blur: float
-
-    :param color: Shadow color in (RR,GG,BB,AA) \
-            (default is (0, 0, 0, 64)).
-    :type color: Tuple[int, int, int, int]
-
-    :param down_shift: Shift shadow down by this percentage of the \
-            canvas size (default is 3.125)
-    :type down_shift: float
-
-    :param right_shift: Shift shadow right by this percentage of the\
-            canvas size (default is 9.375).
-    :type right_shift: float
-    """
-
-    add_shadows: bool = False
-    blur: float = 3.125
-    color: Color = (0, 0, 0, 64)
-    down_shift: float = 3.125
-    right_shift: float = 9.375
-
-
-ConfigFrame = Tuple[int, int, int, str, int]
-
-
-class WindowsCursor:
-    """Build **Windows cursors** from ``.in`` configs files. This class \
-    code is inspired on `anicursorgen.py`.
-
-    Yaru Icons \
-        <https://github.com/ubuntu/yaru/blob/master/icons/src/cursors/anicursorgen.py>
-
-    Copyright (C) 2015 Руслан Ижбулатов <lrn1986@gmail.com>
-    Copyright (C) 2021 Kaiz Khatri <kaizmandhu@gmail.com>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-    """
-
-    options: Options
-    config_file: Path
-    prefix: Path
-    out_dir: Path
-    out: Path
-
-    def __init__(self, config_file: Path, out_dir: Path, options: Options) -> None:
-        """Initiate WindowsCursor instance.
-
-        :param config_file: Cursor config file location.
-        :type config_file: ``pathlib.Path``
-
-        :param out_dir: directory path where ``Windows Cursor`` generated.
-        :type out_dir: ``pathlib.Path``
-
-        :param options: ``anicursorgen`` commandline arguments.
-        :type options: Options
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        self.config_file = config_file
-        self.prefix = config_file.parent
-        self.out_dir = out_dir
-        self.options = options
-
-        self.out_dir.mkdir(exist_ok=True, parents=True)
-
-    def get_frames(self) -> List[ConfigFrame]:
-        """Internal method for passing cursor's config file to ``List``.
-
-        :returns: This method is return frames of ``config_file``. **Frames** \
-                are basically line of config file structure with python \
-                typing ``Tuple``.
-        :rtype: ``List[ConfigFrame]``
-        """
-
-        in_buffer = self.config_file.open("r")
-        frames = []
-
-        for line in in_buffer.readlines():
-            words = shlex.split(line.rstrip("\n").rstrip("\r"))
-
-            size = int(words[0])
-            xhot = int(words[1]) - 1
-            yhot = int(words[2]) - 1
-            filename = words[3]
-            if not Path(filename).is_absolute():
-                filename = str(self.prefix / filename)
-
-            if len(words) > 4:
-                duration = int(words[4])
-            else:
-                duration = 0
-
-            frames.append((size, xhot, yhot, filename, duration))
-
-        in_buffer.close()
-        return frames
-
-    @staticmethod
-    def frames_have_animation(frames: List[ConfigFrame]) -> bool:
-        """Internal **static method** for checking passed ``config_file`` is \
-                animated or not.
-
-        :returns: ``True`` for animated and ``False`` for static.
-        :rtype: ``bool``
-        """
-
-        sizes = set()
-        for frame in frames:
-            if frame[4] == 0:
-                continue
-            if frame[0] in sizes:
-                return True
-            sizes.add(frame[0])
-
-        return False
-
-    @staticmethod
-    def make_framesets(frames: List[ConfigFrame]) -> List[List[ConfigFrame]]:
-        """Internal **static method** for convert ``frames`` to ``framessets``.
-
-        **framessets** are group of similar pixel sizes. Each **frameset** is \
-                structured with python structure ``List``.
-
-        :param frames: ``config_file`` lines with List & Tuple typing.
-        :type frames: ``List[ConfigFrame]``
-
-        :returns: Grouped frames in ``List`` structure.
-        :rtype: ``List[List[ConfigFrame]]``
-
-        :raise ValueError: If config_file lines are not sorted with pixel \
-                size & frame number.
-        :raise ValueError: If some frames are missing from pixel size inside \
-                config_file.
-        :raise ValueError: If frame's **animation durations** are not same \
-                as other frame.
-        """
-
-        framesets: List[List[ConfigFrame]] = []
-        sizes = set()
-
-        # This assumes that frames are sorted
-        size: int = 0
-        counter: int = 0
-        for i, frame in enumerate(frames):
-
-            if size == 0 or frame[0] != size:
-                counter = 0
-                size = frame[0]
-
-                if size in sizes:
-                    raise ValueError(
-                        f"Frames are not sorted: frame {i} has size {size}, but we have seen that already"
-                    )
-
-                sizes.add(size)
-
-            if counter >= len(framesets):
-                framesets.append([])
-
-            framesets[counter].append(frame)
-            counter += 1
-
-        for i in range(1, len(framesets)):
-            if len(framesets[i - 1]) != len(framesets[i]):
-                raise ValueError(
-                    f"Frameset {i} has size {len(framesets[i])}, expected {len(framesets[i - 1])}"
-                )
-
-        for frameset in framesets:
-            for i in range(1, len(frameset)):
-                if frameset[i - 1][4] != frameset[i][4]:
-                    raise ValueError(
-                        f"Frameset {i} has duration {int(frameset[i][4])} for framesize {int(frameset[i][0])}, but {int(frameset[i - 1][4])} for framesize {int(frameset[i - 1][0])}"
-                    )
-        framesets = sorted(framesets, reverse=True)
-
-        return framesets
-
-    @staticmethod
-    def copy_to(out: Union[io.BytesIO, io.BufferedWriter], buf: io.BytesIO) -> None:
-        """Internal **static method** for copy buffer value to another.
-
-        :param out: Buffer where value copied.
-        :type out: ``io.BytesIO`` or ``io.BufferedWriter``
-
-        :param buf: input Buffer.
-        :type buf: ``io.BytesIO``
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        buf.seek(0, io.SEEK_SET)
-        while True:
-            b = buf.read(1024)
-            if len(b) == 0:
-                break
-            out.write(b)
-
-    def make_ani(
-        self,
-        frames: List[ConfigFrame],
-        out_buffer: Union[io.BytesIO, io.BufferedWriter],
-    ) -> None:
-        """Generate `.ani` from config file's ``frames``.
-
-        :param frames: List of ``config_file`` lines.
-        :type frames: ``List[ConfigFrame]``
-
-        :param out_buffer: Where `.ani` cursor data stored.
-        :type out_buffer: ``io.BytesIO`` or ``io.BufferedWriter``
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        framesets = self.make_framesets(frames)
-
-        buf = io.BytesIO()
-
-        buf.write(b"RIFF")
-        riff_len_pos = buf.seek(0, io.SEEK_CUR)
-        buf.write(pack("<I", 0))
-        riff_len_start = buf.seek(0, io.SEEK_CUR)
-
-        buf.write(b"ACON")
-        buf.write(b"anih")
-        buf.write(
-            pack(
-                "<IIIIIIIIII",
-                36,
-                36,
-                len(framesets),
-                len(framesets),
-                0,
-                0,
-                32,
-                1,
-                int(framesets[0][0][4]),
-                0x01,
-            )
-        )
-        rates = {frameset[0][4] for frameset in framesets}
-        if len(rates) != 1:
-            buf.write(b"rate")
-            buf.write(pack("<I", len(framesets) * 4))
-            for frameset in framesets:
-                buf.write(pack("<I", frameset[0][4]))
-
-        buf.write(b"LIST")
-        list_len_pos = buf.seek(0, io.SEEK_CUR)
-        buf.write(pack("<I", 0))
-        list_len_start = buf.seek(0, io.SEEK_CUR)
-
-        buf.write(b"fram")
-
-        for frameset in framesets:
-            buf.write(b"icon")
-            cur = self.make_cur(frameset, animated=True)
-            cur_size = cur.seek(0, io.SEEK_END)
-            # aligned_cur_size = cur_size
-            # if cur_size % 4 != 0:
-            #  aligned_cur_size += 4 - cur_size % 2
-            buf.write(pack("<i", cur_size))
-            self.copy_to(buf, cur)
-            pos = buf.seek(0, io.SEEK_END)
-            if pos % 2 != 0:
-                buf.write(("\x00" * (2 - (pos % 2))).encode())
-
-        end_at = buf.seek(0, io.SEEK_CUR)
-        buf.seek(riff_len_pos, io.SEEK_SET)
-        buf.write(pack("<I", end_at - riff_len_start))
-        buf.seek(list_len_pos, io.SEEK_SET)
-        buf.write(pack("<I", end_at - list_len_start))
-
-        self.copy_to(out_buffer, buf)
-
-    @staticmethod
-    def shadowize(shadow: Image.Image, orig: Image.Image, color: Color) -> None:
-        """Create shadow effect to the cursor.
-
-        This effect is enable by providing ``options`` parameter \
-        inside constructor
-
-        :param shadow: PIL.Image.Image instance where shadowed image will stored.
-        :type frames: ``PIL.Image.Image``
-
-        :param orig: Cursor PIL.Image.Image instance frame.
-        :type orig: ``PIL.Image.Image``
-
-        :param color: Shadow color in ``Tuple`` structure.(RGBA format)
-        :type color: ``Tuple[int, int, int, int]``
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        o_pxs: Any = orig.load()
-        s_pxs: Any = shadow.load()
-        for y in range(orig.size[1]):
-            for x in range(orig.size[0]):
-                o_px = o_pxs[x, y]
-                if o_px[3] > 0:
-                    s_pxs[x, y] = (
-                        int(color[0]),
-                        int(color[1]),
-                        int(color[2]),
-                        int(color[3] * (o_px[3] / 255.0)),
-                    )
-
-    def create_shadow(self, orig: Image.Image) -> Tuple[int, Image.Image]:
-        """Add shadows to Windows Cursor.
-
-        This effect is enable by providing ``options`` parameter \
-        inside constructor
-
-        :param orig: Cursor PIL.Image.Image instance frame.
-        :type orig: ``PIL.Image.Image``
-
-        :returns: Tuple of code status and Image instance.
-        :rtype: ``Tuple[int, PIL.Image.Image]``
-        """
-
-        blur_px = orig.size[0] / 100.0 * self.options.blur
-        right_px = int(orig.size[0] / 100.0 * self.options.right_shift)
-        down_px = int(orig.size[1] / 100.0 * self.options.down_shift)
-
-        shadow = Image.new("RGBA", orig.size, (0, 0, 0, 0))
-        self.shadowize(shadow, orig, self.options.color)
-        shadow.load()
-
-        if self.options.blur > 0:
-            crop = (
-                int(math.floor(-blur_px)),
-                int(math.floor(-blur_px)),
-                orig.size[0] + int(math.ceil(blur_px)),
-                orig.size[1] + int(math.ceil(blur_px)),
-            )
-            right_px += int(math.floor(-blur_px))
-            down_px += int(math.floor(-blur_px))
-            shadow = shadow.crop(crop)
-            flt = ImageFilter.GaussianBlur(blur_px)
-            shadow = shadow.filter(flt)
-        shadow.load()
-
-        shadowed = Image.new("RGBA", orig.size, (0, 0, 0, 0))
-        shadowed.paste(shadow, (right_px, down_px))
-        shadowed.crop((0, 0, orig.size[0], orig.size[1]))
-        shadowed = Image.alpha_composite(shadowed, orig)
-
-        return (0, shadowed)
-
-    @staticmethod
-    def write_png(
-        out: Union[io.BytesIO, io.BufferedWriter], frame_png: Image.Image
-    ) -> None:
-        """Write buffer value to PIL.Image.Image instance.
-
-        :param out: Buffer of `.png` frame.
-        :type out: ``io.BytesIO`` or ``io.BufferedWriter``
-
-        :param frame_png: ``.png`` frame PIL.Image.Image instance.
-        :type frame_png: ``PIL.Image.Image``
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        frame_png.save(out, "png", optimize=True)
-
-    @staticmethod
-    def write_cur(
-        out: Union[io.BytesIO, io.BufferedWriter],
-        frame: ConfigFrame,
-        frame_png: Image.Image,
-    ) -> None:
-        """Generate Windows Cursor data of single frame.
-
-        :param out: Buffer of `.png` frame.
-        :type out: ``io.BytesIO`` or ``io.BufferedWriter``
-
-        :param frame: ``config_file`` lines.
-        :type frame: ConfigFrame
-
-        :param frame_png: ``.png`` frame PIL.Image.Image instance.
-        :type frame_png: ``PIL.Image.Image``
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        pixels: Any = frame_png.load()
-
-        out.write(
-            pack("<I II HH IIIIII", 40, frame[0], frame[0] * 2, 1, 32, 0, 0, 0, 0, 0, 0)
-        )
-
-        for y in reversed(list(range(frame[0]))):
-            for x in range(frame[0]):
-                pixel = pixels[x, y]
-                out.write(pack("<BBBB", pixel[2], pixel[1], pixel[0], pixel[3]))
-
-        acc = 0
-        acc_pos = 0
-        for y in reversed(list(range(frame[0]))):
-            wrote = 0
-            for x in range(frame[0]):
-                if pixels[x, y][3] <= 127:
-                    acc = acc | (1 << acc_pos)
-                acc_pos += 1
-                if acc_pos == 8:
-                    acc_pos = 0
-                    out.write(chr(acc).encode())
-                    wrote += 1
-            if wrote % 4 != 0:
-                out.write(b"\x00" * (4 - wrote % 4))
-
-    def make_cur(self, frames: List[ConfigFrame], animated: bool = False) -> io.BytesIO:
-        """Generate `.cur` from config file's ``frames``.
-
-        :param frames: List of ``config_file`` lines.
-        :type frames: ``List[ConfigFrame]``
-
-        :param animated: Enable for compression.
-        :type animated: ``bool``
-
-        :returns: Buffer of `.cur` typed cursor.
-        :rtype: ``io.BytesIO``
-        """
-
-        buf = io.BytesIO()
-        buf.write(pack("<HHH", 0, 2, len(frames)))
-        frame_offsets = []
-
-        frames = sorted(frames, reverse=True)
-
-        for frame in frames:
-            width = frame[0]
-            if width > 255:
-                width = 0
-            height = width
-
-            xhot = 0 if frame[1] == -1 else frame[1]
-            yhot = 0 if frame[2] == -1 else frame[2]
-
-            buf.write(pack("<BBBB HH", width, height, 0, 0, xhot, yhot))
-            size_offset_pos = buf.seek(0, io.SEEK_CUR)
-
-            buf.write(pack("<II", 0, 0))
-            frame_offsets.append([size_offset_pos])
-
-        for i, frame in enumerate(frames):
-            frame_offset = buf.seek(0, io.SEEK_CUR)
-            frame_offsets[i].append(frame_offset)
-
-            frame_png = Image.open(frame[3])
-
-            if self.options.add_shadows:
-                succeeded, shadowed = self.create_shadow(frame_png)
-                if succeeded == 0:
-                    frame_png.close()
-                    frame_png = shadowed
-
-            #   Windows 10 fails to read PNG-compressed cursors for some reason
-            #   and the information about storing PNG-compressed cursors is
-            #   sparse. This is why PNG compression is not used.
-            #   Previously this was conditional on cursor size (<= 48 to be uncompressed).
-            compressed = False
-
-            #   On the other hand, Windows 10 refuses to read very large
-            #   uncompressed animated cursor files, but does accept
-            #   PNG-compressed animated cursors for some reason. Go figure.
-            if animated:
-                compressed = True
-
-            if compressed:
-                self.write_png(buf, frame_png)
-            else:
-                self.write_cur(buf, frame, frame_png)
-
-            frame_png.close()
-
-            frame_end = buf.seek(0, io.SEEK_CUR)
-            frame_offsets[i].append(frame_end - frame_offset)
-
-        for frame_offset in frame_offsets:
-            buf.seek(frame_offset[0])
-            buf.write(pack("<II", frame_offset[2], frame_offset[1]))
-
-        return buf
-
-    def generate(self) -> None:
-        """Generate Windows Cursor from ``config_file``.
-        Automatically identify cursor type ``.ani`` or ``.cur``.
-
-        :returns: None.
-        :rtype: ``None``
-        """
-
-        frames = self.get_frames()
-        is_animated = self.frames_have_animation(frames)
-
-        name = self.config_file.stem
-        ani_name = f"{name}.ani"
-        cur_name = f"{name}.cur"
-
-        cursor = ani_name if is_animated else cur_name
-        self.out = self.out_dir / cursor
-
-        # Remove Windows cursor, Which has the same 'name' inside 'output' directory.
-        # This is useful for rebuilding the cursor to 'semi-animated'.
-        # Remove only one extension can generate issues in the `WindowsPackager` module.
-        remove_util(self.out_dir / ani_name)
-        remove_util(self.out_dir / cur_name)
-
-        with self.out.open("wb") as out:
-            if is_animated:
-                self.make_ani(frames, out)
-            else:
-                buf = self.make_cur(frames)
-                self.copy_to(out, buf)
-
-    @classmethod
-    def create(cls, alias_file: Path, out_dir: Path, options=Options()) -> Path:
-        """This method gives ability to generate ``Windows Cursor`` without \
-        initiate the WindowsCursor object.
-
-        :param alias_file: Cursor config file location.
-        :type alias_file: ``pathlib.Path``
-
-        :param out_dir: directory path where ``Windows Cursor`` generated.
-        :type out_dir: ``pathlib.Path``
-
-        :param options: ``anicursorgen`` commandline arguments.
-        :type options: Options
-
-        :returns: Generated ``Windows Cursor`` pathlib.Path object.
-        :rtype: ``pathlib.Path``
-        """
-
-        cursor = cls(alias_file, out_dir, options)
-        cursor.generate()
-        return cursor.out
-
-    @classmethod
-    def from_bitmap(cls, **kwargs) -> Path:
-
-        if "png" not in kwargs:
-            raise KeyError("argument 'png' required")
-        elif "hotspot" not in kwargs:
-            raise KeyError("argument 'hotspot' required")
-        elif "size" not in kwargs:
-            raise KeyError("argument 'size' required")
-        elif "canvas_size" not in kwargs:
-            raise KeyError("argument 'canvas_size' required")
-        elif "out_dir" not in kwargs:
-            raise KeyError("argument 'out_dir' required")
-
-        position: str
-        if "position" in kwargs:
-            position = kwargs["position"]
-        else:
-            position = "center"
-
-        options: Options
-        if "options" in kwargs:
-            options = kwargs["options"]
-        else:
-            options = Options()
-
-        with CursorAlias.from_bitmap(kwargs["png"], kwargs["hotspot"]) as alias:
-            if alias.bitmap.animated:
-                if "delay" not in kwargs:
-                    raise KeyError("argument 'delay' required")
-                else:
-                    alias.create(kwargs["size"], kwargs["delay"])
-                    alias.reproduce(
-                        kwargs["size"], kwargs["canvas_size"], position, kwargs["delay"]
-                    )
-            else:
-                alias.create(kwargs["size"])
-                alias.reproduce(kwargs["size"], kwargs["canvas_size"], position)
-
-            cursor = cls(alias.alias_file, kwargs["out_dir"], options)
-            cursor.generate()
-            return cursor.out
diff --git a/src/clickgen/builders.pyi b/src/clickgen/builders.pyi
deleted file mode 100644
index d6c1f37d..00000000
--- a/src/clickgen/builders.pyi
+++ /dev/null
@@ -1,60 +0,0 @@
-import io
-from PIL import Image
-from clickgen import __path__ as __path__
-from clickgen.core import CursorAlias as CursorAlias
-from clickgen.util import remove_util as remove_util
-from pathlib import Path
-from typing import Any, List, NamedTuple, Tuple, Union
-
-clickgen_pypi_path: Any
-
-class XCursor:
-    config_file: Path
-    prefix: Path
-    out_dir: Path
-    out: Path
-    def __init__(self, config_file: Path, out_dir: Path) -> None: ...
-    def gen_argv_ctypes(self, argv: List[str]) -> Any: ...
-    def generate(self) -> None: ...
-    @classmethod
-    def create(cls, alias_file: Path, out_dir: Path) -> Path: ...
-    @classmethod
-    def from_bitmap(cls, **kwargs) -> Path: ...
-Color = Tuple[int, int, int, int]
-
-class Options(NamedTuple):
-    add_shadows: bool
-    blur: float
-    color: Color
-    down_shift: float
-    right_shift: float
-ConfigFrame = Tuple[int, int, int, str, int]
-
-class WindowsCursor:
-    options: Options
-    config_file: Path
-    prefix: Path
-    out_dir: Path
-    out: Path
-    def __init__(self, config_file: Path, out_dir: Path, options: Options) -> None: ...
-    def get_frames(self) -> List[ConfigFrame]: ...
-    @staticmethod
-    def frames_have_animation(frames: List[ConfigFrame]) -> bool: ...
-    @staticmethod
-    def make_framesets(frames: List[ConfigFrame]) -> List[List[ConfigFrame]]: ...
-    @staticmethod
-    def copy_to(out: Union[io.BytesIO, io.BufferedWriter], buf: io.BytesIO) -> None: ...
-    def make_ani(self, frames: List[ConfigFrame], out_buffer: Union[io.BytesIO, io.BufferedWriter]) -> None: ...
-    @staticmethod
-    def shadowize(shadow: Image.Image, orig: Image.Image, color: Color) -> None: ...
-    def create_shadow(self, orig: Image.Image) -> Tuple[int, Image.Image]: ...
-    @staticmethod
-    def write_png(out: Union[io.BytesIO, io.BufferedWriter], frame_png: Image.Image) -> None: ...
-    @staticmethod
-    def write_cur(out: Union[io.BytesIO, io.BufferedWriter], frame: ConfigFrame, frame_png: Image.Image) -> None: ...
-    def make_cur(self, frames: List[ConfigFrame], animated: bool = ...) -> io.BytesIO: ...
-    def generate(self) -> None: ...
-    @classmethod
-    def create(cls, alias_file: Path, out_dir: Path, options=...) -> Path: ...
-    @classmethod
-    def from_bitmap(cls, **kwargs) -> Path: ...
diff --git a/src/clickgen/configparser/__init__.py b/src/clickgen/configparser/__init__.py
new file mode 100644
index 00000000..6ab4f716
--- /dev/null
+++ b/src/clickgen/configparser/__init__.py
@@ -0,0 +1,11 @@
+import configparser
+
+from clickgen.configparser.sections import ClickgenConfig, parse_sections
+
+__all__ = ["parse_config_file", "ClickgenConfig"]
+
+
+def parse_config_file(fp: str) -> ClickgenConfig:
+    config = configparser.ConfigParser()
+    config.read(fp)
+    return parse_sections(config)
diff --git a/src/clickgen/configparser/__init__.pyi b/src/clickgen/configparser/__init__.pyi
new file mode 100644
index 00000000..da56cbe1
--- /dev/null
+++ b/src/clickgen/configparser/__init__.pyi
@@ -0,0 +1,3 @@
+from clickgen.configparser.sections import ClickgenConfig as ClickgenConfig
+
+def parse_config_file(fp: str) -> ClickgenConfig: ...
diff --git a/src/clickgen/configparser/sections/__init__.py b/src/clickgen/configparser/sections/__init__.py
new file mode 100644
index 00000000..e4cfca6b
--- /dev/null
+++ b/src/clickgen/configparser/sections/__init__.py
@@ -0,0 +1,32 @@
+from configparser import ConfigParser
+from dataclasses import dataclass
+from typing import Dict, List, Tuple
+
+from clickgen.configparser.sections.config import ConfigSection, parse_config_section
+from clickgen.configparser.sections.hotspots import parse_hotspot_section
+from clickgen.configparser.sections.symlinks import parse_symlinks_section
+from clickgen.configparser.sections.theme import ThemeSection, parse_theme_section
+
+__all__ = [
+    "parse_sections",
+    "parse_theme_section",
+    "parse_config_section",
+    "parse_hotspot_section",
+    "parse_symlinks_section",
+]
+
+
+@dataclass
+class ClickgenConfig:
+    theme: ThemeSection
+    config: ConfigSection
+    hotspots: Dict[str, Tuple[int, int]]
+    symlinks: Dict[str, List[str]]
+
+
+def parse_sections(cp: ConfigParser) -> ClickgenConfig:
+    theme = parse_theme_section(cp)
+    config = parse_config_section(cp)
+    hotspots = parse_hotspot_section(cp)
+    symlinks = parse_symlinks_section(cp)
+    return ClickgenConfig(theme, config, hotspots, symlinks)
diff --git a/src/clickgen/configparser/sections/__init__.pyi b/src/clickgen/configparser/sections/__init__.pyi
new file mode 100644
index 00000000..bc552a86
--- /dev/null
+++ b/src/clickgen/configparser/sections/__init__.pyi
@@ -0,0 +1,15 @@
+from clickgen.configparser.sections.config import ConfigSection, parse_config_section as parse_config_section
+from clickgen.configparser.sections.hotspots import parse_hotspot_section as parse_hotspot_section
+from clickgen.configparser.sections.symlinks import parse_symlinks_section as parse_symlinks_section
+from clickgen.configparser.sections.theme import ThemeSection, parse_theme_section as parse_theme_section
+from configparser import ConfigParser
+from typing import Dict, List, Tuple
+
+class ClickgenConfig:
+    theme: ThemeSection
+    config: ConfigSection
+    hotspots: Dict[str, Tuple[int, int]]
+    symlinks: Dict[str, List[str]]
+    def __init__(self, theme, config, hotspots, symlinks) -> None: ...
+
+def parse_sections(cp: ConfigParser) -> ClickgenConfig: ...
diff --git a/src/clickgen/configparser/sections/config.py b/src/clickgen/configparser/sections/config.py
new file mode 100644
index 00000000..615b933b
--- /dev/null
+++ b/src/clickgen/configparser/sections/config.py
@@ -0,0 +1,23 @@
+from configparser import ConfigParser
+from dataclasses import dataclass
+from pathlib import Path
+from typing import List
+
+from clickgen.configparser.utils import str_to_list
+
+
+@dataclass
+class ConfigSection:
+    bitmaps_dir: Path
+    out_dir: Path
+    sizes: List[int]
+    platforms: List[str]
+
+
+def parse_config_section(cp: ConfigParser) -> ConfigSection:
+    bitmaps_dir = Path(cp.get("config", "bitmaps"))
+    out_dir = Path(cp.get("config", "out"))
+    sizes = list(map(int, str_to_list(cp.get("config", "sizes"))))
+    platforms = str_to_list(cp.get("config", "platforms"))
+
+    return ConfigSection(bitmaps_dir, out_dir, sizes, platforms)
diff --git a/src/clickgen/configparser/sections/config.pyi b/src/clickgen/configparser/sections/config.pyi
new file mode 100644
index 00000000..f7f5beb2
--- /dev/null
+++ b/src/clickgen/configparser/sections/config.pyi
@@ -0,0 +1,13 @@
+from clickgen.configparser.utils import str_to_list as str_to_list
+from configparser import ConfigParser
+from pathlib import Path
+from typing import List
+
+class ConfigSection:
+    bitmaps_dir: Path
+    out_dir: Path
+    sizes: List[int]
+    platforms: List[str]
+    def __init__(self, bitmaps_dir, out_dir, sizes, platforms) -> None: ...
+
+def parse_config_section(cp: ConfigParser) -> ConfigSection: ...
diff --git a/src/clickgen/configparser/sections/hotspots.py b/src/clickgen/configparser/sections/hotspots.py
new file mode 100644
index 00000000..2c0f59d5
--- /dev/null
+++ b/src/clickgen/configparser/sections/hotspots.py
@@ -0,0 +1,11 @@
+from configparser import ConfigParser
+from typing import Dict, Tuple
+
+
+def parse_hotspot_section(cp: ConfigParser) -> Dict[str, Tuple[int, int]]:
+    d = cp["config.hotspots"]
+    result: Dict[str, Tuple[int, int]] = {}
+    for key, value in d.items():
+        hotspot = tuple(int(k.strip()) for k in value[1:-1].split(","))
+        result[key] = hotspot
+    return result
diff --git a/src/clickgen/configparser/sections/hotspots.pyi b/src/clickgen/configparser/sections/hotspots.pyi
new file mode 100644
index 00000000..152033c3
--- /dev/null
+++ b/src/clickgen/configparser/sections/hotspots.pyi
@@ -0,0 +1,4 @@
+from configparser import ConfigParser
+from typing import Dict, Tuple
+
+def parse_hotspot_section(cp: ConfigParser) -> Dict[str, Tuple[int, int]]: ...
diff --git a/src/clickgen/configparser/sections/symlinks.py b/src/clickgen/configparser/sections/symlinks.py
new file mode 100644
index 00000000..eabdab5a
--- /dev/null
+++ b/src/clickgen/configparser/sections/symlinks.py
@@ -0,0 +1,12 @@
+from configparser import ConfigParser
+from typing import Dict, List
+
+from clickgen.configparser.utils import str_to_list
+
+
+def parse_symlinks_section(cp: ConfigParser) -> Dict[str, List[str]]:
+    d = cp["config.x11.symlinks"]
+    result: Dict[str, List[str]] = {}
+    for key, value in d.items():
+        result[key] = str_to_list(value)
+    return result
diff --git a/src/clickgen/configparser/sections/symlinks.pyi b/src/clickgen/configparser/sections/symlinks.pyi
new file mode 100644
index 00000000..b0841ae5
--- /dev/null
+++ b/src/clickgen/configparser/sections/symlinks.pyi
@@ -0,0 +1,5 @@
+from clickgen.configparser.utils import str_to_list as str_to_list
+from configparser import ConfigParser
+from typing import Dict, List
+
+def parse_symlinks_section(cp: ConfigParser) -> Dict[str, List[str]]: ...
diff --git a/src/clickgen/configparser/sections/theme.py b/src/clickgen/configparser/sections/theme.py
new file mode 100644
index 00000000..f710a20f
--- /dev/null
+++ b/src/clickgen/configparser/sections/theme.py
@@ -0,0 +1,16 @@
+from configparser import ConfigParser
+from dataclasses import dataclass
+
+
+@dataclass
+class ThemeSection:
+    name: str
+    comment: str
+    website: str
+
+
+def parse_theme_section(cp: ConfigParser) -> ThemeSection:
+    name = cp.get("theme", "name")
+    comment = cp.get("theme", "comment")
+    website = cp.get("theme", "website")
+    return ThemeSection(name, comment, website)
diff --git a/src/clickgen/configparser/sections/theme.pyi b/src/clickgen/configparser/sections/theme.pyi
new file mode 100644
index 00000000..22137284
--- /dev/null
+++ b/src/clickgen/configparser/sections/theme.pyi
@@ -0,0 +1,9 @@
+from configparser import ConfigParser
+
+class ThemeSection:
+    name: str
+    comment: str
+    website: str
+    def __init__(self, name, comment, website) -> None: ...
+
+def parse_theme_section(cp: ConfigParser) -> ThemeSection: ...
diff --git a/src/clickgen/configparser/utils.py b/src/clickgen/configparser/utils.py
new file mode 100644
index 00000000..460e2091
--- /dev/null
+++ b/src/clickgen/configparser/utils.py
@@ -0,0 +1,7 @@
+from typing import List
+
+
+def str_to_list(s: str) -> List[str]:
+    str_l = s.split(",")
+    results = [s.strip() for s in str_l]
+    return list(filter(None, results))
diff --git a/src/clickgen/configparser/utils.pyi b/src/clickgen/configparser/utils.pyi
new file mode 100644
index 00000000..b3a743ab
--- /dev/null
+++ b/src/clickgen/configparser/utils.pyi
@@ -0,0 +1,3 @@
+from typing import List
+
+def str_to_list(s: str) -> List[str]: ...
diff --git a/src/clickgen/core.py b/src/clickgen/core.py
deleted file mode 100644
index 64eff942..00000000
--- a/src/clickgen/core.py
+++ /dev/null
@@ -1,830 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import shutil
-from copy import deepcopy
-from pathlib import Path
-from tempfile import mkdtemp
-from typing import Dict, List, Optional, Tuple, Union
-
-from PIL import Image
-
-from clickgen.util import remove_util
-
-# Typing
-Size = Tuple[int, int]
-LikePath = Union[str, Path]
-LikePathList = Union[List[str], List[Path]]
-
-
-class Bitmap:
-    """The ``Bitmap`` class is used to represent a ``.png`` or sequences of \
-    ``.png`` image. The class also provides a number of factory functions, \
-    including functions to **rename** ``.png`` images from files, and \
-    **reproduce** size of same image/s.
-    """
-
-    animated: bool
-    png: Path
-    grouped_png: List[Path]
-
-    key: str
-
-    x_hot: int
-    y_hot: int
-
-    size: Tuple[int, int]
-    width: int
-    height: int
-
-    compress: int = 0
-
-    def __init__(
-        self,
-        png: Union[LikePath, LikePathList],
-        hotspot: Tuple[int, int],
-    ) -> None:
-        """
-        :param png: File location. Use ``List`` for animated Cursor.
-        :type png: ``str`` or ``pathlib.Path`` or ``List[str]`` or ``List[pathlib.Path]``
-
-        :param hotspot: Hotspot is coordinate value in Tuple. Cursor change \
-                        state is calculated from this value.
-        :type hotspot: ``Tuple[int, int]``
-
-        :returns: None.
-        :rtype: ``None``
-
-        :raise TypeError: If provided ``.png`` file/s location is not type \
-                          **str** or **pathlib.Path**
-        """
-
-        super().__init__()
-
-        # Is png == _P             => 'static' bitmap
-        # Or png == [_P, _P, ...]  => 'animated' bitmap
-        # Or png == [_P]           => 'static' bitmap
-        # else TypeError()
-
-        err: str = f"argument should be a 'str' object or 'Path' object , not \
-              {type(png)}"
-
-        if isinstance(png, (str, Path)):
-            self.__set_as_static(png, hotspot)
-
-        elif isinstance(png, list):
-            if len(png) == 1:
-                self.__set_as_static(png[0], hotspot)
-            else:
-                self.__set_as_animated(png, hotspot)
-        else:
-            raise TypeError(err)
-
-    def __str__(self) -> str:
-        common: str = f"key={self.key}, animated={self.animated}, size={self.size}, width={self.width}, height={self.height}, x_hot={self.x_hot}, y_hot={self.y_hot}"
-        if self.animated:
-            return f"Bitmap(grouped_png={self.grouped_png}, {common})"
-        return f"Bitmap(png={self.png}, {common})"
-
-    def __repr__(self) -> str:
-        common: str = f"'key':'{self.key}', 'animated':{self.animated}, 'size':{self.size}, 'width':{self.width}, 'height':{self.height}, 'x_hot':{self.x_hot}, 'y_hot':{self.y_hot}"
-        if self.animated:
-            return f"{{ 'grouped_png':{self.grouped_png}, {common} }}"
-        return f"{{ 'png':{self.png}, {common} }}"
-
-    # Context manager support
-    def __enter__(self) -> "Bitmap":
-        return self
-
-    def __exit__(self, exception_type, exception_value, traceback):  # type: ignore
-        return
-
-    #
-    # Private methods
-    #
-    def __set_as_static(self, png: LikePath, hotspot: Tuple[int, int]) -> None:
-        """Set this Bitmap as **static**, It means this bitmap hold single ``png``.
-
-                .. note:: This method called by ``self.__init__``.
-
-        :param png: ``.png`` file location.
-        :type png: ``str`` or ``pathlib.Path``
-
-        :param hotspot: Hotspot is coordinate value in Tuple. Cursor change \
-                        state is calculated from this value.
-        :type hotspot: ``Tuple[int, int]``
-
-        :returns: None.
-        :rtype: ``None``
-
-        :raise FileNotFoundError: If ``.png`` file not found
-        :raise ValueError: If provided bitmap is not ``.png``
-        :raise ValueError: If image width & height are not same
-        :raise ValueError: If grouped ``.png`` files naming is invalid
-        :raise ValueError: If hotspot is set higher or lower then image pixels
-        """
-
-        self.png = self._check_bitmap(png)
-        self._set_key(self.png, check=False)
-        self._set_size(self.png)
-        self._set_hotspot(self.png, hotspot)
-        self.animated = False
-
-    def __set_as_animated(self, png: LikePathList, hotspot: Tuple[int, int]) -> None:
-        """Set this Bitmap as **animated**, It means this bitmap holds multiple \
-                ``png``.
-
-                .. note:: This method called by ``self.__init__``.
-
-        :param png: ``.png`` file location ***List***.
-        :type png: ``List[str]`` or ``List[pathlib.Path]``
-
-        :param hotspot: Hotspot is coordinate value in Tuple. Cursor change \
-                        state is calculated from this value.
-        :type hotspot: ``Tuple[int,`` int]
-
-        :returns: None.
-        :rtype: ``None``
-
-        :raise FileNotFoundError: If ``.png`` file not found
-        :raise ValueError: If provided bitmap is not ``.png``
-        :raise ValueError: If image width & height are not same
-        :raise ValueError: If grouped ``.png`` files naming is invalid
-        :raise ValueError: If hotspot is set higher or lower then image pixels
-        """
-
-        self.grouped_png = []
-        for p in png:
-            frame: Path = self._check_bitmap(p)
-
-            self.grouped_png.append(frame)
-            self._set_key(frame, check=True)
-            self._set_size(frame)
-
-        self.grouped_png.sort()
-
-        # Don't worry, All images sizes are equal.
-        self._set_hotspot(self.grouped_png[0], hotspot)
-        self.animated = True
-
-    #
-    # Protected methods
-    #
-    def _check_bitmap(self, bmp_path: LikePath) -> Path:
-        """Checks bitmap is supported by clickgen.
-
-        :param bmp_path: Bitmap file location.
-        :type bmp_path: ``str`` or ``pathlib.Path``
-
-        :returns: Return the ``pathlib.Path`` instant, If provided \
-                **bitmap path** checks passed.
-        :rtype: ``pathlib.Path``
-
-        :raise FileNotFoundError: If ``.png`` file not found
-        :raise ValueError: If provided bitmap is not ``.png``
-        """
-
-        p: Path = Path(bmp_path)
-        if not p.exists():
-            raise FileNotFoundError(
-                f"Not a such file '{p.name}' in '{p.parent.absolute()}'"
-            )
-
-        # Supported bitmap type
-        # => *.png
-        for bmp_pattern in ("*.png",):
-            if not p.match(bmp_pattern):
-                raise ValueError(
-                    f"{self.__class__} supports '{bmp_pattern}' bitmaps type, not '{p.suffix}'"
-                )
-        return p
-
-    def _set_size(self, bmp_path: Path, prev_check: bool = True) -> None:
-        """Set or overwrite size of this bitmap.
-
-        :param bmp_path: Bitmap file location.
-        :type bmp_path: ``pathlib.Path``
-
-        :param prev_check: If you want to match size with previous size. This \
-                flag is useful for grouped ``png``.(@default True )
-        :type prev_check: ``bool``
-
-        :raise ValueError: If image width & height are not same.
-        """
-
-        with Image.open(bmp_path) as i:
-
-            def __set() -> None:
-                self.size = i.size
-                self.width = i.width
-                self.height = i.height
-
-            if i.width == i.height:
-                if prev_check:
-
-                    try:
-                        if self.size != i.size:
-                            raise ValueError("All .png file's size must be equal")
-                        __set()
-                    except AttributeError:
-                        __set()
-
-                else:
-                    __set()
-            else:
-                raise ValueError(
-                    f"frame '{bmp_path.name}' must had equal width & height."
-                )
-
-    def _set_key(self, bmp_path: Path, check: bool) -> None:
-        """Set unique identity for this bitmap.
-
-        :param bmp_path: Bitmap file location.
-        :type bmp_path: ``pathlib.Path``
-
-        :param check: If you want sure multiple ``png`` files identity is same.
-        :type check: bool
-
-        :returns: None.
-        :return: ``None``
-
-        :raise ValueError: If grouped png indexing invalid.
-        :raise ValueError: If new identity not matched with old.
-        """
-
-        if check:
-            try:
-                k, _ = bmp_path.stem.rsplit("-", 1)
-            except ValueError:
-                raise ValueError(
-                    f"Invalid Bitmap name '{bmp_path.name}': Grouped Bitmaps must-have frame number followed by '-'. Like 'bitmap-000.png'"
-                ) from None
-
-            try:
-                if self.key != k:
-                    raise ValueError(
-                        f"Bitmap '{bmp_path.name}' not matched with key '{self.key}'. Provide a Grouped Bitmaps with frame number followed by '-'.  Like 'bitmap-000.png','bitmap-001.png' "
-                    )
-                self.key = k
-            except AttributeError:
-                self.key = k
-        else:
-            self.key = bmp_path.stem
-
-    def _set_hotspot(self, img_path: Path, hotspot: Tuple[int, int]) -> None:
-        """Set this bitmap reaction state.
-
-        :param img_path: Bitmap file location.
-        :type img_path: ``pathlib.Path``
-
-        :returns: None.
-        :return: ``None``
-
-        :param hotspot: ``xy`` coordinates for this bitmap.
-        :type hotspot: ``Tuple[int, int]``
-        """
-
-        x = hotspot[0]
-        y = hotspot[1]
-        with Image.open(img_path) as i:
-            if x > i.width or y > i.height:
-                raise ValueError("'Hotspot' value is an overflow")
-
-            if x < 0 or y < 0:
-                raise ValueError("'Hotspot' value is an underflow")
-
-            self.x_hot = x
-            self.y_hot = y
-
-    def _update_hotspots(self, new_size: Size) -> None:
-        """Update this bitmap reaction state.
-
-        :param new_size: Bitmap width & height Tuple (in pixel).
-        :type new_size: ``Tuple[int, int]``
-
-        :returns: None.
-        :return: ``None``
-        """
-
-        if self.size != new_size:
-            self.x_hot = int(round(new_size[0] / self.width * self.x_hot))
-            self.y_hot = int(round(new_size[1] / self.height * self.y_hot))
-
-    #
-    # Public methods
-    #
-    def resize(
-        self,
-        size: Size,
-        save: bool = True,
-    ) -> Optional[Union[Image.Image, List[Image.Image]]]:
-        """Resize this bitmap.
-
-        :param size: New width & height in pixel.
-        :type size: ``Tuple[int, int]``
-
-        :param save: If you want to overwrite resized bitmap to actual png \
-                file. Neither it return pillow ``Image`` buffer.
-        :type save: ``bool``
-
-        :returns: Returns image buffers, If *save* flag is set to ``False``.
-        :rtype: ``Image`` or ``List[Image]`` or ``None``
-
-        :raise ValueError: If image width & height are not same.
-        """
-
-        def __resize(p: Path, frame: int) -> Image.Image:
-            img: Image.Image = Image.open(p)
-
-            # Preventing image quality degrades
-            if img.size != size:
-                img = img.resize(size, resample=Image.BICUBIC)
-                # If save => Update attribute
-                if save:
-                    self._set_size(p)
-                    if frame == 0:
-                        self._update_hotspots(size)
-                    img.save(p, compress=self.compress)
-            return img
-
-        if self.animated:
-            images: List[Image.Image] = []
-            for i, png in enumerate(self.grouped_png):
-                img: Image.Image = __resize(png, i)
-                images.append(img)
-            if not save:
-                return images
-            return None
-
-        img: Image.Image = __resize(self.png, 0)
-        if not save:
-            return img
-        return None
-
-    def reproduce(
-        self,
-        size: Size = (24, 24),
-        canvas_size: Size = (32, 32),
-        position: str = "center",
-        save=True,
-    ) -> Optional[Union[Image.Image, List[Image.Image]]]:
-        """Resize bitmap with more options.
-
-        :param size: Bitmap width & height in pixel.
-        :type size: ``Tuple[int, int]``
-
-        :param canvas_size: Bitmap's canvas width & height in pixel.
-        :type canvas_size: ``Tuple[int, int]``
-
-        :param position: Bitmap's canvas width & height in pixel. \
-                (@default "center")
-        :type position: "center" or "top_left" or "top_right" or \
-            "bottom_left" or "bottom_right"
-
-        :param save: If you want to overwrite resized bitmap to actual png \
-                       file. Neither it return pillow ``Image`` buffer.
-        :type save: ``bool``
-
-        :returns: Returns image buffers, If *save* flag is set to ``False``.
-        :rtype: ``Image`` or ``List[Image]`` or ``None``
-
-        :raise ValueError: If image width & height are not same.
-        """
-
-        def __reproduce(p: Path) -> Image.Image:
-            frame = Image.open(p).resize(size)
-            x, y = tuple(map(lambda i, j: i - j, canvas_size, size))
-
-            switch: Dict[str, Tuple[int, int]] = {
-                "top_left": (0, 0),
-                "top_right": (x, 0),
-                "bottom_left": (0, y),
-                "bottom_right": (x, y),
-                "center": (round(x / 2), round(y / 2)),
-            }
-
-            box: Tuple[int, int] = switch[position]
-
-            canvas: Image.Image = Image.new("RGBA", canvas_size, color=(256, 0, 0, 0))
-            canvas.paste(frame, box=box)
-
-            if save:
-                canvas.save(p, compress=self.compress)
-                self.x_hot = int(round(size[0] / self.width * self.x_hot) + box[0])
-                self.y_hot = int(round(size[1] / self.height * self.y_hot) + box[1])
-                self._set_size(p, prev_check=False)
-            return canvas
-
-        if self.animated:
-            images: List[Image.Image] = []
-            for png in self.grouped_png:
-                images.append(__reproduce(png))
-            if not save:
-                return images
-            return None
-
-        image: Image.Image = __reproduce(self.png)
-        if not save:
-            return image
-        return None
-
-    def rename(self, key: str) -> None:
-        """Rename unique identity of bitmap.
-
-        :param key: Bitmap unique identity.
-        :type key: ``str``
-
-        :returns: None.
-        :return: ``None``
-
-        :raise ValueError: If grouped png indexing invalid.
-        :raise ValueError: If new identity not matched with old.
-        """
-        old_key = self.key
-        if key != old_key:
-
-            def __rename(png: Path, check: bool) -> Path:
-                name: str = f"{png.stem.replace(old_key, key, 1)}{png.suffix}"
-                renamed_path: Path = png.with_name(name)
-                png.rename(renamed_path)
-                self._set_key(renamed_path, check)
-                return renamed_path
-
-            if self.animated:
-                new_pngs = []
-                self.key = key
-                for png in self.grouped_png:
-                    new_pngs.append(__rename(png, check=True))
-                self.grouped_png = new_pngs
-            else:
-                self.png = __rename(self.png, check=False)
-
-    def copy(self, path: Optional[LikePath] = None) -> "Bitmap":
-        """Generate deepcopy of this bitmap.
-
-        :param path: Provide custom path to store deepcopy bitmaps. \
-                     Set ``None`` to store in temporary directory. \
-                     (@default None)
-        :type path: ``str``
-
-        :returns: deepcopy of this bitmap state.
-        :rtype: Bitmap
-
-        :raise NotADirectoryError: If provided path is not directory.
-        """
-        if not path:
-            p_obj: Path = Path(mkdtemp(prefix=f"{self.key}__copy__"))
-        else:
-            p_obj: Path = Path(path)
-
-        if p_obj.is_file():
-            raise NotADirectoryError(f"path '{p_obj.absolute()}' is not a directory")
-
-        p_obj.mkdir(parents=True, exist_ok=True)
-
-        def __copy(src: Path) -> Path:
-            dst: Path = p_obj / src.name
-            shutil.copy2(src, dst)
-            return dst
-
-        if self.animated:
-            pngs: List[Path] = []
-            for p in self.grouped_png:
-                pngs.append(__copy(p))
-            return Bitmap(pngs, (self.x_hot, self.y_hot))
-
-        p = __copy(self.png)
-        return Bitmap(p, (self.x_hot, self.y_hot))
-
-
-class CursorAlias:
-    """Cursor Config ``.in`` or ``.alias`` file provider."""
-
-    bitmap: Bitmap
-    prefix: str
-    alias_dir: Path
-    alias_file: Path
-    garbage_dirs: List[Path] = []
-
-    def __init__(
-        self,
-        bitmap: Bitmap,
-    ) -> None:
-        """
-        :param bitmap: Cursor :py:class:`~clickgen.core.Bitmap` instant.
-        :type bitmap: Bitmap
-
-        :returns: None.
-        :return: ``None``
-        """
-        super().__init__()
-
-        self.bitmap = bitmap
-        self.prefix = f"{self.bitmap.key}__alias"
-        self.alias_dir = Path(mkdtemp(prefix=self.prefix))
-
-    def __get_alias_file(self) -> Optional[Path]:
-        """
-        :returns: Return cursor alias file path, If it's exists.
-        :rtype: ``pathlib.Path`` or ``None``
-        """
-
-        if hasattr(self, "alias_file"):
-            return self.alias_file
-        return None
-
-    def __str__(self) -> str:
-        return f"CursorAlias(bitmap={self.bitmap!s}, prefix={self.prefix}, alias_dir={self.alias_dir}, alias_file={self.__get_alias_file()}, garbage_dirs={self.garbage_dirs})"
-
-    def __repr__(self) -> str:
-        return f"{{ 'bitmap':{self.bitmap!r}, 'prefix':{self.prefix}, 'alias_dir':{self.alias_dir}, 'alias_file':{self.__get_alias_file()}, 'garbage_dirs':{self.garbage_dirs} }}"
-
-    # Context manager support
-    def __enter__(self) -> "CursorAlias":
-        return self
-
-    def __exit__(self, exception_type, exception_value, traceback):  # type: ignore
-
-        if hasattr(self, "alias_dir"):
-            remove_util(self.alias_dir)
-
-        if hasattr(self, "garbage_dirs"):
-            for p in self.garbage_dirs:
-                remove_util(p)
-
-    @classmethod
-    def from_bitmap(
-        cls,
-        png: Union[LikePath, LikePathList],
-        hotspot: Tuple[int, int],
-    ) -> "CursorAlias":
-        """Create cursor alias config file from ``.png`` files instant.
-
-        :param png: File location. Use ``List`` for animated Cursor.
-        :type png: ``str`` or ``pathlib.Path`` or ``List[str]`` or ``List[pathlib.Path]``
-
-        :param hotspot: Hotspot is coordinate value in Tuple. Cursor change \
-                        state is calculated from this value.
-        :type hotspot: ``Tuple[int, int]``
-
-        :raise TypeError: If provided ``.png`` file/s location is not type \
-                          **str** or **pathlib.Path**
-        """
-
-        bmp: Bitmap = Bitmap(png, hotspot)
-        return cls(bmp)
-
-    def create(
-        self,
-        sizes: Union[Size, List[Size]],
-        delay: int = 10,
-    ) -> Path:
-        """Generate and store cursor's config file at ``temporary`` storage.
-
-        :param sizes: Cursor pixel size Tuple.
-        :type sizes: ``Tuple[int, int]`` or ``List[Tuple[int, int]]``
-
-        :param delay: Delay between every cursor frame.(Affect on only \
-                animated :py:class:`~clickgen.core.Bitmap`)
-        :type delay: ``int``
-
-        :returns: Cursor alias file path.
-        :rtype: ``pathlib.Path``
-
-        :raise TypeError: If provided ``size`` is not type of \
-            ``Tuple[int, int]`` or ``List``.
-        """
-
-        def __generate(size: Size) -> List[str]:
-            d: Path = self.alias_dir / f"{size[0]}x{size[1]}"
-
-            bmp: Bitmap = self.bitmap.copy(d)
-            bmp.resize(size)
-
-            l: List[str] = []
-
-            for file in d.glob("*.png"):
-                fp: str = f"{file.relative_to(self.alias_dir)}"
-
-                line: str = f"{size[0]} {bmp.x_hot} {bmp.y_hot} {fp}"
-                if self.bitmap.animated:
-                    line = f"{line} {delay}"
-
-                l.append(f"{line}\n")
-
-            return l
-
-        def __write_alias(lines: List[str]) -> None:
-            # sorting all lines according to size (24x24, 28x28, ..)
-            lines.sort()
-            # remove newline from EOF
-            lines[-1] = lines[-1].rstrip("\n")
-
-            cfg: Path = self.alias_dir / f"{self.bitmap.key}.alias"
-            with cfg.open("w") as f:
-                f.writelines(lines)
-            self.alias_file = cfg
-
-        sizes_type_err: str = (
-            "argument 'sizes' should be Tuple[int, int] type or List[Tuple[int, int]]."
-        )
-
-        # Multiple sizes
-        if isinstance(sizes, list):
-            # Removing duplicate sizes
-            sizes = sorted(set(sizes))
-
-            lines: List[str] = []
-            for size in sizes:
-                if isinstance(size, tuple):
-                    lines.extend(__generate(size))
-                else:
-                    raise TypeError(sizes_type_err)
-
-            __write_alias(lines)
-
-        # Single size
-        elif isinstance(sizes, tuple):
-            lines = __generate(sizes)
-            __write_alias(lines)
-
-        else:
-            raise TypeError(sizes_type_err)
-
-        return self.alias_file
-
-    def check_alias(self) -> None:
-        """Checks cursor  config file exists on filesystem.
-
-        :raise FileNotFoundError:  If cursor config file not generate. \
-                Calling :py:meth:`~clickgen.core.CursorAlias.create` to \
-                recreate it.
-        """
-
-        if not any(self.alias_dir.iterdir()):
-            raise FileNotFoundError("Alias directory is empty or not exists.")
-
-    def extension(self, ext: Optional[str] = None) -> Union[str, Path]:
-        """``get`` or ``change`` cursor config file extension. This method helps \
-        to change default config extension ``.alias`` to ``.in``.
-
-        :param ext: Provide custom cursor's config file extension. Like \
-                    ``o.extension(ext = ".in")``. (@default None)
-        :type ext: ``str`` or ``None``
-
-        :returns: Provide ``None`` value to retrieve current extension. \
-                Either returns the updated cursor's config file path.
-        :rtype: ``str`` or ``pathlib.Path``
-        """
-
-        self.check_alias()
-        if ext:
-            new_path: Path = self.alias_file.with_suffix(ext)
-            self.alias_file = self.alias_file.rename(new_path)
-            return self.alias_file
-
-        return self.alias_file.suffix
-
-    def copy(self, dst: Optional[LikePath] = None) -> "CursorAlias":
-        """Copy current bitmap config to directory.
-
-             .. note::
-                 This method **creates** the ``dst`` directory if not exists.
-
-        :param dst: Custom directory path for store deepcopy of cursor config \
-                file and it's bitmaps. Provide ``None`` value to store at \
-                temporary storage.
-        :type dst: ``str`` or ``pathlib.Path`` or ``None``
-
-        :returns: deepcopy object of current ``CursorAlias`` state.
-        :rtype: CursorAlias
-
-        :raise FileNotFoundError:  If cursor config file not generate. \
-                Calling :py:meth:`~clickgen.core.CursorAlias.create` to \
-                recreate it.
-        :raise NotADirectoryError: If provided ``dst`` is not a directory.
-        """
-
-        self.check_alias()
-
-        dest: Path
-
-        if dst is None:
-            dest = Path(mkdtemp(prefix=self.prefix))
-        else:
-            dest = Path(dst)
-
-        if dest.is_file():
-            raise NotADirectoryError(f"path '{dest.absolute()}' is not a directory")
-
-        replica_object = deepcopy(self)
-
-        shutil.copytree(
-            self.alias_dir, dest, dirs_exist_ok=True, copy_function=shutil.copy
-        )
-        replica_object.alias_dir = dest
-        replica_object.prefix = dest.stem
-        replica_object.alias_file = dest / self.alias_file.name
-
-        return replica_object
-
-    def rename(self, key: str) -> Path:
-        """Rename current cursor's config file.
-
-        Renaming cursor config is affects naming of bitmaps. \
-        This method is calls :py:meth:`~clickgen.core.Bitmap.rename` \
-        internaly for renaming bitmaps name.
-
-        :param key: Unique identity.
-        :type key: ``str``
-
-        :returns: Renamed config file path.
-        :rtype: ``pathlib.Path``
-
-        :raise FileNotFoundError:  If cursor config file not generate. \
-                Calling :py:meth:`~clickgen.core.CursorAlias.create` to \
-                recreate it.
-        :raise ValueError: If grouped png indexing invalid.
-        :raise ValueError: If new identity not matched with old.
-        """
-
-        self.check_alias()
-        old_key: str = self.bitmap.key
-
-        if old_key != key:
-            # Setting new_prefix & renaming alias directory
-            new_prefix = f"{key}__alias"
-            new_alias_dir = self.alias_dir.with_name(
-                self.alias_dir.name.replace(self.prefix, new_prefix)
-            )
-            shutil.move(f"{self.alias_dir.absolute()}", new_alias_dir)
-            self.prefix = new_prefix
-            self.alias_dir = new_alias_dir
-
-            # Renaming content
-            def __rename(p: Path) -> Path:
-                name: str = f"{p.stem.replace(old_key, key, 1)}{p.suffix}"
-                path: Path = p.with_name(name)
-                return p.rename(path)
-
-            for f in self.alias_dir.iterdir():
-                if f.is_dir():
-                    for png in f.glob("*.png"):
-                        png = __rename(png)
-                elif f.is_file or f.absolute() == self.alias_file.absolute():
-                    updated_data: str = f.read_text().replace(old_key, key)
-                    f.write_text(updated_data)
-                    self.alias_file = __rename(f)
-
-        return self.alias_file
-
-    def reproduce(
-        self,
-        size: Size = (24, 24),
-        canvas_size: Size = (32, 32),
-        position: str = "center",
-        delay: int = 3,
-    ) -> "CursorAlias":
-        """Resize cursor config and bitmap.
-
-        :param size: Bitmap width & height in pixel.
-        :type size: ``Tuple[int, int]``
-
-        :param canvas_size: Bitmap's canvas width & height in pixel.
-        :type canvas_size: ``Tuple[int, int]``
-
-        :param position: Bitmap's canvas width & height in pixel. \
-            (@default "center")
-        :type position: "center" or "top_left" or "top_right" or \
-            "bottom_left" or "bottom_right"
-
-        :param save: If you want to overwrite resized bitmap to actual png \
-                       file. Neither it return pillow ``Image`` buffer.
-        :type save: ``bool``
-
-        :returns: Returns reproduced ``CursorAlias`` object.
-        :rtype: CursorAlias
-
-        :raise FileNotFoundError:  If cursor config file not generate. \
-                Calling :py:meth:`~clickgen.core.CursorAlias.create` to \
-                recreate it.
-        :raise ValueError: If image width & height are not same.
-        """
-
-        self.check_alias()
-
-        tmp_bitmaps_dir: Path = Path(mkdtemp(prefix=f"{self.prefix}__garbage_bmps__"))
-        tmp_bitmap = self.bitmap.copy(tmp_bitmaps_dir)
-        tmp_bitmap.reproduce(size, canvas_size, position)
-
-        # Will Deleted, When __exit__ being called
-        self.garbage_dirs.append(tmp_bitmaps_dir)
-
-        cls: CursorAlias = CursorAlias(tmp_bitmap)
-        cls.create(canvas_size, delay)
-
-        return cls
diff --git a/src/clickgen/core.pyi b/src/clickgen/core.pyi
deleted file mode 100644
index c744c6b3..00000000
--- a/src/clickgen/core.pyi
+++ /dev/null
@@ -1,45 +0,0 @@
-from PIL import Image
-from clickgen.util import remove_util as remove_util
-from pathlib import Path
-from typing import List, Optional, Tuple, Union
-
-Size = Tuple[int, int]
-LikePath = Union[str, Path]
-LikePathList = Union[List[str], List[Path]]
-
-class Bitmap:
-    animated: bool
-    png: Path
-    grouped_png: List[Path]
-    key: str
-    x_hot: int
-    y_hot: int
-    size: Tuple[int, int]
-    width: int
-    height: int
-    compress: int
-    def __init__(self, png: Union[LikePath, LikePathList], hotspot: Tuple[int, int]) -> None: ...
-    def __enter__(self) -> Bitmap: ...
-    def __exit__(self, exception_type, exception_value, traceback) -> None: ...
-    def resize(self, size: Size, save: bool = ...) -> Optional[Union[Image.Image, List[Image.Image]]]: ...
-    def reproduce(self, size: Size = ..., canvas_size: Size = ..., position: str = ..., save: bool = ...) -> Optional[Union[Image.Image, List[Image.Image]]]: ...
-    def rename(self, key: str) -> None: ...
-    def copy(self, path: Optional[LikePath] = ...) -> Bitmap: ...
-
-class CursorAlias:
-    bitmap: Bitmap
-    prefix: str
-    alias_dir: Path
-    alias_file: Path
-    garbage_dirs: List[Path]
-    def __init__(self, bitmap: Bitmap) -> None: ...
-    def __enter__(self) -> CursorAlias: ...
-    def __exit__(self, exception_type, exception_value, traceback) -> None: ...
-    @classmethod
-    def from_bitmap(cls, png: Union[LikePath, LikePathList], hotspot: Tuple[int, int]) -> CursorAlias: ...
-    def create(self, sizes: Union[Size, List[Size]], delay: int = ...) -> Path: ...
-    def check_alias(self) -> None: ...
-    def extension(self, ext: Optional[str] = ...) -> Union[str, Path]: ...
-    def copy(self, dst: Optional[LikePath] = ...) -> CursorAlias: ...
-    def rename(self, key: str) -> Path: ...
-    def reproduce(self, size: Size = ..., canvas_size: Size = ..., position: str = ..., delay: int = ...) -> CursorAlias: ...
diff --git a/src/clickgen/cursors.py b/src/clickgen/cursors.py
new file mode 100644
index 00000000..3d29c5ed
--- /dev/null
+++ b/src/clickgen/cursors.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from typing import Iterator, List, Tuple
+
+from PIL.Image import Image
+
+
+class CursorImage:
+    image: Image
+    hotspot: Tuple[int, int]
+    nominal: int
+
+    def __init__(self, image: Image, hotspot: Tuple[int, int], nominal: int) -> None:
+        self.image = image
+        self.hotspot = hotspot
+        self.nominal = nominal
+
+    def __repr__(self) -> str:
+        return f"CursorImage(image={self.image!r}, hotspot={self.hotspot!r}, nominal={self.nominal!r})"
+
+
+class CursorFrame:
+    images: List[CursorImage]
+    delay: int
+
+    def __init__(self, images: List[CursorImage], delay: int = 0) -> None:
+        self.images = images
+        self.delay = delay
+
+    def __getitem__(self, item: int) -> CursorImage:
+        return self.images[item]
+
+    def __len__(self) -> int:
+        return len(self.images)
+
+    def __iter__(self) -> Iterator[CursorImage]:
+        return iter(self.images)
+
+    def __repr__(self) -> str:
+        return f"CursorFrame(images={self.images!r}, delay={self.delay!r})"
diff --git a/src/clickgen/cursors.pyi b/src/clickgen/cursors.pyi
new file mode 100644
index 00000000..f476a1f5
--- /dev/null
+++ b/src/clickgen/cursors.pyi
@@ -0,0 +1,16 @@
+from PIL.Image import Image as Image
+from typing import Iterator, List, Tuple
+
+class CursorImage:
+    image: Image
+    hotspot: Tuple[int, int]
+    nominal: int
+    def __init__(self, image: Image, hotspot: Tuple[int, int], nominal: int) -> None: ...
+
+class CursorFrame:
+    images: List[CursorImage]
+    delay: int
+    def __init__(self, images: List[CursorImage], delay: int = ...) -> None: ...
+    def __getitem__(self, item: int) -> CursorImage: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[CursorImage]: ...
diff --git a/src/clickgen/db.py b/src/clickgen/db.py
deleted file mode 100644
index 8323d01c..00000000
--- a/src/clickgen/db.py
+++ /dev/null
@@ -1,288 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-from difflib import SequenceMatcher as SM
-from pathlib import Path
-from typing import Dict, List, Optional, Set
-
-# Typing
-Data = List[Set[str]]
-
-DATA: Data = [
-    {
-        "X_cursor",
-        "x-cursor",
-        "kill",
-        "pirate",
-    },
-    {"kill", "pirate"},
-    {"all-scroll", "fleur", "size_all"},
-    {
-        "bd_double_arrow",
-        "c7088f0f3e6c8088236ef8e1e3e70000",
-        "38c5dff7c7b8962045400281044508d2",
-        "size_fdiag",
-        "nwse-resize",
-    },
-    {"bottom_left_corner", "sw-resize"},
-    {"bottom_right_corner", "se-resize"},
-    {"bottom_side", "s-resize"},
-    {"bottom_tee"},
-    {"center_ptr"},
-    {
-        "circle",
-        "forbidden",
-    },
-    {"context-menu"},
-    {
-        "1081e37283d90000800003c07f3ef6bf",
-        "6407b0e94181790501fd1e167b474872",
-        "b66166c04f8c3109214a4fbd64a50fc8",
-        "08ffe1cb5fe6fc01f906f1c063814ccf",
-        "copy",
-    },
-    {
-        "cross",
-        "cross_reverse",
-        "diamond_cross",
-    },
-    {"crossed_circle", "03b6e0fcb3499374a867c041f52298f0", "not-allowed"},
-    {"crosshair"},
-    {"dnd-ask"},
-    {"dnd-copy"},
-    {"dnd-link", "alias"},
-    {"dnd-move"},
-    {
-        "dnd-none",
-        "closedhand",
-        "208530c400c041818281048008011002",
-        "fcf21c00b30f7e3f83fe0dfd12e71cff",
-    },
-    {"dnd_no_drop", "no-drop"},
-    {"dotbox", "dot_box_mask", "draped_box", "icon", "target"},
-    {
-        "fcf1c3c7cd4491d801f1e1c78f100000",
-        "50585d75b494802d0151028115016902",
-        "fd_double_arrow",
-        "nesw-resize",
-        "size_bdiag",
-    },
-    {"grabbing"},
-    {"hand"},
-    {
-        "hand1",
-        "grab",
-        "5aca4d189052212118709018842178c0",
-        "openhand",
-    },
-    {
-        "9d800788f1b08800ae810202380a0822",
-        "e29285e634086352946a0e7090d73106",
-        "hand2",
-        "pointer",
-        "pointing_hand",
-    },
-    {
-        "left_ptr",
-        "arrow",
-        "default",
-    },
-    {
-        "00000000000000020006000e7e9ffc3f",
-        "08e8e1c95fe2fc01f976f1e063a24ccd",
-        "3ecb610c1bf2410f44200f48c40d3599",
-        "9116a3ea924ed2162ecab71ba103b17f",
-        "left_ptr_watch",
-        "progress",
-    },
-    {"left_side", "w-resize"},
-    {"left_tee"},
-    {
-        "3085a0e285430894940527032f8b26df",
-        "640fb0e74195791501fd1ed57b41487f",
-        "a2a266d0498c3104214a47bd64ab0fc8",
-        "0876e1c15ff2fc01f906f1c363074c0f",
-        "link",
-    },
-    {"ll_angle"},
-    {"lr_angle"},
-    {
-        "4498f0e0c1937ffe01fd06f973665830",
-        "9081237383d90e509aa00f00170e968f",
-        "move",
-    },
-    {
-        "pencil",
-        "draft",
-    },
-    {"plus", "cell"},
-    {"pointer-move"},
-    {
-        "5c6cd98b3f3ebcb1f9c7f1c204630408",
-        "d9ce0ab605698f320427677b458ad60b",
-        "gumby",
-        "help",
-        "left_ptr_help",
-        "question_arrow",
-        "whats_this",
-    },
-    {
-        "right_ptr",
-        "draft_large",
-        "draft_small",
-    },
-    {"right_side", "e-resize"},
-    {"right_tee"},
-    {"sb_down_arrow", "down-arrow"},
-    {
-        "028006030e0e7ebffc7f7070c0600140",
-        "14fef782d02440884392942c11205230",
-        "043a9f68147c53184671403ffa811cc5",
-        "col-resize",
-        "ew-resize",
-        "h_double_arrow",
-        "sb_h_double_arrow",
-        "size-hor",
-        "size_hor",
-        "split_h",
-    },
-    {"sb_left_arrow", "left-arrow"},
-    {"sb_right_arrow", "right-arrow"},
-    {"sb_up_arrow", "up-arrow"},
-    {
-        "00008160000006810000408080010102",
-        "c07385c7190e701020ff7ffffd08103c",
-        "2870a09082c103050810ffdffffe0204",
-        "double_arrow",
-        "ns-resize",
-        "row-resize",
-        "sb_v_double_arrow",
-        "size-ver",
-        "size_ver",
-        "split_v",
-        "v_double_arrow",
-    },
-    {"tcross", "color-picker"},
-    {"top_left_corner", "nw-resize"},
-    {"top_right_corner", "ne-resize"},
-    {"top_side", "n-resize"},
-    {"top_tee"},
-    {"ul_angle"},
-    {"ur_angle"},
-    {"vertical-text", "048008013003cff3c00c801001200000"},
-    {
-        "watch",
-        "clock",
-        "wait",
-        "0426c94ea35c87780ff01dc239897213",
-    },
-    {"wayland-cursor"},
-    {
-        "xterm",
-        "text",
-        "ibeam",
-    },
-    {"zoom-in", "f41c0e382c94c0958e07017e42b00462"},
-    {"zoom-out", "f41c0e382c97c0938e07017e42800402"},
-]
-
-
-class CursorDB:
-    """The CursorDB class provides ``Xcursor`` data available on X11/Wayland\
-    /XWayland.
-    """
-
-    data: Dict[str, List[str]] = {}
-
-    def __init__(self, data: Data) -> None:
-        """Initiate CursorDB instance.
-
-        This call ``self.__seed`` for setting dataset into ``Dict`` structure.
-
-        :param data: Provide custom dataset of cursors. default to ``clickgen.db.DATA``
-        :type data: ``List[Set[str]]``
-
-        :returns: None.
-        :rtype: ``None``
-
-        :raises TypeError: If provided ``data`` is not type of ``List``
-        :raises TypeError: If provided ``data``'s list member is not type of ``Set``.
-        """
-
-        super().__init__()
-        self.__seed(data)
-
-    def __str__(self) -> str:
-        string: List[str] = []
-        for index, data in self.data.items():
-            string.append(f"{index}={data}")
-        return f"CursorDB({', '.join(string)})"
-
-    def __repr__(self) -> str:
-        return f"{self.data!r}"
-
-    def __seed(self, data: Data) -> None:
-        if not isinstance(data, list):
-            raise TypeError("'data' is not type of 'List'")
-        for item in sorted(data):
-            if not isinstance(item, set):
-                raise TypeError(f"Item '{item}' is not type of 'Set'")
-            for i in sorted(item):
-                symlinks: List[str] = list(item)
-                symlinks.remove(i)
-                self.data[i] = symlinks
-
-    def search_symlinks(
-        self, key: str, find_similar: bool = False
-    ) -> Optional[List[str]]:
-        """Retrieve similar implementation Xcursor from database.
-
-        :param key: Name of XCursor.
-        :type key: ``str``
-
-        :param find_similar: If this flag set to ``True``, It return similar \
-        named ``XCursor`` from database.
-        :type find_similar: ``bool``
-
-        :return: List of missing ``XCursor`` in string list or None.
-        :rtype: ``list[str]`` or ``None``
-        """
-        if find_similar:
-            key = self.__find_similar(key)
-
-        return self.data.get(key)
-
-    def __find_similar(self, key: str) -> str:
-        compare_ratio: float = 0.5
-        result: str = key
-        for word in self.data:
-            ratio: float = SM(None, key.lower(), word.lower()).ratio()
-            if ratio > compare_ratio:
-                compare_ratio = ratio
-                result = word
-
-        return result
-
-    def rename_file(self, fp: Path) -> Optional[Path]:
-        """Search similar named Xcursor in database. If name of XCursor is not
-        correct this return ``pathlib.Path`` Object.
-
-        :param fp: Path to XCursor.
-        :type fp: ``pathlib.Path``
-
-        :return: Return renamed ``Path`` If XCursor name not correct.
-        :rtype: ``pathlib.Path`` or ``None``
-
-        :raise FileNotFoundError: If file is not found in file system or it's \
-        a directory.
-        """
-        if not fp.is_file():
-            raise FileNotFoundError(f"'{fp.absolute()}' is not file")
-        key: str = self.__find_similar(fp.stem)
-        if key != fp.stem:
-            return fp.with_name(f"{key}{fp.suffix}")
-        return None
diff --git a/src/clickgen/db.pyi b/src/clickgen/db.pyi
deleted file mode 100644
index 10046703..00000000
--- a/src/clickgen/db.pyi
+++ /dev/null
@@ -1,11 +0,0 @@
-from pathlib import Path
-from typing import Dict, List, Optional, Set
-
-Data = List[Set[str]]
-DATA: Data
-
-class CursorDB:
-    data: Dict[str, List[str]]
-    def __init__(self, data: Data) -> None: ...
-    def search_symlinks(self, key: str, find_similar: bool = ...) -> Optional[List[str]]: ...
-    def rename_file(self, fp: Path) -> Optional[Path]: ...
diff --git a/src/clickgen/packagers.pyi b/src/clickgen/packagers.pyi
deleted file mode 100644
index 443f7e43..00000000
--- a/src/clickgen/packagers.pyi
+++ /dev/null
@@ -1,13 +0,0 @@
-from pathlib import Path
-from string import Template
-from typing import Any, Dict, Optional, Set
-
-THEME_FILES_TEMPLATES: Dict[str, Template]
-
-def XPackager(directory: Path, theme_name: str, comment: str) -> None: ...
-
-INSTALL_INF: Any
-REQUIRED_WIN_CURSORS: Set[str]
-UNINSTALL_BAT: Any
-
-def WindowsPackager(directory: Path, theme_name: str, comment: str, author: str, website_url: Optional[str] = ...) -> None: ...
diff --git a/src/clickgen/packer/__init__.py b/src/clickgen/packer/__init__.py
new file mode 100644
index 00000000..f28a7a03
--- /dev/null
+++ b/src/clickgen/packer/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from clickgen.packer.windows import pack_win
+from clickgen.packer.x11 import pack_x11
+
+__all__ = ["pack_win", "pack_x11"]
diff --git a/src/clickgen/packer/__init__.pyi b/src/clickgen/packer/__init__.pyi
new file mode 100644
index 00000000..8362c71a
--- /dev/null
+++ b/src/clickgen/packer/__init__.pyi
@@ -0,0 +1,2 @@
+from clickgen.packer.windows import pack_win as pack_win
+from clickgen.packer.x11 import pack_x11 as pack_x11
diff --git a/src/clickgen/packagers.py b/src/clickgen/packer/windows.py
similarity index 55%
rename from src/clickgen/packagers.py
rename to src/clickgen/packer/windows.py
index 27968487..1513d848 100644
--- a/src/clickgen/packagers.py
+++ b/src/clickgen/packer/windows.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-"""Generate Cursors without hassle.
+"""Generate Windows install and uninstall files.
 
 .. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
 """
@@ -10,49 +10,11 @@
 from string import Template
 from typing import Dict, List, Optional, Set
 
-# --- X11
-
-THEME_FILES_TEMPLATES: Dict[str, Template] = {
-    "cursor.theme": Template('[Icon Theme]\nName=$theme_name\nInherits="$theme_name"'),
-    "index.theme": Template(
-        '[Icon Theme]\nName=$theme_name\nComment=$comment\nInherits="hicolor"'
-    ),
-}
-
-
-def XPackager(directory: Path, theme_name: str, comment: str) -> None:
-    """This packager generates ``cursor.theme`` & ``index.theme`` files at \
-        ``directory``.
-
-    :param directory: Path where ``.theme`` files save.
-    :param directory: ``pathlib.Path``
-
-    :param theme_name: Cursor theme name.
-    :param theme_name: ``str``
-
-    :param comment: Extra information about this cursor theme.
-    :param comment: ``str``
-
-    :returns: None.
-    :rtype: ``None``
-    """
-
-    # Writing all .theme files
-    files: Dict[str, str] = {}
-    for file, template in THEME_FILES_TEMPLATES.items():
-        files[file] = template.safe_substitute(theme_name=theme_name, comment=comment)
-
-    for f, data in files.items():
-        fp: Path = directory / f
-        fp.write_text(data)
-
-
-# --- Windows
-
-INSTALL_INF = Template(
-    """[Version]
+FILE_TEMPLATES: Dict[str, Template] = {
+    "install.inf": Template(
+        """[Version]
 signature="$CHICAGO$"
-$comment
+$info
 
 [DefaultInstall]
 CopyFiles = Scheme.Cur, Scheme.Txt
@@ -85,8 +47,8 @@ def XPackager(directory: Path, theme_name: str, comment: str) -> None:
 "$Alternate"
 
 [Strings]
-CUR_DIR           = "Cursors\\$theme_name Cursors"
-SCHEME_NAME       = "$theme_name Cursors"
+CUR_DIR           = "Cursors\\$theme_name"
+SCHEME_NAME       = "$theme_name"
 pointer           = "$Default"
 help              = "$Help"
 work              = "$Work"
@@ -103,9 +65,30 @@ def XPackager(directory: Path, theme_name: str, comment: str) -> None:
 alternate         = "$Alternate"
 link              = "$Link"
 """
-)
+    ),
+    "uninstall.bat": Template(
+        """@echo off
+:: ===========================================================
+::
+:: Replace the name of cursor according to the cursor schemes.
+:: Credit: https://github.com/smit-sms
+:: More Information: https://github.com/ful1e5/apple_cursor/issues/79
+::
+:: ===========================================================
+
+REG DELETE "HKCU\\Control Panel\\Cursors\\Schemes" /v "$theme_name" /f
+
+:: ===============================================================================
+:: This enables a popup message box to indicate a user for the operation complete.
+:: ===============================================================================
+echo x=msgbox("Successfully deleted the cursor!", 0+64, "Cursor") > %tmp%\tmp.vbs
+wscript %tmp%\tmp.vbs
+del %tmp%\tmp.vbs
+"""
+    ),
+}
 
-REQUIRED_WIN_CURSORS: Set[str] = {
+REQUIRED_CURSORS: Set[str] = {
     "Work",
     "Busy",
     "Default",
@@ -123,52 +106,27 @@ def XPackager(directory: Path, theme_name: str, comment: str) -> None:
     "Alternate",
 }
 
-UNINSTALL_BAT = Template(
-    """@echo off
-:: ===========================================================
-::
-:: Replace the name of cursor according to the cursor schemes.
-:: Credit: https://github.com/smit-sms
-:: More Information: https://github.com/ful1e5/apple_cursor/issues/79
-::
-:: ===========================================================
-
-REG DELETE "HKCU\\Control Panel\\Cursors\\Schemes" /v "$theme_name Cursors" /f
-
-:: ===============================================================================
-:: This enables a popup message box to indicate a user for the operation complete.
-:: ===============================================================================
-echo x=msgbox("Successfully deleted the cursor!", 0+64, "Cursor") > %tmp%\tmp.vbs
-wscript %tmp%\tmp.vbs
-del %tmp%\tmp.vbs
-"""
-)
-
 
-def WindowsPackager(
-    directory: Path,
+def pack_win(
+    dir: Path,
     theme_name: str,
     comment: str,
-    author: str,
-    website_url: Optional[str] = None,
+    website: Optional[str] = None,
 ) -> None:
     """This packager generates ``install.inf`` files at ``directory``. Also, \
     Cursor extensions is identified by its type (.cur/.ani).
 
-    :param directory: Path where ``.theme`` files save.
-    :param directory: ``pathlib.Path``
+    :param dir: Path where ``.theme`` files save.
+    :param dir: ``pathlib.Path``
 
-    :param theme_name: Cursor theme name.
+    :param theme_name: Name of theme.
     :param theme_name: ``str``
 
-    :param comment: Extra information about this cursor theme.
+    :param comment: Extra information about theme.
     :param comment: ``str``
 
-    :param author: Author name.
-    :param author: ``str``
-
-    :param website_url: Website web address.(Useful for **bug reports**)
-    :param author: ``str`` or ``None``
+    :param website: Website web address.(Useful for **bug reports**)
+    :param website: ``str`` or ``None``
 
     :returns: None.
     :rtype: ``None``
@@ -179,41 +137,39 @@ def WindowsPackager(
 
     files: List[Path] = []
 
-    for extensions in ("*.ani", "*.cur"):
-        for i in sorted(directory.glob(extensions)):
-            if i.stem in REQUIRED_WIN_CURSORS:
+    for ext in ("*.ani", "*.cur"):
+        for i in sorted(dir.glob(ext)):
+            if i.stem in REQUIRED_CURSORS:
                 files.append(i)
 
-    cursors: Set[Path] = set(files)
+    cursors = set(files)
 
     # Checking cursor files
     if not cursors:
         raise FileNotFoundError(
-            f"Windows cursors '*.cur' or '*.ani' not found in '{directory}'"
+            f"Windows cursors '*.cur' or '*.ani' not found in '{dir}'"
         )
 
-    if len(cursors) < len(REQUIRED_WIN_CURSORS):
+    if len(cursors) < len(REQUIRED_CURSORS):
         # Some cursors are missing
         c = set(map(lambda x: x.stem, cursors))
-        missing = sorted(REQUIRED_WIN_CURSORS - set(c))
+        missing = sorted(REQUIRED_CURSORS - set(c))
         raise FileNotFoundError(f"Windows cursors are missing {missing}")
 
-    if website_url:
-        comment = f"{comment}\n{website_url}"
+    info: str = ""
+    if website:
+        info = f"{comment}\n{website}"
 
-    # Real magic of python
-    # replace $Default => Default.ani | Default.cur (as file was provided)
+    # replace $Default => Default.ani | Default.cur
     cursor_data: Dict[str, str] = {}
     for cur in cursors:
         cursor_data[cur.stem] = cur.name
 
-    # Store install.inf file
-    data: str = INSTALL_INF.safe_substitute(
-        theme_name=theme_name, comment=comment, author=author, **cursor_data
-    )
-    install_inf: Path = directory / "install.inf"
-    install_inf.write_text(data)
-
-    # Store uninstall.bat file
-    uninstall_bat: Path = directory / "uninstall.bat"
-    uninstall_bat.write_text(UNINSTALL_BAT.safe_substitute(theme_name=theme_name))
+    for fname, template in FILE_TEMPLATES.items():
+        data: str = template.safe_substitute(
+            theme_name=f"{theme_name} Cursors",
+            info=info,
+            **cursor_data,
+        )
+        f = dir / fname
+        f.write_text(data)
diff --git a/src/clickgen/packer/windows.pyi b/src/clickgen/packer/windows.pyi
new file mode 100644
index 00000000..ec3ec45e
--- /dev/null
+++ b/src/clickgen/packer/windows.pyi
@@ -0,0 +1,8 @@
+from pathlib import Path
+from string import Template
+from typing import Dict, Optional, Set
+
+FILE_TEMPLATES: Dict[str, Template]
+REQUIRED_CURSORS: Set[str]
+
+def pack_win(dir: Path, theme_name: str, comment: str, website: Optional[str] = ...) -> None: ...
diff --git a/src/clickgen/packer/x11.py b/src/clickgen/packer/x11.py
new file mode 100644
index 00000000..d907b84b
--- /dev/null
+++ b/src/clickgen/packer/x11.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""Generate X11 .theme files.
+
+.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
+"""
+
+from pathlib import Path
+from string import Template
+from typing import Dict
+
+FILE_TEMPLATES: Dict[str, Template] = {
+    "cursor.theme": Template('[Icon Theme]\nName=$theme_name\nInherits="$theme_name"'),
+    "index.theme": Template(
+        '[Icon Theme]\nName=$theme_name\nComment=$comment\nInherits="hicolor"'
+    ),
+}
+
+
+def pack_x11(dir: Path, theme_name: str, comment: str) -> None:
+    """This method generates ``cursor.theme`` & ``index.theme`` files at \
+        ``directory``.
+
+    :param dir: Path where ``.theme`` files save.
+    :param dir: ``pathlib.Path``
+
+    :param theme_name: Name of theme.
+    :param theme_name: ``str``
+
+    :param comment: Extra information about theme.
+    :param comment: ``str``
+
+    :returns: None.
+    :rtype: ``None``
+    """
+
+    for fname, template in FILE_TEMPLATES.items():
+        data = template.safe_substitute(theme_name=theme_name, comment=comment)
+        fp: Path = dir / fname
+        fp.write_text(data)
diff --git a/src/clickgen/packer/x11.pyi b/src/clickgen/packer/x11.pyi
new file mode 100644
index 00000000..0988a106
--- /dev/null
+++ b/src/clickgen/packer/x11.pyi
@@ -0,0 +1,7 @@
+from pathlib import Path
+from string import Template
+from typing import Dict
+
+FILE_TEMPLATES: Dict[str, Template]
+
+def pack_x11(dir: Path, theme_name: str, comment: str) -> None: ...
diff --git a/src/clickgen/parser/__init__.py b/src/clickgen/parser/__init__.py
new file mode 100644
index 00000000..12fcfae2
--- /dev/null
+++ b/src/clickgen/parser/__init__.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from typing import List, Optional, Tuple, Type, Union
+
+from clickgen.parser.base import BaseParser
+from clickgen.parser.png import MultiPNGParser, SinglePNGParser
+
+__all__ = ["SinglePNGParser", "MultiPNGParser", "open_blob"]
+
+PARSERS: List[Type[BaseParser]] = [SinglePNGParser, MultiPNGParser]
+
+
+def open_blob(
+    blob: Union[bytes, List[bytes]],
+    hotspot: Tuple[int, int],
+    sizes: Optional[List[int]] = None,
+    delay: Optional[int] = None,
+) -> BaseParser:
+    for parser in PARSERS:
+        if parser.can_parse(blob):
+            return parser(blob, hotspot, sizes, delay)  # type: ignore
+    raise ValueError("Unsupported file format")
diff --git a/src/clickgen/parser/__init__.pyi b/src/clickgen/parser/__init__.pyi
new file mode 100644
index 00000000..e25f110c
--- /dev/null
+++ b/src/clickgen/parser/__init__.pyi
@@ -0,0 +1,5 @@
+from clickgen.parser.base import BaseParser
+from clickgen.parser.png import MultiPNGParser as MultiPNGParser, SinglePNGParser as SinglePNGParser
+from typing import List, Optional, Tuple, Union
+
+def open_blob(blob: Union[bytes, List[bytes]], hotspot: Tuple[int, int], sizes: Optional[List[int]] = ..., delay: Optional[int] = ...) -> BaseParser: ...
diff --git a/src/clickgen/parser/base.py b/src/clickgen/parser/base.py
new file mode 100644
index 00000000..bdecdcf8
--- /dev/null
+++ b/src/clickgen/parser/base.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from abc import ABCMeta, abstractmethod
+from typing import Any, List
+
+from clickgen.cursors import CursorFrame
+
+
+class BaseParser(metaclass=ABCMeta):
+    blob: bytes
+    frames: List[CursorFrame]
+
+    @abstractmethod
+    def __init__(self, blob: bytes) -> None:
+        self.blob = blob
+
+    @classmethod
+    @abstractmethod
+    def can_parse(cls, blob: Any) -> bool:
+        raise NotImplementedError()
diff --git a/src/clickgen/parser/base.pyi b/src/clickgen/parser/base.pyi
new file mode 100644
index 00000000..114f99d1
--- /dev/null
+++ b/src/clickgen/parser/base.pyi
@@ -0,0 +1,12 @@
+from abc import ABCMeta, abstractmethod
+from clickgen.cursors import CursorFrame as CursorFrame
+from typing import Any, List
+
+class BaseParser(metaclass=ABCMeta):
+    blob: bytes
+    frames: List[CursorFrame]
+    @abstractmethod
+    def __init__(self, blob: bytes): ...
+    @classmethod
+    @abstractmethod
+    def can_parse(cls, blob: Any) -> bool: ...
diff --git a/src/clickgen/parser/png.py b/src/clickgen/parser/png.py
new file mode 100644
index 00000000..176b6ec5
--- /dev/null
+++ b/src/clickgen/parser/png.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import io
+from typing import List, Optional, Tuple
+
+from PIL import Image
+
+from clickgen.cursors import CursorFrame, CursorImage
+from clickgen.parser.base import BaseParser
+
+SIZES = [22, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96]
+DELAY = 0
+
+
+class SinglePNGParser(BaseParser):
+    MAGIC = bytes.fromhex("89504e47")
+
+    @classmethod
+    def can_parse(cls, blob: bytes) -> bool:
+        return blob[: len(cls.MAGIC)] == cls.MAGIC
+
+    def __init__(
+        self,
+        blob: bytes,
+        hotspot: Tuple[int, int],
+        sizes: Optional[List[int]] = None,
+        delay: Optional[int] = None,
+    ) -> None:
+        super().__init__(blob)
+        self._image = Image.open(io.BytesIO(self.blob))
+
+        if not sizes:
+            # 'set' to prevent value duplication
+            self.sizes = set(SIZES)
+        else:
+            self.sizes = set(sizes)
+
+        if not delay:
+            self.delay = DELAY
+        else:
+            self.delay = delay
+
+        if hotspot[0] > self._image.size[0]:
+            raise ValueError(f"Hotspot x-coordinate too large: {hotspot[0]}")
+        if hotspot[1] > self._image.size[1]:
+            raise ValueError(f"Hotspot x-coordinate too large: {hotspot[1]}")
+        self.hotspot = hotspot
+
+        self.frames = self._parse()
+
+    def _cal_hotspot(self, res_img: Image.Image) -> Tuple[int, int]:
+        def _dim(i: int) -> int:
+            return int((self.hotspot[i] * (res_img.size[i] / self._image.size[i])))
+
+        return _dim(0), _dim(1)
+
+    def _parse(self) -> List[CursorFrame]:
+        images: List[CursorImage] = []
+        for s in sorted(self.sizes):
+            res_img = self._image.resize(
+                size=(s, s), resample=3  # resample= Image.BICUBIC
+            )
+            res_hotspot = self._cal_hotspot(res_img)
+            images.append(CursorImage(image=res_img, hotspot=res_hotspot, nominal=s))
+
+        return [CursorFrame(images, delay=self.delay)]
+
+
+class MultiPNGParser(BaseParser):
+    @classmethod
+    def can_parse(cls, blobs: List[bytes]) -> bool:
+        checks: List[bool] = []
+        for blob in blobs:
+            checks.append(SinglePNGParser.can_parse(blob))
+        return all(checks)
+
+    def __init__(
+        self,
+        blobs: List[bytes],
+        hotspot: Tuple[int, int],
+        sizes: Optional[List[int]] = None,
+        delay: Optional[int] = None,
+    ) -> None:
+        super().__init__(blobs[0])
+        self.frames = []
+        for blob in blobs:
+            png = SinglePNGParser(blob, hotspot, sizes, delay)
+            self.frames.append(png.frames[0])
diff --git a/src/clickgen/parser/png.pyi b/src/clickgen/parser/png.pyi
new file mode 100644
index 00000000..a52a235a
--- /dev/null
+++ b/src/clickgen/parser/png.pyi
@@ -0,0 +1,22 @@
+from clickgen.cursors import CursorFrame as CursorFrame, CursorImage as CursorImage
+from clickgen.parser.base import BaseParser as BaseParser
+from typing import Any, List, Optional, Tuple
+
+SIZES: Any
+DELAY: int
+
+class SinglePNGParser(BaseParser):
+    MAGIC: Any
+    @classmethod
+    def can_parse(cls, blob: bytes) -> bool: ...
+    sizes: Any
+    delay: Any
+    hotspot: Any
+    frames: Any
+    def __init__(self, blob: bytes, hotspot: Tuple[int, int], sizes: Optional[List[int]] = ..., delay: Optional[int] = ...) -> None: ...
+
+class MultiPNGParser(BaseParser):
+    @classmethod
+    def can_parse(cls, blobs: List[bytes]) -> bool: ...
+    frames: Any
+    def __init__(self, blobs: List[bytes], hotspot: Tuple[int, int], sizes: Optional[List[int]] = ..., delay: Optional[int] = ...) -> None: ...
diff --git a/src/clickgen/scripts/clickgen.py b/src/clickgen/scripts/clickgen.py
index a7ed4b4b..e4031a0b 100644
--- a/src/clickgen/scripts/clickgen.py
+++ b/src/clickgen/scripts/clickgen.py
@@ -2,12 +2,18 @@
 # -*- coding: utf-8 -*-
 
 import argparse
-import shutil
-import tempfile
+import os
+import sys
+import traceback
 from pathlib import Path
+from threading import Lock
+from typing import BinaryIO, List
 
-from clickgen.builders import WindowsCursor, XCursor
-from clickgen.core import CursorAlias
+import clickgen
+from clickgen.parser import open_blob
+from clickgen.parser.png import DELAY, SIZES
+from clickgen.writer.windows import to_win
+from clickgen.writer.x11 import to_x11
 
 
 def main() -> None:
@@ -16,92 +22,93 @@ def main() -> None:
         description="The hassle-free cursor building toolbox",
     )
 
-    # Positional Args.
     parser.add_argument(
-        "png",
-        metavar="PNG",
-        type=str,
+        "files",
+        type=argparse.FileType("rb"),
         nargs="+",
-        help="Path to .png file. If animated, Try to mask frame-number using asterisk( * ). For Example 'wait-*.png'",
+        help="Cursor bitmap files to generate (*.png)",
     )
     parser.add_argument(
         "-o",
-        "--out-dir",
-        dest="out_dir",
-        metavar="OUT",
-        type=str,
-        default="./",
-        help="To change output directory. (default: %(default)s)",
+        "--output",
+        "--output-dir",
+        default=os.curdir,
+        help="Directory to store generated cursor file.",
+    )
+    parser.add_argument(
+        "-p",
+        "--platform",
+        choices=["windows", "x11", "all"],
+        default="all",
+        help="Platform for generated cursor file.",
     )
-
-    # Optional Args.
     parser.add_argument(
-        "-hot",
-        "--hotspot",
-        dest="hotspot",
-        metavar="cord",
-        nargs=2,
-        default=(0, 0),
+        "-x",
+        "--hotspot-x",
         type=int,
-        help="To set 'x' & 'y' coordinates of cursor's hotspot. (default: %(default)s)",
+        default=0,
+        help="x-offset of cursor (as fraction of width)",
     )
     parser.add_argument(
-        "-t",
-        "--type",
-        dest="type",
-        choices=("windows", "xcursor", "all"),
-        default="all",
-        help="Set cursor type, Which you want to build. (default: '%(default)s')",
+        "-y",
+        "--hotspot-y",
+        type=int,
+        default=0,
+        help="y-offset of cursor (as fraction of height)",
     )
-
     parser.add_argument(
         "-s",
         "--sizes",
         dest="sizes",
-        metavar="size",
         nargs="+",
-        default=[22],
+        default=SIZES,
         type=int,
-        help="Set pixel-size for cursor. (default: %(default)s)",
+        help="Set pixel-size for cursor.",
     )
-
     parser.add_argument(
         "-d",
         "--delay",
-        dest="delay",
-        metavar="ms",
-        default=50,
+        default=DELAY,
         type=int,
-        help="Set animated cursor's frames delay in 'micro-second'. (default: %(default)s)",
+        help="Set delay between frames of cursor.",
+    )
+    parser.add_argument(
+        "-v",
+        "--version",
+        action="version",
+        version=f"%(prog)s {clickgen.__version__}",  # type: ignore
     )
 
     args = parser.parse_args()
+    print_lock = Lock()
+    files: List[BinaryIO] = args.files
+
+    hotspot = (args.hotspot_x, args.hotspot_y)
+    name = Path(files[0].name.split("-")[0])
+    output = Path(args.output, name.stem)
+    blobs: List[bytes] = [f.read() for f in files]
+
+    try:
+        cursor = open_blob(blobs, hotspot, args.sizes, args.delay)
+    except Exception:
+        with print_lock:
+            print(f"Error occurred while processing {name.name}:", file=sys.stderr)
+            traceback.print_exc()
+    else:
 
-    hotspot = (args.hotspot[0], args.hotspot[1])
-    sizes = [(s, s) for s in args.sizes]
-    out_dir = Path(args.out_dir)
-    if not out_dir.exists():
-        out_dir.mkdir(parents=True, exist_ok=True)
+        def gen_xcursor() -> None:
+            result = to_x11(cursor.frames)
+            output.write_bytes(result)
 
-    with CursorAlias.from_bitmap(args.png, hotspot) as alias:
-        cfg = alias.create(sizes)
+        def gen_wincursor() -> None:
+            ext, result = to_win(cursor.frames)
+            win_output = output.with_suffix(ext)
+            win_output.write_bytes(result)
 
-        if args.type == "windows":
-            WindowsCursor.create(cfg, out_dir)
-        elif args.type == "xcursor":
-            # Using Temporary directory, Because 'XCursor' create inside 'cursors' directory.
-            tmp_dir = Path(tempfile.mkdtemp())
-            try:
-                xcursor = XCursor.create(cfg, tmp_dir)
-                shutil.move(str(xcursor.absolute()), str(out_dir.absolute()))
-            finally:
-                shutil.rmtree(tmp_dir)
+        if args.platform == "x11":
+            gen_xcursor()
+        elif args.platform == "windows":
+            gen_wincursor()
         else:
-            tmp_dir = Path(tempfile.mkdtemp())
-            try:
-                xcursor = XCursor.create(cfg, tmp_dir)
-                win_cursor = WindowsCursor.create(cfg, tmp_dir)
-                shutil.move(str(xcursor.absolute()), str(out_dir.absolute()))
-                shutil.move(str(win_cursor.absolute()), str(out_dir.absolute()))
-            finally:
-                shutil.rmtree(tmp_dir)
+            gen_xcursor()
+            gen_wincursor()
diff --git a/src/clickgen/scripts/clickgen.pyi b/src/clickgen/scripts/clickgen.pyi
index 2f729e97..9746672c 100644
--- a/src/clickgen/scripts/clickgen.pyi
+++ b/src/clickgen/scripts/clickgen.pyi
@@ -1,4 +1,6 @@
-from clickgen.builders import WindowsCursor as WindowsCursor, XCursor as XCursor
-from clickgen.core import CursorAlias as CursorAlias
+from clickgen.parser import open_blob as open_blob
+from clickgen.parser.png import DELAY as DELAY, SIZES as SIZES
+from clickgen.writer.windows import to_win as to_win
+from clickgen.writer.x11 import to_x11 as to_x11
 
 def main() -> None: ...
diff --git a/src/clickgen/util.py b/src/clickgen/util.py
deleted file mode 100644
index 1ebfabe8..00000000
--- a/src/clickgen/util.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""Generate Cursors without hassle.
-
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import os
-import re
-import shutil
-from contextlib import contextmanager
-from pathlib import Path
-from typing import List, Union
-
-from clickgen.db import DATA, CursorDB, Data
-
-
-@contextmanager
-def chdir(directory: Union[str, Path]):
-    """Temporary change working directory using `with` syntax.
-
-    :param directory: path to directory.
-    :type directory: ``str`` or ``pathlib.Path``
-    """
-
-    prev_cwd = os.getcwd()
-    os.chdir(directory)
-    try:
-        yield
-    finally:
-        os.chdir(prev_cwd)
-
-
-def remove_util(p: Union[str, Path]) -> None:
-    """Remove this file, directory or symlink.
-
-    :param p: path to directory.
-    :type p: ``str`` or ``pathlib.Path``
-
-    :return: None.
-    :rtype: ``None``
-    """
-
-    p_obj: Path = Path(p)
-
-    if p_obj.exists():
-        if p_obj.is_dir():
-            shutil.rmtree(p_obj)
-        else:
-            p_obj.unlink()
-    else:
-        pass
-
-
-class PNGProvider:
-    """Provide organized `.png` files."""
-
-    bitmaps_dir: Path
-    __pngs: List[str] = []
-
-    def __init__(self, bitmaps_dir: Union[str, Path]) -> None:
-        """Init `PNGProvider`.
-
-        :param bitmaps_dir: Path to directory where `.png` files are stored.
-        :type bitmaps_dir: ``str`` or ``pathlib.Path``
-
-        :returns: None.
-        :return: ``None``
-        """
-
-        super().__init__()
-        self.bitmaps_dir = Path(bitmaps_dir)
-        for file in sorted(self.bitmaps_dir.iterdir()):
-            self.__pngs.append(file.name)
-
-        if len(self.__pngs) == 0:
-            raise FileNotFoundError(
-                f"'*.png' files not found in '{self.bitmaps_dir.absolute()}'"
-            )
-
-    def get(self, key: str) -> Union[List[Path], Path]:
-        """Retrieve `pathlib.Path` of filtered `.png` file/s.
-
-        This method return file location in `pathlib.Path` object.
-
-        Runtime directory `sync` is **not supported**, Which means creating \
-        or deleting a file on programs execution is not update class `__pngs` \
-        state.
-
-        :param key: `key` is filename.
-        :type key: ``str``
-
-        :returns: Returns `pathlib.Path` object or `List` of `pathlib.Path`
-                 object/s.
-        :rtype: ``List[pathlib.Path]`` or ``pathlib.Path``
-        """
-
-        k = key.split(".")
-        if len(k) == 1:
-            reg = re.compile(rf"^{k[0]}(?:-\d+)?.png$")
-        else:
-            reg = re.compile(rf"^{k[0]}(?:-\d+)?.{k[1]}$")
-
-        matched_pngs = filter(reg.match, self.__pngs)
-
-        paths = list(set(map(lambda x: self.bitmaps_dir / x, matched_pngs)))
-        if len(paths) == 1:
-            return paths[0]
-        return paths
-
-
-def add_missing_xcursors(
-    directory: Path,
-    data: Data = DATA,
-    rename: bool = False,
-    force: bool = False,
-) -> None:
-    """Create symlinks of missing ``Xcursor``.
-
-    :param directory: path to Xcursor directory.
-    :type directory: ``pathlib.Path``
-
-    :param data: Provide custom dataset of cursors. default to \
-    ~``clickgen.db.DATA``
-    :type data: ``List[set[str]]``
-
-    :param rename: If you want to rename ``Xcursor`` according to \
-        ``data``.
-    :type rename: ``bool``
-
-    :param force: To enable force renaming.
-    :type force: ``bool``
-    """
-
-    if not directory.exists() or not directory.is_dir():
-        raise NotADirectoryError(directory.absolute())
-
-    cur_db: CursorDB = CursorDB(data)
-
-    # Removing all symlinks cursors
-    if force:
-        for xcursor in directory.iterdir():
-            if xcursor.is_symlink():
-                xcursor.unlink(missing_ok=True)
-
-    xcursors = sorted(directory.iterdir())
-
-    for xcursor in xcursors:
-        # Rename Xcursor according to Database, If necessary
-        if rename:
-            new_path = cur_db.rename_file(xcursor)
-            if new_path:
-                xcursor = xcursor.rename(new_path)
-
-        # Creating symlinks
-        links = cur_db.search_symlinks(xcursor.stem)
-        if links:
-            for link in links:
-                with chdir(directory):
-                    os.symlink(xcursor, link)
diff --git a/src/clickgen/util.pyi b/src/clickgen/util.pyi
deleted file mode 100644
index cd7d99bb..00000000
--- a/src/clickgen/util.pyi
+++ /dev/null
@@ -1,13 +0,0 @@
-from clickgen.db import CursorDB as CursorDB, DATA as DATA, Data as Data
-from pathlib import Path
-from typing import List, Union
-
-def chdir(directory: Union[str, Path]): ...
-def remove_util(p: Union[str, Path]) -> None: ...
-
-class PNGProvider:
-    bitmaps_dir: Path
-    def __init__(self, bitmaps_dir: Union[str, Path]) -> None: ...
-    def get(self, key: str) -> Union[List[Path], Path]: ...
-
-def add_missing_xcursors(directory: Path, data: Data = ..., rename: bool = ..., force: bool = ...) -> None: ...
diff --git a/src/clickgen/writer/__init__.py b/src/clickgen/writer/__init__.py
new file mode 100644
index 00000000..f924d4d9
--- /dev/null
+++ b/src/clickgen/writer/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from clickgen.writer.windows import to_cur, to_win
+from clickgen.writer.x11 import to_x11
+
+__all__ = ["to_x11", "to_cur", "to_win"]
diff --git a/src/clickgen/writer/__init__.pyi b/src/clickgen/writer/__init__.pyi
new file mode 100644
index 00000000..d0078786
--- /dev/null
+++ b/src/clickgen/writer/__init__.pyi
@@ -0,0 +1,2 @@
+from clickgen.writer.windows import to_cur as to_cur, to_win as to_win
+from clickgen.writer.x11 import to_x11 as to_x11
diff --git a/src/clickgen/writer/windows.py b/src/clickgen/writer/windows.py
new file mode 100644
index 00000000..a2182cd0
--- /dev/null
+++ b/src/clickgen/writer/windows.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import struct
+from io import BytesIO
+from itertools import chain
+from typing import List, Tuple
+
+from clickgen.cursors import CursorFrame
+
+# .CUR FILE FORMAT
+MAGIC = b"\0\0\02\0"
+ICO_TYPE_CUR = 2
+ICON_DIR = struct.Struct("<HHH")
+ICON_DIR_ENTRY = struct.Struct("<BBBBHHII")
+
+
+def to_cur(frame: CursorFrame) -> bytes:
+    header = ICON_DIR.pack(0, ICO_TYPE_CUR, len(frame))
+    directory: List[bytes] = []
+    image_data: List[bytes] = []
+    offset = ICON_DIR.size + len(frame) * ICON_DIR_ENTRY.size
+
+    for image in frame:
+        clone = image.image.copy()
+        width, height = clone.size
+        if width > 256 or height > 256:
+            raise ValueError(f"Image too big for CUR format: {width}x{height}")
+        blob = BytesIO()
+        image.image.save(blob, "PNG")
+        blob.seek(0)
+        image_data.append(blob.read())
+        x_offset, y_offset = image.hotspot
+        directory.append(
+            ICON_DIR_ENTRY.pack(
+                height & 0xFF,
+                height & 0xFF,
+                0,
+                0,
+                x_offset,
+                y_offset,
+                blob.getbuffer().nbytes,
+                offset,
+            )
+        )
+        offset += blob.getbuffer().nbytes
+
+    return b"".join(chain([header], directory, image_data))
+
+
+# .ANI FILE FORMAT
+SIGNATURE = b"RIFF"
+ANI_TYPE = b"ACON"
+HEADER_CHUNK = b"anih"
+LIST_CHUNK = b"LIST"
+SEQ_CHUNK = b"seq "
+RATE_CHUNK = b"rate"
+FRAME_TYPE = b"fram"
+ICON_CHUNK = b"icon"
+RIFF_HEADER = struct.Struct("<4sI4s")
+CHUNK_HEADER = struct.Struct("<4sI")
+ANIH_HEADER = struct.Struct("<IIIIIIIII")
+UNSIGNED = struct.Struct("<I")
+SEQUENCE_FLAG = 0x2
+ICON_FLAG = 0x1
+
+
+def get_ani_cur_list(frames: List[CursorFrame]) -> bytes:
+    io = BytesIO()
+    for frame in frames:
+        cur_file = to_cur(frame)
+        io.write(CHUNK_HEADER.pack(ICON_CHUNK, len(cur_file)))
+        io.write(cur_file)
+        if len(cur_file) & 1:
+            io.write(b"\0")
+    return io.getvalue()
+
+
+def get_ani_rate_chunk(frames: List[CursorFrame]) -> bytes:
+    io = BytesIO()
+    io.write(CHUNK_HEADER.pack(RATE_CHUNK, UNSIGNED.size * len(frames)))
+    for frame in frames:
+        io.write(UNSIGNED.pack(int(round(frame.delay * 60))))
+    return io.getvalue()
+
+
+def to_ani(frames: List[CursorFrame]) -> bytes:
+    ani_header = ANIH_HEADER.pack(
+        ANIH_HEADER.size, len(frames), len(frames), 0, 0, 32, 1, 1, ICON_FLAG
+    )
+
+    cur_list = get_ani_cur_list(frames)
+    chunks = [
+        CHUNK_HEADER.pack(HEADER_CHUNK, len(ani_header)),
+        ani_header,
+        RIFF_HEADER.pack(LIST_CHUNK, len(cur_list) + 4, FRAME_TYPE),
+        cur_list,
+        get_ani_rate_chunk(frames),
+    ]
+    body = b"".join(chunks)
+    riff_header: bytes = RIFF_HEADER.pack(SIGNATURE, len(body) + 4, ANI_TYPE)
+    return riff_header + body
+
+
+def to_win(frames: List[CursorFrame]) -> Tuple[str, bytes]:
+    if len(frames) == 1:
+        return ".cur", to_cur(frames[0])
+    else:
+        return ".ani", to_ani(frames)
diff --git a/src/clickgen/writer/windows.pyi b/src/clickgen/writer/windows.pyi
new file mode 100644
index 00000000..8d9d8168
--- /dev/null
+++ b/src/clickgen/writer/windows.pyi
@@ -0,0 +1,29 @@
+from clickgen.cursors import CursorFrame as CursorFrame
+from typing import Any, List, Tuple
+
+MAGIC: bytes
+ICO_TYPE_CUR: int
+ICON_DIR: Any
+ICON_DIR_ENTRY: Any
+
+def to_cur(frame: CursorFrame) -> bytes: ...
+
+SIGNATURE: bytes
+ANI_TYPE: bytes
+HEADER_CHUNK: bytes
+LIST_CHUNK: bytes
+SEQ_CHUNK: bytes
+RATE_CHUNK: bytes
+FRAME_TYPE: bytes
+ICON_CHUNK: bytes
+RIFF_HEADER: Any
+CHUNK_HEADER: Any
+ANIH_HEADER: Any
+UNSIGNED: Any
+SEQUENCE_FLAG: int
+ICON_FLAG: int
+
+def get_ani_cur_list(frames: List[CursorFrame]) -> bytes: ...
+def get_ani_rate_chunk(frames: List[CursorFrame]) -> bytes: ...
+def to_ani(frames: List[CursorFrame]) -> bytes: ...
+def to_win(frames: List[CursorFrame]) -> Tuple[str, bytes]: ...
diff --git a/src/clickgen/writer/x11.py b/src/clickgen/writer/x11.py
new file mode 100644
index 00000000..2b23c2ba
--- /dev/null
+++ b/src/clickgen/writer/x11.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import struct
+from itertools import chain
+from operator import itemgetter
+from typing import List
+
+from clickgen.cursors import CursorFrame
+
+# XCURSOR FILE FORMAT
+MAGIC = b"Xcur"
+VERSION = 0x1_0000
+FILE_HEADER = struct.Struct("<4sIII")
+TOC_CHUNK = struct.Struct("<III")
+CHUNK_IMAGE = 0xFFFD0002
+IMAGE_HEADER = struct.Struct("<IIIIIIIII")
+
+
+def to_x11(frames: List[CursorFrame]) -> bytes:
+    chunks = []
+
+    for frame in frames:
+        for cursor in frame:
+            hx, hy = cursor.hotspot
+            header = IMAGE_HEADER.pack(
+                IMAGE_HEADER.size,
+                CHUNK_IMAGE,
+                cursor.nominal,
+                1,
+                cursor.image.width,
+                cursor.image.height,
+                hx,
+                hy,
+                int(frame.delay * 1000),
+            )
+            chunks.append(
+                (
+                    CHUNK_IMAGE,
+                    cursor.nominal,
+                    header + cursor.image.tobytes("raw", "BGRA"),
+                )
+            )
+
+    header = FILE_HEADER.pack(
+        MAGIC,
+        FILE_HEADER.size,
+        VERSION,
+        len(chunks),
+    )
+
+    offset = FILE_HEADER.size + len(chunks) * TOC_CHUNK.size
+    toc = []
+    for chunk_type, chunk_subtype, chunk in chunks:
+        toc.append(
+            TOC_CHUNK.pack(
+                chunk_type,
+                chunk_subtype,
+                offset,
+            )
+        )
+        offset += len(chunk)
+
+    return b"".join(chain([header], toc, map(itemgetter(2), chunks)))
diff --git a/src/clickgen/writer/x11.pyi b/src/clickgen/writer/x11.pyi
new file mode 100644
index 00000000..7382fda5
--- /dev/null
+++ b/src/clickgen/writer/x11.pyi
@@ -0,0 +1,11 @@
+from clickgen.cursors import CursorFrame as CursorFrame
+from typing import Any, List
+
+MAGIC: bytes
+VERSION: int
+FILE_HEADER: Any
+TOC_CHUNK: Any
+CHUNK_IMAGE: int
+IMAGE_HEADER: Any
+
+def to_x11(frames: List[CursorFrame]) -> bytes: ...
diff --git a/src/xcursorgen/Makefile b/src/xcursorgen/Makefile
deleted file mode 100644
index 1757ab53..00000000
--- a/src/xcursorgen/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-all: clean xcursorgen.so
-.PHONY: all
-
-CC ?= gcc
-LIBS = -lX11 -lXcursor -lpng -lz
-OUT_DIR = ../clickgen
-
-xcursorgen.so: xcursorgen.c
-	$(CC) -Wextra -Wall $(CFLAGS) $(LDFLAGS) -Wl,-zdefs -shared -o $@ -fPIC  $< $(LIBS)
-	cp $@ $(OUT_DIR)/xcursorgen.so
-
-clean:
-	@rm -rf *.o *.so $(OUT_DIR)/xcursorgen.so
diff --git a/src/xcursorgen/xcursorgen.c b/src/xcursorgen/xcursorgen.c
deleted file mode 100644
index 345ea7f2..00000000
--- a/src/xcursorgen/xcursorgen.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * xcursorgen.c
- *
- * Copyright (C) 2002 Manish Singh
- *
- * Permission to use, copy, modify, distribute, and sell this software and its
- * documentation for any purpose is hereby granted without fee, provided that
- * the above copyright notice appear in all copies and that both that
- * copyright notice and this permission notice appear in supporting
- * documentation, and that the name of Manish Singh not be used in
- * advertising or publicity pertaining to distribution of the software without
- * specific, written prior permission.  Manish Singh makes no
- * representations about the suitability of this software for any purpose.  It
- * is provided "as is" without express or implied warranty.
- *
- * MANISH SINGH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
- * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
- * EVENT SHALL MANISH SINGH BE LIABLE FOR ANY SPECIAL, INDIRECT OR
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
- * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <X11/Xcursor/Xcursor.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <ctype.h>
-#include <png.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define PACKAGE_VERSION "1.0.0"
-#define UNUSED(x) (void)(x)
-
-struct flist {
-  int size;
-  int xhot, yhot;
-  int delay;
-  char *pngfile;
-  struct flist *next;
-};
-
-static void usage(const char *name) {
-  fprintf(stderr,
-          "usage: %s [-V] [--version] [-?] [--help] [-p <dir>] [--prefix "
-          "<dir>] [CONFIG [OUT]]\n%s",
-          name,
-          "Generate an Xcursor file from a series of PNG images\n"
-          "\n"
-          "  -V, --version      display the version number and exit\n"
-          "  -?, --help         display this message and exit\n"
-          "  -p, --prefix <dir> find cursor images in <dir>\n"
-          "\n"
-          "With no CONFIG, or when CONFIG is -, read standard input. "
-          "Same with OUT and\n"
-          "standard output.\n");
-}
-
-static int read_config_file(const char *config, struct flist **list) {
-  FILE *fp;
-  char line[4096], pngfile[4000];
-  int size, xhot, yhot, delay;
-  struct flist *start = NULL, *end = NULL, *curr;
-  int count = 0;
-
-  if (strcmp(config, "-") != 0) {
-    fp = fopen(config, "r");
-    if (!fp) {
-      *list = NULL;
-      return 0;
-    }
-  } else
-    fp = stdin;
-
-  while (fgets(line, sizeof(line), fp) != NULL) {
-    if (line[0] == '#') continue;
-
-    switch (sscanf(line, "%d %d %d %3999s %d", &size, &xhot, &yhot, pngfile,
-                   &delay)) {
-      case 4:
-        delay = 50;
-        break;
-      case 5:
-        break;
-      default: {
-        fprintf(stderr, "Bad config file data!\n");
-        fclose(fp);
-        return 0;
-      }
-    }
-
-    curr = malloc(sizeof(struct flist));
-    if (curr == NULL) {
-      fprintf(stderr, "malloc() failed\n");
-      fclose(fp);
-      return 0;
-    }
-
-    curr->size = size;
-    curr->xhot = xhot;
-    curr->yhot = yhot;
-
-    curr->delay = delay;
-
-    curr->pngfile = strdup(pngfile);
-    if (curr->pngfile == NULL) {
-      fprintf(stderr, "strdup() failed\n");
-      fclose(fp);
-      free(curr);
-      return 0;
-    }
-
-    curr->next = NULL;
-
-    if (start) {
-      end->next = curr;
-      end = curr;
-    } else {
-      start = curr;
-      end = curr;
-    }
-
-    count++;
-  }
-
-  fclose(fp);
-
-  *list = start;
-  return count;
-}
-
-#define div_255(x) (((x) + 0x80 + (((x) + 0x80) >> 8)) >> 8)
-
-static void premultiply_data(png_structp png, png_row_infop row_info,
-                             png_bytep data) {
-  UNUSED(png);
-  int i;
-
-  for (i = 0; (unsigned)i < row_info->rowbytes; i += 4) {
-    unsigned char *base = &data[i];
-    unsigned char blue = base[0];
-    unsigned char green = base[1];
-    unsigned char red = base[2];
-    unsigned char alpha = base[3];
-    XcursorPixel p;
-
-    red = div_255((unsigned)red * (unsigned)alpha);
-    green = div_255((unsigned)green * (unsigned)alpha);
-    blue = div_255((unsigned)blue * (unsigned)alpha);
-    p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
-    memcpy(base, &p, sizeof(XcursorPixel));
-  }
-}
-
-static XcursorImage *load_image(struct flist *list, const char *prefix) {
-  XcursorImage *image;
-  png_structp png;
-  png_infop info;
-  png_bytepp rows;
-  FILE *fp;
-  int i;
-  png_uint_32 width, height;
-  int depth, color, interlace;
-  char *file;
-
-  png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-  if (png == NULL) return NULL;
-
-  info = png_create_info_struct(png);
-  if (info == NULL) {
-    png_destroy_read_struct(&png, NULL, NULL);
-    return NULL;
-  }
-
-  if (setjmp(png_jmpbuf(png))) {
-    png_destroy_read_struct(&png, &info, NULL);
-    return NULL;
-  }
-
-  if (prefix) {
-    file = malloc(strlen(prefix) + 1 + strlen(list->pngfile) + 1);
-    if (file == NULL) {
-      fprintf(stderr, "malloc() failed\n");
-      png_destroy_read_struct(&png, &info, NULL);
-      return NULL;
-    }
-    strcpy(file, prefix);
-    strcat(file, "/");
-    strcat(file, list->pngfile);
-  } else
-    file = list->pngfile;
-  fp = fopen(file, "rb");
-  if (prefix) free(file);
-
-  if (fp == NULL) {
-    png_destroy_read_struct(&png, &info, NULL);
-    return NULL;
-  }
-
-  png_init_io(png, fp);
-  png_read_info(png, info);
-  png_get_IHDR(png, info, &width, &height, &depth, &color, &interlace, NULL,
-               NULL);
-
-  /* TODO: More needs to be done here maybe */
-
-  if (color == PNG_COLOR_TYPE_PALETTE && depth <= 8) png_set_expand(png);
-
-  if (color == PNG_COLOR_TYPE_GRAY && depth < 8) png_set_expand(png);
-
-  if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_expand(png);
-
-  if (depth == 16) png_set_strip_16(png);
-
-  if (depth < 8) png_set_packing(png);
-
-  if (color == PNG_COLOR_TYPE_GRAY || color == PNG_COLOR_TYPE_GRAY_ALPHA)
-    png_set_gray_to_rgb(png);
-
-  if (interlace != PNG_INTERLACE_NONE) png_set_interlace_handling(png);
-
-  png_set_bgr(png);
-  png_set_filler(png, 255, PNG_FILLER_AFTER);
-
-  png_set_read_user_transform_fn(png, premultiply_data);
-
-  png_read_update_info(png, info);
-
-  image = XcursorImageCreate(width, height);
-
-  image->size = list->size;
-  image->xhot = list->xhot;
-  image->yhot = list->yhot;
-  image->delay = list->delay;
-
-  rows = malloc(sizeof(png_bytep) * height);
-  if (rows == NULL) {
-    fclose(fp);
-    png_destroy_read_struct(&png, &info, NULL);
-    return NULL;
-  }
-
-  for (i = 0; (unsigned)i < height; i++)
-    rows[i] = (png_bytep)(image->pixels + i * width);
-
-  png_read_image(png, rows);
-  png_read_end(png, info);
-
-  free(rows);
-  fclose(fp);
-  png_destroy_read_struct(&png, &info, NULL);
-
-  return image;
-}
-
-static int write_cursors(int count, struct flist *list, const char *filename,
-                         const char *prefix) {
-  XcursorImages *cimages;
-  XcursorImage *image;
-  int i;
-  FILE *fp;
-  int ret;
-
-  if (strcmp(filename, "-") != 0) {
-    fp = fopen(filename, "wb");
-    if (!fp) return 1;
-  } else
-    fp = stdout;
-
-  cimages = XcursorImagesCreate(count);
-
-  cimages->nimage = count;
-
-  for (i = 0; i < count; i++, list = list->next) {
-    image = load_image(list, prefix);
-    if (image == NULL) {
-      fprintf(stderr, "PNG error while reading %s!\n", list->pngfile);
-      fclose(fp);
-      return 1;
-    }
-
-    cimages->images[i] = image;
-  }
-
-  ret = XcursorFileSaveImages(fp, cimages);
-
-  fclose(fp);
-
-  return ret ? 0 : 1;
-}
-
-static int check_image(char *image) {
-  unsigned int width, height;
-  unsigned char *data;
-  int x_hot, y_hot;
-  unsigned char hash[XCURSOR_BITMAP_HASH_SIZE];
-  int i;
-
-  if (XReadBitmapFileData(image, &width, &height, &data, &x_hot, &y_hot) !=
-      BitmapSuccess) {
-    fprintf(stderr, "Can't open bitmap file \"%s\"\n", image);
-    return 1;
-  } else {
-    XImage ximage = {.width = width,
-                     .height = height,
-                     .depth = 1,
-                     .bits_per_pixel = 1,
-                     .xoffset = 0,
-                     .format = XYPixmap,
-                     .data = (char *)data,
-                     .byte_order = LSBFirst,
-                     .bitmap_unit = 8,
-                     .bitmap_bit_order = LSBFirst,
-                     .bitmap_pad = 8,
-                     .bytes_per_line = (width + 7) / 8};
-    XcursorImageHash(&ximage, hash);
-  }
-  printf("%s: ", image);
-  for (i = 0; i < XCURSOR_BITMAP_HASH_SIZE; i++) printf("%02x", hash[i]);
-  printf("\n");
-  return 0;
-}
-
-int main(int argc, char *argv[]) {
-  struct flist *list;
-  int count;
-  const char *in = NULL, *out = NULL;
-  const char *prefix = NULL;
-  int i;
-
-  for (i = 1; i < argc; i++) {
-    if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
-      printf("xcursorgen version %s\n", PACKAGE_VERSION);
-      return 0;
-    }
-
-    if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) {
-      usage(argv[0]);
-      return 0;
-    }
-    if (strcmp(argv[i], "-image") == 0) {
-      int ret = 0;
-      while (argv[++i]) {
-        if (check_image(argv[i])) ret = i;
-      }
-      return ret;
-    }
-    if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--prefix") == 0) {
-      i++;
-      if (argv[i] == NULL) {
-        fprintf(stderr, "%s: %s requires an argument\n", argv[0], argv[i - 1]);
-        usage(argv[0]);
-        return 1;
-      }
-      prefix = argv[i];
-      continue;
-    }
-
-    if (!in)
-      in = argv[i];
-    else if (!out)
-      out = argv[i];
-    else {
-      fprintf(stderr, "%s: unexpected argument '%s'\n", argv[0], argv[i]);
-      usage(argv[0]);
-      return 1;
-    }
-  }
-
-  if (!in) in = "-";
-  if (!out) out = "-";
-
-  count = read_config_file(in, &list);
-  if (count == 0) {
-    fprintf(stderr, "Error reading config file!\n");
-    return 1;
-  }
-
-  return write_cursors(count, list, out, prefix);
-}
diff --git a/tox.ini b/tox.ini
index 323554f5..5a76db57 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,14 +1,21 @@
 [tox]
 minversion = 3.8.0
-envlist = py38, py39, py310, flake8, mypy
+envlist = py{37,38,39,310}-{linux,macos,windows}, flake8, mypy
 isolated_build = true
 
 [gh-actions]
 python =
+    3.7: py37
     3.8: py38, flake8, mypy
     3.9: py39
     3.10: py310
 
+[gh-actions:env]
+PLATFORM =
+    ubuntu-latest: linux
+    macos-latest: macos
+    windows-latest: windows
+
 [testenv]
 setenv =
     PYTHONPATH = {toxinidir}


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/tests/builders/__init__.py b/tests/builders/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/tests/builders/test_wincursor.py b/tests/builders/test_wincursor.py
deleted file mode 100644
index feef8059..00000000
--- a/tests/builders/test_wincursor.py
+++ /dev/null
@@ -1,321 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-from random import randint
-
-import pytest
-
-from clickgen.builders import Options, WindowsCursor
-from clickgen.core import CursorAlias
-from tests.utils import create_test_image
-
-
-def test_WindowsCursor_from_bitmap_with_static_png(
-    static_png, hotspot, image_dir
-) -> None:
-    """Testing WindowsCursor `form_bitmap` classmethod for generating **static** cursor."""
-    win = WindowsCursor.from_bitmap(
-        png=static_png,
-        hotspot=hotspot,
-        size=(5, 5),
-        canvas_size=(10, 10),
-        out_dir=image_dir,
-    )
-    assert win.exists() is True
-    assert win.__sizeof__() > 0
-
-
-def test_WindowsCursor_from_bitmap_with_static_png_exceptions(
-    static_png, hotspot, image_dir
-) -> None:
-    """Testing WindowsCursor `form_bitmap` classmethod exceptions for generating **static** cursor."""
-    with pytest.raises(KeyError) as excinfo1:
-        WindowsCursor.from_bitmap()
-    assert str(excinfo1.value) == "\"argument 'png' required\""
-
-    with pytest.raises(KeyError) as excinfo2:
-        WindowsCursor.from_bitmap(png=static_png)
-    assert str(excinfo2.value) == "\"argument 'hotspot' required\""
-
-    with pytest.raises(KeyError) as excinfo3:
-        WindowsCursor.from_bitmap(png=static_png, hotspot=hotspot)
-    assert str(excinfo3.value) == "\"argument 'size' required\""
-
-    with pytest.raises(KeyError) as excinfo4:
-        WindowsCursor.from_bitmap(png=static_png, hotspot=hotspot, size=(5, 5))
-    assert str(excinfo4.value) == "\"argument 'canvas_size' required\""
-
-    with pytest.raises(KeyError) as excinfo5:
-        WindowsCursor.from_bitmap(
-            png=static_png, hotspot=hotspot, size=(5, 5), canvas_size=(10, 10)
-        )
-    assert str(excinfo5.value) == "\"argument 'out_dir' required\""
-
-    WindowsCursor.from_bitmap(
-        png=static_png,
-        hotspot=hotspot,
-        canvas_size=(10, 10),
-        size=(5, 5),
-        out_dir=image_dir,
-        position="top_left",
-    )
-
-    WindowsCursor.from_bitmap(
-        png=static_png,
-        hotspot=hotspot,
-        canvas_size=(10, 10),
-        size=(5, 5),
-        out_dir=image_dir,
-        position="top_left",
-        options=Options(add_shadows=True),
-    )
-
-
-def test_WindowsCursor_from_bitmap_with_animated_png(
-    animated_png, hotspot, image_dir
-) -> None:
-    """Testing WindowsCursor `form_bitmap` classmethod for generating **animated** cursor."""
-    win = WindowsCursor.from_bitmap(
-        png=animated_png,
-        hotspot=hotspot,
-        size=(5, 5),
-        canvas_size=(10, 10),
-        out_dir=image_dir,
-        delay=1,
-    )
-    assert win.exists() is True
-    assert win.__sizeof__() > 0
-
-
-def test_WindowsCursor_from_bitmap_with_animated_png_exceptions(
-    animated_png, hotspot, image_dir
-) -> None:
-    """Testing WindowsCursor `form_bitmap` classmethod exceptions for generating **animated** \
-    cursor.
-    """
-    with pytest.raises(KeyError) as excinfo1:
-        WindowsCursor.from_bitmap()
-    assert str(excinfo1.value) == "\"argument 'png' required\""
-
-    with pytest.raises(KeyError) as excinfo2:
-        WindowsCursor.from_bitmap(png=animated_png)
-    assert str(excinfo2.value) == "\"argument 'hotspot' required\""
-
-    with pytest.raises(KeyError) as excinfo3:
-        WindowsCursor.from_bitmap(png=animated_png, hotspot=hotspot)
-    assert str(excinfo3.value) == "\"argument 'size' required\""
-
-    with pytest.raises(KeyError) as excinfo4:
-        WindowsCursor.from_bitmap(png=animated_png, hotspot=hotspot, size=(5, 5))
-    assert str(excinfo4.value) == "\"argument 'canvas_size' required\""
-
-    with pytest.raises(KeyError) as excinfo5:
-        WindowsCursor.from_bitmap(
-            png=animated_png, hotspot=hotspot, size=(5, 5), canvas_size=(10, 10)
-        )
-    assert str(excinfo5.value) == "\"argument 'out_dir' required\""
-
-    with pytest.raises(KeyError) as excinfo6:
-        WindowsCursor.from_bitmap(
-            png=animated_png,
-            hotspot=hotspot,
-            canvas_size=(10, 10),
-            size=(5, 5),
-            out_dir=image_dir,
-        )
-    assert str(excinfo6.value) == "\"argument 'delay' required\""
-
-    WindowsCursor.from_bitmap(
-        png=animated_png,
-        hotspot=hotspot,
-        canvas_size=(10, 10),
-        size=(5, 5),
-        out_dir=image_dir,
-        position="top_left",
-        delay=2,
-    )
-
-    WindowsCursor.from_bitmap(
-        png=animated_png,
-        hotspot=hotspot,
-        canvas_size=(10, 10),
-        size=(5, 5),
-        out_dir=image_dir,
-        position="top_left",
-        delay=2,
-        options=Options(add_shadows=True),
-    )
-
-
-def test_WindowsCursor_exceptions(static_config, image_dir) -> None:
-    """Testing WindowsCursor `framesets` Exceptions."""
-    win = WindowsCursor(static_config, image_dir, options=Options())
-
-    f1 = [
-        (10, -1, -1, "test-0.png", 10),
-        (9, -1, -1, "test-0.png", 10),
-        (10, -1, -1, "test-1.png", 10),
-        (9, -1, -1, "test-1.png", 10),
-        (10, -1, -1, "test-2.png", 10),
-        (9, -1, -1, "test-2.png", 10),
-        (10, -1, -1, "test-3.png", 10),
-        (9, -1, -1, "test-3.png", 10),
-    ]
-
-    with pytest.raises(ValueError) as excinfo:
-        win.make_framesets(f1)
-
-    assert (
-        str(excinfo.value)
-        == "Frames are not sorted: frame 2 has size 10, but we have seen that already"
-    )
-
-    f2 = [
-        (10, -1, -1, "test-0.png", 10),
-        (10, -1, -1, "test-1.png", 10),
-        (10, -1, -1, "test-2.png", 10),
-        (10, -1, -1, "test-3.png", 10),
-        (9, -1, -1, "test-0.png", 10),
-        (9, -1, -1, "test-1.png", 10),
-        (9, -1, -1, "test-2.png", 10),
-    ]
-
-    with pytest.raises(ValueError) as excinfo:
-        win.make_framesets(f2)
-
-    assert str(excinfo.value) == "Frameset 3 has size 1, expected 2"
-
-    f3 = [
-        (10, -1, -1, "test-0.png", 10),
-        (10, -1, -1, "test-1.png", 10),
-        (10, -1, -1, "test-2.png", 10),
-        (10, -1, -1, "test-3.png", 10),
-        (9, -1, -1, "test-0.png", 10),
-        (9, -1, -1, "test-1.png", 10),
-        (9, -1, -1, "test-2.png", 10),
-        (9, -1, -1, "test-3.png", 1),
-    ]
-
-    with pytest.raises(ValueError) as excinfo:
-        win.make_framesets(f3)
-
-    assert (
-        str(excinfo.value)
-        == "Frameset 1 has duration 1 for framesize 9, but 10 for framesize 10"
-    )
-
-    f4 = [
-        (10, -1, -1, "test-0.png", 10),
-        (10, -1, -1, "test-1.png", 10),
-        (10, -1, -1, "test-2.png", 10),
-        (10, -1, -1, "test-3.png", 10),
-        (9, -1, -1, "test-0.png", 10),
-        (9, -1, -1, "test-1.png", 10),
-        (9, -1, -1, "test-2.png", 10),
-        (9, -1, -1, "test-3.png", 10),
-    ]
-    framesets = win.make_framesets(f4)
-    ff = framesets[0][0]
-    print(ff[0])
-
-
-def test_WindowsCursor(static_config, image_dir) -> None:
-    """Testing WindowsCursor members value."""
-    win = WindowsCursor(static_config, image_dir, options=Options())
-    assert win.config_file == static_config
-
-    # We know 'out_dir' is not exists
-    assert win.out_dir.exists() is True
-
-
-def test_WindowsCursor_generate_with_static_config(static_config, image_dir) -> None:
-    """Testing WindowsCursor generate method for generating static (.cur) \
-    cursor.
-    """
-    win = WindowsCursor(static_config, image_dir, options=Options())
-    win.generate()
-
-    assert win.out.exists() is True
-    assert win.out.suffix == ".cur"
-    assert win.out.__sizeof__() > 0
-
-
-def test_WindowsCursor_generate_with_animated_config(
-    animated_config, image_dir
-) -> None:
-    """Testing WindowsCursor generate method for generating animated (.ani) \
-    cursor.
-    """
-    win = WindowsCursor(animated_config, image_dir, options=Options())
-    win.generate()
-
-    assert win.out.exists() is True
-    assert win.out.suffix == ".ani"
-    assert win.out.__sizeof__() > 0
-
-
-def test_WindowsCursor_generate_with_static_config_and_shadow(
-    static_config, image_dir
-) -> None:
-    """Testing WindowsCursor generate method for generating static (.cur) \
-    cursor with shadows.
-    """
-    options = Options(add_shadows=True)
-    win = WindowsCursor(static_config, image_dir, options)
-    win.generate()
-
-    assert win.out.exists() is True
-    assert win.out.suffix == ".cur"
-    assert win.out.__sizeof__() > 0
-
-
-def test_WindowsCursor_generate_with_animated_config_and_shadow(
-    animated_config, image_dir
-) -> None:
-    """Testing WindowsCursor generate method for generating animated (.ani) \
-    cursor with shadows.
-    """
-    options = Options(add_shadows=True)
-    win = WindowsCursor(animated_config, image_dir, options)
-    win.generate()
-
-    assert win.out.exists() is True
-    assert win.out.suffix == ".ani"
-    assert win.out.__sizeof__() > 0
-
-
-def test_WindowsCursor_create_with_static_config(static_config, image_dir) -> None:
-    """Testing WindowsCursor generate static (.cur) cursor with ``create`` \
-    classmethod.
-    """
-    with WindowsCursor.create(static_config, image_dir) as win:
-        assert win.exists() is True
-        assert win.suffix == ".cur"
-        assert win.__sizeof__() > 0
-
-
-def test_WindowsCursor_create_with_animated_config(hotspot, image_dir) -> None:
-    """Testing WindowsCursor generate animated (.ani) cursor with ``create`` \
-    classmethod.
-    """
-    animated_png = create_test_image(image_dir, 4)
-    with CursorAlias.from_bitmap(animated_png, hotspot) as alias:
-        cfg = alias.create((10, 10), delay=999999999)
-
-        # Cover more than one delay sets
-        new_lines = []
-        with cfg.open("r") as f:
-            lines = f.readlines()
-            for l in lines:
-                new_lines.append(l.replace("999999999", str(randint(20, 30))))
-        with cfg.open("w") as f:
-            f.writelines(new_lines)
-
-        with WindowsCursor.create(cfg, image_dir) as win:
-            assert win.exists() is True
-            assert win.suffix == ".ani"
-            assert win.__sizeof__() > 0
diff --git a/tests/builders/test_xcursor.py b/tests/builders/test_xcursor.py
deleted file mode 100644
index 0d9dd5bf..00000000
--- a/tests/builders/test_xcursor.py
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-from pathlib import Path
-
-import pytest
-
-from clickgen.builders import XCursor
-
-
-def test_XCursor_config_file_not_found_exception(image_dir) -> None:
-    """Testing XCursor raises **FileNotFoundError** for config_file or not."""
-    with pytest.raises(FileNotFoundError) as excinfo:
-        XCursor(image_dir, image_dir)
-    assert str(excinfo.value) == f"'{image_dir.name}' is not found or not a config file"
-
-
-def test_XCursor(static_config, image_dir) -> None:
-    """Testing XCursor members value."""
-
-    x = XCursor(static_config, image_dir)
-    assert x.config_file == static_config
-
-    # We know 'out_dir' is not exists
-    assert x.out_dir.exists() is True
-    assert x.out_dir == image_dir / "cursors"
-
-
-def test_XCursor_generate_exception(image_dir) -> None:
-    """Testing XCursor generate **RuntimeError** exception."""
-    cfg: Path = image_dir / "test.alias"
-    cfg.write_text("10 10 10 test/test.png")
-    x = XCursor(cfg, image_dir)
-    with pytest.raises(RuntimeError) as excinfo:
-        x.generate()
-
-    assert (
-        str(excinfo.value)
-        == "'xcursorgen' failed to generate XCursor from 'test.alias'"
-    )
-
-
-def test_XCursor_generate_with_static_config(static_config, image_dir) -> None:
-    """Testing XCursor generate **static** cursors."""
-    x = XCursor(static_config, image_dir)
-    x.generate()
-
-    assert x.out.exists() is True
-    assert x.out.__sizeof__() > 0
-
-
-def test_XCursor_generate_with_animated_config(animated_config, image_dir) -> None:
-    """Testing XCursor generate **animated** cursors."""
-    x = XCursor(animated_config, image_dir)
-    x.generate()
-
-    assert x.out.exists() is True
-    assert x.out.__sizeof__() > 0
-
-
-def test_XCursor_create_with_static_config(static_config, image_dir) -> None:
-    """Testing XCursor `create` classmethod for generating **static** \
-    cursor.
-    """
-    x = XCursor.create(static_config, image_dir)
-    assert x.exists() is True
-    assert x.__sizeof__() > 0
-
-
-def test_XCursor_create_with_animated_config(animated_config, image_dir) -> None:
-    """Testing XCursor `create` classmethod for generating **animated** \
-    cursor.
-    """
-    x = XCursor.create(animated_config, image_dir)
-    assert x.exists() is True
-    assert x.__sizeof__() > 0
-
-
-def test_XCursor_from_bitmap_with_static_png(static_png, hotspot, image_dir) -> None:
-    """Testing XCursor `form_bitmap` classmethod for generating **static** \
-    cursor.
-    """
-    x = XCursor.from_bitmap(
-        png=static_png, hotspot=hotspot, x_sizes=(10, 10), out_dir=image_dir
-    )
-    assert x.exists() is True
-    assert x.__sizeof__() > 0
-
-    x1 = XCursor.from_bitmap(
-        png=static_png, hotspot=hotspot, x_sizes=[(10, 10)], out_dir=image_dir
-    )
-    assert x1.exists() is True
-    assert x1.__sizeof__() > 0
-
-    x2 = XCursor.from_bitmap(
-        png=static_png, hotspot=hotspot, x_sizes=[(10, 10), (20, 20)], out_dir=image_dir
-    )
-    assert x2.exists() is True
-    assert x2.__sizeof__() > 0
-
-
-def test_XCursor_from_bitmap_with_static_png_exceptions(static_png, hotspot) -> None:
-    """Testing XCursor `form_bitmap` classmethod exceptions for generating **static** \
-    cursor.
-    """
-    with pytest.raises(KeyError) as excinfo1:
-        XCursor.from_bitmap()
-    assert str(excinfo1.value) == "\"argument 'png' required\""
-
-    with pytest.raises(KeyError) as excinfo2:
-        XCursor.from_bitmap(png=static_png)
-    assert str(excinfo2.value) == "\"argument 'hotspot' required\""
-
-    with pytest.raises(KeyError) as excinfo3:
-        XCursor.from_bitmap(png=static_png, hotspot=hotspot)
-    assert str(excinfo3.value) == "\"argument 'x_sizes' required\""
-
-    with pytest.raises(KeyError) as excinfo4:
-        XCursor.from_bitmap(png=static_png, hotspot=hotspot, x_sizes=(10, 10))
-    assert str(excinfo4.value) == "\"argument 'out_dir' required\""
-
-
-def test_XCursor_from_bitmap_with_animated_png(
-    animated_png, hotspot, image_dir
-) -> None:
-    """Testing XCursor `form_bitmap` classmethod for generating **animated** \
-    cursor.
-    """
-    # testing with single size
-    x = XCursor.from_bitmap(
-        png=animated_png,
-        hotspot=hotspot,
-        x_sizes=[(10, 10)],
-        out_dir=image_dir,
-        delay=1,
-    )
-    assert x.exists() is True
-    assert x.__sizeof__() > 0
-
-    x1 = XCursor.from_bitmap(
-        png=animated_png, hotspot=hotspot, x_sizes=(10, 10), out_dir=image_dir, delay=1
-    )
-    assert x1.exists() is True
-    assert x1.__sizeof__() > 0
-
-    # testing with multiple sizes
-    x2 = XCursor.from_bitmap(
-        png=animated_png,
-        hotspot=hotspot,
-        x_sizes=[(10, 10), (20, 20)],
-        out_dir=image_dir,
-        delay=1,
-    )
-    assert x2.exists() is True
-    assert x2.__sizeof__() > 0
-
-
-def test_XCursor_from_bitmap_with_animated_png_exceptions(
-    animated_png, hotspot, image_dir
-) -> None:
-    """Testing XCursor `form_bitmap` classmethod exceptions for generating **animated** \
-    cursor.
-    """
-    with pytest.raises(KeyError) as excinfo1:
-        XCursor.from_bitmap()
-    assert str(excinfo1.value) == "\"argument 'png' required\""
-
-    with pytest.raises(KeyError) as excinfo2:
-        XCursor.from_bitmap(png=animated_png)
-    assert str(excinfo2.value) == "\"argument 'hotspot' required\""
-
-    with pytest.raises(KeyError) as excinfo3:
-        XCursor.from_bitmap(png=animated_png, hotspot=hotspot)
-    assert str(excinfo3.value) == "\"argument 'x_sizes' required\""
-
-    with pytest.raises(KeyError) as excinfo4:
-        XCursor.from_bitmap(png=animated_png, hotspot=hotspot, x_sizes=(10, 10))
-    assert str(excinfo4.value) == "\"argument 'out_dir' required\""
-
-    with pytest.raises(KeyError) as excinfo5:
-        XCursor.from_bitmap(
-            png=animated_png, hotspot=hotspot, x_sizes=(10, 10), out_dir=image_dir
-        )
-    assert str(excinfo5.value) == "\"argument 'delay' required\""
diff --git a/tests/configparser/sections/test_config.py b/tests/configparser/sections/test_config.py
new file mode 100644
index 00000000..8d3da830
--- /dev/null
+++ b/tests/configparser/sections/test_config.py
@@ -0,0 +1,11 @@
+from pathlib import Path
+
+from clickgen.configparser.sections.config import parse_config_section
+
+
+def test_parse_config_section(cp):
+    o = parse_config_section(cp)
+    assert isinstance(o.bitmaps_dir, Path)
+    assert isinstance(o.out_dir, Path)
+    assert o.sizes == [22, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96]
+    assert o.platforms == ["windows", "x11"]
diff --git a/tests/configparser/sections/test_hotspot.py b/tests/configparser/sections/test_hotspot.py
new file mode 100644
index 00000000..8db69ffb
--- /dev/null
+++ b/tests/configparser/sections/test_hotspot.py
@@ -0,0 +1,9 @@
+from clickgen.configparser.sections.hotspots import parse_hotspot_section
+
+
+def test_parse_hotspot_section(cp):
+    o = parse_hotspot_section(cp)
+    assert isinstance(o["a"], tuple)
+    assert o["a"] == (10, 10)
+    assert isinstance(o["b"], tuple)
+    assert o["b"] == (10, 10)
diff --git a/tests/configparser/sections/test_symlinks.py b/tests/configparser/sections/test_symlinks.py
new file mode 100644
index 00000000..6647e56b
--- /dev/null
+++ b/tests/configparser/sections/test_symlinks.py
@@ -0,0 +1,11 @@
+from clickgen.configparser.sections.symlinks import parse_symlinks_section
+
+
+def test_parse_symlinks_section(cp):
+    o = parse_symlinks_section(cp)
+
+    assert isinstance(o["a"], list)
+    assert o["a"] == ["c"]
+
+    assert isinstance(o["b"], list)
+    assert o["b"] == ["d", "e", "f"]
diff --git a/tests/configparser/sections/test_theme.py b/tests/configparser/sections/test_theme.py
new file mode 100644
index 00000000..18290385
--- /dev/null
+++ b/tests/configparser/sections/test_theme.py
@@ -0,0 +1,8 @@
+from clickgen.configparser.sections.theme import parse_theme_section
+
+
+def test_parse_theme_section(cp):
+    o = parse_theme_section(cp)
+    assert o.name == "Sample"
+    assert o.comment == "This is sample cursor theme"
+    assert o.website == "https://www.example.com/"
diff --git a/tests/configparser/test_sections.py b/tests/configparser/test_sections.py
new file mode 100644
index 00000000..1252b35f
--- /dev/null
+++ b/tests/configparser/test_sections.py
@@ -0,0 +1,12 @@
+from clickgen.configparser.sections import ClickgenConfig, parse_sections
+from clickgen.configparser.sections.config import ConfigSection
+from clickgen.configparser.sections.theme import ThemeSection
+
+
+def test_parse_sections(cp):
+    o = parse_sections(cp)
+    assert isinstance(o, ClickgenConfig)
+    assert isinstance(o.theme, ThemeSection)
+    assert isinstance(o.config, ConfigSection)
+    assert isinstance(o.hotspots, dict)
+    assert isinstance(o.symlinks, dict)
diff --git a/tests/configparser/test_utils.py b/tests/configparser/test_utils.py
new file mode 100644
index 00000000..bfe1bc42
--- /dev/null
+++ b/tests/configparser/test_utils.py
@@ -0,0 +1,17 @@
+import pytest
+
+from clickgen.configparser.utils import str_to_list
+
+
+@pytest.mark.parametrize(
+    "test_input",
+    [
+        "a,b,c",
+        "a,b,   c",
+        "a, b,c ",
+        "a,b,c ,",
+        "a,,,b,c ,",
+    ],
+)
+def test_str_to_list(test_input):
+    assert str_to_list(test_input) == ["a", "b", "c"]
diff --git a/tests/conftest.py b/tests/conftest.py
index ede0950d..2a699c91 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,98 +1,136 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import shutil
+import io
+from configparser import ConfigParser
 from pathlib import Path
-from random import randint
+from typing import List, Tuple
 
 import pytest
+from PIL.Image import Image, open
 
-from clickgen.core import Bitmap, CursorAlias
+from clickgen.cursors import CursorFrame, CursorImage
+from clickgen.packer.windows import REQUIRED_CURSORS
 
-from .utils import create_test_image
 
+@pytest.fixture
+def samples_dir() -> Path:
+    return Path(__file__).parents[1] / "samples"
 
-@pytest.fixture(scope="module")
-def image_dir(tmpdir_factory: pytest.TempdirFactory):
-    """Autoremoved directory for storing images, Using \
-    ``TempdirFactory`` understood."""
 
-    directory = Path(tmpdir_factory.mktemp("test_image"))
-    yield directory
-    shutil.rmtree(directory)
+@pytest.fixture
+def blob(samples_dir) -> bytes:
+    pointer_png = samples_dir / "pngs/pointer.png"
+    return pointer_png.read_bytes()
 
 
-@pytest.fixture(scope="function")
-def test_file(image_dir):
-    """provide single test file named ``test.test``."""
+@pytest.fixture
+def blobs(blob) -> List[bytes]:
+    return [blob, blob]
 
-    file: Path = image_dir / "test.test"
-    file.write_text("testing")
-    return file
 
+@pytest.fixture
+def dummy_blob(samples_dir) -> bytes:
+    txt = samples_dir / "sample.cfg"
+    return txt.read_bytes()
 
-@pytest.fixture(scope="function")
-def static_png(image_dir):
-    """provide single test image file with ``.png`` type."""
 
-    p = create_test_image(image_dir, 1)
-    return p[0]
+@pytest.fixture
+def dummy_blobs(dummy_blob) -> List[bytes]:
+    return [dummy_blob, dummy_blob]
 
 
-@pytest.fixture(scope="function")
-def animated_png(image_dir):
-    """provide multiple test image file with ``.png`` type."""
-    p = create_test_image(image_dir, randint(2, 5))
+@pytest.fixture
+def image(blob) -> Image:
+    return open(io.BytesIO(blob))
+
+
+@pytest.fixture
+def hotspot() -> Tuple[int, int]:
+    return (100, 105)
+
+
+@pytest.fixture
+def nominal() -> int:
+    return 24
+
+
+@pytest.fixture
+def cursor_image(image, hotspot, nominal) -> CursorImage:
+    return CursorImage(image, hotspot, nominal)
 
-    return p
 
+@pytest.fixture
+def images(cursor_image) -> List[CursorImage]:
+    return [cursor_image, cursor_image, cursor_image]
 
-@pytest.fixture(scope="function")
-def hotspot():
-    """Mock hotspot tuple."""
 
-    return (0, 0)
+@pytest.fixture
+def sizes() -> List[int]:
+    return [12, 12, 24]
 
 
-@pytest.fixture(scope="function")
-def static_bitmap(static_png, hotspot):
-    """Mock static bitmap instace."""
+@pytest.fixture
+def delay() -> int:
+    return 5
 
-    return Bitmap(static_png, hotspot)
 
+@pytest.fixture
+def cursor_frame(images, delay) -> CursorFrame:
+    return CursorFrame(images, delay)
 
-@pytest.fixture(scope="function")
-def animated_bitmap(animated_png, hotspot):
-    """Mock animated bitmap instace."""
 
-    return Bitmap(animated_png, hotspot)
+@pytest.fixture
+def sample_cfg(samples_dir) -> str:
+    txt = samples_dir / "sample.cfg"
+    return txt.read_text()
 
 
-@pytest.fixture(scope="function")
-def static_config(static_bitmap):
-    """Mock static cursor config file."""
-    alias = CursorAlias(static_bitmap)
-    yield alias.create((10, 10))
+@pytest.fixture
+def cp_path(samples_dir) -> str:
+    cfg = samples_dir / "sample.cfg"
+    return str(cfg)
 
-    shutil.rmtree(alias.alias_dir)
 
+@pytest.fixture
+def cp(samples_dir) -> ConfigParser:
+    cfg = samples_dir / "sample.cfg"
 
-@pytest.fixture(scope="function")
-def animated_config(animated_bitmap):
-    """Mock animated cursor config file."""
+    cp = ConfigParser()
+    cp.read(cfg)
+    return cp
 
-    alias = CursorAlias(animated_bitmap)
-    yield alias.create((10, 10))
 
-    shutil.rmtree(alias.alias_dir)
+@pytest.fixture
+def theme_name() -> str:
+    return "test"
 
 
-@pytest.fixture(scope="function")
-def data():
-    """Mock cursor data dict."""
+@pytest.fixture
+def comment() -> str:
+    return "comment"
 
-    return [{"aa"}, {"bb", "cc"}, {"ddddd", "ffffff"}]
+
+@pytest.fixture
+def website() -> str:
+    return "https://www.example.com"
+
+
+@pytest.fixture(scope="session")
+def x11_tmp_dir(tmpdir_factory) -> Path:
+    return Path(tmpdir_factory.mktemp("x11_tmp"))
+
+
+@pytest.fixture(scope="session")
+def win_cur_tmp_dir(tmpdir_factory) -> Path:
+    p = Path(tmpdir_factory.mktemp("x11_tmp"))
+    for f in REQUIRED_CURSORS:
+        cfile = p.joinpath(f"{f}.cur")
+        cfile.write_text("test win cursors")
+    return p
+
+
+@pytest.fixture(scope="session")
+def win_ani_tmp_dir(tmpdir_factory) -> Path:
+    p = Path(tmpdir_factory.mktemp("x11_tmp"))
+    for f in REQUIRED_CURSORS:
+        cfile = p.joinpath(f"{f}.ani")
+        cfile.write_text("test win cursors")
+    return p
diff --git a/tests/packer/test_windows_packer.py b/tests/packer/test_windows_packer.py
new file mode 100644
index 00000000..1433e079
--- /dev/null
+++ b/tests/packer/test_windows_packer.py
@@ -0,0 +1,54 @@
+from pathlib import Path
+
+import pytest
+
+from clickgen.packer import pack_win
+from clickgen.packer.windows import REQUIRED_CURSORS
+
+
+def test_windows_packer_raises(x11_tmp_dir: Path, theme_name, comment, website):
+    with pytest.raises(FileNotFoundError) as e:
+        pack_win(x11_tmp_dir, theme_name, comment, website)
+
+    assert "Windows cursors '*.cur' or '*.ani' not found" in str(e.value)
+
+    x11_tmp_dir.joinpath("Work.ani").write_text("test")
+
+    with pytest.raises(FileNotFoundError) as e1:
+        pack_win(x11_tmp_dir, theme_name, comment, website)
+
+    assert (
+        "Windows cursors are missing ['Alternate', 'Busy', 'Cross', 'Default', 'Diagonal_1', 'Diagonal_2', 'Handwriting', 'Help', 'Horizontal', 'IBeam', 'Link', 'Move', 'Unavailiable', 'Vertical']"
+        in str(e1.value)
+    )
+
+
+def test_windows_packer_with_cur(win_cur_tmp_dir: Path, theme_name, comment, website):
+    pack_win(win_cur_tmp_dir, theme_name, comment, website)
+
+    install_inf = win_cur_tmp_dir.joinpath("install.inf")
+    uninstall_bat = win_cur_tmp_dir.joinpath("uninstall.bat")
+    assert install_inf.exists()
+    assert uninstall_bat.exists()
+    install_data = install_inf.read_text()
+
+    assert theme_name in install_data
+    assert comment in install_data
+    assert website in install_data
+
+    for c in REQUIRED_CURSORS:
+        fname = f"{c}.cur"
+        assert win_cur_tmp_dir.joinpath(fname).exists()
+        assert fname in install_data
+
+
+def test_windows_packer_with_ani(win_ani_tmp_dir: Path, theme_name, comment, website):
+    pack_win(win_ani_tmp_dir, theme_name, comment, website)
+
+    install_inf = win_ani_tmp_dir.joinpath("install.inf")
+    install_data = install_inf.read_text()
+
+    for c in REQUIRED_CURSORS:
+        fname = f"{c}.ani"
+        assert win_ani_tmp_dir.joinpath(fname).exists()
+        assert fname in install_data
diff --git a/tests/packer/test_x11_packer.py b/tests/packer/test_x11_packer.py
new file mode 100644
index 00000000..e43a6d77
--- /dev/null
+++ b/tests/packer/test_x11_packer.py
@@ -0,0 +1,16 @@
+from pathlib import Path
+
+from clickgen.packer import pack_x11
+
+
+def test_x11_packer(x11_tmp_dir: Path):
+    pack_x11(x11_tmp_dir, theme_name="test", comment="test")
+
+    f1 = x11_tmp_dir.joinpath("cursor.theme")
+    f2 = x11_tmp_dir.joinpath("index.theme")
+
+    assert f1.exists()
+    assert f2.exists()
+
+    assert f1.read_text() == '[Icon Theme]\nName=test\nInherits="test"'
+    assert f2.read_text() == '[Icon Theme]\nName=test\nComment=test\nInherits="hicolor"'
diff --git a/tests/parser/test_base_parser.py b/tests/parser/test_base_parser.py
new file mode 100644
index 00000000..8978d7e2
--- /dev/null
+++ b/tests/parser/test_base_parser.py
@@ -0,0 +1,23 @@
+from unittest.mock import patch
+
+import pytest
+
+from clickgen.parser.base import BaseParser
+
+
+def test_base_parser(blob):
+    p = patch.multiple(BaseParser, __abstractmethods__=set())
+    p.start()
+
+    b = BaseParser(blob)  # type: ignore
+
+    assert isinstance(b.blob, bytes)
+    assert b.blob is blob
+
+    with pytest.raises(AttributeError):
+        b.frames
+
+    with pytest.raises(NotImplementedError):
+        b.can_parse(blob)
+
+    p.stop()
diff --git a/tests/parser/test_png_parser.py b/tests/parser/test_png_parser.py
new file mode 100644
index 00000000..d30a7183
--- /dev/null
+++ b/tests/parser/test_png_parser.py
@@ -0,0 +1,64 @@
+import pytest
+
+from clickgen.cursors import CursorFrame, CursorImage
+from clickgen.parser.png import SIZES, MultiPNGParser, SinglePNGParser
+
+
+def test_single_png_parser(blob, hotspot, sizes, delay):
+    p = SinglePNGParser(blob, hotspot, sizes=sizes, delay=delay)
+
+    sizes = [12, 24]
+    assert sorted(list(p.sizes)) == sizes
+
+    assert p.delay == delay
+
+    for i, s in enumerate(sizes):
+        assert isinstance(p.frames[0], CursorFrame)
+        assert isinstance(p.frames[0][i], CursorImage)
+        assert p.frames[0][i].nominal == s
+        assert p.frames[0][i].image.size == (s, s)
+
+    assert p.frames[0][0].hotspot == (6, 6)
+    assert p.frames[0][1].hotspot == (12, 12)
+
+    with pytest.raises(IndexError):
+        p.frames[1]
+
+
+def test_single_png_parser_default_args(blob, hotspot):
+    p = SinglePNGParser(blob, hotspot)
+    assert p.delay == 0
+    assert sorted(list(p.sizes)) == SIZES
+
+
+def test_single_png_parser_can_parse(blob, dummy_blob):
+    assert SinglePNGParser.can_parse(blob)
+    assert not SinglePNGParser.can_parse(dummy_blob)
+
+
+def test_single_png_parser_raises_01(blob):
+    with pytest.raises(ValueError):
+        SinglePNGParser(blob, hotspot=(201, 201))
+    with pytest.raises(ValueError):
+        SinglePNGParser(blob, hotspot=(100, 201))
+    with pytest.raises(ValueError):
+        SinglePNGParser(blob, hotspot=(201, 100))
+
+
+def test_multi_png_parser(blobs, hotspot, sizes, delay):
+    p = MultiPNGParser(blobs, hotspot, sizes, delay)
+
+    assert p.frames[0].delay == delay
+    assert p.frames[1].delay == delay
+
+    for i in [0, 1]:
+        for j, s in enumerate(sorted(set(sizes))):
+            assert isinstance(p.frames[i], CursorFrame)
+            assert isinstance(p.frames[i][j], CursorImage)
+            assert p.frames[i][j].nominal == s
+            assert p.frames[i][j].image.size == (s, s)
+
+
+def test_multi_png_parser_can_parse(blobs, dummy_blobs):
+    assert MultiPNGParser.can_parse(blobs)
+    assert not SinglePNGParser.can_parse(dummy_blobs)
diff --git a/tests/scripts/test_clickgen.py b/tests/scripts/test_clickgen.py
new file mode 100644
index 00000000..f7b31801
--- /dev/null
+++ b/tests/scripts/test_clickgen.py
@@ -0,0 +1,81 @@
+import argparse
+from unittest import mock
+
+import pytest
+
+from clickgen.parser.png import DELAY, SIZES
+from clickgen.scripts.clickgen import main
+
+
+def test_clickgen_all_cursor_build(samples_dir, x11_tmp_dir, hotspot):
+    fp = samples_dir / "pngs/pointer.png"
+    with open(fp, "rb") as f:
+        with mock.patch(
+            "argparse.ArgumentParser.parse_args",
+            return_value=argparse.Namespace(
+                files=[f],
+                output=x11_tmp_dir,
+                hotspot_x=hotspot[0],
+                hotspot_y=hotspot[1],
+                sizes=SIZES,
+                delay=DELAY,
+                platform="all",
+            ),
+        ):
+            main()
+
+
+def test_clickgen_all_x11_build(samples_dir, x11_tmp_dir, hotspot):
+    fp = samples_dir / "pngs/pointer.png"
+    with open(fp, "rb") as f:
+        with mock.patch(
+            "argparse.ArgumentParser.parse_args",
+            return_value=argparse.Namespace(
+                files=[f],
+                output=x11_tmp_dir,
+                hotspot_x=hotspot[0],
+                hotspot_y=hotspot[1],
+                sizes=SIZES,
+                delay=DELAY,
+                platform="x11",
+            ),
+        ):
+            main()
+
+
+def test_clickgen_all_windows_build(samples_dir, x11_tmp_dir, hotspot):
+    fp = samples_dir / "pngs/pointer.png"
+    with open(fp, "rb") as f:
+        with mock.patch(
+            "argparse.ArgumentParser.parse_args",
+            return_value=argparse.Namespace(
+                files=[f],
+                output=x11_tmp_dir,
+                hotspot_x=hotspot[0],
+                hotspot_y=hotspot[1],
+                sizes=SIZES,
+                delay=DELAY,
+                platform="windows",
+            ),
+        ):
+            main()
+
+
+def test_clickgen_raises(capsys, samples_dir, x11_tmp_dir, hotspot):
+    fp = samples_dir / "sample.cfg"
+    with open(fp, "rb") as f:
+        with mock.patch(
+            "argparse.ArgumentParser.parse_args",
+            return_value=argparse.Namespace(
+                files=[f],
+                output=x11_tmp_dir,
+                hotspot_x=hotspot[0],
+                hotspot_y=hotspot[1],
+                sizes=SIZES,
+                delay=DELAY,
+                platform="all",
+            ),
+        ):
+            main()
+            captured = capsys.readouterr()
+            assert "Error occurred while processing sample.cfg" in captured.err
diff --git a/tests/test_configparser.py b/tests/test_configparser.py
new file mode 100644
index 00000000..4634302e
--- /dev/null
+++ b/tests/test_configparser.py
@@ -0,0 +1,7 @@
+from clickgen.configparser import parse_config_file
+from clickgen.configparser.sections import ClickgenConfig
+
+
+def test_parse_config_file(cp_path):
+    o = parse_config_file(cp_path)
+    assert isinstance(o, ClickgenConfig)
diff --git a/tests/test_core.py b/tests/test_core.py
deleted file mode 100644
index a328918a..00000000
--- a/tests/test_core.py
+++ /dev/null
@@ -1,1086 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import shutil
-import tempfile
-from pathlib import Path
-from random import randint
-from typing import List, Optional
-
-import pytest
-from PIL import Image
-
-from clickgen.core import Bitmap, CursorAlias
-
-from .utils import create_test_image
-
-#
-# Bitmap Test Cases
-#
-
-
-def test_static_Bitmap_as_str(static_png, hotspot) -> None:
-    """Testing Bitmap class members value for **static png**."""
-    str_static_png = str(static_png)
-    bmp = Bitmap(str_static_png, hotspot)
-
-    with pytest.raises(AttributeError) as excinfo:
-        assert bmp.grouped_png
-    assert str(excinfo.value) == "'Bitmap' object has no attribute 'grouped_png'"
-
-    assert bmp.png == static_png
-    assert bmp.animated is False
-    assert bmp.height == 20
-    assert bmp.width == 20
-    assert bmp.compress == 0
-    assert bmp.key == "test-0"
-    assert bmp.x_hot == hotspot[0]
-    assert bmp.y_hot == hotspot[1]
-
-
-def test_animated_Bitmap_as_str(animated_png, hotspot) -> None:
-    """Testing Bitmap class members value for **animated png**."""
-    str_animated_png: List[str] = list(map(lambda x: str(x.absolute()), animated_png))
-    bmp = Bitmap(str_animated_png, hotspot)
-
-    with pytest.raises(AttributeError) as excinfo:
-        assert bmp.png
-    assert str(excinfo.value) == "'Bitmap' object has no attribute 'png'"
-
-    assert bmp.grouped_png == animated_png
-    assert bmp.animated is True
-    assert bmp.height == 20
-    assert bmp.width == 20
-    assert bmp.compress == 0
-    assert bmp.key == "test"
-    assert bmp.x_hot == hotspot[0]
-    assert bmp.y_hot == hotspot[1]
-
-
-def test_static_Bitmap_as_Path(static_png, hotspot) -> None:
-    """Testing Bitmap class with passing args as `Path` type for \
-    **static png**.
-    """
-    bmp = Bitmap(static_png, hotspot)
-
-    with pytest.raises(AttributeError) as excinfo:
-        assert bmp.grouped_png
-    assert str(excinfo.value) == "'Bitmap' object has no attribute 'grouped_png'"
-
-    assert bmp.png == static_png
-    assert bmp.animated is False
-    assert bmp.height == 20
-    assert bmp.width == 20
-    assert bmp.compress == 0
-    assert bmp.key == "test-0"
-    assert bmp.x_hot == hotspot[0]
-    assert bmp.y_hot == hotspot[1]
-
-
-def test_animated_Bitmap_as_Path(animated_png, hotspot) -> None:
-    """Testing Bitmap class with passing args as `Path` type for \
-    **animated png**.
-    """
-    bmp = Bitmap(animated_png, hotspot)
-
-    with pytest.raises(AttributeError) as excinfo:
-        assert bmp.png
-    assert str(excinfo.value) == "'Bitmap' object has no attribute 'png'"
-
-    assert bmp.grouped_png == animated_png
-    assert bmp.animated is True
-    assert bmp.height == 20
-    assert bmp.width == 20
-    assert bmp.compress == 0
-    assert bmp.key == "test"
-    assert bmp.x_hot == hotspot[0]
-    assert bmp.y_hot == hotspot[1]
-
-
-@pytest.mark.parametrize(
-    "png",
-    [
-        bytes(),
-        [bytes()],
-        [bytes(), bytes()],
-        (2),
-        [(2)],
-        [(2), (2)],
-        [2],
-        [[2]],
-        [[2], [2]],
-        2,
-        [2],
-        [2, 2],
-    ],
-)
-def test_Bitmap_png_type_error_exception(png, hotspot) -> None:
-    """Testing Bitmap class ``TypeError`` exception."""
-    with pytest.raises(TypeError):
-        assert Bitmap(png, hotspot)
-
-
-notfound = "notfound.png"
-notfound_path = Path.cwd() / notfound
-
-
-@pytest.mark.parametrize(
-    "png",
-    [
-        notfound,
-        [notfound],
-        [notfound, notfound, notfound],
-        notfound_path,
-        [notfound_path],
-        [notfound_path, notfound_path, notfound_path],
-    ],
-)
-def test_Bitmap_png_not_found_exception(png, hotspot) -> None:
-    """Testing Bitmap class ``FileNotFoundError`` exception."""
-    with pytest.raises(FileNotFoundError):
-        assert Bitmap(png, hotspot)
-
-
-def test_Bitmap_non_png_exception(test_file, hotspot) -> None:
-    """Testing Bitmap class ``ValueError`` (Not a valid image type) exception."""
-    with pytest.raises(ValueError):
-        assert Bitmap(test_file, hotspot)
-
-
-def test_static_Bitmap_hotspot_underflow_exception(static_png) -> None:
-    """Testing Bitmap class hotspot ``ValueError`` (provided ``hotspot`` is \
-    smaller than image pixel size) exception for static png.
-    """
-    with pytest.raises(ValueError):
-        assert Bitmap(static_png, (2, -3))
-        assert Bitmap(static_png, (-2, -3))
-
-
-def test_animated_Bitmap_hotspot_underflow_exception(animated_png) -> None:
-    """Testing Bitmap class hotspot ``ValueError`` (provided ``hotspot`` is \
-    smaller than image pixel size) exception for animated png.
-    """
-    with pytest.raises(ValueError):
-        assert Bitmap(animated_png, (2, -3))
-        assert Bitmap(animated_png, (-2, -3))
-
-
-def test_static_Bitmap_hotspot_overflow_exception(static_png) -> None:
-    """Testing Bitmap class hotspot ``ValueError`` (provided ``hotspot`` is \
-    larger than image pixel size) exception for static png.
-    """
-    with pytest.raises(ValueError):
-        assert Bitmap(static_png, (12, 60))
-        assert Bitmap(static_png, (55, 60))
-
-
-def test_animated_Bitmap_hotspot_overflow_exception(animated_png) -> None:
-    """Testing Bitmap class hotspot ``ValueError`` (provided ``hotspot`` is \
-    larger than image pixel size) exception for animated png.
-    """
-    with pytest.raises(ValueError):
-        assert Bitmap(animated_png, (12, 60))
-        assert Bitmap(animated_png, (55, 60))
-
-
-def test_static_Bitmap_str(static_png, hotspot) -> None:
-    """Testing Bitmap class ``__str__`` datamethod for **static png**."""
-    bmp = Bitmap(static_png, hotspot)
-    assert (
-        bmp.__str__()
-        == f"Bitmap(png={static_png}, key={static_png.stem}, animated=False, size=(20, 20), width=20, height=20, x_hot={hotspot[0]}, y_hot={hotspot[1]})"
-    )
-
-
-def test_animated_Bitmap_str(animated_png, hotspot) -> None:
-    """Testing Bitmap class ``__str__`` datamethod for **animated png**."""
-    bmp = Bitmap(animated_png, hotspot)
-    assert (
-        bmp.__str__()
-        == f"Bitmap(grouped_png={animated_png}, key={animated_png[0].stem.rsplit('-',1)[0]}, animated=True, size=(20, 20), width=20, height=20, x_hot={hotspot[0]}, y_hot={hotspot[1]})"
-    )
-
-
-def test_static_Bitmap_repr(static_png, hotspot) -> None:
-    """Testing Bitmap class ``__repr__`` datamethod for **static png**."""
-    bmp = Bitmap(static_png, hotspot)
-    assert (
-        bmp.__repr__()
-        == f"{{ 'png':{static_png}, 'key':'test-0', 'animated':False, 'size':(20, 20), 'width':20, 'height':20, 'x_hot':{hotspot[0]}, 'y_hot':{hotspot[1]} }}"
-    )
-
-
-def test_animated_Bitmap_repr(animated_png, hotspot) -> None:
-    """Testing Bitmap class ``__repr__`` datamethod for **animated png**."""
-    bmp = Bitmap(animated_png, hotspot)
-    assert (
-        bmp.__repr__()
-        == f"{{ 'grouped_png':{animated_png}, 'key':'test', 'animated':True, 'size':(20, 20), 'width':20, 'height':20, 'x_hot':{hotspot[0]}, 'y_hot':{hotspot[1]} }}"
-    )
-
-
-def test_static_Bitmap_context_manager(static_png, hotspot) -> None:
-    """Testing Bitmap class contextmanagment datamethod for **static png**."""
-    with Bitmap(static_png, hotspot) as bmp:
-        with pytest.raises(AttributeError):
-            assert bmp.grouped_png
-        assert bmp.png == static_png
-        assert bmp.animated is False
-        assert bmp.height == 20
-        assert bmp.width == 20
-        assert bmp.compress == 0
-        assert bmp.key == "test-0"
-        assert bmp.x_hot == hotspot[0]
-        assert bmp.y_hot == hotspot[1]
-
-
-def test_animated_Bitmap_context_manager(animated_png, hotspot) -> None:
-    """Testing Bitmap class contextmanagment datamethod for **animated png**."""
-    with Bitmap(animated_png, hotspot) as bmp:
-        with pytest.raises(AttributeError):
-            assert bmp.png
-        assert bmp.grouped_png == animated_png
-        assert bmp.animated is True
-        assert bmp.height == 20
-        assert bmp.width == 20
-        assert bmp.compress == 0
-        assert bmp.key == "test"
-        assert bmp.x_hot == hotspot[0]
-        assert bmp.y_hot == hotspot[1]
-
-
-def test_Bitmap_png_must_had_equal_width_and_height_exception(
-    image_dir, hotspot
-) -> None:
-    """Testing Bitmap class ``ValueError`` (Image width and height not same) \
-    exception for **static png**.
-    """
-    png = create_test_image(image_dir, 1, size=(2, 3))
-    with pytest.raises(ValueError):
-        assert Bitmap(png, hotspot)
-
-
-def test_animated_Bitmap_all_png_size_must_be_equal_exception(
-    image_dir, hotspot
-) -> None:
-    """Testing Bitmap class ``ValueError`` (Image width and height not same) \
-    exception for **animated png**.
-    """
-    png = create_test_image(image_dir, 2, size=(2, 2))
-    png.extend(create_test_image(image_dir, 1, size=(3, 6)))
-    png.extend(create_test_image(image_dir, 1, size=(3, 3)))
-
-    with pytest.raises(ValueError):
-        assert Bitmap(png, hotspot)
-
-
-def test_invalid_animated_Bitmap_name_exception(image_dir, hotspot) -> None:
-    """Testing Bitmap class ``ValueError`` (Invalid image name) \
-    exception for **animated png**.
-    """
-    png = []
-    images = create_test_image(image_dir, 3, size=(5, 5))
-
-    for idx, p in enumerate(images):
-        target = p.with_name(f"notvalidframe{idx}.png")
-        p.rename(target)
-        png.append(target)
-
-    with pytest.raises(ValueError):
-        assert Bitmap(png, hotspot)
-
-
-def test_animated_Bitmap_group_had_same_key_exception(image_dir, hotspot) -> None:
-    """Testing Bitmap class ``ValueError`` (Invalid image key) \
-    exception for **animated png**.
-    """
-    png = []
-    images = create_test_image(image_dir, 3, size=(5, 5))
-
-    for idx, p in enumerate(images):
-        target = p.with_name(f"{str(randint(9999,453334))}-{idx}.png")
-        p.rename(target)
-        png.append(target)
-
-    with pytest.raises(ValueError):
-        assert Bitmap(png, hotspot)
-
-
-#
-# Bitmap public method
-#
-
-
-def test_static_Bitmap_resize_without_save(static_png) -> None:
-    """Testing Bitmap ``resize`` method without ``save`` flag for static png."""
-    new_size = (10, 10)
-    bmp = Bitmap(static_png, (10, 10))
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-    return_image = bmp.resize(size=new_size, save=False)
-    assert return_image is not None
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-
-    assert isinstance(return_image, Image.Image)
-    assert return_image.size == new_size
-
-
-def test_animated_Bitmap_resize_without_save(animated_png) -> None:
-    """Testing Bitmap ``resize`` method without ``save`` flag for animated png."""
-    new_size = (10, 10)
-    bmp = Bitmap(animated_png, (10, 10))
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-    return_images = bmp.resize(size=new_size, save=False)
-    assert return_images is not None
-    assert isinstance(return_images, list)
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-    for image in return_images:
-        assert image.size == new_size
-
-
-def test_static_Bitmap_resize_with_save(static_png) -> None:
-    """Testing Bitmap ``resize`` method with ``save`` flag for static png."""
-    new_size = (10, 10)
-    bmp = Bitmap(static_png, (10, 10))
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-    is_return = bmp.resize(size=new_size, save=True)
-    assert is_return is None
-    assert bmp.x_hot == 5
-    assert bmp.y_hot == 5
-    with Image.open(static_png) as i:
-        assert i.size == new_size
-
-
-def test_animated_Bitmap_resize_with_save(animated_png) -> None:
-    """Testing Bitmap ``resize`` method with ``save`` flag for animated png."""
-    new_size = (10, 10)
-    bmp = Bitmap(animated_png, (10, 10))
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-    is_return = bmp.resize(size=new_size, save=True)
-    assert is_return is None
-    assert bmp.x_hot == 5
-    assert bmp.y_hot == 5
-    for frame in animated_png:
-        with Image.open(frame) as i:
-            assert i.size == new_size
-
-
-def test_static_Bitmap_reproduce_with_save(static_png) -> None:
-    """Testing Bitmap ``reproduce`` method with ``save`` flag for static png."""
-    bmp = Bitmap(static_png, (10, 10))
-    return_value = bmp.reproduce(
-        size=(10, 10), canvas_size=(10, 10), position="center", save=True
-    )
-    assert return_value is None
-    assert bmp.size == (10, 10)
-    assert bmp.x_hot == 5
-    assert bmp.y_hot == 5
-    with Image.open(static_png) as i:
-        assert i.size == (10, 10)
-
-
-def test_static_Bitmap_reproduce_without_save(static_png) -> None:
-    """Testing Bitmap ``reproduce`` method without ``save`` flag for static png."""
-    bmp = Bitmap(static_png, (10, 10))
-    return_value = bmp.reproduce(
-        size=(10, 10), canvas_size=(10, 10), position="center", save=False
-    )
-    assert return_value is not None
-    assert isinstance(return_value, list) is False
-    assert isinstance(return_value, Image.Image)
-    assert return_value.size == (10, 10)
-
-    assert bmp.size == (20, 20)
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-
-
-def test_animated_Bitmap_reproduce_with_save(animated_png) -> None:
-    """Testing Bitmap ``reproduce`` method with ``save`` flag for animated png."""
-    bmp = Bitmap(animated_png, (10, 10))
-    return_value = bmp.reproduce(
-        size=(10, 10), canvas_size=(10, 10), position="center", save=True
-    )
-    assert return_value is None
-    assert bmp.size == (10, 10)
-    assert bmp.x_hot == 5
-    assert bmp.y_hot == 5
-    for frame in animated_png:
-        with Image.open(frame) as i:
-            assert i.size == (10, 10)
-
-
-def test_animated_Bitmap_reproduce_without_save(animated_png) -> None:
-    """Testing Bitmap ``reproduce`` method without ``save`` flag for animated png."""
-    bmp = Bitmap(animated_png, (10, 10))
-    return_value = bmp.reproduce(
-        size=(10, 10), canvas_size=(10, 10), position="center", save=False
-    )
-    assert return_value is not None
-    assert isinstance(return_value, list)
-    assert bmp.size == (20, 20)
-    assert bmp.x_hot == 10
-    assert bmp.y_hot == 10
-
-    for frame in return_value:
-        assert frame.size == (10, 10)
-
-
-def test_static_Bitmap_rename(static_png: Path, hotspot) -> None:
-    """Testing Bitmap ``rename`` method with static png."""
-    bmp = Bitmap(static_png, hotspot)
-    assert bmp.key == "test-0"
-    assert bmp.png == static_png
-    with pytest.raises(AttributeError):
-        assert bmp.grouped_png
-
-    bmp.rename("new_test")
-    assert bmp.key == "new_test"
-    assert bmp.png == static_png.with_name("new_test.png")
-    with pytest.raises(AttributeError):
-        assert bmp.grouped_png
-
-
-def test_animated_Bitmap_rename(animated_png: List[Path], hotspot) -> None:
-    """Testing Bitmap ``rename`` method with animated png."""
-    bmp = Bitmap(animated_png, hotspot)
-    assert bmp.key == "test"
-    assert bmp.grouped_png == animated_png
-    with pytest.raises(AttributeError):
-        assert bmp.png
-
-    bmp.rename("new_test")
-    assert bmp.key == "new_test"
-    for i, frame in enumerate(bmp.grouped_png):
-        stem: str = animated_png[i].stem.replace("test", "new_test", 1)
-        assert frame == frame.with_name(f"{stem}.png")
-
-
-def test_Bitmap_copy_is_raising_not_a_directory_exception(
-    static_png, hotspot, test_file
-) -> None:
-    """Testing Bitmap ``copy`` method ``NotADirectoryError`` exception."""
-    bmp = Bitmap(static_png, hotspot)
-    with pytest.raises(NotADirectoryError):
-        bmp.copy(test_file)
-
-
-def test_Bitmap_copy_is_not_creating_not_exists_directory(static_png, hotspot) -> None:
-    """Testing Bitmap ``copy`` method creating directory is not exists."""
-    bmp = Bitmap(static_png, hotspot)
-    copy_dir = Path(tempfile.mkdtemp("test_copy_dir"))
-    shutil.rmtree(copy_dir)
-    bmp.copy(copy_dir)
-
-
-def test_static_Bitmap_copy_with_path_argument(static_png, hotspot) -> None:
-    """Testing Bitmap ``copy`` method with passing ``Path`` type argument for \
-    static png.
-    """
-    bmp = Bitmap(static_png, hotspot)
-
-    assert bmp.png == static_png
-    with pytest.raises(AttributeError):
-        assert bmp.grouped_png
-
-    copy_dir = Path(tempfile.mkdtemp("test_copy_dir"))
-    copy_file = copy_dir / static_png.name
-
-    copy_bmp = bmp.copy(copy_dir)
-    assert copy_file.exists()
-    assert copy_bmp.png == copy_file
-    with pytest.raises(AttributeError):
-        assert bmp.grouped_png
-    shutil.rmtree(copy_dir)
-
-
-def test_animated_Bitmap_copy_with_path_argument(animated_png, hotspot) -> None:
-    """Testing Bitmap ``copy`` method with passing ``Path`` type argument for \
-    animated png.
-    """
-    bmp = Bitmap(animated_png, hotspot)
-
-    assert bmp.grouped_png == animated_png
-    with pytest.raises(AttributeError):
-        assert bmp.png
-
-    copy_dir = Path(tempfile.mkdtemp("test_copy_dir"))
-    copy_files = list(map(lambda x: x.name, animated_png))
-    copy_bmp = bmp.copy(copy_dir)
-
-    for i, f in enumerate(copy_files):
-        copy_file = copy_dir / f
-        assert copy_file.exists()
-        assert copy_bmp.grouped_png[i] == copy_file
-    with pytest.raises(AttributeError):
-        assert bmp.png
-    shutil.rmtree(copy_dir)
-
-
-def test_static_Bitmap_copy_without_path_argument(static_png, hotspot) -> None:
-    """Testing Bitmap ``copy`` method without passing ``Path`` type argument for \
-    static png.
-    """
-    bmp = Bitmap(static_png, hotspot)
-
-    assert bmp.png == static_png
-    with pytest.raises(AttributeError):
-        assert bmp.grouped_png
-
-    copy_bmp = bmp.copy()
-
-    # Without `path` argument Bitmap.copy() is creating temporary directory or not.
-    assert str(tempfile.tempdir) in str(copy_bmp.png)
-
-    is_tmp_dir = copy_bmp.png.parent
-    assert is_tmp_dir.exists()
-    assert is_tmp_dir.name.__contains__(copy_bmp.key)
-    assert is_tmp_dir.name.__contains__("__copy__")
-
-    with pytest.raises(AttributeError):
-        assert bmp.grouped_png
-
-
-def test_animated_Bitmap_copy_without_path_argument(animated_png, hotspot) -> None:
-    """Testing Bitmap ``copy`` method without passing ``Path`` type argument for \
-    animated png.
-    """
-    bmp = Bitmap(animated_png, hotspot)
-
-    assert bmp.grouped_png == animated_png
-    with pytest.raises(AttributeError):
-        assert bmp.png
-
-    copy_bmp = bmp.copy()
-
-    # Without `path` argument Bitmap.copy() is creating temporary directory or not.
-    assert str(tempfile.tempdir) in str(copy_bmp.grouped_png[0])
-
-    is_tmp_dir = copy_bmp.grouped_png[0].parent
-    assert is_tmp_dir.exists()
-    assert is_tmp_dir.name.__contains__(copy_bmp.key)
-    assert is_tmp_dir.name.__contains__("__copy__")
-
-    with pytest.raises(AttributeError):
-        assert bmp.png
-
-
-#
-# CursorAlias Test Cases
-#
-
-
-def test_CursorAlias_with_static_Bitmap(
-    static_bitmap: Bitmap,
-) -> None:
-    """Testing CursorAlias class members value with static bitmap."""
-    alias = CursorAlias(static_bitmap)
-
-    assert static_bitmap.key in alias.prefix
-    assert alias.prefix in str(alias.alias_dir)
-    assert str(tempfile.tempdir) in str(alias.alias_dir)
-    assert alias.alias_dir.exists() is True
-    assert alias.bitmap == static_bitmap
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_with_animated_Bitmap(
-    animated_bitmap: Bitmap,
-) -> None:
-    """Testing CursorAlias class members value with animated bitmap."""
-    alias = CursorAlias(animated_bitmap)
-
-    assert animated_bitmap.key in alias.prefix
-    assert alias.prefix in str(alias.alias_dir)
-    assert str(tempfile.tempdir) in str(alias.alias_dir)
-    assert alias.alias_dir.exists() is True
-    assert alias.bitmap == animated_bitmap
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_from_bitmap(static_png, hotspot) -> None:
-    """Testing CursorAlias ``from_bitmap`` method."""
-    directory: Path
-
-    with CursorAlias.from_bitmap(static_png, hotspot) as ca:
-        assert isinstance(ca, CursorAlias)
-        assert ca.bitmap is not None
-        assert ca.alias_dir is not None
-        assert ca.alias_dir.exists() is True
-        assert ca.garbage_dirs == []
-        directory = ca.alias_dir
-
-    assert directory.exists() is False or list(directory.iterdir()) == []
-
-    with CursorAlias.from_bitmap(static_png, hotspot) as ca1:
-        with pytest.raises(AttributeError) as excinfo:
-            assert ca1.alias_file is not None
-        assert (
-            str(excinfo.value) == "'CursorAlias' object has no attribute 'alias_file'"
-        )
-        ca1.create((10, 10))
-        assert ca1.alias_file is not None
-
-
-def test_static_CursorAlias_str(static_bitmap):
-    """Testing CursorAlias ``__str__`` datamethod."""
-    alias = CursorAlias(static_bitmap)
-    assert (
-        alias.__str__()
-        == f"CursorAlias(bitmap={static_bitmap!s}, prefix={alias.prefix}, alias_dir={alias.alias_dir}, alias_file=None, garbage_dirs=[])"
-    )
-    alias.create((10, 10))
-    assert (
-        alias.__str__()
-        == f"CursorAlias(bitmap={static_bitmap!s}, prefix={alias.prefix}, alias_dir={alias.alias_dir}, alias_file={alias.alias_file}, garbage_dirs=[])"
-    )
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_static_CursorAlias_repr(static_bitmap):
-    """Testing CursorAlias ``__repr__`` datamethod."""
-    alias = CursorAlias(static_bitmap)
-    assert (
-        alias.__repr__()
-        == f"{{ 'bitmap':{static_bitmap!r}, 'prefix':{alias.prefix}, 'alias_dir':{alias.alias_dir}, 'alias_file':None, 'garbage_dirs':[] }}"
-    )
-    alias.create((10, 10))
-    assert (
-        alias.__repr__()
-        == f"{{ 'bitmap':{static_bitmap!r}, 'prefix':{alias.prefix}, 'alias_dir':{alias.alias_dir}, 'alias_file':{alias.alias_file}, 'garbage_dirs':[] }}"
-    )
-    shutil.rmtree(alias.alias_dir)
-
-
-@pytest.mark.parametrize(
-    "mock_sizes",
-    [
-        2,
-        [2],
-        "test",
-        ["test", "test"],
-    ],
-)
-def test_CursorAlias_create_type_error_exception(mock_sizes, static_bitmap) -> None:
-    """Testing CursorAlias create method ``TypeError`` exception."""
-    alias = CursorAlias(static_bitmap)
-    with pytest.raises(TypeError) as excinfo:
-        assert alias.create(sizes=mock_sizes)
-    assert (
-        str(excinfo.value)
-        == "argument 'sizes' should be Tuple[int, int] type or List[Tuple[int, int]]."
-    )
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_create_with_static_bitmap_and_single_size(
-    static_bitmap, static_png
-) -> None:
-    """Testing CursorAlias create method with single pixel size and static bitmap."""
-    alias = CursorAlias(static_bitmap)
-
-    assert len(sorted(alias.alias_dir.iterdir())) == 0
-    alias.create((10, 10), delay=999999)
-
-    for file in alias.alias_dir.iterdir():
-        if file.is_dir():
-            alias_png = list(file.iterdir())[0]
-            assert static_png.name == alias_png.name
-            i = Image.open(alias_png)
-            assert i.size == (10, 10)
-            i.close()
-        else:
-            assert file.stem == static_bitmap.key
-
-            with file.open("r") as f:
-                assert f.readlines() == ["10 0 0 10x10/test-0.png"]
-
-    files = [f.name for f in alias.alias_dir.glob("**/*")]
-    assert sorted(files) == sorted(["10x10", "test-0.alias", "test-0.png"])
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_create_with_static_bitmap_and_multiple_size(static_png) -> None:
-    """Testing CursorAlias create method with multiple pixel size and static bitmap."""
-    static_bitmap = Bitmap(static_png, (9, 13))
-    mock_sizes = [(10, 10), (15, 15), (16, 16)]
-    alias = CursorAlias(static_bitmap)
-
-    assert len(sorted(alias.alias_dir.iterdir())) == 0
-    alias.create(mock_sizes, delay=99999)
-
-    for file in alias.alias_dir.iterdir():
-        if file.is_dir():
-            alias_png = list(file.iterdir())[0]
-            assert static_png.name == alias_png.name
-            i = Image.open(alias_png)
-            assert i.size in mock_sizes
-            i.close()
-        else:
-            assert file.stem == static_bitmap.key
-
-            with file.open("r") as f:
-                assert f.readlines() == [
-                    "10 4 6 10x10/test-0.png\n",
-                    "15 7 10 15x15/test-0.png\n",
-                    "16 7 10 16x16/test-0.png",
-                ]
-
-    files = [f.name for f in alias.alias_dir.glob("**/*")]
-    assert sorted(files) == sorted(
-        [
-            "10x10",
-            "15x15",
-            "16x16",
-            "test-0.alias",
-            "test-0.png",  # Because it's generate 3 size of png in individual directory.
-            "test-0.png",
-            "test-0.png",
-        ]
-    )
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_create_with_animated_bitmap_and_single_size(image_dir) -> None:
-    """Testing CursorAlias create method with single pixel size and animated bitmap."""
-    animated_png = create_test_image(image_dir, 4)
-    animated_bitmap = Bitmap(animated_png, (13, 6))
-    alias = CursorAlias(animated_bitmap)
-
-    assert len(sorted(alias.alias_dir.iterdir())) == 0
-    alias.create((10, 10), delay=999999)
-
-    def as_list(frames: List[Path]) -> List[str]:
-        return sorted(map(lambda x: x.stem, frames))
-
-    for file in alias.alias_dir.iterdir():
-        if file.is_dir():
-            frames = list(file.iterdir())
-            assert as_list(frames) == as_list(animated_png)
-        else:
-            assert file.stem == animated_bitmap.key
-
-            with file.open("r") as f:
-                assert f.readlines() == [
-                    "10 6 3 10x10/test-0.png 999999\n",
-                    "10 6 3 10x10/test-1.png 999999\n",
-                    "10 6 3 10x10/test-2.png 999999\n",
-                    "10 6 3 10x10/test-3.png 999999",
-                ]
-
-    files = []
-    for f in alias.alias_dir.glob("**/*"):
-        files.append(f.name)
-
-    assert sorted(files) == sorted(
-        ["10x10", "test-0.png", "test-1.png", "test-2.png", "test-3.png", "test.alias"]
-    )
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_create_with_animated_bitmap_and_multiple_size(image_dir) -> None:
-    """Testing CursorAlias create method with multiple pixel size and animated bitmap."""
-    animated_png = create_test_image(image_dir, 4)
-    animated_bitmap = Bitmap(animated_png, (4, 2))
-    alias = CursorAlias(animated_bitmap)
-
-    assert len(sorted(alias.alias_dir.iterdir())) == 0
-
-    mock_sizes = [(10, 10), (15, 15), (16, 16)]
-    alias.create(mock_sizes, delay=999999)
-
-    def as_list(frames: List[Path]) -> List[str]:
-        return sorted(map(lambda x: x.stem, frames))
-
-    for file in alias.alias_dir.iterdir():
-        if file.is_dir():
-            frames = list(file.iterdir())
-            assert as_list(frames) == as_list(animated_png)
-        else:
-            assert file.stem == animated_bitmap.key
-
-            with file.open("r") as f:
-                assert f.readlines() == [
-                    "10 2 1 10x10/test-0.png 999999\n",
-                    "10 2 1 10x10/test-1.png 999999\n",
-                    "10 2 1 10x10/test-2.png 999999\n",
-                    "10 2 1 10x10/test-3.png 999999\n",
-                    "15 3 2 15x15/test-0.png 999999\n",
-                    "15 3 2 15x15/test-1.png 999999\n",
-                    "15 3 2 15x15/test-2.png 999999\n",
-                    "15 3 2 15x15/test-3.png 999999\n",
-                    "16 3 2 16x16/test-0.png 999999\n",
-                    "16 3 2 16x16/test-1.png 999999\n",
-                    "16 3 2 16x16/test-2.png 999999\n",
-                    "16 3 2 16x16/test-3.png 999999",
-                ]
-
-    files = []
-    for f in alias.alias_dir.glob("**/*"):
-        files.append(f.name)
-
-    assert sorted(files) == sorted(
-        [
-            "10x10",
-            "15x15",
-            "16x16",
-            "test-0.png",
-            "test-0.png",
-            "test-0.png",
-            "test-1.png",
-            "test-1.png",
-            "test-1.png",
-            "test-2.png",
-            "test-2.png",
-            "test-2.png",
-            "test-3.png",
-            "test-3.png",
-            "test-3.png",
-            "test.alias",
-        ]
-    )
-
-    shutil.rmtree(alias.alias_dir)
-
-
-alias_not_exists_err = "Alias directory is empty or not exists."
-
-
-def test_CursorAlias_check_alias(static_bitmap, animated_bitmap) -> None:
-    """Testing CursorAlias ``check_alias`` method."""
-    alias = CursorAlias(static_bitmap)
-
-    with pytest.raises(FileNotFoundError) as excinfo:
-        alias.check_alias()
-    assert str(excinfo.value) == alias_not_exists_err
-
-    alias.create((10, 10))
-    alias.check_alias()
-
-    animated_alias = CursorAlias(animated_bitmap)
-    with pytest.raises(FileNotFoundError) as excinfo:
-        animated_alias.check_alias()
-    assert str(excinfo.value) == alias_not_exists_err
-
-    animated_alias.create((10, 10))
-    animated_alias.check_alias()
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_extension_excpetion(static_bitmap) -> None:
-    """Testing CursorAlias extension ``FileNotFoundError``(If alias file not created) exception."""
-    alias = CursorAlias(static_bitmap)
-    with pytest.raises(FileNotFoundError) as excinfo:
-        alias.extension()
-        alias.extension(".test")
-
-    assert str(excinfo.value) == alias_not_exists_err
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_extension(static_bitmap) -> None:
-    """Testing CursorAlias ``extension`` method."""
-    alias = CursorAlias(static_bitmap)
-    alias.create((10, 10))
-    assert alias.alias_file.suffix == ".alias"
-    assert alias.extension() == ".alias"
-    alias.extension(".test")
-    assert alias.alias_file.suffix == ".test"
-    assert alias.extension() == ".test"
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_copy_alias_not_found_exception(static_bitmap) -> None:
-    """Testing CursorAlias copy method ``FileNotFoundError`` (alias file not created) exception."""
-    alias = CursorAlias(static_bitmap)
-    with pytest.raises(FileNotFoundError) as excinfo:
-        alias.copy()
-    assert str(excinfo.value) == alias_not_exists_err
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_copy_path_not_directory_exception(
-    static_bitmap, test_file
-) -> None:
-    """Testing CursorAlias copy method ``NotADirectoryError`` (provided path is not a directory) exception."""
-    alias = CursorAlias(static_bitmap)
-    alias.create((10, 10))
-    with pytest.raises(NotADirectoryError) as excinfo:
-        alias.copy(test_file)
-    assert str(excinfo.value) == f"path '{test_file.absolute()}' is not a directory"
-
-    shutil.rmtree(alias.alias_dir)
-
-
-# helper
-def file_tree(alias: CursorAlias) -> List[str]:
-    return sorted(map(lambda x: x.name, alias.alias_dir.glob("**/*")))
-
-
-def check_alias_copy(
-    original_alias: CursorAlias,
-    copy_of_alias: CursorAlias,
-    param_dst: Optional[str] = None,
-) -> None:
-    if param_dst:
-        assert param_dst in str(copy_of_alias.alias_dir.absolute())
-    else:
-        assert str(tempfile.tempdir) in str(copy_of_alias.alias_dir.absolute())
-
-    assert copy_of_alias.alias_dir.exists() is True
-    assert copy_of_alias.alias_file.exists() is True
-
-    assert copy_of_alias.alias_file.read_text() == original_alias.alias_file.read_text()
-    assert file_tree(copy_of_alias) == file_tree(original_alias)
-
-
-# tests continue...
-def test_CursorAlias_copy_with_static_bitmap_without_args(static_bitmap) -> None:
-    """Testing CursorAlias copy method with static bitmap without any arguments."""
-    alias = CursorAlias(static_bitmap)
-    alias.create((10, 10))
-    copy_of_alias = alias.copy()
-
-    check_alias_copy(alias, copy_of_alias)
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_copy_with_animated_bitmap_without_args(animated_bitmap) -> None:
-    """Testing CursorAlias copy method with animated bitmap without any arguments."""
-    alias = CursorAlias(animated_bitmap)
-    alias.create((10, 10))
-    copy_of_alias = alias.copy()
-
-    check_alias_copy(alias, copy_of_alias)
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_copy_with_static_bitmap_with_args(animated_bitmap) -> None:
-    """Testing CursorAlias copy method with static bitmap with any arguments."""
-    alias = CursorAlias(animated_bitmap)
-    alias.create((10, 10))
-    param_dst = Path(tempfile.mkdtemp())
-    copy_of_alias = alias.copy(param_dst)
-
-    check_alias_copy(alias, copy_of_alias, param_dst=str(param_dst.absolute()))
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_copy_with_animated_bitmap_with_args(animated_bitmap) -> None:
-    """Testing CursorAlias copy method with animated bitmap with any arguments."""
-    alias = CursorAlias(animated_bitmap)
-    alias.create((10, 10))
-    param_dst = Path(tempfile.mkdtemp())
-    copy_of_alias = alias.copy(param_dst)
-
-    check_alias_copy(alias, copy_of_alias, param_dst=str(param_dst.absolute()))
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_rename_with_static_bitmap(static_bitmap) -> None:
-    """Testing CursorAlias rename method with static bitmap."""
-    alias = CursorAlias(static_bitmap)
-    alias.create((10, 10))
-
-    old_alias = alias
-    alias.rename("test_key")
-
-    assert alias.prefix == "test_key__alias"
-    assert (
-        sorted(filter(lambda x: x.is_file is True, old_alias.alias_dir.glob("*/**")))
-        == []
-    )
-
-    assert file_tree(alias) == ["10x10", "test_key.alias", "test_key.png"]
-
-    with alias.alias_file.open("r") as f:
-        assert f.readlines() == ["10 0 0 10x10/test_key.png"]
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_rename_with_animated_bitmap(image_dir) -> None:
-    """Testing CursorAlias rename method with animated bitmap."""
-    animated_bitmap = Bitmap(create_test_image(image_dir, 4), (0, 0))
-    alias = CursorAlias(animated_bitmap)
-    alias.create((10, 10))
-
-    old_alias = alias
-    alias.rename("test_key")
-
-    assert alias.prefix == "test_key__alias"
-    assert (
-        sorted(filter(lambda x: x.is_file is True, old_alias.alias_dir.glob("*/**")))
-        == []
-    )
-
-    assert file_tree(alias) == [
-        "10x10",
-        "test_key-0.png",
-        "test_key-1.png",
-        "test_key-2.png",
-        "test_key-3.png",
-        "test_key.alias",
-    ]
-
-    with alias.alias_file.open("r") as f:
-        assert f.readlines() == [
-            "10 0 0 10x10/test_key-0.png 10\n",
-            "10 0 0 10x10/test_key-1.png 10\n",
-            "10 0 0 10x10/test_key-2.png 10\n",
-            "10 0 0 10x10/test_key-3.png 10",
-        ]
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_reproduce_exception(static_bitmap) -> None:
-    """Testing CursorAlias reproduce method ``FileNotFoundError`` (If cursor alias not created) exception."""
-    alias = CursorAlias(static_bitmap)
-
-    with pytest.raises(FileNotFoundError) as excinfo:
-        alias.reproduce()
-    assert str(excinfo.value) == alias_not_exists_err
-
-    shutil.rmtree(alias.alias_dir)
-
-
-def test_CursorAlias_reproduce(static_png, hotspot) -> None:
-    """Testing CursorAlias reproduce method with static bitmap."""
-    testing_dirs: List[Path]
-    with CursorAlias.from_bitmap(static_png, hotspot) as alias:
-        alias.create((10, 10))
-
-        assert alias.garbage_dirs == []
-        reproduced_alias = alias.reproduce(
-            size=(24, 24), canvas_size=(32, 32), delay=44
-        )
-        testing_dirs = alias.garbage_dirs
-        assert f"{alias.prefix}__garbage_bmps__" in alias.garbage_dirs[0].name
-        assert file_tree(reproduced_alias) == ["32x32", "test-0.alias", "test-0.png"]
-
-    assert (
-        sorted(filter(lambda x: x.is_file is True, testing_dirs[0].glob("*/**"))) == []
-    )
diff --git a/tests/test_cursors.py b/tests/test_cursors.py
new file mode 100644
index 00000000..ba6c8d17
--- /dev/null
+++ b/tests/test_cursors.py
@@ -0,0 +1,37 @@
+import pytest
+
+from clickgen.cursors import CursorFrame, CursorImage
+
+
+def test_cursor_image(cursor_image, image, hotspot, nominal):
+    assert isinstance(cursor_image, CursorImage)
+
+    assert cursor_image.image is image
+    assert cursor_image.hotspot is hotspot
+    assert cursor_image.nominal is nominal
+
+    assert "hotspot=(100, 105)" in repr(cursor_image)
+    assert "nominal=24" in repr(cursor_image)
+
+
+def test_cursor_frame(cursor_frame, cursor_image, images, delay):
+    assert isinstance(cursor_frame, CursorFrame)
+
+    assert cursor_frame.images is images
+    assert cursor_frame.delay is delay
+
+    assert len(cursor_frame) == 3
+
+    for i in range(10):
+        if i > 2:
+            with pytest.raises(IndexError):
+                cursor_frame.__getitem__(i)
+        else:
+            item = cursor_frame.__getitem__(i)
+            assert item == cursor_image
+            assert isinstance(item, CursorImage)
+
+    for c in iter(cursor_frame):
+        assert isinstance(c, CursorImage)
+
+    assert "delay=5" in repr(cursor_frame)
diff --git a/tests/test_db.py b/tests/test_db.py
deleted file mode 100644
index f0319ef2..00000000
--- a/tests/test_db.py
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import os
-import tempfile
-from pathlib import Path
-
-import pytest
-
-from clickgen.db import CursorDB
-
-
-def test_CursorDB_exceptions() -> None:
-    """Testing CursorDB ``TypeError`` exceptions."""
-    with pytest.raises(TypeError) as excinfo:
-        CursorDB("aa") # type: ignore
-    assert str(excinfo.value) == "'data' is not type of 'List'"
-
-    with pytest.raises(TypeError) as excinfo:
-        CursorDB(["aa"]) # type: ignore
-    assert str(excinfo.value) == "Item 'aa' is not type of 'Set'"
-
-
-def test_CursorDB(data) -> None:
-    """Testing CursorDB class members value."""
-    db = CursorDB(data)
-    assert db.data == {
-        "aa": [],
-        "bb": ["cc"],
-        "cc": ["bb"],
-        "ddddd": ["ffffff"],
-        "ffffff": ["ddddd"],
-    }
-
-
-def test_CursorDB_str(data) -> None:
-    """Testing CursorDB ``__str__`` datamethod."""
-    db = CursorDB(data)
-    assert (
-        db.__str__()
-        == "CursorDB(aa=[], bb=['cc'], cc=['bb'], ddddd=['ffffff'], ffffff=['ddddd'])"
-    )
-
-
-def test_CursorDB_repr(data) -> None:
-    """Testing CursorDB ``__repr__`` datamethod."""
-    db = CursorDB(data)
-    assert (
-        db.__repr__()
-        == "{'aa': [], 'bb': ['cc'], 'cc': ['bb'], 'ddddd': ['ffffff'], 'ffffff': ['ddddd']}"
-    )
-
-
-def test_CursorDB_search_symlinks_without_find_similar(data) -> None:
-    """Testing CursorDB search_symlinks method without ``find_similar`` argument."""
-    db = CursorDB(data)
-    value = db.search_symlinks("a")
-    assert value is None
-
-    value = db.search_symlinks("aa")
-    assert value == []
-
-    value = db.search_symlinks("bb")
-    assert value == ["cc"]
-
-
-def test_CursorDB_search_symlinks_with_find_similar(data) -> None:
-    """Testing CursorDB search_symlinks method with ``find_similar`` argument."""
-    db = CursorDB(data)
-    value = db.search_symlinks("a", find_similar=True)
-    assert value == []
-
-    value = db.search_symlinks("fffaf", find_similar=True)
-    assert value == ["ddddd"]
-
-    value = db.search_symlinks("bb", find_similar=True)
-    assert value == ["cc"]
-
-
-def test_CursorDB_rename_file_is_not_file_exception(data) -> None:
-    """Testing CursorDB rename_file method ``FileNotFoundError`` exception. """
-    db = CursorDB(data)
-    test_p = Path(str(tempfile.tempdir)) / "fffaf"
-    with pytest.raises(FileNotFoundError) as excinfo:
-        db.rename_file(test_p)
-    assert str(excinfo.value) == f"'{test_p}' is not file"
-    test_p.unlink(missing_ok=True)
-
-    test_p1 = Path(tempfile.mkdtemp())
-    with pytest.raises(FileNotFoundError) as excinfo:
-        db.rename_file(test_p1)
-    assert str(excinfo.value) == f"'{test_p1}' is not file"
-    os.rmdir(test_p1)
-
-
-def test_CursorDB_rename_file(data) -> None:
-    """Testing CursorDB rename_file method."""
-    db = CursorDB(data)
-
-    test_p = Path(str(tempfile.tempdir)) / "fffaf"
-    test_p.write_text("test")
-
-    return_p = db.rename_file(test_p)
-    assert str(return_p) == str(Path(str(tempfile.tempdir)) / "ffffff")
-
-    test_p.unlink(missing_ok=True)
-
-    test_p1 = Path(str(tempfile.tempdir)) / "aa"
-    test_p1.write_text("test")
-
-    return_p = db.rename_file(test_p1)
-    assert return_p is None
-
-    test_p1.unlink(missing_ok=True)
diff --git a/tests/test_packagers.py b/tests/test_packagers.py
deleted file mode 100644
index 5bbca6c2..00000000
--- a/tests/test_packagers.py
+++ /dev/null
@@ -1,281 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import shutil
-from pathlib import Path
-
-import pytest
-
-from clickgen.packagers import WindowsPackager, XPackager
-from tests.utils import create_test_cursor
-
-
-def test_XPackger(image_dir: Path) -> None:
-    """Test the ``clickgen.packagers.XPackager`` functionality."""
-    XPackager(image_dir, theme_name="test", comment="testing")
-
-    cur_theme = image_dir / "cursor.theme"
-    idx_theme = image_dir / "index.theme"
-
-    assert cur_theme.exists() is True
-    assert idx_theme.exists() is True
-
-    with cur_theme.open() as f:
-        assert f.readlines() == ["[Icon Theme]\n", "Name=test\n", 'Inherits="test"']
-
-    with idx_theme.open() as f:
-        assert f.readlines() == [
-            "[Icon Theme]\n",
-            "Name=test\n",
-            "Comment=testing\n",
-            'Inherits="hicolor"',
-        ]
-
-
-def test_WindowsPackager_empty_dir_exception(image_dir: Path) -> None:
-    """Testing ``clickgen.packagers.WindowsPackager`` raise the empty directory \
-    exception with exception type **FileNotFoundError**.
-    """
-    with pytest.raises(FileNotFoundError) as excinfo:
-        WindowsPackager(
-            image_dir, theme_name="test", comment="testing", author="Unknown"
-        )
-
-    assert (
-        str(excinfo.value)
-        == f"Windows cursors '*.cur' or '*.ani' not found in '{image_dir}'"
-    )
-
-
-def test_WindowsPackager_missing_cur_exception(image_dir: Path) -> None:
-    """Testing ``clickgen.packagers.WindowsPackager`` raise the missing cursors \
-    exception with exception type **FileNotFoundError**.
-    """
-    create_test_cursor(image_dir, "Work.ani")
-    create_test_cursor(image_dir, "Busy.ani")
-    create_test_cursor(image_dir, "Default.cur")
-    create_test_cursor(image_dir, "Help.cur")
-    create_test_cursor(image_dir, "Link.cur")
-    create_test_cursor(image_dir, "Move.cur")
-    create_test_cursor(image_dir, "Diagonal_2.cur")
-    create_test_cursor(image_dir, "Vertical.cur")
-    create_test_cursor(image_dir, "Horizontal.cur")
-    create_test_cursor(image_dir, "Diagonal_1.cur")
-    create_test_cursor(image_dir, "Handwriting.cur")
-    create_test_cursor(image_dir, "Cross.cur")
-    create_test_cursor(image_dir, "IBeam.cur")
-    with pytest.raises(FileNotFoundError) as excinfo:
-        WindowsPackager(
-            image_dir, theme_name="test", comment="testing", author="Unknown"
-        )
-
-    assert (
-        str(excinfo.value)
-        == "Windows cursors are missing ['Alternate', 'Unavailiable']"
-    )
-
-
-def test_WindowsPackager_with_semi_animated_cursors(
-    tmpdir_factory: pytest.TempdirFactory,
-) -> None:
-    """Testing ``clickgen.packagers.WindowsPackager`` supports 'semi-animated' \
-    cursors.
-
-    This test generates all .ani (animated) cursors and passed them to \
-    **WindowsPackager**. And checks, It generates valid ``install.inf`` file \
-    with appropriate cursor-type or not.
-    """
-
-    d = Path(tmpdir_factory.mktemp("test_image"))
-    create_test_cursor(d, "Work.ani")
-    create_test_cursor(d, "Busy.ani")
-    create_test_cursor(d, "Default.ani")
-    create_test_cursor(d, "Help.ani")
-    create_test_cursor(d, "Link.ani")
-    create_test_cursor(d, "Move.ani")
-    create_test_cursor(d, "Diagonal_2.ani")
-    create_test_cursor(d, "Vertical.ani")
-    create_test_cursor(d, "Horizontal.ani")
-    create_test_cursor(d, "Diagonal_1.ani")
-    create_test_cursor(d, "Handwriting.ani")
-    create_test_cursor(d, "Cross.ani")
-    create_test_cursor(d, "IBeam.ani")
-    create_test_cursor(d, "Unavailiable.ani")
-    create_test_cursor(d, "Alternate.ani")
-
-    WindowsPackager(d, theme_name="test", comment="testing", author="Unknown")
-
-    install_file = d / "install.inf"
-
-    assert install_file.exists() is True
-    data = install_file.read_text()
-
-    assert "Work.ani" in data
-    assert "Busy.ani" in data
-    assert "Default.ani" in data
-    assert "Help.ani" in data
-    assert "Link.ani" in data
-    assert "Move.ani" in data
-    assert "Diagonal_2.ani" in data
-    assert "Vertical.ani" in data
-    assert "Horizontal.ani" in data
-    assert "Diagonal_1.ani" in data
-    assert "Handwriting.ani" in data
-    assert "Cross.ani" in data
-    assert "IBeam.ani" in data
-    assert "Unavailiable.ani" in data
-    assert "Alternate.ani" in data
-
-    shutil.rmtree(d)
-
-
-def test_WindowsPackager_without_website_url(
-    tmpdir_factory: pytest.TempdirFactory,
-) -> None:
-    """Testing ``clickgen.packagers.WindowsPackager`` parameters default value."""
-    d = Path(tmpdir_factory.mktemp("test_image"))
-    create_test_cursor(d, "Work.ani")
-    create_test_cursor(d, "Busy.ani")
-    create_test_cursor(d, "Default.cur")
-    create_test_cursor(d, "Help.cur")
-    create_test_cursor(d, "Link.cur")
-    create_test_cursor(d, "Move.cur")
-    create_test_cursor(d, "Diagonal_2.cur")
-    create_test_cursor(d, "Vertical.cur")
-    create_test_cursor(d, "Horizontal.cur")
-    create_test_cursor(d, "Diagonal_1.cur")
-    create_test_cursor(d, "Handwriting.cur")
-    create_test_cursor(d, "Cross.cur")
-    create_test_cursor(d, "IBeam.cur")
-    create_test_cursor(d, "Unavailiable.cur")
-    create_test_cursor(d, "Alternate.cur")
-
-    WindowsPackager(d, theme_name="test", comment="testing", author="Unknown")
-
-    install_file = d / "install.inf"
-
-    assert install_file.exists() is True
-    data = install_file.read_text()
-
-    assert "Work.ani" in data
-    assert "Busy.ani" in data
-    assert "Default.cur" in data
-    assert "Help.cur" in data
-    assert "Link.cur" in data
-    assert "Move.cur" in data
-    assert "Diagonal_2.cur" in data
-    assert "Vertical.cur" in data
-    assert "Horizontal.cur" in data
-    assert "Diagonal_1.cur" in data
-    assert "Handwriting.cur" in data
-    assert "Cross.cur" in data
-    assert "IBeam.cur" in data
-    assert "Unavailiable.cur" in data
-    assert "Alternate.cur" in data
-
-    shutil.rmtree(d)
-
-
-def test_WindowsPackager_with_website_url(
-    tmpdir_factory: pytest.TempdirFactory,
-) -> None:
-    """Testing ``clickgen.packagers.WindowsPackager`` default parameters \
-            with custom value."""
-    d = Path(tmpdir_factory.mktemp("test_image"))
-    create_test_cursor(d, "Work.ani")
-    create_test_cursor(d, "Busy.ani")
-    create_test_cursor(d, "Default.cur")
-    create_test_cursor(d, "Help.cur")
-    create_test_cursor(d, "Link.cur")
-    create_test_cursor(d, "Move.cur")
-    create_test_cursor(d, "Diagonal_2.cur")
-    create_test_cursor(d, "Vertical.cur")
-    create_test_cursor(d, "Horizontal.cur")
-    create_test_cursor(d, "Diagonal_1.cur")
-    create_test_cursor(d, "Handwriting.cur")
-    create_test_cursor(d, "Cross.cur")
-    create_test_cursor(d, "IBeam.cur")
-    create_test_cursor(d, "Unavailiable.cur")
-    create_test_cursor(d, "Alternate.cur")
-
-    WindowsPackager(
-        d,
-        theme_name="test",
-        comment="testing",
-        author="Unknown",
-        website_url="testing.test",
-    )
-
-    install_file = d / "install.inf"
-
-    assert install_file.exists() is True
-    data = install_file.read_text()
-
-    assert "Work.ani" in data
-    assert "Busy.ani" in data
-    assert "Default.cur" in data
-    assert "Help.cur" in data
-    assert "Link.cur" in data
-    assert "Move.cur" in data
-    assert "Diagonal_2.cur" in data
-    assert "Vertical.cur" in data
-    assert "Horizontal.cur" in data
-    assert "Diagonal_1.cur" in data
-    assert "Handwriting.cur" in data
-    assert "Cross.cur" in data
-    assert "IBeam.cur" in data
-    assert "Unavailiable.cur" in data
-    assert "Alternate.cur" in data
-
-    # assert "testing.test" in data
-
-    shutil.rmtree(d)
-
-
-def test_WindowsPackger_install_and_uninstall_scripts(
-    tmpdir_factory: pytest.TempdirFactory,
-) -> None:
-    """Test the ``clickgen.packagers.WindowsPackager`` functionality."""
-
-    d = Path(tmpdir_factory.mktemp("test_image"))
-    create_test_cursor(d, "Work.ani")
-    create_test_cursor(d, "Busy.ani")
-    create_test_cursor(d, "Default.cur")
-    create_test_cursor(d, "Help.cur")
-    create_test_cursor(d, "Link.cur")
-    create_test_cursor(d, "Move.cur")
-    create_test_cursor(d, "Diagonal_2.cur")
-    create_test_cursor(d, "Vertical.cur")
-    create_test_cursor(d, "Horizontal.cur")
-    create_test_cursor(d, "Diagonal_1.cur")
-    create_test_cursor(d, "Handwriting.cur")
-    create_test_cursor(d, "Cross.cur")
-    create_test_cursor(d, "IBeam.cur")
-    create_test_cursor(d, "Unavailiable.cur")
-    create_test_cursor(d, "Alternate.cur")
-    WindowsPackager(d, theme_name="test", comment="testing", author="Unknown")
-
-    install_script = d / "install.inf"
-    assert install_script.exists() is True
-    install_data = install_script.read_text()
-    assert "test Cursors" in install_data
-    assert "Work.ani" in install_data
-    assert "Busy.ani" in install_data
-    assert "Default.cur" in install_data
-    assert "Help.cur" in install_data
-    assert "Link.cur" in install_data
-    assert "Move.cur" in install_data
-    assert "Diagonal_2.cur" in install_data
-    assert "Vertical.cur" in install_data
-    assert "Horizontal.cur" in install_data
-    assert "Diagonal_1.cur" in install_data
-    assert "Handwriting.cur" in install_data
-    assert "Cross.cur" in install_data
-    assert "IBeam.cur" in install_data
-    assert "Unavailiable.cur" in install_data
-    assert "Alternate.cur" in install_data
-
-    uninstall_script = d / "uninstall.bat"
-    assert uninstall_script.exists() is True
-    uninstall_data = uninstall_script.read_text()
-    assert "test Cursors" in uninstall_data
diff --git a/tests/test_parser.py b/tests/test_parser.py
new file mode 100644
index 00000000..bdba591b
--- /dev/null
+++ b/tests/test_parser.py
@@ -0,0 +1,13 @@
+import pytest
+
+from clickgen.parser import open_blob
+
+
+def test_open_blob(blob, dummy_blob, blobs, dummy_blobs, hotspot):
+    open_blob(blob, hotspot)
+    with pytest.raises(Exception):
+        open_blob(dummy_blob, hotspot)
+
+    open_blob(blobs, hotspot)
+    with pytest.raises(Exception):
+        open_blob(dummy_blobs, hotspot)
diff --git a/tests/test_util.py b/tests/test_util.py
deleted file mode 100644
index 7d305ca9..00000000
--- a/tests/test_util.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-import os
-import shutil
-import tempfile
-from os import getcwd, symlink
-from pathlib import Path
-
-import pytest
-
-from clickgen.builders import XCursor
-from clickgen.core import CursorAlias
-from clickgen.util import PNGProvider, add_missing_xcursors, chdir, remove_util
-from tests.utils import create_test_image
-
-
-def test_chdir() -> None:
-    """Testing ``clickgen.util.chdir`` utility function \
-       using contextmanager & ``with`` keyword.
-    """
-    current = getcwd()
-    with chdir(str(tempfile.tempdir)):
-        assert tempfile.tempdir == getcwd()
-    assert getcwd() == current
-
-
-def test_remove_util() -> None:
-    """ Testing ``clickgen.util.remove_util`` utility function.
-
-    This test create the temporary directory, file, and symlink. And try \
-    to remove with ``remove_util`` function.
-    """
-    tmp_dir = Path(tempfile.mkdtemp())
-    tmp_file = tempfile.mkstemp()[1]
-    tmp_link = tmp_dir / "link"
-    symlink(tmp_file, tmp_link)
-
-    remove_util(tmp_dir)
-    assert tmp_dir.exists() is False
-
-    remove_util(tmp_file)
-    assert os.path.exists(tmp_file) is False
-
-    remove_util(tmp_link)
-    assert tmp_link.exists() is False
-
-
-def test_PNGProvider_exception(tmpdir_factory: pytest.TempdirFactory) -> None:
-    """Testing ``clickgen.util.PNGProvider`` throwing **FileNotFoundError** \
-    exception on non exists file.
-    """
-    directory = Path(tmpdir_factory.mktemp("tt"))
-    with pytest.raises(FileNotFoundError) as excinfo:
-        PNGProvider(directory)
-    assert str(excinfo.value) == f"'*.png' files not found in '{directory.absolute()}'"
-    shutil.rmtree(directory)
-
-
-def test_PNGProvider(image_dir) -> None:
-    """Testing ``clickgen.util.PNGProvider`` member values."""
-    create_test_image(image_dir, 3)
-    p = PNGProvider(image_dir)
-    p_str = PNGProvider(str(image_dir))
-    assert p.bitmaps_dir == p_str.bitmaps_dir
-
-
-def test_PNGProvider_get(tmpdir_factory: pytest.TempdirFactory) -> None:
-    """Testing ``clickgen.util.PNGProvider.get`` method."""
-    d = tmpdir_factory.mktemp("ffff")
-    directory = Path(d)
-
-    # static
-    images = create_test_image(directory, 1, key="static")
-    p = PNGProvider(directory)
-    assert p.get("static") == images[0]
-
-    # animated
-    images1 = create_test_image(directory, 4, key="animated")
-    p1 = PNGProvider(directory)
-    p1_output = p1.get("animated")
-
-    assert isinstance(p1_output, list)
-    assert sorted(p1_output) == sorted(images1)
-
-    shutil.rmtree(d)
-
-
-def test_add_missing_xcursors_exception(
-    data, tmpdir_factory: pytest.TempdirFactory
-) -> None:
-    """Testing ``clickgen.util.add_missing_xcursors`` **NotADirectoryError** \
-    exception on *non exists directory* or *passing a file* inside parameter.
-    """
-    d = tmpdir_factory.mktemp("ffff")
-    tmp_dir = Path(d)
-    shutil.rmtree(tmp_dir)
-
-    with pytest.raises(NotADirectoryError) as excinfo:
-        add_missing_xcursors(tmp_dir, data)
-
-    assert str(excinfo.value) == str(tmp_dir.absolute())
-
-
-def test_add_missing_xcursors_without_rename_and_force(
-    data, tmpdir_factory: pytest.TempdirFactory
-) -> None:
-    d = tmpdir_factory.mktemp("ffff")
-    tmp_dir = Path(d)
-
-    images = create_test_image(tmp_dir, 2, key="fffff0")
-    with CursorAlias.from_bitmap(images, (0, 0)) as alias:
-        config_file = alias.create((10, 10), delay=2)
-        XCursor.create(config_file, tmp_dir)
-
-    x_dir = tmp_dir / "cursors"
-    add_missing_xcursors(x_dir, data)
-
-    assert list(map(lambda x: x.name, x_dir.iterdir())) == ["fffff0"]
-    shutil.rmtree(tmp_dir)
-
-
-def test_add_missing_xcursors_with_rename_without_force(
-    data, tmpdir_factory: pytest.TempdirFactory
-) -> None:
-    d = tmpdir_factory.mktemp("ffff")
-    tmp_dir = Path(d)
-
-    images = create_test_image(tmp_dir, 2, key="ddddf")
-    with CursorAlias.from_bitmap(images, (0, 0)) as alias:
-        config_file = alias.create((10, 10), delay=2)
-        XCursor.create(config_file, tmp_dir)
-
-    x_dir = tmp_dir / "cursors"
-    add_missing_xcursors(x_dir, data, rename=True)
-
-    assert sorted(list(map(lambda x: x.name, x_dir.iterdir()))) == sorted(
-        ["ddddd", "ffffff"]
-    )
-    shutil.rmtree(tmp_dir)
-
-
-def test_add_missing_xcursors_with_rename_and_force(
-    data, tmpdir_factory: pytest.TempdirFactory
-) -> None:
-    d = tmpdir_factory.mktemp("ffff")
-    tmp_dir = Path(d)
-
-    images = create_test_image(tmp_dir, 2, key="ffffd")
-    with CursorAlias.from_bitmap(images, (0, 0)) as alias:
-        config_file = alias.create((10, 10), delay=2)
-        XCursor.create(config_file, tmp_dir)
-
-    x_dir = tmp_dir / "cursors"
-    add_missing_xcursors(x_dir, data, rename=True)
-    with pytest.raises(FileExistsError):
-        add_missing_xcursors(x_dir, data, rename=True)
-    add_missing_xcursors(x_dir, data, rename=True, force=True)
-
-    assert sorted(list(map(lambda x: x.name, x_dir.iterdir()))) == sorted(
-        ["ddddd", "ffffff"]
-    )
-    shutil.rmtree(tmp_dir)
diff --git a/tests/utils.py b/tests/utils.py
deleted file mode 100644
index 197358e1..00000000
--- a/tests/utils.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-.. moduleauthor:: Kaiz Khatri <kaizmandhu@gmail.com>
-"""
-
-from pathlib import Path
-from random import randint
-from typing import List
-
-from PIL import Image
-
-
-def create_test_image(
-    directory: Path, count: int, size=(20, 20), key: str = "test"
-) -> List[Path]:
-    """Create test ``.png`` image file with random color.
-
-    :param directory: Directory where test image will stored.
-    :type directory: pathlib.Path
-
-    :param count: Count of images you wants to generate.
-    :type count: int
-
-    :param key: Name of the image without extension. More than \
-            one images will prefix by number. *(default is **test**)*
-    :type key: str
-    """
-
-    images: List[Path] = []
-    for i in range(count):
-        file = directory / f"{key}-{i}.png"
-        i = Image.new(
-            "RGBA",
-            size=size,
-            color=(randint(0, 255), randint(0, 255), randint(0, 255)),
-        )
-        i.save(file, "png", compress=0)
-        i.close()
-        images.append(file)
-    return images
-
-
-def create_test_cursor(directory: Path, name: str = "test.png") -> Path:
-    """Generate the test cursor file.
-
-    :param directory: Directory where test image will stored.
-    :type directory: pathlib.Path
-
-    :param name: Name of the image without extension. More than \
-            one images will prefix by number. *(default is **test**)*
-    :type name: str
-    """
-
-    file = directory / name
-    i = Image.new(
-        "RGBA",
-        size=(2, 2),
-        color=(randint(0, 255), randint(0, 255), randint(0, 255)),
-    )
-    i.save(file, "png", compress=0)
-    i.close()
-    return file
diff --git a/tests/writer/test_windows_writer.py b/tests/writer/test_windows_writer.py
new file mode 100644
index 00000000..d1138ef4
--- /dev/null
+++ b/tests/writer/test_windows_writer.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import pytest
+from PIL.Image import Image
+
+from clickgen.cursors import CursorFrame, CursorImage
+from clickgen.writer.windows import to_ani, to_cur, to_win
+
+
+def test_windows_cur_writer(cursor_frame, x11_tmp_dir: Path):
+    o = to_cur(cursor_frame)
+    assert isinstance(o, bytes)
+
+    cfile = x11_tmp_dir.joinpath("test.cur")
+    cfile.write_bytes(o)
+
+    assert cfile.exists()
+    assert cfile.is_file()
+
+
+def test_windows_cur_writer_raises(image: Image, hotspot, delay):
+    i = image.resize(size=(500, 500), resample=3)
+    c = CursorImage(i, hotspot, nominal=i.size[0])
+    cf = CursorFrame([c], delay)
+
+    with pytest.raises(ValueError):
+        to_cur(cf)
+
+
+def test_windows_ani_writer(cursor_frame: CursorFrame, x11_tmp_dir: Path):
+    o = to_ani([cursor_frame, cursor_frame])
+    assert isinstance(o, bytes)
+
+    cfile = x11_tmp_dir.joinpath("test.ani")
+    cfile.write_bytes(o)
+
+    assert cfile.exists()
+    assert cfile.is_file()
+
+
+def test_windows_writer(cursor_frame: CursorFrame, x11_tmp_dir: Path):
+    ext, o = to_win([cursor_frame, cursor_frame])
+    assert isinstance(o, bytes)
+
+    cfile = x11_tmp_dir.joinpath(f"test.{ext}")
+    cfile.write_bytes(o)
+
+    assert cfile.exists()
+    assert cfile.is_file()
+    assert cfile.suffix == ".ani"
+
+    ext1, o1 = to_win([cursor_frame])
+    assert isinstance(o1, bytes)
+
+    cfile1 = x11_tmp_dir.joinpath(f"test.{ext1}")
+    cfile1.write_bytes(o1)
+
+    assert cfile1.exists()
+    assert cfile1.is_file()
+    assert cfile1.suffix == ".cur"
diff --git a/tests/writer/test_x11_writer.py b/tests/writer/test_x11_writer.py
new file mode 100644
index 00000000..187dcb66
--- /dev/null
+++ b/tests/writer/test_x11_writer.py
@@ -0,0 +1,14 @@
+from pathlib import Path
+
+from clickgen.writer.x11 import to_x11
+
+
+def test_x11_writer(cursor_frame, x11_tmp_dir: Path):
+    o = to_x11([cursor_frame])
+    assert isinstance(o, bytes)
+
+    xfile = x11_tmp_dir.joinpath("test")
+    xfile.write_bytes(o)
+
+    assert xfile.exists()
+    assert xfile.is_file()