From 657c25fcfa880272026340a7d9cf1727ebfc5c7d Mon Sep 17 00:00:00 2001
From: Quantum <quantum2048@gmail.com>
Date: Sun, 4 Oct 2020 02:26:04 -0400
Subject: [PATCH] Remove dependency on xcursorgen

---
 .github/workflows/build.yml |  2 +-
 win2xcur/main/win2xcur.py   |  3 --
 win2xcur/writer/x11.py      | 83 +++++++++++++++++--------------------
 3 files changed, 40 insertions(+), 48 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 24d28a2..199f27c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,7 +21,7 @@ jobs:
           python -m pip install --upgrade pip
           pip install flake8 flake8-import-order mypy wheel coverage
           pip install -r requirements.txt
-          sudo apt-get install x11-apps dmz-cursor-theme
+          sudo apt-get install dmz-cursor-theme
       - name: Lint with flake8
         run: flake8 .
       - name: Typecheck with mypy
diff --git a/win2xcur/main/win2xcur.py b/win2xcur/main/win2xcur.py
index e50b644..a85f634 100644
--- a/win2xcur/main/win2xcur.py
+++ b/win2xcur/main/win2xcur.py
@@ -10,7 +10,6 @@ from typing import BinaryIO
 from win2xcur import shadow
 from win2xcur.parser import open_blob
 from win2xcur.writer import to_x11
-from win2xcur.writer.x11 import check_xcursorgen
 
 
 def main() -> None:
@@ -37,8 +36,6 @@ def main() -> None:
     args = parser.parse_args()
     print_lock = Lock()
 
-    check_xcursorgen()
-
     def process(file: BinaryIO) -> None:
         name = file.name
         blob = file.read()
diff --git a/win2xcur/writer/x11.py b/win2xcur/writer/x11.py
index 15d8085..3e68fb1 100644
--- a/win2xcur/writer/x11.py
+++ b/win2xcur/writer/x11.py
@@ -1,54 +1,49 @@
-import os
-import subprocess
-import sys
-from tempfile import TemporaryDirectory
+from itertools import chain
+from operator import itemgetter
 from typing import List
 
-from wand.image import Image
-
 from win2xcur.cursor import CursorFrame
-
-xcursorgen_checked = False
-
-
-def check_xcursorgen() -> None:
-    global xcursorgen_checked
-    if xcursorgen_checked:
-        return
-
-    try:
-        subprocess.check_call(['xcursorgen', '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-    except subprocess.CalledProcessError:
-        raise RuntimeError('xcursorgen must be installed to create X11 cursors!')
-    else:
-        xcursorgen_checked = True
+from win2xcur.parser import XCursorParser
 
 
 def to_x11(frames: List[CursorFrame]) -> bytes:
-    check_xcursorgen()
+    chunks = []
 
-    counter = 0
-    configs = []
-    with TemporaryDirectory() as png_dir:
-        for frame in frames:
-            for cursor in frame:
-                name = '%d.png' % (counter,)
-                hx, hy = cursor.hotspot
-                configs.append('%d %d %d %s %d' % (cursor.image.width, hx, hy, name, int(frame.delay * 1000)))
+    for frame in frames:
+        for cursor in frame:
+            hx, hy = cursor.hotspot
+            header = XCursorParser.IMAGE_HEADER.pack(
+                XCursorParser.IMAGE_HEADER.size,
+                XCursorParser.CHUNK_IMAGE,
+                cursor.image.width,
+                1,
+                cursor.image.width,
+                cursor.image.height,
+                hx,
+                hy,
+                int(frame.delay * 1000),
+            )
+            chunks.append((
+                XCursorParser.CHUNK_IMAGE,
+                cursor.image.width,
+                header + bytes(cursor.image.export_pixels(channel_map='BGRA'))
+            ))
 
-                image = Image(image=cursor.image)
-                image.save(filename=os.path.join(png_dir, name))
-                counter += 1
+    header = XCursorParser.FILE_HEADER.pack(
+        XCursorParser.MAGIC,
+        XCursorParser.FILE_HEADER.size,
+        XCursorParser.VERSION,
+        len(chunks),
+    )
 
-        output_file = os.path.join(png_dir, 'cursor')
-        process = subprocess.Popen(['xcursorgen', '-p', png_dir, '-', output_file], stdin=subprocess.PIPE,
-                                   stderr=subprocess.PIPE)
+    offset = XCursorParser.FILE_HEADER.size + len(chunks) * XCursorParser.TOC_CHUNK.size
+    toc = []
+    for chunk_type, chunk_subtype, chunk in chunks:
+        toc.append(XCursorParser.TOC_CHUNK.pack(
+            chunk_type,
+            chunk_subtype,
+            offset,
+        ))
+        offset += len(chunk)
 
-        _, error = process.communicate('\n'.join(configs).encode(sys.getfilesystemencoding()))
-        if process.wait() != 0:
-            raise RuntimeError('xcursorgen failed: %r' % error)
-
-        with open(output_file, 'rb') as f:
-            result = f.read()
-
-    return result
+    return b''.join(chain([header], toc, map(itemgetter(2), chunks)))