xteink-plugins

This commit is contained in:
justin committed 2026-04-01 16:21:24 -07:00
commit 61f6e61a54
11 files changed
+744

No files matched your search

Vendored
BIN
View File
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
![Smaller Fonts plugin settings tab on the xteink device](screenshot.jpg)
# xteink-plugins
A plugin system for customizing and extending [CrossPoint Reader](https://github.com/crosspoint-reader/crosspoint-reader) firmware on your xteink device. Plugins are applied as source-level patches before the firmware is compiled and flashed.
## Plugins
### Smaller Fonts
Adds a **Plugins** settings tab to CrossPoint with a "Smaller Fonts" option. When enabled, your chosen reader font is transparently substituted with a smaller variant — no need to change your font preference.
| Mode | Effect |
|------|--------|
| **Disabled** | No change (default) |
| **Smaller** | Drops the current font size down by one step (e.g. Bookerly 16 → 14) |
| **Smallest** | Drops the current font size down by two steps (e.g. Bookerly 16 → 12) |
Supports Bookerly, Noto Sans, and OpenDyslexic. The plugin also generates and embeds Bookerly at 8pt and 10pt — sizes not included in the stock firmware.
## Requirements
- Python 3.10+
- [PlatformIO](https://platformio.org/) (`pio` on your PATH)
- `git`
- Your xteink device connected via USB
## Usage
From the root of this repository, run:
```bash
python install.py
```
The installer will:
1. Clone the CrossPoint Reader source repository
2. Prompt you to select which plugins to install and apply them as patches
3. Build the firmware with PlatformIO
4. Auto-detect your device's serial port and flash the firmware
> **Note:** This script modifies and flashes custom firmware to your device. The author accepts no responsibility for any damage that may occur to your device as a result of using this installer.
## Repository Structure
```
xteink-plugins/
├── install.py # Interactive installer: clone → patch → build → flash
└── plugins/
└── smallerfonts/
├── patch.py # Patch script applied to the CrossPoint source
├── SmallerFontsPlugin.h/.cpp # Font resolution logic
└── SmallerFontsSettingsPage.h/.cpp # Settings UI activity
```
## Adding a Plugin
1. Create a new directory under `plugins/` with your plugin's name.
2. Add a `patch.py` file with a `patch(repo_dir: str)` function. This function receives the absolute path to the cloned CrossPoint repository and should make all necessary modifications.
The installer will automatically discover and offer to install any directory under `plugins/` that contains a `patch.py`.
+213
View File
@@ -0,0 +1,213 @@
#!/usr/bin/env python3
import os
import sys
import shutil
import subprocess
import glob
import importlib.util
REPO_URL = "https://github.com/crosspoint-reader/crosspoint-reader.git"
REPO_DIR = "crosspoint-reader"
PLUGINS_DIR = "plugins"
def run(cmd, cwd=None, check=True):
print(f" $ {cmd}")
return subprocess.run(cmd, shell=True, cwd=cwd, check=check)
def clone_repo():
print("[1/3] Cloning CrossPoint repository...")
if os.path.exists(REPO_DIR):
print(f" Removing existing '{REPO_DIR}'...")
shutil.rmtree(REPO_DIR)
run(f"git clone --recursive {REPO_URL} {REPO_DIR}")
def run_plugin_patch(plugin_dir: str) -> bool:
patch_path = os.path.join(plugin_dir, "patch.py")
if not os.path.exists(patch_path):
print(f" WARNING: No patch.py found in {plugin_dir}, skipping.")
return False
spec = importlib.util.spec_from_file_location("patch", patch_path)
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module)
if hasattr(module, "patch"):
module.patch(os.path.abspath(REPO_DIR))
return True
except Exception as exc:
print(f" ERROR while running patch.py for {plugin_dir}: {exc}")
return False
def apply_plugins():
print("[2/3] Applying plugins...")
if not os.path.exists(PLUGINS_DIR):
print(f" No '{PLUGINS_DIR}' directory found — skipping plugin step.")
return
plugin_dirs = sorted(
d for d in os.listdir(PLUGINS_DIR)
if os.path.isdir(os.path.join(PLUGINS_DIR, d))
)
if not plugin_dirs:
print(" No plugin directories found.")
return
for plugin_name in plugin_dirs:
plugin_path = os.path.join(PLUGINS_DIR, plugin_name)
print(f"\n Plugin: {plugin_name}")
answer = input(f" Install '{plugin_name}'? [Y/n]: ").strip().lower()
if answer in ("", "y", "yes"):
print(f" Installing {plugin_name}...")
success = run_plugin_patch(plugin_path)
if success:
print(f" ✓ {plugin_name} installed.")
else:
print(f" ✗ {plugin_name} failed or was skipped.")
else:
print(f" Skipping {plugin_name}.")
def list_likely_serial_ports():
patterns = [
"/dev/cu.usb*",
"/dev/tty.usb*",
"/dev/cu.wchusb*",
"/dev/tty.wchusb*",
"/dev/cu.SLAB*",
"/dev/tty.SLAB*",
"/dev/ttyACM*",
"/dev/ttyUSB*",
]
ports = []
for pattern in patterns:
ports.extend(glob.glob(pattern))
return sorted(set(ports))
def detect_serial_ports():
port_map: dict[str, str] = {}
try:
import serial.tools.list_ports
usb_keywords = {
"usb", "cp210", "ftdi", "ch340", "ch9102", "esp",
"serial", "uart", "prolific", "silabs",
}
skip_keywords = {"bluetooth", "bt-", "btle", "airpods", "handsfree"}
for port in serial.tools.list_ports.comports():
combined = (port.device + " " + (port.description or "") +
" " + (port.hwid or "")).lower()
if any(s in combined for s in skip_keywords):
continue
if any(k in combined for k in usb_keywords):
port_map[port.device] = port.description or "USB Serial Device"
except ImportError:
pass
for device in list_likely_serial_ports():
if device not in port_map:
port_map[device] = "Detected via glob pattern"
return sorted(port_map.items())
def prompt_for_upload_port():
print("\n[3/3] Select serial port for flashing...")
max_retries = 3
for attempt in range(1, max_retries + 1):
print(f"\n Attempt {attempt}/{max_retries}")
print(" Ensure your xteink device is connected via USB and awake.")
input(" Press Enter when ready... ")
ports = detect_serial_ports()
if ports:
cu_ports = [(d, desc) for d, desc in ports if d.startswith("/dev/cu.")]
recommended = cu_ports[0][0] if cu_ports else ports[0][0]
print(f"\n Found {len(ports)} serial port(s):")
for i, (device, desc) in enumerate(ports, 1):
tag = " ← recommended" if device == recommended else ""
print(f" {i}. {device} — {desc}{tag}")
choice = input(f"\n Press ENTER to use {recommended}, or enter 1-{len(ports)}: ").strip()
if not choice:
print(f" Selected: {recommended}")
return recommended
if choice.isdigit():
idx = int(choice) - 1
if 0 <= idx < len(ports):
print(f" Selected: {ports[idx][0]}")
return ports[idx][0]
print(" Invalid choice, please try again.")
else:
print(" No USB serial ports detected.")
if attempt < max_retries:
print(" Make sure the device is connected, powered on, and awake.")
print("\n Could not auto-detect a port.")
print(" Common names: macOS: /dev/cu.usb* Linux: /dev/ttyUSB* Windows: COMx")
while True:
port = input("\n Enter port manually (or 'quit' to exit): ").strip()
if port.lower() == "quit":
sys.exit(1)
if port:
return port
print(" Please enter a valid port name.")
def build_and_flash():
print("\nBuilding firmware with PlatformIO...")
run("pio run --environment default", cwd=REPO_DIR)
port = prompt_for_upload_port()
print(f"\n Flashing to {port}...")
run(
f"pio run --target upload --environment default --upload-port {port}",
cwd=REPO_DIR,
)
def main():
print("=" * 60)
print(" xteink Plugin Installer")
print("=" * 60)
print()
print(" This script modifies and flashes custom firmware to your")
print(" device. The author of this script (Justin Oros) accepts")
print(" no responsibility for any damage that may occur to your")
print(" device as a result of using this installer.")
print()
answer = input(" Do you wish to proceed? [Y/n]: ").strip().lower()
if answer not in ("", "y", "yes"):
print(" Aborted.")
sys.exit(0)
print()
clone_repo()
apply_plugins()
build_and_flash()
print("\nDone.")
if __name__ == "__main__":
main()
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
@@ -0,0 +1,65 @@
#include "SmallerFontsPlugin.h"
#include "fontIds.h"
namespace SmallerFontsPlugin {
const char* modeName(SmallerFontsMode mode) {
switch (mode) {
case SmallerFontsMode::SMALLER: return "Smaller";
case SmallerFontsMode::SMALLEST: return "Smallest";
default: return "Disabled";
}
}
int resolveReaderFontId(int baseFontId, SmallerFontsMode mode) {
if (mode == SmallerFontsMode::MODE_OFF) return baseFontId;
if (baseFontId == BOOKERLY_12_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return BOOKERLY_10_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return BOOKERLY_8_FONT_ID;
}
if (baseFontId == BOOKERLY_14_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return BOOKERLY_12_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return BOOKERLY_10_FONT_ID;
}
if (baseFontId == BOOKERLY_16_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return BOOKERLY_14_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return BOOKERLY_12_FONT_ID;
}
if (baseFontId == BOOKERLY_18_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return BOOKERLY_16_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return BOOKERLY_14_FONT_ID;
}
if (baseFontId == NOTOSANS_12_FONT_ID) return NOTOSANS_12_FONT_ID;
if (baseFontId == NOTOSANS_14_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return NOTOSANS_12_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return NOTOSANS_12_FONT_ID;
}
if (baseFontId == NOTOSANS_16_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return NOTOSANS_14_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return NOTOSANS_12_FONT_ID;
}
if (baseFontId == NOTOSANS_18_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return NOTOSANS_16_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return NOTOSANS_14_FONT_ID;
}
if (baseFontId == OPENDYSLEXIC_8_FONT_ID) return OPENDYSLEXIC_8_FONT_ID;
if (baseFontId == OPENDYSLEXIC_10_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return OPENDYSLEXIC_8_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return OPENDYSLEXIC_8_FONT_ID;
}
if (baseFontId == OPENDYSLEXIC_12_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return OPENDYSLEXIC_10_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return OPENDYSLEXIC_8_FONT_ID;
}
if (baseFontId == OPENDYSLEXIC_14_FONT_ID) {
if (mode == SmallerFontsMode::SMALLER) return OPENDYSLEXIC_12_FONT_ID;
if (mode == SmallerFontsMode::SMALLEST) return OPENDYSLEXIC_10_FONT_ID;
}
return baseFontId;
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <Arduino.h>
enum class SmallerFontsMode : uint8_t {
MODE_OFF = 0,
SMALLER = 1,
SMALLEST = 2,
};
namespace SmallerFontsPlugin {
const char* modeName(SmallerFontsMode mode);
int resolveReaderFontId(int baseFontId, SmallerFontsMode mode);
}
@@ -0,0 +1,22 @@
#include "SmallerFontsSettingsActivity.h"
#include "fontIds.h"
void SmallerFontsSettingsActivity::render(GfxRenderer& renderer, SmallerFontsMode currentMode) {
const int LABEL_X = 10;
const int VALUE_X = 160;
const int ROW_Y = 60;
renderer.drawText(UI_12_FONT_ID, LABEL_X, ROW_Y, "Smaller Fonts");
renderer.drawText(UI_12_FONT_ID, VALUE_X, ROW_Y, SmallerFontsPlugin::modeName(currentMode));
}
SmallerFontsMode SmallerFontsSettingsActivity::handleNext(SmallerFontsMode currentMode) {
uint8_t v = static_cast<uint8_t>(currentMode);
if (v < 3) v++;
return static_cast<SmallerFontsMode>(v);
}
SmallerFontsMode SmallerFontsSettingsActivity::handlePrev(SmallerFontsMode currentMode) {
uint8_t v = static_cast<uint8_t>(currentMode);
if (v > 0) v--;
return static_cast<SmallerFontsMode>(v);
}
@@ -0,0 +1,11 @@
#pragma once
#include <GfxRenderer.h>
#include "SmallerFontsPlugin.h"
class SmallerFontsSettingsActivity {
public:
static void render(GfxRenderer& renderer, SmallerFontsMode currentMode);
static SmallerFontsMode handleNext(SmallerFontsMode currentMode);
static SmallerFontsMode handlePrev(SmallerFontsMode currentMode);
};
+357
View File
@@ -0,0 +1,357 @@
#!/usr/bin/env python3
import os
import sys
import glob
import shutil
import hashlib
import subprocess
def read_file(path):
with open(path, "r", encoding="utf-8") as f:
return f.read()
def write_file(path, content):
with open(path, "w", encoding="utf-8") as f:
f.write(content)
def find_first(filename, repo_dir):
results = glob.glob(os.path.join(repo_dir, "**", filename), recursive=True)
if not results:
sys.exit(f"ERROR: Could not locate {filename} in {repo_dir}")
return results[0]
def font_id_from_files(paths):
total = 0
for p in paths:
h = hashlib.sha256(open(p, "rb").read()).hexdigest()
total += int(h, 16)
return total % (2 ** 32) - (2 ** 31)
def generate_fonts(repo_dir):
scripts_dir = os.path.join(repo_dir, "lib", "EpdFont", "scripts")
fonts_dir = os.path.join(repo_dir, "lib", "EpdFont", "builtinFonts")
source_dir = os.path.join(fonts_dir, "source")
fontconvert = os.path.join(scripts_dir, "fontconvert.py")
subprocess.run(
[sys.executable, "-m", "pip", "install", "freetype-py==2.5.1", "fonttools", "--break-system-packages", "-q"],
check=True
)
new_fonts = [
("bookerly", 8, "Bookerly", "ttf", ["Regular", "Bold", "Italic", "BoldItalic"]),
("bookerly", 10, "Bookerly", "ttf", ["Regular", "Bold", "Italic", "BoldItalic"]),
]
generated = []
for family, size, folder, ext, styles in new_fonts:
for style in styles:
fname = f"{family}_{size}_{style.lower()}"
out_path = os.path.join(fonts_dir, f"{fname}.h")
if os.path.exists(out_path):
print(f" already exists: {fname}.h")
generated.append((family, size, style, out_path))
continue
src_path = os.path.join(source_dir, folder, f"{folder}-{style}.{ext}")
if not os.path.exists(src_path):
src_path = os.path.join(source_dir, folder, f"{folder.replace('NotoSans','NotoSans')}-{style}.{ext}")
extra = ["--2bit", "--compress"] if family != "ubuntu" else []
with open(out_path, "w") as f:
result = subprocess.run(
[sys.executable, fontconvert, fname, str(size), src_path] + extra,
capture_output=True, text=True
)
if result.returncode != 0:
print(f" ERROR generating {fname}: {result.stderr[:200]}")
if os.path.exists(out_path):
os.remove(out_path)
continue
if not result.stdout.strip():
print(f" ERROR: {fname} generated empty output")
if os.path.exists(out_path):
os.remove(out_path)
continue
f.write(result.stdout)
print(f" ✓ generated {fname}.h")
generated.append((family, size, style, out_path))
return fonts_dir
def compute_new_font_ids(fonts_dir):
ids = {}
def fid(name, size, suffixes):
paths = [os.path.join(fonts_dir, f"{name}_{size}_{s}.h") for s in suffixes]
if all(os.path.exists(p) for p in paths):
ids[f"{name.upper()}_{size}_FONT_ID"] = font_id_from_files(paths)
fid("bookerly", 8, ["regular", "bold", "italic", "bolditalic"])
fid("bookerly", 10, ["regular", "bold", "italic", "bolditalic"])
return ids
def patch_font_ids_h(repo_dir, new_ids):
path = find_first("fontIds.h", repo_dir)
content = read_file(path)
lines = []
for name, val in new_ids.items():
if name not in content:
lines.append(f"#define {name} ({val})")
if not lines:
print(" fontIds.h already patched, skipping.")
return
write_file(path, content.rstrip() + "\n" + "\n".join(lines) + "\n")
print(f" fontIds.h: added {len(lines)} new font IDs.")
def patch_all_h(repo_dir, new_ids):
path = find_first("all.h", repo_dir)
content = read_file(path)
includes = {
"BOOKERLY_8_FONT_ID": ["bookerly_8_bold", "bookerly_8_bolditalic", "bookerly_8_italic", "bookerly_8_regular"],
"BOOKERLY_10_FONT_ID": ["bookerly_10_bold", "bookerly_10_bolditalic", "bookerly_10_italic", "bookerly_10_regular"],
}
added = 0
for key, fnames in includes.items():
if key not in new_ids:
continue
for fname in fnames:
inc = f'#include <builtinFonts/{fname}.h>'
if inc not in content:
content += inc + "\n"
added += 1
if added:
write_file(path, content)
print(f" all.h: added {added} includes.")
else:
print(" all.h already patched, skipping.")
def patch_main_cpp(repo_dir, new_ids):
path = find_first("main.cpp", repo_dir)
content = read_file(path)
font_defs = {
"BOOKERLY_8_FONT_ID": ("bookerly", 8, True),
"BOOKERLY_10_FONT_ID": ("bookerly", 10, True),
}
decl_lines = []
insert_lines = []
for key, (family, size, has_styles) in font_defs.items():
if key not in new_ids:
continue
varbase = f"{family}{size}"
if f"EpdFontFamily {varbase}FontFamily" in content:
continue
decl_lines += [
f'EpdFont {varbase}RegularFont(&{family}_{size}_regular);',
f'EpdFont {varbase}BoldFont(&{family}_{size}_bold);',
f'EpdFont {varbase}ItalicFont(&{family}_{size}_italic);',
f'EpdFont {varbase}BoldItalicFont(&{family}_{size}_bolditalic);',
f'EpdFontFamily {varbase}FontFamily(&{varbase}RegularFont, &{varbase}BoldFont, &{varbase}ItalicFont, &{varbase}BoldItalicFont);',
]
insert_lines.append(f' renderer.insertFont({key}, {varbase}FontFamily);')
if not decl_lines:
print(" main.cpp already patched, skipping.")
return
anchor_decl = '#ifndef OMIT_FONTS'
content = content.replace(anchor_decl, "\n".join(decl_lines) + "\n" + anchor_decl, 1)
anchor_insert = ' renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);'
content = content.replace(anchor_insert, "\n".join(insert_lines) + "\n" + anchor_insert, 1)
write_file(path, content)
print(f" main.cpp: added {len(decl_lines)//5} font families.")
def copy_plugin_sources(plugin_dir, repo_dir):
dest = os.path.join(repo_dir, "src", "activities", "settings")
os.makedirs(dest, exist_ok=True)
skip = {"SmallerFontsSettingsPage.h", "SmallerFontsSettingsPage.cpp"}
for fname in os.listdir(plugin_dir):
if fname.endswith((".h", ".cpp", ".hpp")) and fname not in skip:
src = os.path.join(plugin_dir, fname)
dst = os.path.join(dest, fname)
shutil.copy2(src, dst)
print(f" ✓ {fname}")
def patch_cross_point_settings_h(repo_dir):
path = find_first("CrossPointSettings.h", repo_dir)
content = read_file(path)
if "smallerFontsMode" in content:
print(" CrossPointSettings.h already patched, skipping.")
return
content = content.replace(
'#pragma once\n#include <HalStorage.h>',
'#pragma once\n#include <HalStorage.h>\n#include "activities/settings/SmallerFontsPlugin.h"'
)
content = content.replace(
' uint8_t imageRendering = IMAGES_DISPLAY;',
' uint8_t imageRendering = IMAGES_DISPLAY;\n uint8_t smallerFontsMode = 0;'
)
write_file(path, content)
print(" CrossPointSettings.h patched.")
def patch_cross_point_settings_cpp(repo_dir):
path = find_first("CrossPointSettings.cpp", repo_dir)
content = read_file(path)
if "resolveReaderFontId" in content:
print(" CrossPointSettings.cpp already patched, skipping.")
return
content = content.replace(
'int CrossPointSettings::getReaderFontId() const {',
'int CrossPointSettings::getReaderFontId() const {\n auto resolve = [this](int baseId) {\n return SmallerFontsPlugin::resolveReaderFontId(baseId, static_cast<SmallerFontsMode>(smallerFontsMode));\n };\n'
)
for name in ["BOOKERLY_12", "BOOKERLY_14", "BOOKERLY_16", "BOOKERLY_18",
"NOTOSANS_12", "NOTOSANS_14", "NOTOSANS_16", "NOTOSANS_18",
"OPENDYSLEXIC_8", "OPENDYSLEXIC_10", "OPENDYSLEXIC_12", "OPENDYSLEXIC_14"]:
content = content.replace(f'return {name}_FONT_ID;', f'return resolve({name}_FONT_ID);')
write_file(path, content)
print(" CrossPointSettings.cpp patched.")
def patch_settings_h(repo_dir):
path = find_first("SettingsActivity.h", repo_dir)
content = read_file(path)
if "pluginsSettings" in content:
print(" SettingsActivity.h already patched, skipping.")
return
content = content.replace(
'#include <I18n.h>',
'#include <I18n.h>\n#include "SmallerFontsPlugin.h"'
)
content = content.replace(
'static constexpr int categoryCount = 4;',
'static constexpr int categoryCount = 5;'
)
content = content.replace(
' std::vector<SettingInfo> systemSettings;',
' std::vector<SettingInfo> systemSettings;\n std::vector<SettingInfo> pluginsSettings;'
)
write_file(path, content)
print(" SettingsActivity.h patched.")
def patch_settings_cpp(repo_dir):
path = find_first("SettingsActivity.cpp", repo_dir)
content = read_file(path)
if "pluginsSettings" in content:
print(" SettingsActivity.cpp already patched, skipping.")
return
content = content.replace(
'const StrId SettingsActivity::categoryNames[categoryCount] = {StrId::STR_CAT_DISPLAY, StrId::STR_CAT_READER,\n StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM};',
'const StrId SettingsActivity::categoryNames[categoryCount] = {StrId::STR_CAT_DISPLAY, StrId::STR_CAT_READER,\n StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM,\n StrId::STR_NONE_OPT};'
)
content = content.replace(
' systemSettings.clear();',
' systemSettings.clear();\n pluginsSettings.clear();'
)
content = content.replace(
' readerSettings.push_back(SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));',
' readerSettings.push_back(SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));\n\n'
' pluginsSettings.push_back(SettingInfo::Enum(\n'
' StrId::STR_NONE_OPT,\n'
' &CrossPointSettings::smallerFontsMode,\n'
' {StrId::STR_NONE_OPT, StrId::STR_NONE_OPT, StrId::STR_NONE_OPT},\n'
' "smallerFontsMode"\n'
' ));'
)
content = content.replace(
' case 3:\n currentSettings = &systemSettings;\n break;',
' case 3:\n currentSettings = &systemSettings;\n break;\n case 4:\n currentSettings = &pluginsSettings;\n break;'
)
content = content.replace(
' std::vector<TabInfo> tabs;\n tabs.reserve(categoryCount);\n for (int i = 0; i < categoryCount; i++) {\n tabs.push_back({I18N.get(categoryNames[i]), selectedCategoryIndex == i});\n }',
' std::vector<TabInfo> tabs;\n tabs.reserve(categoryCount);\n for (int i = 0; i < categoryCount; i++) {\n const char* tabLabel = (i == 4) ? "Plugins" : I18N.get(categoryNames[i]);\n tabs.push_back({tabLabel, selectedCategoryIndex == i});\n }'
)
content = content.replace(
' const auto confirmLabel = (selectedSettingIndex == 0)\n ? I18N.get(categoryNames[(selectedCategoryIndex + 1) % categoryCount])\n : tr(STR_TOGGLE);',
' const int nextCatIndex = (selectedCategoryIndex + 1) % categoryCount;\n const char* nextCatLabel = (nextCatIndex == 4) ? "Plugins" : I18N.get(categoryNames[nextCatIndex]);\n const auto confirmLabel = (selectedSettingIndex == 0) ? nextCatLabel : tr(STR_TOGGLE);'
)
content = content.replace(
' [&settings](int index) { return std::string(I18N.get(settings[index].nameId)); },',
' [&settings, this](int index) -> std::string {\n'
' if (selectedCategoryIndex == 4) return "Smaller Fonts";\n'
' return std::string(I18N.get(settings[index].nameId));\n'
' },'
)
content = content.replace(
' } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {\n const uint8_t value = SETTINGS.*(setting.valuePtr);\n valueText = I18N.get(setting.enumValues[value]);\n }',
' } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {\n const uint8_t value = SETTINGS.*(setting.valuePtr);\n if (setting.key && std::string(setting.key) == "smallerFontsMode") {\n valueText = SmallerFontsPlugin::modeName(static_cast<SmallerFontsMode>(value));\n } else {\n valueText = I18N.get(setting.enumValues[value]);\n }\n }'
)
write_file(path, content)
print(" SettingsActivity.cpp patched.")
def patch(repo_dir: str):
plugin_dir = os.path.dirname(os.path.abspath(__file__))
print(" Copying plugin sources...")
copy_plugin_sources(plugin_dir, repo_dir)
print(" Generating smaller font files...")
fonts_dir = generate_fonts(repo_dir)
print(" Computing new font IDs...")
new_ids = compute_new_font_ids(fonts_dir)
for k, v in new_ids.items():
print(f" {k} = {v}")
print(" Patching fontIds.h...")
patch_font_ids_h(repo_dir, new_ids)
print(" Patching all.h...")
patch_all_h(repo_dir, new_ids)
print(" Patching main.cpp...")
patch_main_cpp(repo_dir, new_ids)
print(" Patching CrossPointSettings.h...")
patch_cross_point_settings_h(repo_dir)
print(" Patching CrossPointSettings.cpp...")
patch_cross_point_settings_cpp(repo_dir)
print(" Patching SettingsActivity.h...")
patch_settings_h(repo_dir)
print(" Patching SettingsActivity.cpp...")
patch_settings_cpp(repo_dir)
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit("Usage: python patch.py <path-to-crosspoint-repo>")
patch(sys.argv[1])
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB