Merge pull request #16 from hkfuertes/mf68e_display_controller_logo
MF68E Display Monitor and Boot Logo
This commit is contained in:
18 files changed
+751
-202
No files matched your search
@@ -3,6 +3,7 @@ CONFIG_TARGET_msm89xx_msm8916=y
|
||||
CONFIG_TARGET_msm89xx_msm8916_DEVICE_generic-mf68e=y
|
||||
CONFIG_BUSYBOX_CUSTOM=y
|
||||
CONFIG_BUSYBOX_CONFIG_FBSET=y
|
||||
CONFIG_BUSYBOX_CONFIG_USLEEP=y
|
||||
CONFIG_LIBQMI_COLLECTION_BASIC=y
|
||||
CONFIG_LIBQMI_WITH_MBIM_QMUX=y
|
||||
CONFIG_LIBQMI_WITH_QRTR_GLIB=y
|
||||
|
||||
@@ -44,9 +44,10 @@ define Device/generic-mf68e
|
||||
DEVICE_VENDOR := Generic
|
||||
DEVICE_MODEL := MF68E
|
||||
FILESYSTEMS := squashfs
|
||||
CMDLINE := "earlycon console=ttyMSM0,115200 quiet loglevel=0 vt.global_cursor_default=0 vt.cur_default=0 root=/dev/mmcblk0p14 rootfstype=squashfs rootwait"
|
||||
DEVICE_PACKAGES := configs-mf68e wpad-basic-wolfssl rmtfs uci-usb-gadget \
|
||||
block-mount f2fs-tools prepare-rootfs-data \
|
||||
msm-firmware-dumper power-button-daemon kmod-fbtft-gc9107
|
||||
msm-firmware-dumper kmod-fbtft-gc9107 router-display
|
||||
endef
|
||||
TARGET_DEVICES += generic-mf68e
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- /dev/null
|
||||
+++ b/arch/arm64/boot/dts/qcom/msm8916-generic-mf68e.dts
|
||||
@@ -0,0 +1,158 @@
|
||||
@@ -0,0 +1,165 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+/dts-v1/;
|
||||
@@ -8,154 +8,161 @@
|
||||
+#include "msm8916-ufi.dtsi"
|
||||
+#include <dt-bindings/gpio/gpio.h>
|
||||
+#include <dt-bindings/pinctrl/qcom,pmic-mpp.h>
|
||||
+#include <dt-bindings/pwm/pwm.h>
|
||||
+
|
||||
+/ {
|
||||
+ model = "MF68E 4G LTE Mobile WiFi Router";
|
||||
+ compatible = "generic,mf68e", "qcom,msm8916";
|
||||
+ model = "MF68E 4G LTE Mobile WiFi Router";
|
||||
+ compatible = "generic,mf68e", "qcom,msm8916";
|
||||
+
|
||||
+ bat: battery {
|
||||
+ compatible = "simple-battery";
|
||||
+ voltage-min-design-microvolt = <3400000>;
|
||||
+ voltage-max-design-microvolt = <4350000>;
|
||||
+ energy-full-design-microwatt-hours = <11100000>;
|
||||
+ charge-full-design-microamp-hours = <3000000>;
|
||||
+ bat: battery {
|
||||
+ compatible = "simple-battery";
|
||||
+ voltage-min-design-microvolt = <3400000>;
|
||||
+ voltage-max-design-microvolt = <4350000>;
|
||||
+ energy-full-design-microwatt-hours = <11100000>;
|
||||
+ charge-full-design-microamp-hours = <3000000>;
|
||||
+
|
||||
+ ocv-capacity-celsius = <25>;
|
||||
+ ocv-capacity-table-0 = <4330000 100>, <4265000 95>,
|
||||
+ <4208000 90>, <4154000 85>, <4102000 80>,
|
||||
+ <4063000 75>, <4006000 70>, <3972000 65>,
|
||||
+ <3931000 60>, <3880000 55>, <3841000 50>,
|
||||
+ <3817000 45>, <3798000 40>, <3783000 35>,
|
||||
+ <3767000 30>, <3747000 25>, <3729000 20>,
|
||||
+ <3700000 16>, <3687000 13>, <3683000 11>,
|
||||
+ <3681000 10>, <3680000 9>, <3678000 8>,
|
||||
+ <3674000 7>, <3666000 6>, <3641000 5>,
|
||||
+ <3597000 4>, <3537000 3>, <3457000 2>,
|
||||
+ <3336000 1>, <3000000 0>;
|
||||
+ };
|
||||
+ ocv-capacity-celsius = <25>;
|
||||
+ ocv-capacity-table-0 = <4330000 100>, <4265000 95>,
|
||||
+ <4208000 90>, <4154000 85>, <4102000 80>,
|
||||
+ <4063000 75>, <4006000 70>, <3972000 65>,
|
||||
+ <3931000 60>, <3880000 55>, <3841000 50>,
|
||||
+ <3817000 45>, <3798000 40>, <3783000 35>,
|
||||
+ <3767000 30>, <3747000 25>, <3729000 20>,
|
||||
+ <3700000 16>, <3687000 13>, <3683000 11>,
|
||||
+ <3681000 10>, <3680000 9>, <3678000 8>,
|
||||
+ <3674000 7>, <3666000 6>, <3641000 5>,
|
||||
+ <3597000 4>, <3537000 3>, <3457000 2>,
|
||||
+ <3336000 1>, <3000000 0>;
|
||||
+ };
|
||||
+
|
||||
+ usb_id: usb-id {
|
||||
+ compatible = "linux,extcon-usb-gpio";
|
||||
+ id-gpios = <&tlmm 110 GPIO_ACTIVE_HIGH>;
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&usb_id_default>;
|
||||
+ };
|
||||
+ usb_id: usb-id {
|
||||
+ compatible = "linux,extcon-usb-gpio";
|
||||
+ id-gpios = <&tlmm 110 GPIO_ACTIVE_HIGH>;
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&usb_id_default>;
|
||||
+ };
|
||||
+
|
||||
+ backlight: backlight {
|
||||
+ compatible = "pwm-backlight";
|
||||
+ pwms = <&pm8916_pwm 0 100000>;
|
||||
+ brightness-levels = <0 4 8 16 32 64 128 255>;
|
||||
+ num-interpolated-steps = <255>;
|
||||
+ default-brightness-level = <6>;
|
||||
+ };
|
||||
+ backlight: backlight {
|
||||
+ compatible = "pwm-backlight";
|
||||
+ pwms = <&pm8916_pwm 0 100000>;
|
||||
+ brightness-levels = <255 128 64 32 16 8 4 0>;
|
||||
+ num-interpolated-steps = <255>;
|
||||
+ default-brightness-level = <6>;
|
||||
+ status = "okay";
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+/* GC9107 Panel on SPI5 (0x78b9000) */
|
||||
+&blsp_spi5 {
|
||||
+ status = "okay";
|
||||
+
|
||||
+ pinctrl-names = "default", "sleep";
|
||||
+ pinctrl-0 = <&spi4_default>;
|
||||
+ pinctrl-1 = <&spi4_sleep>;
|
||||
+ status = "okay";
|
||||
+
|
||||
+ pinctrl-names = "default", "sleep";
|
||||
+ pinctrl-0 = <&spi5_default>;
|
||||
+ pinctrl-1 = <&spi5_sleep>;
|
||||
+
|
||||
+ panel@0 {
|
||||
+ compatible = "galaxycore,gc9107";
|
||||
+ reg = <0>;
|
||||
+ spi-max-frequency = <16000000>;
|
||||
+ panel@0 {
|
||||
+ compatible = "galaxycore,gc9107";
|
||||
+ reg = <0>;
|
||||
+ spi-max-frequency = <16000000>;
|
||||
+
|
||||
+ reset-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>;
|
||||
+ dc-gpios = <&tlmm 30 GPIO_ACTIVE_HIGH>;
|
||||
+ backlight = <&backlight>;
|
||||
+ reset-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>;
|
||||
+ dc-gpios = <&tlmm 30 GPIO_ACTIVE_HIGH>;
|
||||
+ backlight = <&backlight>;
|
||||
+
|
||||
+ vdd-supply = <&pm8916_l17>;
|
||||
+ vddio-supply = <&pm8916_l6>;
|
||||
+ vdd-supply = <&pm8916_l17>;
|
||||
+ vddio-supply = <&pm8916_l6>;
|
||||
+
|
||||
+ width-mm = <23>;
|
||||
+ height-mm = <23>;
|
||||
+ width-mm = <23>;
|
||||
+ height-mm = <23>;
|
||||
+
|
||||
+ buswidth = <8>;
|
||||
+ width = <128>;
|
||||
+ height = <128>;
|
||||
+ rotate = <0>;
|
||||
+ bgr;
|
||||
+ fps = <27>;
|
||||
+ debug = <1>;
|
||||
+ };
|
||||
+ buswidth = <8>;
|
||||
+ width = <128>;
|
||||
+ height = <128>;
|
||||
+ rotate = <0>;
|
||||
+ bgr;
|
||||
+ fps = <27>;
|
||||
+ debug = <1>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&pm8916_l6 {
|
||||
+ regulator-always-on;
|
||||
+ regulator-always-on;
|
||||
+};
|
||||
+
|
||||
+&pm8916_l13 {
|
||||
+ regulator-boot-on;
|
||||
+ regulator-allow-set-load;
|
||||
+};
|
||||
+
|
||||
+&pm8916_rpm_regulators {
|
||||
+ pm8916_l17: l17 {
|
||||
+ regulator-min-microvolt = <2850000>;
|
||||
+ regulator-max-microvolt = <2850000>;
|
||||
+ regulator-always-on;
|
||||
+ };
|
||||
+ pm8916_l17: l17 {
|
||||
+ regulator-min-microvolt = <2850000>;
|
||||
+ regulator-max-microvolt = <2850000>;
|
||||
+ regulator-always-on;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&pm8916_pwm {
|
||||
+ status = "okay";
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&pwm_out>;
|
||||
+ status = "okay";
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&pwm_out>;
|
||||
+};
|
||||
+
|
||||
+&pm8916_mpps {
|
||||
+ pwm_out: mpp4-state {
|
||||
+ pins = "mpp4";
|
||||
+ function = "digital";
|
||||
+ output-low;
|
||||
+ qcom,dtest = <1>;
|
||||
+ power-source = <PM8916_MPP_VPH>;
|
||||
+ };
|
||||
+ pwm_out: mpp4-state {
|
||||
+ pins = "mpp4";
|
||||
+ function = "digital";
|
||||
+ output-low;
|
||||
+ qcom,dtest = <1>;
|
||||
+ power-source = <PM8916_MPP_VPH>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&pm8916_bms {
|
||||
+ status = "okay";
|
||||
+ monitored-battery = <&bat>;
|
||||
+ power-supplies = <&pm8916_charger>;
|
||||
+ status = "okay";
|
||||
+ monitored-battery = <&bat>;
|
||||
+ power-supplies = <&pm8916_charger>;
|
||||
+};
|
||||
+
|
||||
+&pm8916_charger {
|
||||
+ status = "okay";
|
||||
+ monitored-battery = <&bat>;
|
||||
+ qcom,fast-charge-safe-current = <900000>;
|
||||
+ qcom,fast-charge-safe-voltage = <4350000>;
|
||||
+ status = "okay";
|
||||
+ monitored-battery = <&bat>;
|
||||
+ qcom,fast-charge-safe-current = <900000>;
|
||||
+ qcom,fast-charge-safe-voltage = <4350000>;
|
||||
+};
|
||||
+
|
||||
+&usb {
|
||||
+ status = "okay";
|
||||
+ extcon = <&usb_id>, <&usb_id>;
|
||||
+ status = "okay";
|
||||
+ extcon = <&usb_id>, <&usb_id>;
|
||||
+};
|
||||
+
|
||||
+&usb_hs_phy {
|
||||
+ extcon = <&usb_id>;
|
||||
+ extcon = <&usb_id>;
|
||||
+};
|
||||
+
|
||||
+&sdhc_1 {
|
||||
+ status = "okay";
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&tlmm {
|
||||
+ spi4_default: spi4-default-state {
|
||||
+ pins = "gpio16", "gpio17", "gpio19";
|
||||
+ function = "blsp_spi5";
|
||||
+ drive-strength = <12>;
|
||||
+ bias-disable;
|
||||
+ };
|
||||
+ spi5_default: spi5-default-state {
|
||||
+ pins = "gpio16", "gpio17", "gpio19";
|
||||
+ function = "blsp_spi5";
|
||||
+ drive-strength = <12>;
|
||||
+ bias-disable;
|
||||
+ };
|
||||
+
|
||||
+ spi4_sleep: spi4-sleep-state {
|
||||
+ pins = "gpio16", "gpio17", "gpio19";
|
||||
+ function = "gpio";
|
||||
+ drive-strength = <2>;
|
||||
+ bias-pull-down;
|
||||
+ };
|
||||
+ spi5_sleep: spi5-sleep-state {
|
||||
+ pins = "gpio16", "gpio17", "gpio19";
|
||||
+ function = "gpio";
|
||||
+ drive-strength = <2>;
|
||||
+ bias-pull-down;
|
||||
+ };
|
||||
+
|
||||
+ usb_id_default: usb-id-default-state {
|
||||
+ pins = "gpio110";
|
||||
+ function = "gpio";
|
||||
+ drive-strength = <8>;
|
||||
+ bias-pull-up;
|
||||
+ };
|
||||
+ usb_id_default: usb-id-default-state {
|
||||
+ pins = "gpio110";
|
||||
+ function = "gpio";
|
||||
+ drive-strength = <8>;
|
||||
+ bias-pull-up;
|
||||
+ };
|
||||
+};
|
||||
@@ -27,4 +27,21 @@ uci commit
|
||||
wifi reload
|
||||
/etc/init.d/network restart
|
||||
|
||||
cat > /etc/rc.button/power << 'EOF'
|
||||
#!/bin/sh
|
||||
|
||||
SECONDS=3
|
||||
|
||||
[ "${ACTION}" = "pressed" ] && exit ${SECONDS}
|
||||
|
||||
[ "${ACTION}" = "timeout" ] && {
|
||||
logger "Power held for ${SECONDS}: Shutting down system"
|
||||
poweroff
|
||||
exit 0
|
||||
}
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x /etc/rc.button/power
|
||||
|
||||
exit 0
|
||||
@@ -23,9 +23,13 @@ define KernelPackage/fbtft-gc9107
|
||||
CONFIG_BACKLIGHT_PWM=y \
|
||||
CONFIG_FONTS=y \
|
||||
CONFIG_FONT_8x16=y \
|
||||
CONFIG_FRAMEBUFFER_CONSOLE=y \
|
||||
CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=n \
|
||||
CONFIG_VT_HW_CONSOLE_BINDING=y \
|
||||
CONFIG_VT_CONSOLE_SLEEP=y \
|
||||
CONFIG_STAGING=y \
|
||||
CONFIG_FB_TFT=m \
|
||||
CONFIG_FB_TFT_GC9107=m
|
||||
CONFIG_FB_TFT=y \
|
||||
CONFIG_FB_TFT_GC9107=y
|
||||
FILES:= \
|
||||
$(LINUX_DIR)/drivers/staging/fbtft/fbtft.ko \
|
||||
$(LINUX_DIR)/drivers/staging/fbtft/fb_gc9107.ko
|
||||
@@ -33,10 +37,20 @@ define KernelPackage/fbtft-gc9107
|
||||
endef
|
||||
|
||||
define KernelPackage/fbtft-gc9107/description
|
||||
GC9107 framebuffer driver for MF68E device.
|
||||
GC9107 framebuffer driver for MF68E device with boot logo support.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define KernelPackage/fbtft-gc9107/install
|
||||
$(INSTALL_DIR) $(1)/etc
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_DIR) $(1)/lib/preinit
|
||||
$(INSTALL_DATA) ./files/boot_logo.fb $(1)/etc/boot_logo.fb
|
||||
$(INSTALL_DATA) ./files/poweroff_logo.fb $(1)/etc/poweroff_logo.fb
|
||||
$(INSTALL_BIN) ./files/05_display_logo $(1)/lib/preinit/05_display_logo
|
||||
$(INSTALL_BIN) ./files/01_poweroff_logo $(1)/etc/init.d/01_poweroff_logo
|
||||
endef
|
||||
|
||||
$(eval $(call KernelPackage,fbtft-gc9107))
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=01
|
||||
|
||||
start() {
|
||||
return 0
|
||||
}
|
||||
|
||||
stop() {
|
||||
cat /etc/poweroff_logo.fb > /dev/fb0 2>/dev/null
|
||||
sleep 2
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
show_logo() {
|
||||
(
|
||||
local BACKLIGHT="/sys/class/backlight/backlight"
|
||||
local i=0
|
||||
|
||||
while [ ! -c /dev/fb0 ] && [ $i -lt 100 ]; do
|
||||
usleep 100000 # 100ms = 0.1s
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [ -c /dev/fb0 ] && [ -f /etc/boot_logo.fb ]; then
|
||||
cat /etc/boot_logo.fb > /dev/fb0 2>/dev/null
|
||||
fi
|
||||
|
||||
i=0
|
||||
while [ ! -d "$BACKLIGHT" ] && [ $i -lt 50 ]; do
|
||||
usleep 100000 # 100ms = 0.1s
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [ -d "$BACKLIGHT" ]; then
|
||||
echo 0 > $BACKLIGHT/bl_power 2>/dev/null
|
||||
echo 1 > $BACKLIGHT/brightness 2>/dev/null
|
||||
fi
|
||||
) &
|
||||
}
|
||||
|
||||
boot_hook_add preinit_main show_logo
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
## Create boot image for framebuffer
|
||||
We are converting the image using `ffmpeg`. Ensure you have it installed.
|
||||
```
|
||||
ffmpeg -i <image> -vf "scale=128:128" -pix_fmt rgb565le -f rawvideo <output>.fb
|
||||
```
|
||||
@@ -1,35 +0,0 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=power-button-daemon
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Power button monitor daemon
|
||||
DEPENDS:=+evtest
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Monitors power button and triggers poweroff on long press (3s).
|
||||
Designed for MSM8916-based devices with PM8916 PON.
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/power_button_monitor.sh $(1)/usr/sbin/power_button_monitor
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/power_button.init $(1)/etc/init.d/power_button
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/sbin/power_button_monitor
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
DEVICE="/dev/input/event0"
|
||||
LONG_PRESS_TIME=3
|
||||
|
||||
echo "Power button monitor started - Hold ${LONG_PRESS_TIME}s to shutdown"
|
||||
logger -t pwrkey "Daemon started"
|
||||
|
||||
# Monitor button events and check duration in real-time
|
||||
evtest "$DEVICE" 2>/dev/null | while read -r line; do
|
||||
# Button pressed - start background timer
|
||||
if echo "$line" | grep -q "code 116.*value 1"; then
|
||||
PRESS_START=$(date +%s)
|
||||
logger -t pwrkey "Button pressed"
|
||||
|
||||
# Background timer to trigger poweroff
|
||||
(
|
||||
sleep "$LONG_PRESS_TIME"
|
||||
# Check if button is still pressed (no release event yet)
|
||||
echo "Long press ${LONG_PRESS_TIME}s reached - Shutting down!"
|
||||
logger -t pwrkey "Long press ${LONG_PRESS_TIME}s: poweroff triggered"
|
||||
poweroff
|
||||
) &
|
||||
TIMER_PID=$!
|
||||
fi
|
||||
|
||||
# Button released - cancel timer
|
||||
if echo "$line" | grep -q "code 116.*value 0"; then
|
||||
if [ -n "$TIMER_PID" ]; then
|
||||
# Kill the background timer if button released early
|
||||
kill "$TIMER_PID" 2>/dev/null
|
||||
|
||||
CURRENT_TIME=$(date +%s)
|
||||
PRESS_DURATION=$((CURRENT_TIME - PRESS_START))
|
||||
|
||||
if [ "$PRESS_DURATION" -lt "$LONG_PRESS_TIME" ]; then
|
||||
echo "Button released after ${PRESS_DURATION}s - Ignored"
|
||||
logger -t pwrkey "Short press ${PRESS_DURATION}s - Ignored"
|
||||
fi
|
||||
|
||||
TIMER_PID=""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,67 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=router-display
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
PKG_QRGEN_NAME:=QR-Code-generator
|
||||
PKG_QRGEN_VERSION:=v1.8.0
|
||||
PKG_QRGEN_SOURCE:=$(PKG_QRGEN_VERSION).tar.gz
|
||||
PKG_QRGEN_URL:=https://github.com/nayuki/QR-Code-generator/archive/refs/tags
|
||||
PKG_QRGEN_HASH:=skip
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/router-display
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Framebuffer router status display
|
||||
DEPENDS:=+libfreetype +kmod-button-hotplug +dejavu-fonts-ttf-DejaVuSans-Bold +dejavu-fonts-ttf-DejaVuSans
|
||||
endef
|
||||
|
||||
define Package/router-display/description
|
||||
Display router status on RGB565 framebuffer
|
||||
endef
|
||||
|
||||
define Download/qrcodegen
|
||||
URL:=$(PKG_QRGEN_URL)
|
||||
FILE:=qrcodegen-$(PKG_QRGEN_VERSION).tar.gz
|
||||
URL_FILE:=$(PKG_QRGEN_VERSION).tar.gz
|
||||
HASH:=$(PKG_QRGEN_HASH)
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
mkdir -p $(PKG_BUILD_DIR)
|
||||
$(CP) ./src/main.c $(PKG_BUILD_DIR)/
|
||||
$(eval $(call Download,qrcodegen))
|
||||
tar -xzf $(DL_DIR)/qrcodegen-$(PKG_QRGEN_VERSION).tar.gz -C $(PKG_BUILD_DIR)
|
||||
$(CP) $(PKG_BUILD_DIR)/$(PKG_QRGEN_NAME)-*/c/qrcodegen.c $(PKG_BUILD_DIR)/
|
||||
$(CP) $(PKG_BUILD_DIR)/$(PKG_QRGEN_NAME)-*/c/qrcodegen.h $(PKG_BUILD_DIR)/
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(TARGET_CC) $(TARGET_CFLAGS) -O3 -ffast-math \
|
||||
$(TARGET_CPPFLAGS) \
|
||||
-I$(STAGING_DIR)/usr/include/freetype2 \
|
||||
$(TARGET_LDFLAGS) \
|
||||
-o $(PKG_BUILD_DIR)/router-display \
|
||||
$(PKG_BUILD_DIR)/main.c \
|
||||
$(PKG_BUILD_DIR)/qrcodegen.c \
|
||||
-lfreetype -lm
|
||||
endef
|
||||
|
||||
define Package/router-display/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/router-display $(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/update-display.sh $(1)/usr/sbin/update-display
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/button
|
||||
$(INSTALL_BIN) ./files/hotplugs/20_power_dimm $(1)/etc/hotplug.d/button/20_power_dimm
|
||||
$(INSTALL_BIN) ./files/hotplugs/30_power_update $(1)/etc/hotplug.d/button/30_power_update
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,router-display))
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/bin/sh
|
||||
# /etc/hotplug.d/button/20_power_dimm
|
||||
|
||||
# Only process power button events
|
||||
[ "$BUTTON" = "power" ] || exit 0
|
||||
|
||||
# Configuration variables
|
||||
TIMEOUT_DIM=5
|
||||
TIMEOUT_OFF=3
|
||||
BACKLIGHT_PATH="/sys/class/backlight/backlight"
|
||||
BRIGHTNESS_DIM_DIVISOR=8
|
||||
BRIGHTNESS_OFF=1
|
||||
|
||||
# Calculated variables
|
||||
MAX_BRIGHTNESS=$(cat $BACKLIGHT_PATH/max_brightness 2>/dev/null || echo 1785)
|
||||
BRIGHTNESS_FULL=$MAX_BRIGHTNESS
|
||||
BRIGHTNESS_DIM=$((MAX_BRIGHTNESS / BRIGHTNESS_DIM_DIVISOR))
|
||||
|
||||
# PID file for timer management
|
||||
TIMER_PID_FILE="/tmp/screen_timer.pid"
|
||||
|
||||
maybe_set_lockscreen(){
|
||||
# Check if system is shutting down
|
||||
if pgrep poweroff >/dev/null 2>&1 || \
|
||||
pgrep halt >/dev/null 2>&1 || \
|
||||
pgrep reboot >/dev/null 2>&1; then
|
||||
logger -t display "System shutting down - skipping lockscreen"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Show lockscreen if brightness is OFF
|
||||
[ -n "$1" ] && [ "$1" = "$BRIGHTNESS_OFF" ] && [ -f /etc/boot_logo.fb ] && \
|
||||
cat /etc/boot_logo.fb > /dev/fb0 2>/dev/null
|
||||
}
|
||||
|
||||
# Set brightness level
|
||||
set_brightness() {
|
||||
echo "$1" > "$BACKLIGHT_PATH/brightness"
|
||||
|
||||
# Check if we should show lockscreen
|
||||
maybe_set_lockscreen "$1"
|
||||
}
|
||||
|
||||
# Function to start a timer
|
||||
# Parameters: $1=timeout (seconds), $2=brightness level, $3=log message
|
||||
start_timer() {
|
||||
local timeout=$1
|
||||
local brightness=$2
|
||||
local log_msg=$3
|
||||
|
||||
(
|
||||
sleep "$timeout"
|
||||
logger -t display "$log_msg"
|
||||
set_brightness "$brightness"
|
||||
) >/dev/null 2>&1 </dev/null &
|
||||
|
||||
echo $!
|
||||
}
|
||||
|
||||
# Cancel all active timers
|
||||
cancel_timers() {
|
||||
if [ -f "$TIMER_PID_FILE" ]; then
|
||||
while read pid; do
|
||||
kill "$pid" 2>/dev/null
|
||||
done < "$TIMER_PID_FILE"
|
||||
rm -f "$TIMER_PID_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Only act on button release
|
||||
[ "${ACTION}" = "released" ] && {
|
||||
logger -t display "Button event detected"
|
||||
|
||||
# Cancel previous timers if any
|
||||
cancel_timers
|
||||
|
||||
# Set full brightness immediately
|
||||
logger -t display "Backlight: FULL"
|
||||
set_brightness $BRIGHTNESS_FULL
|
||||
|
||||
# Start timer 1: DIM after TIMEOUT_DIM seconds
|
||||
pid1=$(start_timer $TIMEOUT_DIM $BRIGHTNESS_DIM "Backlight: DIM")
|
||||
echo "$pid1" >> "$TIMER_PID_FILE"
|
||||
|
||||
# Start timer 2: OFF after (TIMEOUT_DIM + TIMEOUT_OFF) seconds
|
||||
TOTAL_TIMEOUT=$((TIMEOUT_DIM + TIMEOUT_OFF))
|
||||
pid2=$(start_timer $TOTAL_TIMEOUT $BRIGHTNESS_OFF "Backlight: OFF")
|
||||
echo "$pid2" >> "$TIMER_PID_FILE"
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
# /etc/hotplug.d/button/30_power_update
|
||||
|
||||
# Only process power button events
|
||||
[ "$BUTTON" = "power" ] || exit 0
|
||||
|
||||
# Execute update-display on button release
|
||||
[ "${ACTION}" = "released" ] && {
|
||||
# Check if system is shutting down
|
||||
if pgrep poweroff >/dev/null 2>&1 || \
|
||||
pgrep halt >/dev/null 2>&1 || \
|
||||
pgrep reboot >/dev/null 2>&1; then
|
||||
logger -t display "System shutting down - skipping update-display"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
/usr/sbin/update-display
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/sh
|
||||
# /usr/sbin/update-display
|
||||
# Optimized display update script - caches operator name (requires reboot to change SIM)
|
||||
|
||||
# === BATTERY ===
|
||||
BMS_PATH="/sys/class/power_supply/pm8916-bms-vm"
|
||||
|
||||
VOLTAGE=$(cat "$BMS_PATH/voltage_now" 2>/dev/null)
|
||||
VMAX=$(cat "$BMS_PATH/voltage_max_design" 2>/dev/null)
|
||||
VMIN=$(cat "$BMS_PATH/voltage_min_design" 2>/dev/null)
|
||||
|
||||
if [ -n "$VOLTAGE" ] && [ -n "$VMAX" ] && [ -n "$VMIN" ]; then
|
||||
BATTERY=$(awk "BEGIN {pct = (($VOLTAGE - $VMIN) / ($VMAX - $VMIN)) * 100; if(pct < 0) pct=0; if(pct > 100) pct=100; printf \"%.0f\", pct}")
|
||||
else
|
||||
BATTERY=100
|
||||
fi
|
||||
|
||||
# === WiFi AP ===
|
||||
SHOW_QR=0
|
||||
if ip link show phy0-ap0 2>/dev/null | grep -q "state UP"; then
|
||||
SHOW_QR=1
|
||||
fi
|
||||
|
||||
# === MODEM (OPTIMIZED - single mmcli call + operator cache) ===
|
||||
OPERATOR_CACHE="/tmp/modem_operator.cache"
|
||||
MODEM_IDX=$(mmcli -L 2>/dev/null | grep -oE 'Modem/[0-9]+' | head -1 | cut -d'/' -f2)
|
||||
|
||||
if [ -n "$MODEM_IDX" ]; then
|
||||
# Single mmcli call - cache all modem data
|
||||
MODEM_DATA=$(mmcli -m "$MODEM_IDX" -K 2>/dev/null)
|
||||
|
||||
# Try to get operator from cache first (SIM doesn't change without reboot)
|
||||
if [ -f "$OPERATOR_CACHE" ]; then
|
||||
OPERATOR=$(cat "$OPERATOR_CACHE")
|
||||
else
|
||||
# Cache miss - fetch operator from SIM
|
||||
SIM_IDX=$(echo "$MODEM_DATA" | grep 'modem.generic.sim' | awk -F': ' '{print $2}' | grep -oE '[0-9]+$')
|
||||
|
||||
if [ -n "$SIM_IDX" ]; then
|
||||
OPERATOR=$(mmcli -i "$SIM_IDX" -K 2>/dev/null | grep 'sim.properties.operator-name' | awk -F': ' '{print $2}')
|
||||
fi
|
||||
|
||||
# Fallback: operator from network (use cached modem data)
|
||||
if [ -z "$OPERATOR" ]; then
|
||||
OPERATOR=$(echo "$MODEM_DATA" | grep 'modem.3gpp.operator-name' | awk -F': ' '{print $2}')
|
||||
fi
|
||||
|
||||
# Cache operator for future calls
|
||||
if [ -n "$OPERATOR" ]; then
|
||||
echo "$OPERATOR" > "$OPERATOR_CACHE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Access technology (use cached modem data)
|
||||
ACCESS_TECH=$(echo "$MODEM_DATA" | grep 'modem.generic.access-technologies' | awk -F': ' '{print $2}')
|
||||
|
||||
# Determine network type only if connected
|
||||
if [ -n "$ACCESS_TECH" ] && [ "$ACCESS_TECH" != "unknown" ]; then
|
||||
case "$ACCESS_TECH" in
|
||||
*lte*) NETWORK="4G" ;;
|
||||
*umts*|*hspa*|*hsupa*|*hsdpa*) NETWORK="3G" ;;
|
||||
*edge*|*gprs*|*gsm*) NETWORK="2G" ;;
|
||||
*) NETWORK="$ACCESS_TECH" ;;
|
||||
esac
|
||||
else
|
||||
# No connection: show operator but no network type
|
||||
NETWORK=""
|
||||
fi
|
||||
else
|
||||
# No modem
|
||||
OPERATOR=""
|
||||
NETWORK=""
|
||||
fi
|
||||
|
||||
# === WiFi Info ===
|
||||
SSID=$(uci get wireless.@wifi-iface[0].ssid 2>/dev/null || echo "WiFi")
|
||||
PASSWORD=$(uci get wireless.@wifi-iface[0].key 2>/dev/null || echo "")
|
||||
HOSTNAME=$(uci get system.@system[0].hostname 2>/dev/null || echo "Router")
|
||||
|
||||
# === Update Display ===
|
||||
if [ "$SHOW_QR" -eq 1 ]; then
|
||||
/usr/bin/router-display -q -b "$BATTERY" -n "$OPERATOR" -t "$NETWORK" -s "$SSID" -p "$PASSWORD" -h "$HOSTNAME" > /dev/fb0
|
||||
else
|
||||
/usr/bin/router-display -b "$BATTERY" -n "$OPERATOR" -t "$NETWORK" -s "$SSID" -p "$PASSWORD" -h "$HOSTNAME" > /dev/fb0
|
||||
fi
|
||||
@@ -0,0 +1,292 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include "qrcodegen.h"
|
||||
|
||||
#define WIDTH 128
|
||||
#define HEIGHT 128
|
||||
#define RGB565(r,g,b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
|
||||
|
||||
#define COLOR_WHITE RGB565(255, 255, 255)
|
||||
#define COLOR_BLACK RGB565(0, 0, 0)
|
||||
|
||||
#define QR_SIZE 108
|
||||
#define QR_TOP_MARGIN 6
|
||||
#define TEXT_HEIGHT 13
|
||||
#define MIN_MODULE_SIZE 3 // Minimum pixels per QR module for readability
|
||||
|
||||
typedef struct {
|
||||
uint16_t data[WIDTH * HEIGHT];
|
||||
} Framebuffer;
|
||||
|
||||
typedef struct {
|
||||
int battery;
|
||||
char operator[32];
|
||||
char network_type[8];
|
||||
char ssid[64];
|
||||
char password[64];
|
||||
char hostname[32];
|
||||
int show_qr;
|
||||
int uppercase;
|
||||
} DisplayConfig;
|
||||
|
||||
void fb_init(Framebuffer *fb) {
|
||||
memset(fb->data, 0, sizeof(fb->data));
|
||||
}
|
||||
|
||||
void fb_put_pixel(Framebuffer *fb, int x, int y, uint16_t color) {
|
||||
if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {
|
||||
fb->data[y * WIDTH + x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void fb_blend_pixel(Framebuffer *fb, int x, int y, unsigned char gray) {
|
||||
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
|
||||
if (gray == 0) return;
|
||||
|
||||
uint16_t bg_color = fb->data[y * WIDTH + x];
|
||||
|
||||
uint8_t bg_r = (bg_color >> 11) << 3;
|
||||
uint8_t bg_g = ((bg_color >> 5) & 0x3F) << 2;
|
||||
uint8_t bg_b = (bg_color & 0x1F) << 3;
|
||||
|
||||
float alpha = gray / 255.0f;
|
||||
uint8_t r = (uint8_t)(255 * alpha + bg_r * (1.0f - alpha));
|
||||
uint8_t g = (uint8_t)(255 * alpha + bg_g * (1.0f - alpha));
|
||||
uint8_t b = (uint8_t)(255 * alpha + bg_b * (1.0f - alpha));
|
||||
|
||||
fb->data[y * WIDTH + x] = RGB565(r, g, b);
|
||||
}
|
||||
|
||||
void fb_draw_rect(Framebuffer *fb, int x, int y, int w, int h, uint16_t color) {
|
||||
for (int j = 0; j < h; j++) {
|
||||
for (int i = 0; i < w; i++) {
|
||||
fb_put_pixel(fb, x + i, y + j, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fb_draw_text(Framebuffer *fb, FT_Face face, const char *text, int x, int y, int size) {
|
||||
int pen_x = x;
|
||||
|
||||
for (const char *p = text; *p; p++) {
|
||||
FT_Set_Pixel_Sizes(face, 0, size);
|
||||
|
||||
if (FT_Load_Char(face, *p, FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
FT_Bitmap bitmap = slot->bitmap;
|
||||
|
||||
for (unsigned int row = 0; row < bitmap.rows; row++) {
|
||||
for (unsigned int col = 0; col < bitmap.width; col++) {
|
||||
int px = pen_x + slot->bitmap_left + col;
|
||||
int py = y - slot->bitmap_top + row;
|
||||
|
||||
unsigned char gray = bitmap.buffer[row * bitmap.pitch + col];
|
||||
|
||||
if (gray > 0) {
|
||||
fb_blend_pixel(fb, px, py, gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pen_x += slot->advance.x >> 6;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_get_text_width(FT_Face face, const char *text, int size) {
|
||||
int width = 0;
|
||||
FT_Set_Pixel_Sizes(face, 0, size);
|
||||
|
||||
for (const char *p = text; *p; p++) {
|
||||
if (FT_Load_Char(face, *p, FT_LOAD_RENDER)) {
|
||||
continue;
|
||||
}
|
||||
width += face->glyph->advance.x >> 6;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
int fb_draw_qr(Framebuffer *fb, const char *text, int x, int y, int size) {
|
||||
uint8_t qr_data[qrcodegen_BUFFER_LEN_MAX];
|
||||
uint8_t temp_buffer[qrcodegen_BUFFER_LEN_MAX];
|
||||
|
||||
bool ok = qrcodegen_encodeText(text, temp_buffer, qr_data,
|
||||
qrcodegen_Ecc_LOW,
|
||||
qrcodegen_VERSION_MIN,
|
||||
qrcodegen_VERSION_MAX,
|
||||
qrcodegen_Mask_AUTO, true);
|
||||
|
||||
if (!ok) {
|
||||
fprintf(stderr, "QR generation failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qr_modules = qrcodegen_getSize(qr_data);
|
||||
int module_size = size / qr_modules;
|
||||
|
||||
// Check if QR modules are too small (minimum pixels per module)
|
||||
if (module_size < MIN_MODULE_SIZE) {
|
||||
fprintf(stderr, "QR too large for display area (needs %d modules, only fits %dpx modules)\n",
|
||||
qr_modules, module_size);
|
||||
return 0; // Don't draw, QR would be unreadable
|
||||
}
|
||||
|
||||
// Calculate actual QR size and center horizontally, align top vertically
|
||||
int actual_qr_size = qr_modules * module_size;
|
||||
int offset_x = (size - actual_qr_size) / 2; // Center horizontally
|
||||
int offset_y = 0; // Top aligned
|
||||
|
||||
for (int row = 0; row < qr_modules; row++) {
|
||||
for (int col = 0; col < qr_modules; col++) {
|
||||
if (qrcodegen_getModule(qr_data, col, row)) {
|
||||
fb_draw_rect(fb,
|
||||
x + offset_x + col * module_size,
|
||||
y + offset_y + row * module_size,
|
||||
module_size, module_size,
|
||||
COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // QR drawn successfully
|
||||
}
|
||||
|
||||
void str_to_upper(char *str) {
|
||||
for (char *p = str; *p; p++) {
|
||||
*p = toupper((unsigned char)*p);
|
||||
}
|
||||
}
|
||||
|
||||
void generate_display(Framebuffer *fb, DisplayConfig *cfg, FT_Face face) {
|
||||
fb_init(fb);
|
||||
|
||||
char operator_text[32], network_text[8], hostname_text[32];
|
||||
strncpy(operator_text, cfg->operator, 31);
|
||||
strncpy(network_text, cfg->network_type, 7);
|
||||
strncpy(hostname_text, cfg->hostname, 31);
|
||||
|
||||
if (cfg->uppercase) {
|
||||
str_to_upper(operator_text);
|
||||
str_to_upper(network_text);
|
||||
str_to_upper(hostname_text);
|
||||
}
|
||||
|
||||
// Calculate QR position: centered horizontally, at top with margin
|
||||
int qr_x = (WIDTH - QR_SIZE) / 2;
|
||||
int qr_y = QR_TOP_MARGIN;
|
||||
|
||||
if (cfg->show_qr) {
|
||||
char wifi_qr[256];
|
||||
snprintf(wifi_qr, sizeof(wifi_qr),
|
||||
"WIFI:T:WPA;S:%s;P:%s;;", cfg->ssid, cfg->password);
|
||||
|
||||
// Try to draw QR, if it doesn't fit (returns 0), screen stays black
|
||||
fb_draw_qr(fb, wifi_qr, qr_x, qr_y, QR_SIZE);
|
||||
}
|
||||
// If no QR or QR too large: stay black (fb_init already cleared to black)
|
||||
|
||||
int fontsz = 12;
|
||||
int line1_y = HEIGHT - fontsz - 3;
|
||||
int line2_y = HEIGHT - 3;
|
||||
|
||||
fb_draw_text(fb, face, operator_text, 2, line1_y, fontsz);
|
||||
|
||||
int net_width = fb_get_text_width(face, network_text, fontsz);
|
||||
fb_draw_text(fb, face, network_text, WIDTH - net_width - 2, line1_y, fontsz);
|
||||
|
||||
fb_draw_text(fb, face, hostname_text, 2, line2_y, fontsz);
|
||||
|
||||
char battery_text[8];
|
||||
snprintf(battery_text, sizeof(battery_text), "%d%%", cfg->battery);
|
||||
int battery_width = fb_get_text_width(face, battery_text, fontsz);
|
||||
fb_draw_text(fb, face, battery_text, WIDTH - battery_width - 2, line2_y, fontsz);
|
||||
}
|
||||
|
||||
void print_usage(const char *prog) {
|
||||
fprintf(stderr, "Usage: %s [OPTIONS]\n", prog);
|
||||
fprintf(stderr, " -b NUM Battery (0-100)\n");
|
||||
fprintf(stderr, " -n NAME Operator name\n");
|
||||
fprintf(stderr, " -t TYPE Network type (4G, LTE)\n");
|
||||
fprintf(stderr, " -s SSID WiFi SSID\n");
|
||||
fprintf(stderr, " -p PASS WiFi password\n");
|
||||
fprintf(stderr, " -h HOST Hostname\n");
|
||||
fprintf(stderr, " -q Show QR code (default: show logo)\n");
|
||||
fprintf(stderr, " -c Convert text to UPPERCASE\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
DisplayConfig cfg = {
|
||||
.battery = 100,
|
||||
.operator = "Unknown",
|
||||
.network_type = "4G",
|
||||
.ssid = "WiFi",
|
||||
.password = "password",
|
||||
.hostname = "Router",
|
||||
.show_qr = 0,
|
||||
.uppercase = 0
|
||||
};
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "b:n:t:s:p:h:qc")) != -1) {
|
||||
switch (opt) {
|
||||
case 'b': cfg.battery = atoi(optarg); break;
|
||||
case 'n': strncpy(cfg.operator, optarg, 31); break;
|
||||
case 't': strncpy(cfg.network_type, optarg, 7); break;
|
||||
case 's': strncpy(cfg.ssid, optarg, 63); break;
|
||||
case 'p': strncpy(cfg.password, optarg, 63); break;
|
||||
case 'h': strncpy(cfg.hostname, optarg, 31); break;
|
||||
case 'q': cfg.show_qr = 1; break;
|
||||
case 'c': cfg.uppercase = 1; break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
FT_Library ft;
|
||||
if (FT_Init_FreeType(&ft)) {
|
||||
fprintf(stderr, "FreeType init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FT_Face face;
|
||||
const char *fonts[] = {
|
||||
"/usr/share/fonts/ttf-dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/ttf-dejavu/DejaVuSans-Bold.ttf",
|
||||
NULL
|
||||
};
|
||||
|
||||
int found = 0;
|
||||
for (int i = 0; fonts[i]; i++) {
|
||||
if (FT_New_Face(ft, fonts[i], 0, &face) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
fprintf(stderr, "No font found\n");
|
||||
FT_Done_FreeType(ft);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Framebuffer fb;
|
||||
generate_display(&fb, &cfg, face);
|
||||
|
||||
fwrite(fb.data, sizeof(uint16_t), WIDTH * HEIGHT, stdout);
|
||||
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(ft);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user