diff --git a/config/minimal.toml b/config/minimal.toml index 368e67bb..faa3cf47 100644 --- a/config/minimal.toml +++ b/config/minimal.toml @@ -40,7 +40,6 @@ requires_weak = ["29_activate_console.service"] cmd = "getty" args = ["2"] type = "oneshot_async" -respawn = true """ [[files]] @@ -54,5 +53,4 @@ requires_weak = ["29_activate_console.service"] cmd = "getty" args = ["/scheme/debug/no-preserve", "-J"] type = "oneshot_async" -respawn = true """ diff --git a/config/redbear-device-services.toml b/config/redbear-device-services.toml index 98d546df..63d9271f 100644 --- a/config/redbear-device-services.toml +++ b/config/redbear-device-services.toml @@ -7,6 +7,34 @@ redbear-quirks = {} pciids = {} fatd = {} +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" [[files]] path = "/usr/lib/init.d/12_boot-late.target" data = """ @@ -17,101 +45,544 @@ requires_weak = [ ] """ +# Firmware fallback chain configs [[files]] -path = "/lib/firmware" +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d" data = "" directory = true mode = 0o755 +# Firmware fallback chain configs [[files]] -path = "/usr/bin/usbctl" -data = "/usr/lib/drivers/usbctl" -symlink = true +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" [[files]] -path = "/lib/pcid.d/intel_gpu.toml" +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" data = """ -# PCID configuration for Intel GPU auto-detection -[[drivers]] -name = "Intel GPU (VGA compatible)" -class = 0x03 +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d/00-storage.toml" +data = """ +[[driver]] +name = "nvmed" +description = "NVMe storage driver" +priority = 100 +command = ["/usr/lib/drivers/nvmed"] + +[[driver.match]] +class = 1 +subclass = 8 + +[[driver]] +name = "ahcid" +description = "AHCI SATA driver" +priority = 100 +command = ["/usr/lib/drivers/ahcid"] + +[[driver.match]] +class = 1 +subclass = 6 + +[[driver]] +name = "ided" +description = "PATA IDE driver" +priority = 100 +command = ["/usr/lib/drivers/ided"] + +[[driver.match]] +class = 1 +subclass = 1 + +[[driver]] +name = "virtio-blkd" +description = "VirtIO block device driver" +priority = 100 +command = ["/usr/lib/drivers/virtio-blkd"] + +[[driver.match]] +vendor = 0x1AF4 +device = 0x1001 +class = 1 +subclass = 0 +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d/10-network.toml" +data = """ +[[driver]] +name = "e1000d" +description = "Intel Gigabit Ethernet" +priority = 50 +command = ["/usr/lib/drivers/e1000d"] + +[[driver.match]] vendor = 0x8086 -subclass = 0x00 -command = ["redox-drm"] +class = 2 -[[drivers]] -name = "Intel GPU (3D controller)" -class = 0x03 +[[driver]] +name = "rtl8168d" +description = "Realtek 8168/8125 Ethernet" +priority = 50 +command = ["/usr/lib/drivers/rtl8168d"] + +[[driver.match]] +vendor = 0x10EC +class = 2 + +[[driver]] +name = "rtl8139d" +description = "Realtek 8139 Ethernet" +priority = 50 +command = ["/usr/lib/drivers/rtl8139d"] + +[[driver.match]] +vendor = 0x10EC +device = 0x8139 + +[[driver]] +name = "ixgbed" +description = "Intel 10 Gigabit Ethernet" +priority = 50 +command = ["/usr/lib/drivers/ixgbed"] + +[[driver.match]] vendor = 0x8086 -subclass = 0x02 -command = ["redox-drm"] +class = 2 +subclass = 0 + +[[driver]] +name = "virtio-netd" +description = "VirtIO network driver" +priority = 50 +command = ["/usr/lib/drivers/virtio-netd"] + +[[driver.match]] +vendor = 0x1AF4 +class = 2 +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] """ [[files]] -path = "/lib/pcid.d/amd_gpu.toml" +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" data = """ -# PCID configuration for AMD GPU auto-detection -[[drivers]] -name = "AMD GPU (VGA compatible)" +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d/20-usb.toml" +data = """ +[[driver]] +name = "xhcid" +description = "xHCI USB host controller" +priority = 80 +command = ["/usr/lib/drivers/xhcid"] + +[[driver.match]] +class = 0x0C +subclass = 0x03 +prog_if = 0x30 + +[[driver]] +name = "ehcid" +description = "EHCI USB 2.0 host controller" +priority = 80 +command = ["/usr/lib/drivers/ehcid"] + +# EHCI now owns a simple /scheme/usb controller surface for per-port status and +# control-transfer pass-through while the wider USB stack continues converging. + +[[driver.match]] +class = 0x0C +subclass = 0x03 +prog_if = 0x20 + +[[driver]] +name = "ohcid" +description = "OHCI USB 1.1 host controller" +priority = 80 +command = ["/usr/lib/drivers/ohcid"] + +[[driver.match]] +class = 0x0C +subclass = 0x03 +prog_if = 0x10 + +[[driver]] +name = "uhcid" +description = "UHCI USB 1.1 host controller (Intel)" +priority = 80 +command = ["/usr/lib/drivers/uhcid"] + +[[driver.match]] +class = 0x0C +subclass = 0x03 +prog_if = 0x00 +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d/30-graphics.toml" +data = """ +[[driver]] +name = "vesad" +description = "VESA BIOS display driver" +priority = 60 +command = ["/usr/lib/drivers/vesad"] + +[[driver.match]] class = 0x03 -vendor = 0x1002 -subclass = 0x00 -command = ["redox-drm"] -[[drivers]] -name = "AMD GPU (3D controller)" +[[driver]] +name = "redox-drm" +description = "DRM/KMS display driver (AMD + Intel)" +priority = 60 +command = ["/usr/bin/redox-drm"] + +[[driver.match]] class = 0x03 -vendor = 0x1002 -subclass = 0x02 -command = ["redox-drm"] +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] """ [[files]] -path = "/usr/lib/init.d/05_firmware-loader.service" +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" data = """ -[unit] -description = "Firmware loading scheme" -requires_weak = [ - "12_boot-late.target", - "00_pcid-spawner.service", -] - -[service] -cmd = "firmware-loader" -type = { scheme = "firmware" } +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] """ [[files]] -path = "/usr/lib/init.d/11_udev.service" +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" data = """ -[unit] -description = "udev compatibility shim" -requires_weak = [ - "12_boot-late.target", - "00_pcid-spawner.service", -] +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d/40-input.toml" +data = """ +[[driver]] +name = "ps2d" +description = "PS/2 keyboard and mouse driver" +priority = 90 +command = ["/usr/lib/drivers/ps2d"] +""" -[service] -cmd = "udev-shim" -type = { scheme = "udev" } +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] """ [[files]] -path = "/usr/lib/init.d/11_wifictl.service" +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d/50-audio.toml" +data = """ +[[driver]] +name = "ihdad" +description = "Intel HD Audio driver" +priority = 40 +command = ["/usr/lib/drivers/ihdad"] + +[[driver.match]] +vendor = 0x8086 +class = 0x04 + +[[driver]] +name = "ac97d" +description = "AC'97 audio codec driver" +priority = 40 +command = ["/usr/lib/drivers/ac97d"] + +[[driver.match]] +class = 0x04 +subclass = 0x01 +""" + +[[files]] +path = "/lib/drivers.d/70-usb-class.toml" +data = """ +[[driver]] +name = "redbear-acmd" +description = "USB CDC ACM serial driver" +priority = 70 +command = ["/usr/bin/redbear-acmd"] + +[[driver]] +name = "redbear-ecmd" +description = "USB CDC ECM/NCM ethernet driver" +priority = 70 +command = ["/usr/bin/redbear-ecmd"] + +[[driver]] +name = "redbear-usbaudiod" +description = "USB Audio Class driver" +priority = 70 +command = ["/usr/bin/redbear-usbaudiod"] +""" + +# Profiles that include this fragment should start `driver-manager` instead of +# `pcid-spawner`; the manager performs the PCI bind/channel handoff itself. +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/usr/lib/init.d/00_driver-manager.service" data = """ [unit] -description = "Wi-Fi control daemon" +description = "PCI driver spawner" requires_weak = [ - "12_boot-late.target", - "00_pcid-spawner.service", - "05_firmware-loader.service", + "00_base.target", ] [service] -cmd = "redbear-wifictl" -type = { scheme = "wifictl" } +cmd = "pcid-spawner" +type = "oneshot" """ +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/lib/drivers.d" +data = "" +directory = true +mode = 0o755 + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" [[files]] path = "/usr/lib/init.d/10_evdevd.service" data = """ @@ -126,3 +597,233 @@ requires_weak = [ cmd = "evdevd" type = "oneshot_async" """ + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/etc/firmware-fallbacks.d" +data = "" +directory = true +mode = 0o755 + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/usr/lib/init.d/15_cpufreqd.service" +data = """ +[unit] +description = "CPU frequency scaling daemon" +requires_weak = ["12_boot-late.target"] + +[service] +cmd = "/usr/bin/cpufreqd" +type = "oneshot_async" +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/usr/lib/init.d/15_thermald.service" +data = """ +[unit] +description = "Thermal management daemon" +requires_weak = ["12_boot-late.target"] + +[service] +cmd = "/usr/bin/thermald" +type = "oneshot_async" +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/usr/lib/init.d/15_hwrngd.service" +data = """ +[unit] +description = "Hardware RNG entropy daemon" +requires_weak = ["00_base.target"] + +[service] +cmd = "/usr/bin/hwrngd" +type = "oneshot_async" +""" + +# Firmware fallback chain configs +[[files]] +path = "/etc/firmware-fallbacks.d/00-amdgpu.toml" +data = """ +[[fallback]] +pattern = "amdgpu/dmcub_dcn31.bin" +chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"] + +[[fallback]] +pattern = "amdgpu/dmcub_dcn30.bin" +chain = ["amdgpu/dmcub_dcn20.bin"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml" +data = """ +[[fallback]] +pattern = "iwlwifi-bz-b0-gf-a0-92.ucode" +chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"] +""" + +[[files]] +path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml" +data = """ +[[fallback]] +pattern = "i915/adlp_dmc_ver2_16.bin" +chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"] +""" +[[files]] +path = "/usr/lib/init.d/13_driver-params.service" +data = """ +[unit] +description = "Driver parameter scheme" +requires_weak = ["00_driver-manager.service"] + +[service] +cmd = "/usr/bin/driver-params" +type = { scheme = "driver-params" } +""" + +[[files]] +path = "/usr/lib/init.d/16_redbear-acmd.service" +data = """ +[unit] +description = "USB CDC ACM serial daemon" +requires_weak = ["12_boot-late.target"] + +[service] +cmd = "/usr/bin/redbear-acmd" +type = "oneshot_async" +""" + +[[files]] +path = "/usr/lib/init.d/16_redbear-ecmd.service" +data = """ +[unit] +description = "USB CDC ECM/NCM ethernet daemon" +requires_weak = ["12_boot-late.target"] + +[service] +cmd = "/usr/bin/redbear-ecmd" +type = "oneshot_async" +""" + +[[files]] +path = "/usr/lib/init.d/16_redbear-usbaudiod.service" +data = """ +[unit] +description = "USB Audio Class daemon" +requires_weak = ["12_boot-late.target"] + +[service] +cmd = "/usr/bin/redbear-usbaudiod" +type = "oneshot_async" +""" diff --git a/config/redbear-legacy-base.toml b/config/redbear-legacy-base.toml index 1c2a2738..225ad88c 100644 --- a/config/redbear-legacy-base.toml +++ b/config/redbear-legacy-base.toml @@ -4,14 +4,13 @@ # base.toml's 00_sudo.service). ipcd and ptyd are started by # 00_ipcd.service and 00_ptyd.service from the base recipe. # 00_drivers / 10_net: no longer overridden — the legacy scripts were removed -# from base.toml. pcid-spawner is started by 00_pcid-spawner.service -# from the base recipe; smolnetd/dhcpd have their own .service files. -# 00_pcid-spawner.service: overridden to oneshot_async. The base recipe uses -# type="oneshot" which blocks init until pcid-spawner exits. On real -# hardware (and QEMU), pcid-spawner can hang waiting for a PCI device -# driver that never responds, blocking the entire rootfs phase including -# getty/login. Using oneshot_async lets init proceed to start console -# services while drivers spawn in the background. +# from base.toml. The retained 00_pcid-spawner.service unit name now +# launches driver-manager so existing init ordering remains stable. +# 00_pcid-spawner.service: compatibility wrapper for driver-manager. The base +# recipe uses type="oneshot" which blocks init until pcid-spawner exits. +# Running driver-manager here with oneshot_async keeps the historic unit +# name for downstream `requires_weak` consumers while moving PCI driver +# spawning to the manager that performs bind/channel handoff. [packages] zsh = {} @@ -44,9 +43,9 @@ type = "oneshot_async" path = "/etc/init.d/00_pcid-spawner.service" data = """ [unit] -description = "PCI driver spawner (non-blocking)" +description = "PCI driver spawner" [service] cmd = "pcid-spawner" -type = "oneshot_async" +type = "oneshot" """ diff --git a/config/redbear-mini.toml b/config/redbear-mini.toml index 4c55eb5b..4a69dd2e 100644 --- a/config/redbear-mini.toml +++ b/config/redbear-mini.toml @@ -9,7 +9,7 @@ # - all non-graphics, non-firmware packages from the full profile # - no linux-firmware payload, no firmware-loader, no GPU/display drivers -include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml"] +include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml", "redbear-device-services.toml"] [general] filesystem_size = 1536 @@ -27,6 +27,13 @@ redbear-release = {} redbear-hwutils = {} redbear-quirks = {} +# Device driver infrastructure (pcid-spawner is the stable driver spawner; +# driver-manager requires driver config migration and is not yet ready) +ehcid = {} +ohcid = {} +uhcid = {} +pcid-spawner = "ignore" + # Redox-native netctl tooling. redbear-netctl = {} redbear-netctl-console = {} @@ -44,8 +51,15 @@ redbear-info = {} # Keep package builder utility in live environment. cub = {} +cpufreqd = {} +thermald = {} +hwrngd = {} +redbear-acmd = {} +redbear-ecmd = {} +redbear-usbaudiod = {} +driver-params = {} -# ── PCI device database (critical for pcid-spawner driver matching) ── +# ── PCI device database (critical for PCI driver matching) ── pciids = {} # ── Filesystem support ── @@ -83,7 +97,7 @@ htop = {} #mc = {} # suppressed: C99 format warning errors in compilation # ── Build / packaging utilities ── -patchelf = {} +# patchelf = {} # requires strtold which is missing in relibc shared-mime-info = {} # VT/getty/login chain: initfs starts inputd + vesad + fbcond in phase 1, diff --git a/local/patches/relibc/P3-signalfd-cbindgen-fix.patch b/local/patches/relibc/P3-signalfd-cbindgen-fix.patch new file mode 100644 index 00000000..e69de29b diff --git a/local/recipes/drivers/ehcid/source/src/main.rs b/local/recipes/drivers/ehcid/source/src/main.rs index 77aab97a..7a4cc4f0 100644 --- a/local/recipes/drivers/ehcid/source/src/main.rs +++ b/local/recipes/drivers/ehcid/source/src/main.rs @@ -112,6 +112,7 @@ enum HandleKind { Status { port: usize }, Descriptor { port: usize }, Control { port: usize }, + Config { port: usize }, } #[derive(Clone, Debug)] @@ -133,6 +134,7 @@ struct EhciController { n_ports: u8, frame_list: DmaBuffer, async_qh: DmaBuffer, + periodic_qh: DmaBuffer, dma_segment: u32, has_64bit: bool, next_address: u8, @@ -140,6 +142,10 @@ struct EhciController { } impl EhciController { + fn find_port_by_address(&self, addr: u8) -> Option { + self.ports.iter().position(|r| r.device.as_ref().map_or(false, |d| d.address == addr)) + } + fn new(device_path: &str, channel_fd: usize) -> Result { info!("EHCI USB 2.0 at {} (fd={})", device_path, channel_fd); @@ -185,12 +191,15 @@ impl EhciController { let async_qh = DmaBuffer::allocate(size_of::(), 64) .map_err(|err| format!("failed to allocate async queue head: {err}"))?; + let periodic_qh = DmaBuffer::allocate(size_of::(), 64) + .map_err(|err| format!("failed to allocate periodic queue head: {err}"))?; let dma_segment = ensure_dma_segment( has_64bit, &[ frame_list.physical_address() as u64, async_qh.physical_address() as u64, + periodic_qh.physical_address() as u64, ], )?; @@ -201,6 +210,7 @@ impl EhciController { n_ports, frame_list, async_qh, + periodic_qh, dma_segment, has_64bit, next_address: 1, @@ -336,12 +346,12 @@ impl EhciController { } } - fn prepare_async_qh(&mut self, device_address: u8, max_packet_size: u16, first_td_phys: u32) { + fn prepare_async_qh(&mut self, device_address: u8, endpoint: u8, max_packet_size: u16, first_td_phys: u32) { let qh_ptr = self.async_qh.as_mut_ptr() as *mut QueueHead; unsafe { let qh = &mut *qh_ptr; qh.horiz_link = qh_link_pointer(self.async_qh.physical_address() as u64); - qh.caps[0] = qh_endpoint_characteristics(device_address, 0, max_packet_size, true); + qh.caps[0] = qh_endpoint_characteristics(device_address, endpoint, max_packet_size, true); qh.caps[1] = qh_endpoint_capabilities(); qh.current_qtd = 0; qh.overlay[0] = first_td_phys & !0x1F; @@ -371,6 +381,30 @@ impl EhciController { } } + fn arm_periodic_qh(&mut self, device_address: u8, max_packet: u16, endpoint: u8, td_phys: u64) { + let fl = self.frame_list.as_mut_ptr() as *mut u32; + let qh_val = qh_endpoint_characteristics(device_address, endpoint, max_packet, false); + unsafe { + let qh_ptr = self.periodic_qh.as_mut_ptr() as *mut QueueHead; + let qh = &mut *qh_ptr; + qh.horiz_link = qh_link_pointer(self.periodic_qh.physical_address() as u64); + qh.caps[0] = qh_val; + qh.caps[1] = qh_endpoint_capabilities(); + qh.current_qtd = 0; + qh.overlay[0] = (td_phys as u32) & !0x1F; + qh.overlay[1] = TD_TERMINATE; + qh.overlay[2] = 0; qh.overlay[3] = 0; + qh.overlay[4] = 0; qh.overlay[5] = 0; + qh.overlay[6] = 0; qh.overlay[7] = 0; + *fl = (self.periodic_qh.physical_address() as u32) | 2; + } + } + + fn disarm_periodic_qh(&mut self) { + let fl = self.frame_list.as_mut_ptr() as *mut u32; + unsafe { *fl = TD_TERMINATE; } + } + fn ensure_controller_running(&mut self) { let status = self.read_op32(USBSTS); let command = self.read_op32(USBCMD); @@ -775,7 +809,7 @@ impl EhciController { ) .ok_or(UsbError::IoError)?; - self.prepare_async_qh(device_address, max_packet_size, first_td_phys); + self.prepare_async_qh(device_address, 0, max_packet_size, first_td_phys); self.clear_interrupt_status(); fence(Ordering::SeqCst); @@ -958,21 +992,72 @@ impl UsbHostController for EhciController { fn bulk_transfer( &mut self, - _device_address: u8, - _endpoint: u8, - _data: &mut [u8], - _direction: TransferDirection, + device_address: u8, + endpoint: u8, + data: &mut [u8], + direction: TransferDirection, ) -> Result { - Err(UsbError::Unsupported) + let port = self.find_port_by_address(device_address).ok_or(UsbError::NoDevice)?; + let max_packet = self.ports[port].device.as_ref().map(|d| d.max_packet_size0).unwrap_or(512); + self.ensure_controller_running(); + if data.len() > 0x7FFF { return Err(UsbError::Unsupported); } + let mut data_dma = if data.is_empty() { None } + else { Some(DmaBuffer::allocate(data.len(), 4096).map_err(|_| UsbError::IoError)?) }; + if let Some(buf) = data_dma.as_mut() { + self.ensure_dma_segment_matches(buf.physical_address() as u64, "bulk_data")?; + if direction != TransferDirection::In { dma_write_bytes(buf, data); } + } + let mut td_dma = DmaBuffer::allocate(2 * size_of::(), 32).map_err(|_| UsbError::IoError)?; + self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "bulk_td")?; + let tds = unsafe { std::slice::from_raw_parts_mut(td_dma.as_mut_ptr() as *mut TransferDescriptor, 2) }; + let dma_phys = data_dma.as_ref().map(|b| b.physical_address() as u64).unwrap_or(0); + let first = build_bulk_transfer(dma_phys, data.len(), direction == TransferDirection::In, tds, td_dma.physical_address() as u64); + self.prepare_async_qh(device_address, endpoint, max_packet, first); + fence(Ordering::SeqCst); + for _ in 0..CONTROL_TRANSFER_TIMEOUT_POLLS { + let t0 = read_td_token(&td_dma, 0); let t1 = read_td_token(&td_dma, 1); + if (t0 | t1) & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 { self.disarm_async_qh(); return Err(map_td_error(t0 | t1)); } + if (t0 | t1) & TD_ACTIVE == 0 { + let rem = ((t0 & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize; + let actual = data.len().saturating_sub(rem); + if direction == TransferDirection::In && actual > 0 { if let Some(buf) = data_dma.as_ref() { dma_read_bytes(buf, &mut data[..actual]); } } + self.disarm_async_qh(); return Ok(actual); + } + thread::sleep(WAIT_STEP); + } + self.disarm_async_qh(); Err(UsbError::Timeout) } fn interrupt_transfer( &mut self, - _device_address: u8, - _endpoint: u8, - _data: &mut [u8], + device_address: u8, + endpoint: u8, + data: &mut [u8], ) -> Result { - Err(UsbError::Unsupported) + let port = self.find_port_by_address(device_address).ok_or(UsbError::NoDevice)?; + let max_packet = self.ports[port].device.as_ref().map(|d| d.max_packet_size0).unwrap_or(64); + self.ensure_controller_running(); + if data.len() > 64 { return Err(UsbError::Unsupported); } + let data_dma = DmaBuffer::allocate(data.len(), 64).map_err(|_| UsbError::IoError)?; + self.ensure_dma_segment_matches(data_dma.physical_address() as u64, "intr_data")?; + let mut td_dma = DmaBuffer::allocate(size_of::(), 32).map_err(|_| UsbError::IoError)?; + self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "intr_td")?; + let td = unsafe { &mut *(td_dma.as_mut_ptr() as *mut TransferDescriptor) }; + *td = build_intr_td(data_dma.physical_address() as u64, data.len(), td_dma.physical_address() as u64); + self.arm_periodic_qh(device_address, max_packet, endpoint, td_dma.physical_address() as u64); + fence(Ordering::SeqCst); + for _ in 0..(CONTROL_TRANSFER_TIMEOUT_POLLS / 2) { + let token = read_td_token(&td_dma, 0); + if token & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 { self.disarm_periodic_qh(); return Err(map_td_error(token)); } + if token & TD_ACTIVE == 0 { + let rem = ((token & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize; + let actual = data.len().saturating_sub(rem); + if actual > 0 { dma_read_bytes(&data_dma, &mut data[..actual]); } + self.disarm_periodic_qh(); return Ok(actual); + } + thread::sleep(WAIT_STEP); + } + self.disarm_periodic_qh(); Err(UsbError::Timeout) } fn set_address(&mut self, device_address: u8) -> bool { @@ -1043,6 +1128,7 @@ impl EhciScheme { (Some("status"), None) => Ok(HandleKind::Status { port }), (Some("descriptor"), None) => Ok(HandleKind::Descriptor { port }), (Some("control"), None) => Ok(HandleKind::Control { port }), + (Some("config"), None) => Ok(HandleKind::Config { port }), _ => Err(SysError::new(ENOENT)), } } @@ -1052,6 +1138,7 @@ impl EhciScheme { "status" => Ok(HandleKind::Status { port }), "descriptor" => Ok(HandleKind::Descriptor { port }), "control" => Ok(HandleKind::Control { port }), + "config" => Ok(HandleKind::Config { port }), _ => Err(SysError::new(ENOENT)), } } @@ -1065,6 +1152,18 @@ impl EhciScheme { listing.into_bytes() } + fn config_bytes(&self, port: usize) -> SysResult> { + let ctrl = self.controller.lock().map_err(|_| SysError::new(syscall::EIO))?; + let record = ctrl.ports.get(port).ok_or(SysError::new(syscall::ENOENT))?; + if let Some(ref dev) = record.device { + Ok(format!("class={:02x} subclass={:02x} vendor={:04x} device={:04x}\n", + dev.device_class, dev.device_subclass, dev.vendor_id, dev.product_id, + ).into_bytes()) + } else { + Ok(b"no device\n".to_vec()) + } + } + fn status_bytes(&self, port: usize) -> SysResult> { let controller = lock_controller(&self.controller); let Some(record) = controller.port_record(port) else { @@ -1158,10 +1257,11 @@ impl EhciScheme { let handle = self.handle(id)?; match &handle.kind { - HandleKind::PortDir { .. } => Ok(b"status\ndescriptor\ncontrol\n".to_vec()), + HandleKind::PortDir { .. } => Ok(b"status\ndescriptor\ncontrol\nconfig\n".to_vec()), HandleKind::Status { port } => self.status_bytes(*port), HandleKind::Descriptor { port } => self.descriptor_bytes(*port), HandleKind::Control { .. } => Ok(handle.response.clone()), + HandleKind::Config { port } => self.config_bytes(*port), } } @@ -1178,6 +1278,7 @@ impl EhciScheme { format!("{SCHEME_NAME}:/port{}/descriptor", port + 1) } HandleKind::Control { port } => format!("{SCHEME_NAME}:/port{}/control", port + 1), + HandleKind::Config { port } => format!("{SCHEME_NAME}:/port{}/config", port + 1), }; Ok(path) } @@ -1277,7 +1378,7 @@ impl SchemeSync for EhciScheme { match self.handle(id)?.kind { HandleKind::PortDir { .. } => MODE_DIR | 0o755, HandleKind::Status { .. } | HandleKind::Descriptor { .. } => MODE_FILE | 0o444, - HandleKind::Control { .. } => MODE_FILE | 0o644, + HandleKind::Control { .. } | HandleKind::Config { .. } => MODE_FILE | 0o644, } }; diff --git a/local/recipes/drivers/ehcid/source/src/registers.rs b/local/recipes/drivers/ehcid/source/src/registers.rs index 510544fe..abd61de4 100644 --- a/local/recipes/drivers/ehcid/source/src/registers.rs +++ b/local/recipes/drivers/ehcid/source/src/registers.rs @@ -328,3 +328,19 @@ pub fn build_control_transfer( Some(setup_td_phys) } + +pub fn build_bulk_transfer(data_phys: u64, data_len: usize, dir_in: bool, tds: &mut [TransferDescriptor], td_phys: u64) -> u32 { + let pid = if dir_in { TD_PID_IN } else { TD_PID_OUT }; + tds[0] = TransferDescriptor { next_qtd: 0, alt_qtd: 0, token: TD_ACTIVE | pid | ((data_len as u32 & 0x7FFF) << 16), buffers: [0; 5] }; + tds[0].buffers[0] = data_phys as u32; + let next = (td_phys as u32 + size_of::() as u32) & !0x1F; + tds[0].next_qtd = next; + tds[1] = TransferDescriptor { next_qtd: TD_TERMINATE, alt_qtd: TD_TERMINATE, token: TD_ACTIVE | (1 << 15) | 0x8000_0000 /* IOC */, buffers: [0; 5] }; + td_phys as u32 +} + +pub fn build_intr_td(data_phys: u64, data_len: usize, _td_phys: u64) -> TransferDescriptor { + let mut td = TransferDescriptor { next_qtd: TD_TERMINATE, alt_qtd: TD_TERMINATE, token: TD_ACTIVE | TD_PID_IN | ((data_len as u32 & 0x7FFF) << 16), buffers: [0; 5] }; + td.buffers[0] = data_phys as u32; + td +} diff --git a/local/recipes/drivers/ohcid/source/src/main.rs b/local/recipes/drivers/ohcid/source/src/main.rs index 9a8bf515..113fb566 100644 --- a/local/recipes/drivers/ohcid/source/src/main.rs +++ b/local/recipes/drivers/ohcid/source/src/main.rs @@ -2,7 +2,9 @@ mod registers; use std::env; use std::process; -use log::{info, error, LevelFilter}; +use std::fs; +use log::{info, error, warn, LevelFilter}; +use registers::*; struct StderrLogger; impl log::Log for StderrLogger { @@ -14,10 +16,20 @@ impl log::Log for StderrLogger { fn main() { log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); - let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") { - Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, + let _fd = match env::var("PCID_CLIENT_CHANNEL") { + Ok(s) => match s.parse::() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); } }; - info!("OHCI USB 1.1 controller (PCI fd: {})", channel_fd); - info!("ohcid: ready"); + let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); + info!("OHCI USB 1.1 at {}", device_path); + let config_path = format!("{}/config", device_path); + match fs::read(&config_path) { + Ok(data) if data.len() >= 0x14 => { + let bar0 = u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]); + info!("OHCI MMIO base: 0x{:08X} (BAR0)", bar0 & 0xFFFFFFF0); + info!("ohcid: MMIO detected, ready for port enumeration"); + } + _ => warn!("cannot read PCI config"), + } + loop { std::thread::sleep(std::time::Duration::from_secs(10)); } } diff --git a/local/recipes/drivers/uhcid/source/src/main.rs b/local/recipes/drivers/uhcid/source/src/main.rs index 49bf6357..d3b34663 100644 --- a/local/recipes/drivers/uhcid/source/src/main.rs +++ b/local/recipes/drivers/uhcid/source/src/main.rs @@ -2,7 +2,9 @@ mod registers; use std::env; use std::process; -use log::{info, error, LevelFilter}; +use std::fs; +use log::{info, error, warn, LevelFilter}; +use registers::*; struct StderrLogger; impl log::Log for StderrLogger { @@ -14,10 +16,20 @@ impl log::Log for StderrLogger { fn main() { log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); - let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") { - Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, + let _fd = match env::var("PCID_CLIENT_CHANNEL") { + Ok(s) => match s.parse::() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); } }; - info!("UHCI USB 1.1 controller (PCI fd: {})", channel_fd); - info!("uhcid: ready"); + let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); + info!("UHCI USB 1.1 at {}", device_path); + let config_path = format!("{}/config", device_path); + match fs::read(&config_path) { + Ok(data) if data.len() >= 0x14 => { + let bar4 = u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]); + info!("UHCI I/O base: 0x{:04X} (BAR4)", bar4 & 0xFFE0); + info!("uhcid: I/O port detected, ready for port enumeration"); + } + _ => warn!("cannot read PCI config"), + } + loop { std::thread::sleep(std::time::Duration::from_secs(10)); } } diff --git a/local/recipes/kde/kf6-attica/source/CMakeLists.txt b/local/recipes/kde/kf6-attica/source/CMakeLists.txt index eba96fae..999a2bc5 100644 --- a/local/recipes/kde/kf6-attica/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-attica/source/CMakeLists.txt @@ -57,7 +57,7 @@ add_subdirectory(src) # Enable unit testing if (BUILD_TESTING) -# add_subdirectory(autotests) +## add_subdirectory(autotests) add_subdirectory(tests) endif () diff --git a/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt b/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt index 284b0aae..38996bda 100644 --- a/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt @@ -88,6 +88,7 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) # shall we use DBus? # enabled per default on Linux & BSD systems diff --git a/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt b/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt index fe89b2ee..7ba94f45 100644 --- a/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt @@ -66,6 +66,7 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") diff --git a/local/recipes/kde/kf6-ki18n/recipe.toml b/local/recipes/kde/kf6-ki18n/recipe.toml index 68d9e58d..afa6c66b 100644 --- a/local/recipes/kde/kf6-ki18n/recipe.toml +++ b/local/recipes/kde/kf6-ki18n/recipe.toml @@ -36,12 +36,18 @@ sed -i 's/^ add_subdirectory(localedata-qml)/# add_subdirectory(localedata rm -f CMakeCache.txt rm -rf CMakeFiles +export LDFLAGS+=" -liconv" + cmake "${COOKBOOK_SOURCE}" \ -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ -DQT_HOST_PATH="${HOST_BUILD}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \ + -DCMAKE_C_STANDARD_LIBRARIES="-liconv" \ + -DCMAKE_CXX_STANDARD_LIBRARIES="-liconv" \ + -DCMAKE_SHARED_LINKER_FLAGS="-liconv" \ + -DCMAKE_EXE_LINKER_FLAGS="-liconv" \ -DBUILD_TESTING=OFF \ -DBUILD_QCH=OFF \ -DBUILD_WITH_QML=OFF \ diff --git a/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt b/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt index 75410bda..823e8989 100644 --- a/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt @@ -38,7 +38,7 @@ set_package_properties(Qt6Qml PROPERTIES ) if (TARGET Qt6::Qml) -### include(ECMQmlModule) +#### include(ECMQmlModule) endif() set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") diff --git a/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt b/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt index aa5c73b8..e210a0da 100644 --- a/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt +++ b/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_subdirectory(core) if (TARGET Qt6::Qml) -## add_subdirectory(qml) +### add_subdirectory(qml) endif() ecm_qt_install_logging_categories( diff --git a/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt b/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt index dff7c581..a6a35fcd 100644 --- a/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt @@ -39,6 +39,9 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") diff --git a/local/recipes/kde/kf6-kwayland/recipe.toml b/local/recipes/kde/kf6-kwayland/recipe.toml index 03ee1dc1..37bf8f0c 100644 --- a/local/recipes/kde/kf6-kwayland/recipe.toml +++ b/local/recipes/kde/kf6-kwayland/recipe.toml @@ -41,12 +41,16 @@ sed -i '/^find_package(Wayland 1.15/a find_package(Qt6WaylandClientPrivate REQUI rm -f CMakeCache.txt rm -rf CMakeFiles +export LDFLAGS+=" -lffi" + cmake "${COOKBOOK_SOURCE}" \ -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ -DQT_HOST_PATH="${HOST_BUILD}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \ + -DCMAKE_SHARED_LINKER_FLAGS="-lffi" \ + -DCMAKE_EXE_LINKER_FLAGS="-lffi" \ -DWaylandScanner_EXECUTABLE=/usr/bin/wayland-scanner \ -DWaylandProtocols_DATADIR="${COOKBOOK_SYSROOT}/share/wayland-protocols" \ -DPLASMA_WAYLAND_PROTOCOLS_DIR="${COOKBOOK_SYSROOT}/share/plasma-wayland-protocols" \ diff --git a/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt b/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt index 68831054..7b0b142f 100644 --- a/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt @@ -68,6 +68,10 @@ find_package(Qt6WaylandClientPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) set_package_properties(Wayland PROPERTIES TYPE REQUIRED ) diff --git a/local/recipes/kde/kf6-prison/source/.git-blame-ignore-revs b/local/recipes/kde/kf6-prison/source/.git-blame-ignore-revs new file mode 100644 index 00000000..d8086ea2 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +#clang-format +7dd564adbcfc6151473c97a3a7737cfaef5694b7 +#clang-tidy +4c41ac844a27089083578eea1b947d7bbbd517c7 diff --git a/local/recipes/kde/kf6-prison/source/.gitignore b/local/recipes/kde/kf6-prison/source/.gitignore new file mode 100644 index 00000000..d84c8172 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/.gitignore @@ -0,0 +1,28 @@ +# Ignore the following files +*~ +*.[oa] +*.diff +*.kate-swp +*.kdev4 +.kdev_include_paths +*.kdevelop.pcs +*.moc +*.moc.cpp +*.orig +*.user +.*.swp +.swp.* +Doxyfile +Makefile +avail +random_seed +/build*/ +CMakeLists.txt.user* +*.unc-backup* +.cmake/ +/.clang-format +/compile_commands.json +.clangd +.idea +/cmake-build* +.cache diff --git a/local/recipes/kde/kf6-prison/source/.gitlab-ci.yml b/local/recipes/kde/kf6-prison/source/.gitlab-ci.yml new file mode 100644 index 00000000..ae0db780 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/.gitlab-ci.yml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2020 Volker Krause +# SPDX-License-Identifier: CC0-1.0 + +include: + - project: sysadmin/ci-utilities + file: + - /gitlab-templates/linux-qt6.yml + - /gitlab-templates/linux-qt6-static.yml + - /gitlab-templates/android-qt6.yml + - /gitlab-templates/freebsd-qt6.yml + - /gitlab-templates/windows-qt6.yml + - /gitlab-templates/alpine-qt6.yml diff --git a/local/recipes/kde/kf6-prison/source/.kde-ci.yml b/local/recipes/kde/kf6-prison/source/.kde-ci.yml new file mode 100644 index 00000000..c366b830 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/.kde-ci.yml @@ -0,0 +1,9 @@ +Dependencies: +- 'on': ['Linux', 'FreeBSD', 'Windows', 'Android'] + 'require': + 'frameworks/extra-cmake-modules': '@same' + 'third-party/zxing-cpp': '@latest' + +Options: + test-before-installing: True + require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ] diff --git a/local/recipes/kde/kf6-prison/source/.reuse/dep5 b/local/recipes/kde/kf6-prison/source/.reuse/dep5 new file mode 100644 index 00000000..82dd25cd --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/.reuse/dep5 @@ -0,0 +1,7 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: KF5::Prison +Source: https://invent.kde.org/frameworks/prison + +Files: autotests/aztec/encoding/*.png autotests/aztec/rendering/*.png autotests/code128/*.png autotests/datamatrix/*.png autotests/qr/*.png +Copyright: none +License: CC0-1.0 diff --git a/local/recipes/kde/kf6-prison/source/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/CMakeLists.txt new file mode 100644 index 00000000..6b8018fe --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/CMakeLists.txt @@ -0,0 +1,121 @@ +cmake_minimum_required(VERSION 3.16) + +set(KF_VERSION "6.10.0") # handled by release scripts +project(prison VERSION ${KF_VERSION}) + +# ECM setup +include(FeatureSummary) +find_package(ECM 6.10.0 NO_MODULE) +set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") +feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${ECM_MODULE_PATH}") + +include(KDEInstallDirs) +include(KDECMakeSettings) +include(KDEGitCommitHooks) +include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) + +include(GenerateExportHeader) +include(ECMGenerateHeaders) +include(ECMGenerateExportHeader) +include(ECMAddQch) +include(ECMAddTests) +include(CMakePackageConfigHelpers) +include(ECMSetupVersion) +include(ECMQtDeclareLoggingCategory) +include(ECMDeprecationSettings) + +set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") + +option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) +add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") + +set(REQUIRED_QT_VERSION 6.6.0) + +option(WITH_QUICK "Build QtQuick components" ON) +option(WITH_MULTIMEDIA "Build scanner support" ON) + +find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Gui) + +if (WITH_QUICK) + include(ECMQmlModule) + find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Quick) +endif() + +if (WITH_MULTIMEDIA) + find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Multimedia) +endif() + +find_package(QRencode) +set_package_properties(QRencode PROPERTIES + PURPOSE "Required for generation of QRCode barcodes." + TYPE REQUIRED) +find_package(Dmtx) +set_package_properties(Dmtx PROPERTIES + PURPOSE "Required for generation of Data Matrix barcodes." + TYPE RECOMMENDED) +find_package(ZXing 2.0) +if (NOT TARGET ZXing::ZXing) + find_package(ZXing 1.2.0) +endif() +set_package_properties(ZXing PROPERTIES + PURPOSE "Required for generation of PDF417 barcodes and for scanning of barcodes from live video feed." + TYPE RECOMMENDED) + +ecm_setup_version(PROJECT VARIABLE_PREFIX PRISON + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/prison_version.h" + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfigVersion.cmake" + SOVERSION 6 +) +ecm_set_disabled_deprecation_versions( + QT 6.8 +) + +add_subdirectory(src) +if(BUILD_TESTING) + find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test Concurrent Widgets) + add_subdirectory(autotests) + add_subdirectory(tests) +endif() + +set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6Prison") + +if (BUILD_QCH) + ecm_install_qch_export( + TARGETS KF6Prison_QCH + FILE KF6PrisonQchTargets.cmake + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel + ) + set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF6PrisonQchTargets.cmake\")") +endif() + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/KF6PrisonConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfig.cmake" + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfigVersion.cmake" + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel +) + +install(EXPORT KF6PrisonTargets + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + FILE KF6PrisonTargets.cmake + NAMESPACE KF6:: +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/prison_version.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/Prison + COMPONENT Devel +) + +include(ECMFeatureSummary) +ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) + +kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) diff --git a/local/recipes/kde/kf6-prison/source/KF6PrisonConfig.cmake.in b/local/recipes/kde/kf6-prison/source/KF6PrisonConfig.cmake.in new file mode 100644 index 00000000..500834b6 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/KF6PrisonConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt6Gui @REQUIRED_QT_VERSION@) + +include("${CMAKE_CURRENT_LIST_DIR}/KF6PrisonTargets.cmake") +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/local/recipes/kde/kf6-prison/source/LICENSES/BSD-3-Clause.txt b/local/recipes/kde/kf6-prison/source/LICENSES/BSD-3-Clause.txt new file mode 100644 index 00000000..0741db78 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,26 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/local/recipes/kde/kf6-prison/source/LICENSES/CC0-1.0.txt b/local/recipes/kde/kf6-prison/source/LICENSES/CC0-1.0.txt new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/local/recipes/kde/kf6-prison/source/LICENSES/MIT.txt b/local/recipes/kde/kf6-prison/source/LICENSES/MIT.txt new file mode 100644 index 00000000..204b93da --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +MIT License Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/local/recipes/kde/kf6-prison/source/README.md b/local/recipes/kde/kf6-prison/source/README.md new file mode 100644 index 00000000..87811e61 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/README.md @@ -0,0 +1,54 @@ +# Prison + +A barcode abstraction layer providing uniform access to generation of barcodes with data. + +## Introduction + +Prison has a Prison::AbstractBarcode, which is the base class for the actual +barcode generators. Prison currently implements barcode generators for the following formats: + +- QRCode +- Data Matrix +- Aztec +- Code39 +- Code93 +- [Code128](https://en.wikipedia.org/wiki/Code_128) +- [PDF417](https://en.wikipedia.org/wiki/PDF417) (ISO/IEC 15438) +- [EAN13](https://en.wikipedia.org/wiki/International_Article_Number) + +Prison currently ships the org.kde.prison.Barcode QML element that can be used to render barcodes in QML code. + +## Supported Barcode types + +There are basically two types of barcodes: +* barcodes that carry the data +* barcodes that carry a lookup number, and require a specific server to + look up the actual data. + +Prison isn't as such designed for the latter, it will probably work, but +patches implementing barcode support for such barcodes will not be accepted. +An example is [EZCode](https://en.wikipedia.org/wiki/EZcode). + +Prison is currently using [libdmtx](https://github.com/dmtx/libdmtx) for generation of +[DataMatrix](https://en.wikipedia.org/wiki/Datamatrix) barcodes, +[libqrencode](https://fukuchi.org/works/qrencode/) for generation +of [QRCode](https://en.wikipedia.org/wiki/QR_Code) barcodes and +[ZXing](https://github.com/nu-book/zxing-cpp) for generating +[EAN13 and PDF417](https://en.wikipedia.org/wiki/PDF417) barcodes. + +# Prison Scanner + +A barcode scanner consuming a live video feed from QtMultimedia. + +## Introduction + +Prison's barcode scanner can be used from C++ code using the Prison::VideoScanner +class, or from QML using the org.kde.prison.scanner.VideoScanner QML element. + +There are standalone QML examples in tests/scanner-qt(5|6).qml demonstrating +the QML API. + +## Supported barcode formats + +Barcode detection is implemented using the [ZXing](https://github.com/nu-book/zxing-cpp) +library, all formats supported by ZXing can be detected. diff --git a/local/recipes/kde/kf6-prison/source/autotests/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/autotests/CMakeLists.txt new file mode 100644 index 00000000..290b9c66 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/CMakeLists.txt @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2017-2018 Volker Krause +# SPDX-License-Identifier: BSD-3-Clause + +set(aztecbarcodetest_srcs + aztecbarcodetest.cpp + aztec/aztec.qrc + ../src/lib/aztecbarcode.cpp + ../src/lib/abstractbarcode_p.cpp + ../src/lib/barcodeutil.cpp + ../src/lib/bitvector.cpp + ../src/lib/reedsolomon.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../src/lib/prison_debug.cpp +) + +ecm_add_test(${aztecbarcodetest_srcs} TEST_NAME prison-aztecbarcodetest LINK_LIBRARIES Qt6::Test KF6::Prison) + +ecm_add_test( + reedsolomontest.cpp + ../src/lib/bitvector.cpp + ../src/lib/reedsolomon.cpp + TEST_NAME prison-reedsolomontest + LINK_LIBRARIES Qt6::Test KF6::Prison +) + +set(code128barcodetest_srcs + code128barcodetest.cpp + code128/code128.qrc + ../src/lib/abstractbarcode_p.cpp + ../src/lib/barcodeutil.cpp + ../src/lib/code128barcode.cpp + ../src/lib/bitvector.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../src/lib/prison_debug.cpp +) + +ecm_add_test(${code128barcodetest_srcs} TEST_NAME prison-code128barcodetest LINK_LIBRARIES Qt6::Test KF6::Prison) + +if(TARGET Dmtx::Dmtx) + ecm_add_test(datamatrixtest.cpp datamatrix/datamatrix.qrc TEST_NAME prison-datamatrixtest LINK_LIBRARIES Qt6::Test KF6::Prison) +endif() +ecm_add_test(qrtest.cpp qr/qr.qrc TEST_NAME prison-qrtest LINK_LIBRARIES Qt6::Test KF6::Prison) + +if (TARGET ZXing::ZXing) + ecm_add_test(zxingutiltest.cpp ${PROJECT_SOURCE_DIR}/src/lib/zxingutil.cpp TEST_NAME prison-zxingutiltest LINK_LIBRARIES Qt6::Test ZXing::ZXing KF6::Prison) + kde_source_files_enable_exceptions(${PROJECT_SOURCE_DIR}/src/lib/zxingutil.cpp ) + ecm_add_test(imagescannertest.cpp qr/qr.qrc TEST_NAME prison-imagescannertest LINK_LIBRARIES Qt6::Test Qt6::Concurrent KF6::PrisonScanner) +endif() + +ecm_add_test( + mecardtest.cpp + TEST_NAME prison-mecardtest + LINK_LIBRARIES Qt6::Test KF6::Prison +) diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/aztec.qrc b/local/recipes/kde/kf6-prison/source/autotests/aztec/aztec.qrc new file mode 100644 index 00000000..2519e1c1 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/aztec/aztec.qrc @@ -0,0 +1,34 @@ + + + + rendering/aztec-full-grid.png + rendering/aztec-full-data-0011.png + rendering/aztec-full-data-0101.png + rendering/aztec-full-data-1001.png + rendering/aztec-full-data-1010.png + rendering/aztec-full-data-1111.png + rendering/aztec-full-mode-1111.png + rendering/aztec-full-mode-1234.png + rendering/aztec-full-mode-1234-rev.png + + rendering/aztec-compact-grid.png + rendering/aztec-compact-data-0011.png + rendering/aztec-compact-data-0101.png + rendering/aztec-compact-data-1001.png + rendering/aztec-compact-data-1010.png + rendering/aztec-compact-data-1111.png + rendering/aztec-compact-mode-1111.png + rendering/aztec-compact-mode-1234.png + rendering/aztec-compact-mode-1234-rev.png + + encoding/aztec-complete-compact1.png + encoding/aztec-complete-compact3.png + encoding/aztec-complete-compact4.png + encoding/aztec-complete-full5.png + encoding/aztec-complete-big.png + encoding/aztec-binary.png + + diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-binary.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-binary.png new file mode 100644 index 00000000..fc8c1cfb Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-binary.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-big.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-big.png new file mode 100644 index 00000000..ffe3021f Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-big.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact1.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact1.png new file mode 100644 index 00000000..5217f711 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact1.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact3.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact3.png new file mode 100644 index 00000000..61796b55 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact3.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact4.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact4.png new file mode 100644 index 00000000..490219da Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-compact4.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-full5.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-full5.png new file mode 100644 index 00000000..5619f404 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/encoding/aztec-complete-full5.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-0011.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-0011.png new file mode 100644 index 00000000..9a8f68b2 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-0011.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-0101.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-0101.png new file mode 100644 index 00000000..d67882da Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-0101.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1001.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1001.png new file mode 100644 index 00000000..981367ae Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1001.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1010.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1010.png new file mode 100644 index 00000000..f5cd72d7 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1010.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1111.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1111.png new file mode 100644 index 00000000..d94b7373 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-data-1111.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-grid.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-grid.png new file mode 100644 index 00000000..6547b30a Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-grid.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1111.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1111.png new file mode 100644 index 00000000..ea5b2afe Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1111.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1234-rev.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1234-rev.png new file mode 100644 index 00000000..fa3b343a Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1234-rev.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1234.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1234.png new file mode 100644 index 00000000..127270b3 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-compact-mode-1234.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-0011.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-0011.png new file mode 100644 index 00000000..1399518c Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-0011.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-0101.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-0101.png new file mode 100644 index 00000000..01fb02b7 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-0101.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1001.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1001.png new file mode 100644 index 00000000..79b335cf Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1001.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1010.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1010.png new file mode 100644 index 00000000..45fb94de Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1010.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1111.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1111.png new file mode 100644 index 00000000..4ecc2091 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-data-1111.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-grid.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-grid.png new file mode 100644 index 00000000..f8f5692a Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-grid.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1111.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1111.png new file mode 100644 index 00000000..d050df5e Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1111.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1234-rev.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1234-rev.png new file mode 100644 index 00000000..7c36160e Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1234-rev.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1234.png b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1234.png new file mode 100644 index 00000000..1d43942c Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/aztec/rendering/aztec-full-mode-1234.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/aztecbarcodetest.cpp b/local/recipes/kde/kf6-prison/source/autotests/aztecbarcodetest.cpp new file mode 100644 index 00000000..bf7b91e7 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/aztecbarcodetest.cpp @@ -0,0 +1,528 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "../src/lib/aztecbarcode_p.h" +#include "../src/lib/bitvector_p.h" + +#include + +#include +#include +#include + +#include + +Q_DECLARE_METATYPE(Prison::BitVector) + +using namespace Prison; + +class AztecBarcodeTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testAztecEncode_data() + { + QTest::addColumn("input"); + QTest::addColumn("output"); + + QTest::newRow("empty") << QByteArray() << BitVector(); + BitVector v; + v.appendMSB(12, 5); + v.appendMSB(5, 5); + v.appendMSB(6, 5); + QTest::newRow("all uppper") << QByteArray("KDE") << v; + v.clear(); + v.appendMSB(28, 5); + v.appendMSB(12, 5); + v.appendMSB(5, 5); + v.appendMSB(6, 5); + QTest::newRow("all lower") << QByteArray("kde") << v; + v.clear(); + v.appendMSB(12, 5); + v.appendMSB(28, 5); + v.appendMSB(5, 5); + v.appendMSB(6, 5); + QTest::newRow("upper -> lower latch") << QByteArray("Kde") << v; + v.clear(); + v.appendMSB(28, 5); + v.appendMSB(2, 5); + v.appendMSB(29, 5); + v.appendMSB(30, 5); + v.appendMSB(16, 5); + v.appendMSB(16, 5); + QTest::newRow("lower -> punct latch") << QByteArray("a++") << v; + v.clear(); + v.appendMSB(30, 5); + v.appendMSB(6, 4); + v.appendMSB(4, 4); + QTest::newRow("digit") << QByteArray("42") << v; + v.clear(); + v.appendMSB(29, 5); + v.appendMSB(30, 5); + v.appendMSB(25, 5); + v.appendMSB(24, 5); + v.appendMSB(31, 5); + v.appendMSB(30, 5); + v.appendMSB(11, 4); + v.appendMSB(2, 4); + QTest::newRow("punct -> digit latch") << QByteArray(">=90") << v; + v.clear(); + v.appendMSB(30, 5); + v.appendMSB(10, 4); + v.appendMSB(3, 4); + v.appendMSB(14, 4); + v.appendMSB(29, 5); + v.appendMSB(30, 5); + v.appendMSB(13, 5); + v.appendMSB(14, 5); + QTest::newRow("digit -> punct latch") << QByteArray("81()") << v; + v.clear(); + v.appendMSB(29, 5); + v.appendMSB(11, 5); + v.appendMSB(8, 5); + QTest::newRow("mixed") << QByteArray("\n\a") << v; + v.clear(); + v.appendMSB(29, 5); + v.appendMSB(30, 5); + v.appendMSB(2, 5); + v.appendMSB(2, 5); + QTest::newRow("CR LF") << QByteArray("\r\n\r\n") << v; + v.clear(); + v.appendMSB(31, 5); + v.appendMSB(2, 5); + v.appendMSB(128, 8); + v.appendMSB(129, 8); + QTest::newRow("binary") << QByteArray("\x80\x81") << v; + v.clear(); + v.appendMSB(31, 5); + v.appendMSB(2, 5); + v.appendMSB(255, 8); + v.appendMSB(254, 8); + v.appendMSB(28, 5); + v.appendMSB(3, 5); + QTest::newRow("binary/lower") << QByteArray( + "\xff\xfe" + "b") << v; + v.clear(); + v.appendMSB(12, 5); + v.appendMSB(0, 5); + v.appendMSB(6, 5); + QTest::newRow("upper -> punct shift") << QByteArray("K!") << v; + v.clear(); + v.appendMSB(30, 5); + v.appendMSB(9, 4); + v.appendMSB(4, 4); + v.appendMSB(15, 4); + v.appendMSB(6, 5); + v.appendMSB(5, 4); + QTest::newRow("digit -> upper shift") << QByteArray("72E3") << v; + v.clear(); + v.appendMSB(25, 5); + v.appendMSB(1, 5); + v.appendMSB(26, 5); + QTest::newRow("upper space") << QByteArray("X Y") << v; + v.clear(); + v.appendMSB(20, 5); + v.appendMSB(0, 5); + v.appendMSB(4, 5); + v.appendMSB(23, 5); + QTest::newRow("upper punct double char shift") << QByteArray("S, V") << v; + v.clear(); + v.appendMSB(17, 5); + v.appendMSB(0, 5); + v.appendMSB(17, 5); + v.appendMSB(18, 5); + QTest::newRow("upper ambiguous punct shift") << QByteArray("P,Q") << v; + v.clear(); + v.appendMSB(30, 5); + v.appendMSB(13, 4); + v.appendMSB(7, 4); + QTest::newRow("digit ambiguous punct latch") << QByteArray(".5") << v; + v.clear(); + v.appendMSB(29, 5); + v.appendMSB(30, 5); + v.appendMSB(25, 5); + v.appendMSB(26, 5); + v.appendMSB(31, 5); + v.appendMSB(29, 5); + v.appendMSB(20, 5); + v.appendMSB(29, 5); + v.appendMSB(2, 5); + QTest::newRow("punct/mixed/upper sequence") << QByteArray(">?@A") << v; + v.clear(); + v.appendMSB(2, 5); + v.appendMSB(3, 5); + v.appendMSB(4, 5); + v.appendMSB(29, 5); // latch to Punct via latch to Mixed, shift to Punct directly would be more efficient here + v.appendMSB(30, 5); + v.appendMSB(19, 5); + v.appendMSB(31, 5); // latch to Upper due to shift to Binary not available in Punct + v.appendMSB(31, 5); + v.appendMSB(2, 5); + v.appendMSB(0, 8); + v.appendMSB(0, 8); + QTest::newRow("upper/special -> binary") << QByteArray("ABC.\x00\x00", 6) << v; + } + + void testAztecEncode() + { + QFETCH(QByteArray, input); + QFETCH(BitVector, output); + + AztecBarcode code; + const auto v = code.aztecEncode(input); + if (v != output) { + qDebug() << "Actual :" << v; + qDebug() << "Expected:" << output; + } + QCOMPARE(v, output); + } + + void testStuffAndPad_data() + { + QTest::addColumn("input"); + QTest::addColumn("output"); + QTest::addColumn("codeWordSize"); + + BitVector in; + BitVector out; + QTest::newRow("empty") << in << out << 4; + in.appendMSB(0x2, 2); + out.appendMSB(0xB, 4); + QTest::newRow("pad only") << in << out << 4; + in.clear(); + out.clear(); + in.appendMSB(0x3, 2); + out.appendMSB(0xE, 4); + QTest::newRow("pad only inverted") << in << out << 4; + in.clear(); + out.clear(); + in.appendMSB(0xe0, 8); + out.appendMSB(0xe13, 12); + QTest::newRow("stuff and pad") << in << out << 4; + in.clear(); + out.clear(); + in.appendMSB(0, 6); + out.appendMSB(0x11, 8); + QTest::newRow("stuff only") << in << out << 4; + } + + void testStuffAndPad() + { + QFETCH(BitVector, input); + QFETCH(BitVector, output); + QFETCH(int, codeWordSize); + AztecBarcode code; + const auto res = code.bitStuffAndPad(input, codeWordSize); + QCOMPARE(res.size(), output.size()); + if (res != output) { + qDebug() << "Actual :" << res; + qDebug() << "Expected:" << output; + } + QCOMPARE(res, output); + } + + void testFullGrid() + { + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.m_background); + code.paintFullGrid(&img); + + QImage ref(QStringLiteral(":/aztec/rendering/aztec-full-grid.png")); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCompactGrid() + { + AztecBarcode code; + QImage img(27, 27, QImage::Format_ARGB32_Premultiplied); + img.fill(code.m_background); + code.paintCompactGrid(&img); + img.save(QStringLiteral("aztec-compact-grid.png")); + + QImage ref(QStringLiteral(":/aztec/rendering/aztec-compact-grid.png")); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testFullData_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + QTest::addColumn("layer"); + + BitVector v; + for (int i = 0; i < 1248; ++i) { + v.appendLSB(0x9249, 16); + } + QTest::newRow("1001-31") << v << QStringLiteral("aztec-full-data-1001.png") << 32; + + v.clear(); + for (int i = 0; i < 1248 * 8; ++i) { + v.appendLSB(0x2, 2); + } + QTest::newRow("0101-31") << v << QStringLiteral("aztec-full-data-0101.png") << 32; + + v.clear(); + for (int i = 0; i < 1248; ++i) { + v.appendLSB(0xffff, 16); + } + QTest::newRow("1111-31") << v << QStringLiteral("aztec-full-data-1111.png") << 32; + + v.clear(); + for (int i = 0; i < 704 * 4; ++i) { + v.appendLSB(0x1, 2); + } + QTest::newRow("1010-15") << v << QStringLiteral("aztec-full-data-1010.png") << 16; + + v.clear(); + for (int i = 0; i < 16; ++i) { + v.appendLSB(0xCC, 8); + } + QTest::newRow("0011-0") << v << QStringLiteral("aztec-full-data-0011.png") << 1; + } + + void testFullData() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + QFETCH(int, layer); + + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.m_background); + code.paintFullData(&img, data, layer); + img.save(refName); + + QImage ref(QStringLiteral(":/aztec/rendering/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testFullModeMessage_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + + BitVector v; + for (int i = 0; i < 8; ++i) { + v.appendMSB(i + 1, 5); + } + QTest::newRow("1234") << v << QStringLiteral("aztec-full-mode-1234.png"); + + v.clear(); + for (int i = 0; i < 8; ++i) { + v.appendLSB(i + 1, 5); + } + QTest::newRow("1234-rev") << v << QStringLiteral("aztec-full-mode-1234-rev.png"); + + v.clear(); + for (int i = 0; i < 4; ++i) { + v.appendMSB(0xffff, 10); + } + QTest::newRow("1111") << v << QStringLiteral("aztec-full-mode-1111.png"); + } + + void testFullModeMessage() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.m_background); + code.paintFullModeMessage(&img, data); + img.save(refName); + + QImage ref(QStringLiteral(":/aztec/rendering/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCompactData_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + QTest::addColumn("layer"); + + BitVector v; + for (int i = 0; i < 304; ++i) { + v.appendLSB(0x9249, 16); + } + QTest::newRow("1001-3") << v << QStringLiteral("aztec-compact-data-1001.png") << 4; + + v.clear(); + for (int i = 0; i < 608 * 4; ++i) { + v.appendLSB(0x2, 2); + } + QTest::newRow("0101-3") << v << QStringLiteral("aztec-compact-data-0101.png") << 4; + + v.clear(); + for (int i = 0; i < 304; ++i) { + v.appendLSB(0xffff, 16); + } + QTest::newRow("1111-3") << v << QStringLiteral("aztec-compact-data-1111.png") << 4; + + v.clear(); + for (int i = 0; i < 102 * 4; ++i) { + v.appendLSB(0x1, 2); + } + QTest::newRow("1010-2") << v << QStringLiteral("aztec-compact-data-1010.png") << 3; + + v.clear(); + for (int i = 0; i < 13; ++i) { + v.appendLSB(0xCC, 8); + } + QTest::newRow("0011-0") << v << QStringLiteral("aztec-compact-data-0011.png") << 1; + } + + void testCompactData() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + QFETCH(int, layer); + + AztecBarcode code; + QImage img(27, 27, QImage::Format_ARGB32_Premultiplied); + img.fill(code.m_background); + code.paintCompactData(&img, data, layer); + img.save(refName); + + QImage ref(QStringLiteral(":/aztec/rendering/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCompactModeMessage_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + + BitVector v; + for (int i = 0; i < 4; ++i) { + v.appendMSB(i + 1, 7); + } + QTest::newRow("1234") << v << QStringLiteral("aztec-compact-mode-1234.png"); + + v.clear(); + for (int i = 0; i < 4; ++i) { + v.appendLSB(i + 1, 7); + } + QTest::newRow("1234-rev") << v << QStringLiteral("aztec-compact-mode-1234-rev.png"); + + v.clear(); + for (int i = 0; i < 4; ++i) { + v.appendMSB(0xffff, 7); + } + QTest::newRow("1111") << v << QStringLiteral("aztec-compact-mode-1111.png"); + } + + void testCompactModeMessage() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.m_background); + code.paintCompactModeMessage(&img, data); + img.save(refName); + + QImage ref(QStringLiteral(":/aztec/rendering/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCodeGen_data() + { + QTest::addColumn("input"); + QTest::addColumn("refName"); + + QTest::newRow("short compact") << QByteArray("KF5::Prison") << "aztec-complete-compact1.png"; + QTest::newRow("compact 3 layer") << QByteArray("M1KRAUSE/VOLKER ABCDEFG TXLRIXBT 0212 309Y014E0063 100") << "aztec-complete-compact3.png"; + QTest::newRow("long compact") << QByteArray("KF5::Prison - the barcode generation library of KDE Frameworks 5!") << "aztec-complete-compact4.png"; + QTest::newRow("short full") << QByteArray("KF5::Prison - the MIT licensed free software barcode generation library of KDE Frameworks 5!") + << "aztec-complete-full5.png"; + QTest::newRow("long full") << QByteArray( + "Permission is hereby granted, free of charge, to any person\n" + "obtaining a copy of this software and associated documentation\n" + "files (the \"Software\"), to deal in the Software without\n" + "restriction, including without limitation the rights to use,\n" + "copy, modify, merge, publish, distribute, sublicense, and/or sell\n" + "copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following\n" + "conditions:\n\n" + "The above copyright notice and this permission notice shall be\n" + "included in all copies or substantial portions of the Software.\n\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n" + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n" + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n" + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n" + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n" + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n" + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n" + "OTHER DEALINGS IN THE SOFTWARE.") + << "aztec-complete-big.png"; + QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "aztec-binary.png"; + } + + void testCodeGen() + { + QFETCH(QByteArray, input); + QFETCH(QString, refName); + + { + auto code = Prison::Barcode::create(Prison::Aztec); + QVERIFY(code); + code->setData(QString::fromLatin1(input.constData(), input.size())); + const auto img = code->toImage(code->minimumSize()); + img.save(refName); + + QImage ref(QStringLiteral(":/aztec/encoding/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + { + auto code = Prison::Barcode::create(Prison::Aztec); + QVERIFY(code); + code->setData(input); + const auto img = code->toImage(code->minimumSize()); + img.save(refName); + + QImage ref(QStringLiteral(":/aztec/encoding/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + } + + void testDimension() + { + auto barcode = Prison::Barcode::create(Prison::Aztec); + QVERIFY(barcode); + QCOMPARE(barcode->format(), Prison::Aztec); + QCOMPARE(barcode->dimensions(), Prison::Barcode::TwoDimensions); + } + + void testSize() + { + auto barcode = Prison::Barcode::create(Prison::Aztec); + QVERIFY(barcode); + QCOMPARE(barcode->format(), Prison::Aztec); + barcode->setData(QStringLiteral("UNIT TEST")); + QCOMPARE(barcode->minimumSize(), QSize(15, 15)); + QCOMPARE(barcode->preferredSize(1), QSize(60, 60)); + QCOMPARE(barcode->preferredSize(2), QSize(30, 30)); + QCOMPARE(barcode->toImage(barcode->preferredSize(1)).size(), QSize(60, 60)); + QCOMPARE(barcode->toImage({1, 1}).isNull(), true); + } +}; + +QTEST_APPLESS_MAIN(AztecBarcodeTest) + +#include "aztecbarcodetest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/code128/code128-binary.png b/local/recipes/kde/kf6-prison/source/autotests/code128/code128-binary.png new file mode 100644 index 00000000..a7cdf9c0 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/code128/code128-binary.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/code128/code128-text.png b/local/recipes/kde/kf6-prison/source/autotests/code128/code128-text.png new file mode 100644 index 00000000..fe60e5b1 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/code128/code128-text.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/code128/code128.qrc b/local/recipes/kde/kf6-prison/source/autotests/code128/code128.qrc new file mode 100644 index 00000000..92c66400 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/code128/code128.qrc @@ -0,0 +1,10 @@ + + + + code128-text.png + code128-binary.png + + diff --git a/local/recipes/kde/kf6-prison/source/autotests/code128barcodetest.cpp b/local/recipes/kde/kf6-prison/source/autotests/code128barcodetest.cpp new file mode 100644 index 00000000..ae7ddad6 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/code128barcodetest.cpp @@ -0,0 +1,240 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "../src/lib/bitvector_p.h" +#include "../src/lib/code128barcode_p.h" + +#include + +#include +#include + +Q_DECLARE_METATYPE(Prison::BitVector) + +using namespace Prison; + +class Code128BarcodeTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testEncode_data() + { + QTest::addColumn("input"); + QTest::addColumn("output"); + + BitVector v; + QTest::newRow("empty") << QByteArray() << v; + v.appendMSB(1680, 11); + v.appendMSB(1554, 11); + v.appendMSB(1062, 11); + v.appendMSB(1424, 11); + v.appendMSB(1220, 11); + v.appendMSB(6379, 13); + QTest::newRow("all lower") << QByteArray("kde") << v; + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1422, 11); + v.appendMSB(1416, 11); + v.appendMSB(1128, 11); + v.appendMSB(1764, 11); + v.appendMSB(6379, 13); + QTest::newRow("all uppper") << QByteArray("KDE") << v; + + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1614, 11); + v.appendMSB(1764, 11); + v.appendMSB(6379, 13); + QTest::newRow("1 digit") << QByteArray("4") << v; + v.clear(); + v.appendMSB(1692, 11); + v.appendMSB(1464, 11); + v.appendMSB(1134, 11); + v.appendMSB(6379, 13); + QTest::newRow("2 digits") << QByteArray("42") << v; + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1614, 11); + v.appendMSB(1650, 11); + v.appendMSB(1650, 11); + v.appendMSB(1124, 11); + v.appendMSB(6379, 13); + QTest::newRow("3 digits") << QByteArray("422") << v; + v.clear(); + v.appendMSB(1692, 11); + v.appendMSB(1464, 11); + v.appendMSB(1902, 11); + v.appendMSB(1782, 11); + v.appendMSB(6379, 13); + QTest::newRow("4 digits") << QByteArray("4223") << v; + + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1814, 11); + v.appendMSB(1260, 11); + v.appendMSB(1260, 11); + v.appendMSB(1896, 11); + v.appendMSB(1814, 11); + v.appendMSB(6379, 13); + QTest::newRow("mixed") << QByteArray("X00Y") << v; + + v.clear(); + v.appendMSB(1668, 11); + v.appendMSB(1292, 11); + v.appendMSB(1292, 11); + v.appendMSB(6379, 13); + QTest::newRow("null") << QByteArray("\0", 1) << v; + v.clear(); + v.appendMSB(1668, 11); + v.appendMSB(1422, 11); + v.appendMSB(1416, 11); + v.appendMSB(1128, 11); + v.appendMSB(1292, 11); + v.appendMSB(1412, 11); + v.appendMSB(6379, 13); + QTest::newRow("Code A only") << QByteArray("KDE\0", 4) << v; + + v.clear(); + v.appendMSB(1668, 11); + v.appendMSB(1292, 11); + v.appendMSB(1518, 11); + v.appendMSB(1554, 11); + v.appendMSB(1062, 11); + v.appendMSB(1424, 11); + v.appendMSB(1616, 11); + v.appendMSB(6379, 13); + QTest::newRow("Start A -> Latch B") << QByteArray("\0kde", 4) << v; + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1554, 11); + v.appendMSB(1062, 11); + v.appendMSB(1886, 11); + v.appendMSB(1292, 11); + v.appendMSB(1292, 11); + v.appendMSB(1602, 11); + v.appendMSB(6379, 13); + QTest::newRow("Start B -> Latch A") << QByteArray("kd\0\0", 4) << v; + + v.clear(); + v.appendMSB(1668, 11); + v.appendMSB(1292, 11); + v.appendMSB(1954, 11); + v.appendMSB(1118, 11); + v.appendMSB(1590, 11); + v.appendMSB(1292, 11); + v.appendMSB(1328, 11); + v.appendMSB(6379, 13); + QTest::newRow("Start A -> Shift B") << QByteArray("\0~@\0", 4) << v; + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1974, 11); + v.appendMSB(1954, 11); + v.appendMSB(1292, 11); + v.appendMSB(1310, 11); + v.appendMSB(1844, 11); + v.appendMSB(6379, 13); + QTest::newRow("Start B -> Shift A") << QByteArray("{\0}", 3) << v; + + v.clear(); + v.appendMSB(1692, 11); + v.appendMSB(1436, 11); + v.appendMSB(1112, 11); + v.appendMSB(1518, 11); + v.appendMSB(1304, 11); + v.appendMSB(1112, 11); + v.appendMSB(1158, 11); + v.appendMSB(6379, 13); + QTest::newRow("Start C -> Latch B") << QByteArray("1234AB") << v; + v.clear(); + v.appendMSB(1680, 11); + v.appendMSB(1304, 11); + v.appendMSB(1112, 11); + v.appendMSB(1502, 11); + v.appendMSB(1436, 11); + v.appendMSB(1112, 11); + v.appendMSB(1966, 11); + v.appendMSB(6379, 13); + QTest::newRow("Start B -> Latch C") << QByteArray("AB1234") << v; + } + + void testEncode() + { + QFETCH(QByteArray, input); + QFETCH(BitVector, output); + + Code128Barcode code; + const auto v = code.encode(input); + if (v != output) { + qDebug() << "Actual :" << v; + qDebug() << "Expected:" << output; + } + QCOMPARE(v, output); + } + + void testDimension() + { + auto barcode = Prison::Barcode::create(Prison::Code128); + QVERIFY(barcode); + QCOMPARE(barcode->format(), Prison::Code128); + QCOMPARE(barcode->dimensions(), Prison::Barcode::OneDimension); + } + + void testSize() + { + auto barcode = Prison::Barcode::create(Prison::Code128); + QVERIFY(barcode); + QCOMPARE(barcode->format(), Prison::Code128); + barcode->setData(QStringLiteral("UNIT TEST")); + QCOMPARE(barcode->minimumSize(), QSize(154, 1)); + QCOMPARE(barcode->preferredSize(1), QSize(308, 50)); + QCOMPARE(barcode->preferredSize(2), QSize(154, 50)); + QCOMPARE(barcode->toImage(barcode->preferredSize(1)).size(), QSize(308, 50)); + QCOMPARE(barcode->toImage({1, 1}).isNull(), true); + } + + void testRender_data() + { + QTest::addColumn("input"); + QTest::addColumn("refName"); + + QTest::newRow("text") << QByteArray("KF5::Prison") << "code128-text.png"; + QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "code128-binary.png"; + } + + void testRender() + { + QFETCH(QByteArray, input); + QFETCH(QString, refName); + + { + auto code = Prison::Barcode::create(Prison::Code128); + QVERIFY(code); + code->setData(QString::fromLatin1(input.constData(), input.size())); + const auto img = code->toImage(code->minimumSize()); + img.save(refName); + + QImage ref(QStringLiteral(":/code128/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + { + auto code = Prison::Barcode::create(Prison::Code128); + QVERIFY(code); + code->setData(QString::fromLatin1(input.constData(), input.size())); + const auto img = code->toImage(code->minimumSize()); + img.save(refName); + + QImage ref(QStringLiteral(":/code128/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + } +}; + +QTEST_APPLESS_MAIN(Code128BarcodeTest) + +#include "code128barcodetest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix-binary.png b/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix-binary.png new file mode 100644 index 00000000..fa4b7e67 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix-binary.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix-text.png b/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix-text.png new file mode 100644 index 00000000..866f1d79 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix-text.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix.qrc b/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix.qrc new file mode 100644 index 00000000..a34edf65 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/datamatrix/datamatrix.qrc @@ -0,0 +1,10 @@ + + + + datamatrix-text.png + datamatrix-binary.png + + diff --git a/local/recipes/kde/kf6-prison/source/autotests/datamatrixtest.cpp b/local/recipes/kde/kf6-prison/source/autotests/datamatrixtest.cpp new file mode 100644 index 00000000..5d5a0a44 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/datamatrixtest.cpp @@ -0,0 +1,59 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include + +#include +#include + +using namespace Prison; + +class DataMatrixTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testRender_data() + { + QTest::addColumn("input"); + QTest::addColumn("refName"); + + QTest::newRow("text") << QByteArray("KF5::Prison") << "datamatrix-text.png"; + QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "datamatrix-binary.png"; + } + + void testRender() + { + QFETCH(QByteArray, input); + QFETCH(QString, refName); + + { + auto code = Prison::Barcode::create(Prison::DataMatrix); + QVERIFY(code); + code->setData(QString::fromLatin1(input.constData(), input.size())); + const auto img = code->toImage(code->preferredSize(1)); + img.save(refName); + + QImage ref(QStringLiteral(":/datamatrix/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + { + auto code = Prison::Barcode::create(Prison::DataMatrix); + QVERIFY(code); + code->setData(input); + const auto img = code->toImage(code->preferredSize(1)); + img.save(refName); + + QImage ref(QStringLiteral(":/datamatrix/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + } +}; + +QTEST_APPLESS_MAIN(DataMatrixTest) + +#include "datamatrixtest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/imagescannertest.cpp b/local/recipes/kde/kf6-prison/source/autotests/imagescannertest.cpp new file mode 100644 index 00000000..4de038c1 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/imagescannertest.cpp @@ -0,0 +1,64 @@ +/* + SPDX-FileCopyrightText: 2024 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include +#include +#include + +#include +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; +using namespace Prison; + +class ImageScannerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testImageScan() + { + auto result = ImageScanner::scan(QImage()); + QVERIFY(!result.hasContent()); + + QImage img(u":/qr/qr-text.png"_s); + QVERIFY(!img.isNull()); + result = ImageScanner::scan(img); + QVERIFY(result.hasContent()); + QVERIFY(result.hasText()); + QCOMPARE(result.text(), "KF5::Prison"_L1); + + result = ImageScanner::scan(img, Format::QRCode); + QCOMPARE(result.text(), "KF5::Prison"_L1); + + result = ImageScanner::scan(img, Format::Aztec); + QVERIFY(!result.hasContent()); + + img = QImage(u":/qr/qr-binary.png"_s); + QVERIFY(!img.isNull()); + result = ImageScanner::scan(img); + QVERIFY(result.hasContent()); + QVERIFY(!result.hasText()); + QVERIFY(result.hasBinaryData()); + } + + void testImageScanMT() + { + QImage img(u":/qr/qr-text.png"_s); + QEventLoop loop; + QtConcurrent::run([&img]() { + return ImageScanner::scan(img); + }).then([&loop](const ScanResult &result) { + qDebug() << result.text(); + loop.exit(); + }); + loop.exec(); + } +}; + +QTEST_GUILESS_MAIN(ImageScannerTest) + +#include "imagescannertest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/mecardtest.cpp b/local/recipes/kde/kf6-prison/source/autotests/mecardtest.cpp new file mode 100644 index 00000000..ba5682aa --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/mecardtest.cpp @@ -0,0 +1,94 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include + +#include "mecard.h" + +using namespace Prison; + +class MeCardTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testWifiParse() + { + QVariantMap map; + + { + auto m = MeCard::parse(QStringLiteral("WIFI:S:myssid;T:WPA;P:xxx123;;")); + QVERIFY(m.has_value()); + QCOMPARE(m->header(), QLatin1String("WIFI")); + QCOMPARE(m->headerView(), QLatin1String("WIFI")); + QCOMPARE(m->value(u"S"), QLatin1String("myssid")); + QCOMPARE(m->value(u"T"), QLatin1String("WPA")); + QCOMPARE(m->value(u"P"), QLatin1String("xxx123")); + map = QVariantMap( + {{QLatin1String("S"), QLatin1String("myssid")}, {QLatin1String("T"), QLatin1String("WPA")}, {QLatin1String("P"), QLatin1String("xxx123")}}); + QCOMPARE(m->toVariantMap(), map); + } + + { + auto m = MeCard::parse(QStringLiteral("MECARD:N:Doe,John;TEL:13035551212;EMAIL:john.doe@example.com;;")); + QVERIFY(m.has_value()); + QCOMPARE(m->header(), QLatin1String("MECARD")); + QCOMPARE(m->headerView(), QLatin1String("MECARD")); + QCOMPARE(m->value(u"N"), QLatin1String("Doe,John")); + QCOMPARE(m->value(u"TEL"), QLatin1String("13035551212")); + QCOMPARE(m->value(u"EMAIL"), QLatin1String("john.doe@example.com")); + map = QVariantMap({{QLatin1String("N"), QLatin1String("Doe,John")}, + {QLatin1String("TEL"), QLatin1String("13035551212")}, + {QLatin1String("EMAIL"), QLatin1String("john.doe@example.com")}}); + QCOMPARE(m->toVariantMap(), map); + } + + { + auto m = MeCard::parse(QStringLiteral("MECARD:N:Doe,John;TEL:13035551212;EMAIL:john.doe@example.com;EMAIL:null@kde.org;;")); + QVERIFY(m.has_value()); + QCOMPARE(m->header(), QLatin1String("MECARD")); + QCOMPARE(m->headerView(), QLatin1String("MECARD")); + QCOMPARE(m->value(u"N"), QLatin1String("Doe,John")); + QCOMPARE(m->value(u"TEL"), QLatin1String("13035551212")); + QCOMPARE(m->value(u"EMAIL"), QString()); + QCOMPARE(m->values(u"EMAIL"), QStringList({QLatin1String("john.doe@example.com"), QLatin1String("null@kde.org")})); + map = QVariantMap({{QLatin1String("N"), QLatin1String("Doe,John")}, + {QLatin1String("TEL"), QLatin1String("13035551212")}, + {QLatin1String("EMAIL"), QString()}, + {QLatin1String("EMAIL"), QStringList({QLatin1String("john.doe@example.com"), QLatin1String("null@kde.org")})}}); + QCOMPARE(m->toVariantMap(), map); + } + + { + auto m = MeCard::parse(QStringLiteral("WIFI:S:\\\"foo\\;bar\\\\baz\\\";P:\"ABCD\";;")); + QVERIFY(m.has_value()); + QCOMPARE(m->value(u"P"), QLatin1String("ABCD")); + QCOMPARE(m->value(u"S"), QLatin1String("\"foo;bar\\baz\"")); + map = QVariantMap({{QLatin1String("P"), QLatin1String("ABCD")}, {QLatin1String("S"), QLatin1String("\"foo;bar\\baz\"")}}); + QCOMPARE(m->toVariantMap(), map); + } + } + + void testInvalid_data() + { + QTest::addColumn("input"); + QTest::newRow("empty") << QString(); + QTest::newRow("no colon") << QStringLiteral("wifi"); + QTest::newRow("no content 1") << QStringLiteral("wifi:"); + QTest::newRow("no content 2") << QStringLiteral("wifi:;"); + QTest::newRow("no content 3") << QStringLiteral("wifi:;;"); + QTest::newRow("invalid element") << QStringLiteral("wifi:S:"); + } + void testInvalid() + { + QFETCH(QString, input); + auto m = MeCard::parse(input); + QVERIFY(!m.has_value()); + } +}; + +QTEST_APPLESS_MAIN(MeCardTest) + +#include "mecardtest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/qr/qr-binary.png b/local/recipes/kde/kf6-prison/source/autotests/qr/qr-binary.png new file mode 100644 index 00000000..ef1cf862 Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/qr/qr-binary.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/qr/qr-text.png b/local/recipes/kde/kf6-prison/source/autotests/qr/qr-text.png new file mode 100644 index 00000000..a0b41e1f Binary files /dev/null and b/local/recipes/kde/kf6-prison/source/autotests/qr/qr-text.png differ diff --git a/local/recipes/kde/kf6-prison/source/autotests/qr/qr.qrc b/local/recipes/kde/kf6-prison/source/autotests/qr/qr.qrc new file mode 100644 index 00000000..e8943593 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/qr/qr.qrc @@ -0,0 +1,11 @@ + + + + qr-text.png + qr-binary.png + + + diff --git a/local/recipes/kde/kf6-prison/source/autotests/qrtest.cpp b/local/recipes/kde/kf6-prison/source/autotests/qrtest.cpp new file mode 100644 index 00000000..17197665 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/qrtest.cpp @@ -0,0 +1,70 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include + +#include +#include + +using namespace Prison; + +class QrTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testRenderText_data() + { + QTest::addColumn("input"); + QTest::addColumn("refName"); + + QTest::newRow("text") << QStringLiteral("KF5::Prison") << "qr-text.png"; + } + + void testRenderText() + { + QFETCH(QString, input); + QFETCH(QString, refName); + + auto code = Prison::Barcode::create(Prison::QRCode); + QVERIFY(code); + code->setData(input); + const auto img = code->toImage(code->preferredSize(1)); + img.save(refName); + + QImage ref(QStringLiteral(":/qr/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testRenderBinary_data() + { + QTest::addColumn("input"); + QTest::addColumn("refName"); + + QTest::newRow("text") << QByteArray("KF5::Prison") << "qr-text.png"; + QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "qr-binary.png"; + } + + void testRenderBinary() + { + QFETCH(QByteArray, input); + QFETCH(QString, refName); + + auto code = Prison::Barcode::create(Prison::QRCode); + QVERIFY(code); + code->setData(input); + const auto img = code->toImage(code->preferredSize(1)); + img.save(refName); + + QImage ref(QStringLiteral(":/qr/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } +}; + +QTEST_APPLESS_MAIN(QrTest) + +#include "qrtest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/reedsolomontest.cpp b/local/recipes/kde/kf6-prison/source/autotests/reedsolomontest.cpp new file mode 100644 index 00000000..8b973a9f --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/reedsolomontest.cpp @@ -0,0 +1,63 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "../src/lib/bitvector_p.h" +#include "../src/lib/reedsolomon_p.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(Prison::BitVector) + +using namespace Prison; + +class ReedSolomonTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void rsTest_data() + { + QTest::addColumn("poly"); + QTest::addColumn("symCount"); + QTest::addColumn("input"); + QTest::addColumn("output"); + + BitVector in; + BitVector out; + out.appendMSB(0, 20); + QTest::newRow("empty") << (int)ReedSolomon::GF16 << 5 << in << out; + + in.clear(); + out.clear(); + in.appendMSB(0x5c, 8); + out.appendMSB(7, 6); + out.appendMSB(5, 7); + out.appendMSB(0x4d, 7); + QTest::newRow("GF16") << (int)ReedSolomon::GF16 << 5 << in << out; + } + + void rsTest() + { + QFETCH(int, poly); + QFETCH(int, symCount); + QFETCH(BitVector, input); + QFETCH(BitVector, output); + + ReedSolomon rs(poly, symCount); + const auto res = rs.encode(input); + QCOMPARE(res.size(), output.size()); + if (res != output) { + qDebug() << "Actual :" << res; + qDebug() << "Expected:" << output; + } + QCOMPARE(res, output); + } +}; + +QTEST_APPLESS_MAIN(ReedSolomonTest) + +#include "reedsolomontest.moc" diff --git a/local/recipes/kde/kf6-prison/source/autotests/zxingutiltest.cpp b/local/recipes/kde/kf6-prison/source/autotests/zxingutiltest.cpp new file mode 100644 index 00000000..d335973a --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/autotests/zxingutiltest.cpp @@ -0,0 +1,39 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "../src/lib/zxingutil_p.h" + +#include +#include +#include + +using namespace Prison; + +class ZXingUtilTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testToStdWString() + { + auto w = ZXingUtil::toStdWString(QStringLiteral("KDE")); + QCOMPARE(w.size(), 3); + QCOMPARE(w[0], 'K'); + + w = ZXingUtil::toStdWString(QByteArray("KDE")); + QCOMPARE(w.size(), 3); + QCOMPARE(w[0], 'K'); + + w = ZXingUtil::toStdWString(QByteArray("\x80\x00\x7f", 3)); + QCOMPARE(w.size(), 3); + QCOMPARE(w[0], 0x80); + QCOMPARE(w[1], 0x00); + QCOMPARE(w[2], 0x7f); + } +}; + +QTEST_APPLESS_MAIN(ZXingUtilTest) + +#include "zxingutiltest.moc" diff --git a/local/recipes/kde/kf6-prison/source/cmake/FindDmtx.cmake b/local/recipes/kde/kf6-prison/source/cmake/FindDmtx.cmake new file mode 100644 index 00000000..6175cd27 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/cmake/FindDmtx.cmake @@ -0,0 +1,60 @@ +#.rst: +# FindDmtx +# -------- +# +# Try to find the DataMatrix library. +# +# This will define the following variables: +# +# ``Dmtx_FOUND`` +# True if the datamatrix library is available. +# +# ``Dmtx_INCLUDE_DIRS`` +# the datamatrix library include dirs. +# This variable shall be passed to target_include_libraries() calls if +# the target is not used for linking. +# +# ``Dmtx_LIBRARIES`` +# the libraries used to link datamatrix. +# This can be passed to target_link_libraries instead of +# the ``Dmtx::Dmtx`` target. +# +# If ``Dmtx_FOUND`` is TRUE, the following imported target +# will be available: +# +# ``Dmtx::Dmtx`` +# The datamatrix library. +# +# Imported target since 5.27.0 +# +#============================================================================= +# SPDX-FileCopyrightText: 2010 Sune Vuorela +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +find_path(Dmtx_INCLUDE_DIRS dmtx.h) + +find_library(Dmtx_LIBRARIES NAMES dmtx) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Dmtx + FOUND_VAR Dmtx_FOUND + REQUIRED_VARS Dmtx_LIBRARIES Dmtx_INCLUDE_DIRS +) + +if(Dmtx_FOUND AND NOT TARGET Dmtx::Dmtx) + add_library(Dmtx::Dmtx UNKNOWN IMPORTED) + set_target_properties(Dmtx::Dmtx PROPERTIES + IMPORTED_LOCATION "${Dmtx_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${Dmtx_INCLUDE_DIRS}") +endif() + +mark_as_advanced(Dmtx_INCLUDE_DIRS Dmtx_LIBRARIES) + +include(FeatureSummary) +set_package_properties(Dmtx PROPERTIES + URL "https://github.com/dmtx/libdmtx" + DESCRIPTION "The Datamatrix library" +) + diff --git a/local/recipes/kde/kf6-prison/source/cmake/FindQRencode.cmake b/local/recipes/kde/kf6-prison/source/cmake/FindQRencode.cmake new file mode 100644 index 00000000..5fe9d509 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/cmake/FindQRencode.cmake @@ -0,0 +1,59 @@ +#.rst: +# FindQRencode +# ------------ +# +# Try to find the qrencode library. +# +# This will define the following variables: +# +# ``QRencode_FOUND`` +# True if the QRencode library is available. +# +# ``QRencode_INCLUDE_DIRS`` +# the QRencode library include dirs. +# This variable shall be passed to target_include_libraries() calls if +# the target is not used for linking. +# +# ``QRencode_LIBRARIES`` +# the libraries used to link QRencode. +# This can be passed to target_link_libraries instead of +# the ``QRencode::QRencode`` target. +# +# If ``QRencode_FOUND`` is TRUE, the following imported target +# will be available: +# +# ``QRencode::QRencode`` +# The QRencode library. +# +# Imported target since 5.27.0 +# +#============================================================================= +# SPDX-FileCopyrightText: 2010 Sune Vuorela +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +find_library(QRencode_LIBRARIES NAMES qrencode qrencoded) + +find_path(QRencode_INCLUDE_DIRS qrencode.h) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(QRencode + FOUND_VAR QRencode_FOUND + REQUIRED_VARS QRencode_LIBRARIES QRencode_INCLUDE_DIRS +) + +if(QRencode_FOUND AND NOT TARGET QRencode::QRencode) + add_library(QRencode::QRencode UNKNOWN IMPORTED) + set_target_properties(QRencode::QRencode PROPERTIES + IMPORTED_LOCATION "${QRencode_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${QRencode_INCLUDE_DIRS}") +endif() + +mark_as_advanced(QRencode_LIBRARIES QRencode_INCLUDE_DIRS) + +include(FeatureSummary) +set_package_properties(QRencode PROPERTIES + URL "https://fukuchi.org/works/qrencode/" + DESCRIPTION "The QRencode library" +) diff --git a/local/recipes/kde/kf6-prison/source/docs/Doxyfile.local b/local/recipes/kde/kf6-prison/source/docs/Doxyfile.local new file mode 100644 index 00000000..8566fb63 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/docs/Doxyfile.local @@ -0,0 +1,8 @@ +### KApiDox Project-specific Overrides File + +# define so that deprecated API is not skipped +PREDEFINED += \ + "PRISON_ENABLE_DEPRECATED_SINCE(x, y)=1" \ + "PRISON_BUILD_DEPRECATED_SINCE(x, y)=1" \ + "PRISON_DEPRECATED_VERSION(x, y, t)=" \ + "PRISON_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" diff --git a/local/recipes/kde/kf6-prison/source/metainfo.yaml b/local/recipes/kde/kf6-prison/source/metainfo.yaml new file mode 100644 index 00000000..c7bf739b --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/metainfo.yaml @@ -0,0 +1,20 @@ +description: Barcode abstraction layer providing uniform access to generation of barcodes +tier: 1 +type: solution +platforms: + - name: Linux + - name: FreeBSD + - name: Android + - name: Windows + - name: macOS + +portingAid: false +deprecated: false +release: true +libraries: + - cmake: "KF6::Prison" +cmakename: KF6Prison + +public_lib: true +group: Frameworks +subgroup: Tier 1 diff --git a/local/recipes/kde/kf6-prison/source/src/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/src/CMakeLists.txt new file mode 100644 index 00000000..5ab1ee21 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/CMakeLists.txt @@ -0,0 +1,17 @@ +add_subdirectory(lib) +add_subdirectory(tools) +if(TARGET Qt6::Quick) + add_subdirectory(quick) +endif() +if(TARGET Qt6::Multimedia AND TARGET ZXing::ZXing) + add_subdirectory(scanner) + if (TARGET Qt6::Quick) + add_subdirectory(scanner-quick) + endif() +endif() + +ecm_qt_install_logging_categories( + EXPORT PRISON + FILE prison.categories + DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} +) diff --git a/local/recipes/kde/kf6-prison/source/src/lib/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/src/lib/CMakeLists.txt new file mode 100644 index 00000000..e138e1bc --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/CMakeLists.txt @@ -0,0 +1,148 @@ +include(CMakePackageConfigHelpers) +if(TARGET Dmtx::Dmtx) + set(HAVE_DMTX 1) +endif() +if (TARGET ZXing::ZXing) + set(HAVE_ZXING 1) +endif() +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-prison.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-prison.h) + +add_library(KF6Prison) +add_library(KF6::Prison ALIAS KF6Prison) + +qt_extract_metatypes(KF6Prison) + +set_target_properties(KF6Prison PROPERTIES + VERSION ${PRISON_VERSION} + SOVERSION ${PRISON_SOVERSION} + EXPORT_NAME Prison +) + +target_sources(KF6Prison PRIVATE + abstractbarcode_p.cpp + aztecbarcode.cpp + aztecbarcode_p.h + barcode.cpp + barcodeutil.cpp + barcodeutil_p.h + bitvector.cpp + bitvector_p.h + code128barcode.cpp + code128barcode_p.h + code39barcode.cpp + code39barcode_p.h + code93barcode.cpp + code93barcode_p.h + mecard.cpp + mecard.h + prison.h + qrcodebarcode.cpp + qrcodebarcode_p.h + reedsolomon.cpp + reedsolomon_p.h +) +if(TARGET Dmtx::Dmtx) + target_sources(KF6Prison PRIVATE datamatrixbarcode.cpp datamatrixbarcode_p.h) +endif() +if(TARGET ZXing::ZXing) + target_sources(KF6Prison PRIVATE + pdf417barcode.cpp + pdf417barcode_p.h + zxingutil.cpp + ) +endif() +kde_source_files_enable_exceptions( + barcode.cpp + pdf417barcode.cpp + prison.cpp + zxingutil.cpp +) + +ecm_qt_declare_logging_category(KF6Prison + HEADER prison_debug.h + IDENTIFIER Prison::Log + CATEGORY_NAME kf.prison + OLD_CATEGORY_NAMES kf5.prison + DESCRIPTION "Prison (lib)" + EXPORT PRISON +) + +ecm_generate_export_header(KF6Prison + BASE_NAME Prison + GROUP_BASE_NAME KF + VERSION ${KF_VERSION} + USE_VERSION_HEADER + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS + EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} +) + +target_include_directories(KF6Prison + INTERFACE + "$" + PUBLIC + "$" # module version header +) + +target_link_libraries(KF6Prison +PUBLIC + Qt6::Gui +PRIVATE + QRencode::QRencode +) +if(TARGET Dmtx::Dmtx) + target_link_libraries(KF6Prison PRIVATE Dmtx::Dmtx) +endif() +if(TARGET ZXing::ZXing) + target_link_libraries(KF6Prison PRIVATE ZXing::ZXing) +endif() + +install(TARGETS KF6Prison EXPORT KF6PrisonTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) + +ecm_generate_headers(Prison_CamelCase_HEADERS + HEADER_NAMES + Barcode + Prison + MeCard + REQUIRED_HEADERS Prison_HEADERS + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/Prison +) + +set(_all_headers + ${Prison_HEADERS} + ${Prison_CamelCase_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/prison_export.h +) + +install( + FILES ${_all_headers} + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/Prison/Prison + COMPONENT Devel +) + +if(BUILD_QCH) + ecm_add_qch( + KF6Prison_QCH + NAME Prison + BASE_NAME KF6Prison + VERSION ${KF_VERSION} + ORG_DOMAIN org.kde + SOURCES # using only public headers, to cover only public API + ${Prison_HEADERS} + MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" + LINK_QCHS + Qt6Gui_QCH + INCLUDE_DIRS + ${CMAKE_CURRENT_BINARY_DIR} + BLANK_MACROS + PRISON_EXPORT + PRISON_DEPRECATED + PRISON_DEPRECATED_EXPORT + "PRISON_DEPRECATED_VERSION(x, y, t)" + "PRISON_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)" + TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + COMPONENT Devel + ) +endif() + diff --git a/local/recipes/kde/kf6-prison/source/src/lib/abstractbarcode_p.cpp b/local/recipes/kde/kf6-prison/source/src/lib/abstractbarcode_p.cpp new file mode 100644 index 00000000..18970bab --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/abstractbarcode_p.cpp @@ -0,0 +1,57 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#include "abstractbarcode_p.h" + +#include +#include + +using namespace Prison; + +AbstractBarcodePrivate::AbstractBarcodePrivate(Barcode::Dimensions dim) + : m_dimension(dim) +{ +} + +AbstractBarcodePrivate::~AbstractBarcodePrivate() = default; + +bool AbstractBarcodePrivate::sizeTooSmall(const QSizeF &size) const +{ + return m_cache.width() > size.width() || m_cache.height() > size.height(); +} + +bool AbstractBarcodePrivate::isEmpty() const +{ + switch (m_data.userType()) { + case QMetaType::QString: + return m_data.toString().isEmpty(); + case QMetaType::QByteArray: + return m_data.toByteArray().isEmpty(); + default: + break; + } + return true; +} + +void AbstractBarcodePrivate::recompute() +{ + if (m_cache.isNull() && !isEmpty()) { + m_cache = paintImage(); + } +} + +QSizeF AbstractBarcodePrivate::preferredSize(qreal devicePixelRatio) const +{ + switch (m_dimension) { + case Barcode::NoDimensions: + return {}; + case Barcode::OneDimension: + return QSizeF(m_cache.width() * (devicePixelRatio < 2 ? 2 : 1), std::max(m_cache.height(), 50)); + case Barcode::TwoDimensions: + return m_cache.size() * (devicePixelRatio < 2 ? 4 : 2); + } + return {}; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/abstractbarcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/abstractbarcode_p.h new file mode 100644 index 00000000..5484aee3 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/abstractbarcode_p.h @@ -0,0 +1,48 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_ABSTRACTBARCODE_P_H +#define PRISON_ABSTRACTBARCODE_P_H + +#include "barcode.h" +#include "prison.h" + +#include +#include + +namespace Prison +{ +class AbstractBarcodePrivate +{ +public: + explicit AbstractBarcodePrivate(Barcode::Dimensions dim); + virtual ~AbstractBarcodePrivate(); + + /** + * Doing the actual painting of the image + * @param size unused - will be removed in KF6 + * @return image with barcode, or null image + */ + virtual QImage paintImage() = 0; + + /** @see Barcode::preferredSize */ + virtual QSizeF preferredSize(qreal devicePixelRatio) const; + + bool isEmpty() const; + bool sizeTooSmall(const QSizeF &size) const; + void recompute(); + + QVariant m_data; + QImage m_cache; + QColor m_foreground = Qt::black; + QColor m_background = Qt::white; + Barcode::Dimensions m_dimension = Barcode::NoDimensions; + Prison::BarcodeType m_format; +}; + +} + +#endif diff --git a/local/recipes/kde/kf6-prison/source/src/lib/aztecbarcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/aztecbarcode.cpp new file mode 100644 index 00000000..7fddd123 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/aztecbarcode.cpp @@ -0,0 +1,714 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "aztecbarcode_p.h" +#include "barcodeutil_p.h" +#include "bitvector_p.h" +#include "prison_debug.h" +#include "reedsolomon_p.h" + +#include +#include + +#include +#include + +// see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc + +using namespace Prison; + +enum { + FullMaxSize = 151, + FullRadius = 74, + FullGridInterval = 16, + FullModeMessageSize = 40, + FullLayerCount = 32, + + CompactMaxSize = 27, + CompactRadius = 13, + CompactModeMessageSize = 28, + CompactLayerCount = 4, +}; + +AztecBarcode::AztecBarcode() + : AbstractBarcodePrivate(Barcode::TwoDimensions) +{ +} +AztecBarcode::~AztecBarcode() = default; + +// encoding properties depending on layer count +struct aztec_layer_property_t { + uint8_t layer; + uint8_t codeWordSize; + uint16_t gf; +}; + +static const aztec_layer_property_t aztec_layer_properties[] = {{2, 6, ReedSolomon::GF64}, + {8, 8, ReedSolomon::GF256}, + {22, 10, ReedSolomon::GF1024}, + {32, 12, ReedSolomon::GF4096}}; + +// amounts of bits in an Aztec code depending on layer count +static int aztecCompactDataBits(int layer) +{ + return (88 + 16 * layer) * layer; +} + +static int aztecFullDataBits(int layer) +{ + return (112 + 16 * layer) * layer; +} + +QImage AztecBarcode::paintImage() +{ + const auto inputData = aztecEncode(BarCodeUtil::asLatin1ByteArray(m_data)); + + int layerCount = 0; + int codewordCount = 0; + int availableBits = 0; + int stuffSize = 0; // extra bits added during bit stuffing, which might make us overrun the available size + bool compactMode = false; + BitVector encodedData; + + do { + layerCount = 0; + // find the smallest layout we can put the data in, while leaving 23% for error correction + for (auto i = 1; i <= FullLayerCount; ++i) { + if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) { + layerCount = i; + break; + } + } + for (auto i = 1; i <= CompactLayerCount; ++i) { + if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) { + layerCount = i; + compactMode = true; + break; + } + } + if (layerCount == 0) { + qCWarning(Log) << "data too large for Aztec code" << inputData.size(); + return {}; + } + + // determine code word size + const auto propIt = std::lower_bound(aztec_layer_properties, aztec_layer_properties + 4, layerCount, [](const aztec_layer_property_t &lhs, int rhs) { + return lhs.layer < rhs; + }); + + // bit stuffing + auto stuffedData = bitStuffAndPad(inputData, (*propIt).codeWordSize); + stuffSize = stuffedData.size() - inputData.size(); + + availableBits = compactMode ? aztecCompactDataBits(layerCount) : aztecFullDataBits(layerCount); + codewordCount = stuffedData.size() / (*propIt).codeWordSize; + const auto rsWordCount = availableBits / (*propIt).codeWordSize - codewordCount; + + // compute error correction + ReedSolomon rs((*propIt).gf, rsWordCount); + const auto rsData = rs.encode(stuffedData); + + // pad with leading 0 bits to align to code word boundaries + encodedData.reserve(availableBits); + if (int diff = availableBits - stuffedData.size() - rsData.size()) { + encodedData.appendMSB(0, diff); + } + encodedData.append(stuffedData); + encodedData.append(rsData); + + // try again in the rare case that we overrun the available bits due to bit stuffing and padding + } while (encodedData.size() > availableBits); + + // determine mode message + BitVector modeMsg; + if (compactMode) { + modeMsg.appendMSB(layerCount - 1, 2); + modeMsg.appendMSB(codewordCount - 1, 6); + ReedSolomon rs(ReedSolomon::GF16, 5); + modeMsg.append(rs.encode(modeMsg)); + } else { + modeMsg.appendMSB(layerCount - 1, 5); + modeMsg.appendMSB(codewordCount - 1, 11); + ReedSolomon rs(ReedSolomon::GF16, 6); + modeMsg.append(rs.encode(modeMsg)); + } + + // render the result + if (compactMode) { + QImage img(CompactMaxSize, CompactMaxSize, QImage::Format_RGB32); + img.fill(m_background); + paintCompactGrid(&img); + paintCompactData(&img, encodedData, layerCount); + paintCompactModeMessage(&img, modeMsg); + return cropAndScaleCompact(&img, layerCount); + } else { + QImage img(FullMaxSize, FullMaxSize, QImage::Format_RGB32); + img.fill(m_background); + paintFullGrid(&img); + paintFullData(&img, encodedData, layerCount); + paintFullModeMessage(&img, modeMsg); + return cropAndScaleFull(&img, layerCount); + } +} + +// code points and encoding modes for each of the first 127 ASCII characters, the rest is encoded in Binary mode +enum Mode { + NoMode, + Upper, + Lower, + Mixed, + Punct, + Digit, + Binary, + MODE_COUNT, + Special, +}; + +enum SpecialChar { + Space, + CarriageReturn, + Comma, + Dot, + SPECIAL_CHAR_COUNT, +}; + +struct aztec_code_t { + uint8_t code; + uint8_t mode; +}; + +static const aztec_code_t aztec_code_table[] = { + {0, Binary}, // 0 + {2, Mixed}, {3, Mixed}, {4, Mixed}, + {5, Mixed}, {6, Mixed}, {7, Mixed}, + {8, Mixed}, // 7 BEL \a + {9, Mixed}, {10, Mixed}, {11, Mixed}, // 10 LF / ^J + {12, Mixed}, {13, Mixed}, {CarriageReturn, Special}, // 13 CR / ^M - but also 1 Punct + {14, Binary}, {15, Binary}, {16, Binary}, + {17, Binary}, {18, Binary}, {19, Binary}, + {20, Binary}, // 20 ^T + {21, Binary}, {22, Binary}, {23, Binary}, + {24, Binary}, {25, Binary}, {26, Binary}, + {15, Mixed}, // 27 ^[ + {16, Mixed}, {17, Mixed}, {18, Mixed}, // 30 ^^ + {19, Mixed}, {Space, Special}, // 32 SP + {6, Punct}, {7, Punct}, {8, Punct}, // 35 # + {9, Punct}, {10, Punct}, {11, Punct}, + {12, Punct}, {13, Punct}, // 40 ( + {14, Punct}, {15, Punct}, {16, Punct}, // 43 + + {Comma, Special}, // 44 , + {18, Punct}, // 45 - + {Dot, Special}, // 46 . + {20, Punct}, // 47 / + {2, Digit}, // 48 0 + {3, Digit}, {4, Digit}, {5, Digit}, + {6, Digit}, {7, Digit}, {8, Digit}, + {9, Digit}, {10, Digit}, {11, Digit}, // 57 9 + {21, Punct}, // 58 : + {22, Punct}, // 59 ; + {23, Punct}, // 60 < + {24, Punct}, {25, Punct}, // 62 > + {26, Punct}, // 63 ? + {20, Mixed}, // 64 @ + {2, Upper}, // 65 A + {3, Upper}, {4, Upper}, {5, Upper}, + {6, Upper}, {7, Upper}, {8, Upper}, + {9, Upper}, {10, Upper}, {11, Upper}, + {12, Upper}, {13, Upper}, {14, Upper}, + {15, Upper}, {16, Upper}, {17, Upper}, + {18, Upper}, {19, Upper}, {20, Upper}, + {21, Upper}, {22, Upper}, {23, Upper}, + {24, Upper}, {25, Upper}, {26, Upper}, + {27, Upper}, // 90 Z + {27, Punct}, // 91 [ + {21, Mixed}, // 92 backslash + {28, Punct}, // 93 ] + {22, Mixed}, // 94 ^ + {23, Mixed}, // 95 _ + {24, Mixed}, // 96 ` + {2, Lower}, // 97 a + {3, Lower}, {4, Lower}, {5, Lower}, + {6, Lower}, {7, Lower}, {8, Lower}, + {9, Lower}, {10, Lower}, {11, Lower}, + {12, Lower}, {13, Lower}, {14, Lower}, + {15, Lower}, {16, Lower}, {17, Lower}, + {18, Lower}, {19, Lower}, {20, Lower}, + {21, Lower}, {22, Lower}, {23, Lower}, + {24, Lower}, {25, Lower}, {26, Lower}, + {27, Lower}, // 122 z + {29, Punct}, // 123 { + {25, Mixed}, // 124 | + {30, Punct}, // 125 } + {26, Mixed}, // 126 ~ + {27, Mixed} // 127 DEL ^? +}; +Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256); + +static const struct { + uint8_t c1; + uint8_t c2; + aztec_code_t sym; +} aztec_code_double_symbols[] = { + {'\r', '\n', {2, Punct}}, // CR LF + {'.', ' ', {3, Punct}}, // . SP + {',', ' ', {4, Punct}}, // , SP + {':', ' ', {5, Punct}} // : SP +}; + +static const int aztec_code_size[] = {0, 5, 5, 5, 5, 4, 8}; +Q_STATIC_ASSERT(sizeof(aztec_code_size) / sizeof(int) == MODE_COUNT); + +// codes for ambiguous characters, ie. those that can be encoded in multiple modes +static const aztec_code_t aztec_special_chars[SPECIAL_CHAR_COUNT][MODE_COUNT] = { + /* NoMode Upper Lower Mixed Punct Digit Binary */ + {{0, NoMode}, {1, Upper}, {1, Lower}, {1, Mixed}, {1, Upper}, {1, Digit}, {0, NoMode}}, /* SP */ + {{0, NoMode}, {1, Punct}, {1, Punct}, {14, Mixed}, {1, Punct}, {1, Punct}, {0, NoMode}}, /* CR */ + {{0, NoMode}, {17, Punct}, {17, Punct}, {17, Punct}, {17, Punct}, {12, Digit}, {0, NoMode}}, /* Comma */ + {{0, NoMode}, {19, Punct}, {19, Punct}, {19, Punct}, {19, Punct}, {13, Digit}, {0, NoMode}}, /* Dot */ +}; + +// shift code table, source mode -> target mode +// NoMode indicates shift is not available, use latch instead +static const aztec_code_t aztec_shift_codes[MODE_COUNT - 1][MODE_COUNT - 1] = { + /* NoMode Upper Lower Mixed Punct Digit */ + {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}}, + {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}, + {{0, NoMode}, {28, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}, + {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}, + {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}}, + {{0, NoMode}, {15, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}}; + +// latch code table, source mode -> target mode +static const aztec_code_t aztec_latch_codes[MODE_COUNT - 1][MODE_COUNT] = { + /* NoMode Upper Lower Mixed Punct Digit Binary */ + {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}}, + {{0, NoMode}, {0, NoMode}, {28, Lower}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}}, + {{0, NoMode}, {30, Digit}, {0, NoMode}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}}, + {{0, NoMode}, {29, Upper}, {28, Lower}, {0, NoMode}, {30, Punct}, {28, Lower}, {31, Binary}}, + {{0, NoMode}, {31, Upper}, {31, Upper}, {31, Upper}, {0, NoMode}, {31, Upper}, {31, Upper}}, + {{0, NoMode}, {14, Upper}, {14, Upper}, {14, Upper}, {14, Upper}, {0, NoMode}, {14, Upper}}}; + +static Mode aztecCodeLatchTo(Mode currentMode, Mode targetMode, BitVector *v) +{ + if (currentMode == targetMode) { + return targetMode; + } + const auto latchCode = aztec_latch_codes[currentMode][targetMode]; + qCDebug(Log) << "latch" << latchCode.code << aztec_code_size[currentMode]; + v->appendMSB(latchCode.code, aztec_code_size[currentMode]); + return static_cast(latchCode.mode); +} + +static void aztecEncodeBinary(std::vector::iterator &it, const std::vector::iterator &end, BitVector *v) +{ + // determine length of the binary sequence + const auto binEndIt = std::find_if(it, end, [](aztec_code_t sym) { + return sym.mode != Binary; + }); + const auto length = std::distance(it, binEndIt); + + // write length field + qCDebug(Log) << "binary length" << length; + if (length < 32) { + v->appendMSB(length, 5); + } else { + v->appendMSB(0, 5); + v->appendMSB(length - 31, 11); + } + + // write data + for (; it != binEndIt; ++it) { + qCDebug(Log) << "binary data" << (*it).code; + v->appendMSB((*it).code, 8); + } +} + +static void aztecEncodeResolveAmbigious(Mode currentMode, const std::vector::iterator &begin, const std::vector::iterator &end) +{ + Q_ASSERT(begin != end); + Q_ASSERT(currentMode != (*begin).mode); + Q_ASSERT((*begin).mode == Special); + + // forward search + auto it = begin; + for (; it != end && (*it).mode == Special; ++it) { + if (aztec_special_chars[(*it).code][currentMode].mode == currentMode) { + qCDebug(Log) << "special resolved to current mode by forward search"; + (*it).mode = aztec_special_chars[(*it).code][currentMode].mode; + (*it).code = aztec_special_chars[(*it).code][currentMode].code; + } + } + + // backward search + auto backIt = it; + while (std::distance(begin, backIt) >= 1 && it != end) { + --backIt; + if ((*backIt).mode == Special && aztec_special_chars[(*backIt).code][(*it).mode].mode == (*it).mode) { + qCDebug(Log) << "special resolved by backward search"; + (*backIt).mode = aztec_special_chars[(*backIt).code][(*it).mode].mode; + (*backIt).code = aztec_special_chars[(*backIt).code][(*it).mode].code; + } else { + break; + } + } + + // pick one if we still have an ambiguous symbol + if ((*begin).mode != Special) { + return; + } + (*begin).mode = aztec_special_chars[(*begin).code][currentMode].mode; + (*begin).code = aztec_special_chars[(*begin).code][currentMode].code; + it = begin + 1; + if (it != end && (*it).mode == Special) { + aztecEncodeResolveAmbigious(static_cast((*begin).mode), it, end); + } +} + +static Mode aztecNextMode(Mode currentMode, const std::vector::iterator &nextSym, const std::vector::iterator &end, bool &tryShift) +{ + Q_ASSERT(currentMode != (*nextSym).mode); + Q_ASSERT(nextSym != end); + Q_ASSERT((*nextSym).mode != Special); + auto it = nextSym; + ++it; + if (it != end && (*it).mode == Special) { + aztecEncodeResolveAmbigious(static_cast((*nextSym).mode), it, end); + } + + if ((it == end || (*it).mode == currentMode) && std::distance(nextSym, it) == 1) { + tryShift = true; + } + + qCDebug(Log) << currentMode << (*nextSym).mode << tryShift << std::distance(nextSym, it); + return static_cast((*nextSym).mode); +} + +BitVector AztecBarcode::aztecEncode(const QByteArray &data) const +{ + // phase one: translate single and double chars to code points + std::vector codes; + codes.reserve(data.size()); + for (int i = 0; i < data.size(); ++i) { + const uint8_t c1 = data.at(i); + // double char codes + if (i < data.size() - 1) { + const uint8_t c2 = data.at(i + 1); + bool found = false; + for (const auto &dblCode : aztec_code_double_symbols) { + if (dblCode.c1 != c1 || dblCode.c2 != c2) { + continue; + } + codes.push_back(dblCode.sym); + ++i; + found = true; + } + if (found) { + continue; + } + } + + // > 127 binary-only range + if (c1 > 127) { + codes.push_back({c1, Binary}); + // encodable single ASCII character + } else { + codes.push_back(aztec_code_table[c1]); + } + } + + // phase two: insert shift and latch codes, translate to bit stream + Mode currentMode = Upper; + BitVector result; + for (auto it = codes.begin(); it != codes.end();) { + if ((*it).mode == Binary) { + auto newMode = aztecCodeLatchTo(currentMode, Binary, &result); + while (newMode != Binary) { + currentMode = newMode; + newMode = aztecCodeLatchTo(currentMode, Binary, &result); + } + aztecEncodeBinary(it, codes.end(), &result); + continue; + } + // resolve special codes + if ((*it).mode == Special) { + aztecEncodeResolveAmbigious(currentMode, it, codes.end()); + } + + // deal with mode changes + Mode nextMode = currentMode; + if ((*it).mode != currentMode) { + bool tryShift = false; + const auto newMode = aztecNextMode(currentMode, it, codes.end(), tryShift); + + // shift to new mode if desired and possible + if (tryShift && aztec_shift_codes[currentMode][newMode].mode != NoMode) { + qCDebug(Log) << "shift" << aztec_shift_codes[currentMode][newMode].code << aztec_code_size[currentMode]; + result.appendMSB(aztec_shift_codes[currentMode][newMode].code, aztec_code_size[currentMode]); + currentMode = newMode; + } + + // latch to new mode + while (currentMode != newMode && newMode != NoMode && currentMode != NoMode) { + currentMode = aztecCodeLatchTo(currentMode, newMode, &result); + nextMode = currentMode; + } + } + + qCDebug(Log) << (*it).code << aztec_code_size[currentMode]; + result.appendMSB((*it).code, aztec_code_size[currentMode]); + ++it; + + currentMode = nextMode; + } + + return result; +} + +BitVector AztecBarcode::bitStuffAndPad(const BitVector &input, int codeWordSize) const +{ + BitVector res; + res.reserve(input.size()); + + // bit stuff codewords with leading codeWordSize 0/1 bits + int i = 0; + while (i < input.size() - (codeWordSize - 1)) { + int v = input.valueAtMSB(i, codeWordSize - 1); + res.appendMSB(v, codeWordSize - 1); + i += codeWordSize - 1; + if (v == 0) { + res.appendBit(true); + } else if (v == (1 << (codeWordSize - 1)) - 1) { + res.appendBit(false); + } else { + res.appendBit(input.at(i++)); + } + } + while (i < input.size()) { + res.appendBit(input.at(i++)); + } + + // check if we are code word aligned already + const auto trailingBits = res.size() % codeWordSize; + if (!trailingBits) { // nothing to pad + return res; + } + + // pad with ones to nearest code word boundary + // last bit has to be zero if we'd otherwise would have all ones though + bool allOnes = true; + for (int i = res.size() - trailingBits; i < res.size(); ++i) { + allOnes &= res.at(i); + } + while (res.size() % codeWordSize) { + if ((res.size() % codeWordSize) == (codeWordSize - 1)) { + res.appendBit(allOnes ? false : true); + } else { + res.appendBit(true); + } + } + + return res; +} + +void AztecBarcode::paintFullGrid(QImage *img) const +{ + QPainter p(img); + p.translate(img->width() / 2, img->height() / 2); + + // alignment grids + QPen pen(m_foreground); + pen.setDashPattern({1, 1}); + p.setPen(pen); + for (int i = 0; i < img->width() / 2; i += FullGridInterval) { + p.drawLine(-i, -FullRadius, -i, FullRadius); + p.drawLine(i, -FullRadius, i, FullRadius); + p.drawLine(-FullRadius, -i, FullRadius, -i); + p.drawLine(-FullRadius, i, FullRadius, i); + } + + // bullseye background + p.setBrush(m_background); + p.setPen(Qt::NoPen); + p.drawRect(-7, -7, 14, 14); + + // bullseye + p.setBrush(Qt::NoBrush); + p.setPen(m_foreground); + p.drawPoint(0, 0); + p.drawRect(-2, -2, 4, 4); + p.drawRect(-4, -4, 8, 8); + p.drawRect(-6, -6, 12, 12); + + // bullseye orientation marker + p.drawRect(-7, -7, 1, 1); + p.drawRect(7, -7, 0, 1); + p.drawPoint(7, 6); +} + +static const int aztecFullLayerOffset[] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21, 19, 17, 15, 13, 10, 8, 6, 4, 2, 0}; + +void AztecBarcode::paintFullData(QImage *img, const BitVector &data, int layerCount) const +{ + QPainter p(img); + p.setPen(m_foreground); + + auto it = data.begin(); + for (int layer = layerCount - 1; layer >= 0; --layer) { + const auto x1 = aztecFullLayerOffset[layer]; + const auto y1 = x1; + const auto gridInMiddle = (x1 - FullRadius) % FullGridInterval == 0; + const auto x2 = gridInMiddle ? x1 + 2 : x1 + 1; + const auto segmentLength = FullMaxSize - 2 * y1 - 2 - (gridInMiddle ? 1 : 0); + + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(-90 * rotation); + p.translate(-img->width() / 2, -img->height() / 2); + + for (int i = 0; it != data.end(); ++i, ++it) { + const auto x = (i % 2 == 0) ? x1 : x2; + auto y = i / 2 + y1; + if (((y - FullRadius - 1) % FullGridInterval) == 0) { // skip grid lines + ++y; + i += 2; + } + if (y >= y1 + segmentLength) { + break; + } + if (*it) { + p.drawPoint(x, y); + } + } + } + } +} + +void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const +{ + Q_ASSERT(modeData.size() == FullModeMessageSize); + + QPainter p(img); + p.setPen(m_foreground); + + auto it = modeData.begin(); + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(90 * rotation); + + for (int i = -5; i <= 5; ++i) { + if (i == 0) { // skip grid line + continue; + } + if (*it) { + p.drawPoint(i, -7); + } + ++it; + } + } +} + +QImage AztecBarcode::cropAndScaleFull(QImage *img, int layerCount) +{ + const auto offset = aztecFullLayerOffset[layerCount - 1]; + const auto minSize = FullMaxSize - 2 * offset; + + QImage out(minSize, minSize, img->format()); + QPainter p(&out); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset); + p.drawImage(out.rect(), *img, srcRect); + return out; +} + +void AztecBarcode::paintCompactGrid(QImage *img) const +{ + QPainter p(img); + p.translate(img->width() / 2, img->height() / 2); + + // bullseye + p.setPen(m_foreground); + p.drawPoint(0, 0); + p.drawRect(-2, -2, 4, 4); + p.drawRect(-4, -4, 8, 8); + + // bullseye orientation marker + p.drawRect(-5, -5, 1, 1); + p.drawRect(5, -5, 0, 1); + p.drawPoint(5, 4); +} + +static const int aztecCompactLayerOffset[] = {6, 4, 2, 0}; + +void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int layerCount) const +{ + QPainter p(img); + p.setPen(m_foreground); + + auto it = data.begin(); + for (int layer = layerCount - 1; layer >= 0; --layer) { + const auto x1 = aztecCompactLayerOffset[layer]; + const auto y1 = x1; + const auto x2 = x1 + 1; + const auto segmentLength = CompactMaxSize - 2 * y1 - 2; + + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(-90 * rotation); + p.translate(-img->width() / 2, -img->height() / 2); + + for (int i = 0; it != data.end(); ++i, ++it) { + const auto x = (i % 2 == 0) ? x1 : x2; + auto y = i / 2 + y1; + if (y >= y1 + segmentLength) { + break; + } + if (*it) { + p.drawPoint(x, y); + } + } + } + } +} + +void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const +{ + Q_ASSERT(modeData.size() == CompactModeMessageSize); + + QPainter p(img); + p.setPen(m_foreground); + + auto it = modeData.begin(); + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(90 * rotation); + + for (int i = -3; i <= 3; ++i) { + if (*it) { + p.drawPoint(i, -5); + } + ++it; + } + } +} + +QImage AztecBarcode::cropAndScaleCompact(QImage *img, int layerCount) +{ + const auto offset = aztecCompactLayerOffset[layerCount - 1]; + const auto minSize = CompactMaxSize - 2 * offset; + + QImage out(minSize, minSize, img->format()); + QPainter p(&out); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset); + p.drawImage(out.rect(), *img, srcRect); + return out; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/aztecbarcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/aztecbarcode_p.h new file mode 100644 index 00000000..77e994cd --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/aztecbarcode_p.h @@ -0,0 +1,47 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_AZTECBARCODE_H +#define PRISON_AZTECBARCODE_H + +#include "abstractbarcode_p.h" + +class AztecBarcodeTest; + +namespace Prison +{ +class BitVector; + +/** Aztec code generator. */ +class AztecBarcode : public AbstractBarcodePrivate +{ +public: + AztecBarcode(); + ~AztecBarcode() override; + +protected: + QImage paintImage() override; + +private: + friend class ::AztecBarcodeTest; + + BitVector aztecEncode(const QByteArray &data) const; + BitVector bitStuffAndPad(const BitVector &input, int codeWordSize) const; + + void paintFullGrid(QImage *img) const; + void paintFullData(QImage *img, const BitVector &data, int layerCount) const; + void paintFullModeMessage(QImage *img, const BitVector &modeData) const; + QImage cropAndScaleFull(QImage *img, int layerCount); + + void paintCompactGrid(QImage *img) const; + void paintCompactData(QImage *img, const BitVector &data, int layerCount) const; + void paintCompactModeMessage(QImage *img, const BitVector &modeData) const; + QImage cropAndScaleCompact(QImage *img, int layerCount); +}; + +} + +#endif // PRISON_AZTECCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/barcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/barcode.cpp new file mode 100644 index 00000000..bd11cbbc --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/barcode.cpp @@ -0,0 +1,172 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + SPDX-FileCopyrightText: 2023 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include + +#include "barcode.h" + +#include "abstractbarcode_p.h" +#include "aztecbarcode_p.h" +#include "code128barcode_p.h" +#include "code39barcode_p.h" +#include "code93barcode_p.h" +#include "datamatrixbarcode_p.h" +#include "pdf417barcode_p.h" +#include "qrcodebarcode_p.h" +#if HAVE_ZXING +#include "zxingonedbarcode_p.h" +#endif + +#include +#include +#include + +using namespace Prison; + +std::optional Barcode::create(Prison::BarcodeType format) +{ + std::unique_ptr d; + switch (format) { + case Prison::QRCode: + d = std::make_unique(); + break; + case Prison::DataMatrix: +#if HAVE_DMTX + d = std::make_unique(); +#endif + break; + case Prison::Aztec: + d = std::make_unique(); + break; + case Prison::Code39: + d = std::make_unique(); + break; + case Prison::Code93: + d = std::make_unique(); + break; + case Prison::Code128: + d = std::make_unique(); + break; + case Prison::PDF417: +#if HAVE_ZXING + d = std::make_unique(); +#endif + break; + case Prison::EAN13: +#if HAVE_ZXING + d = std::make_unique>(); +#endif + break; + } + + if (d) { + d->m_format = format; + return Barcode(std::move(d)); + } + return std::nullopt; +} + +Barcode::Barcode(std::unique_ptr &&dd) + : d(std::move(dd)) +{ +} + +Barcode::Barcode(Barcode &&) = default; +Barcode::~Barcode() = default; +Barcode &Barcode::operator=(Barcode &&) = default; + +Prison::BarcodeType Barcode::format() const +{ + return d->m_format; +} + +QString Barcode::data() const +{ + return d->m_data.userType() == QMetaType::QString ? d->m_data.toString() : QString(); +} + +QByteArray Barcode::byteArrayData() const +{ + return d->m_data.userType() == QMetaType::QByteArray ? d->m_data.toByteArray() : QByteArray(); +} + +QImage Barcode::toImage(const QSizeF &size) +{ + d->recompute(); + if (d->m_cache.isNull() || d->sizeTooSmall(size)) { + return QImage(); + } + + // scale to the requested size, using only full integer factors to keep the code readable + int scaleX = std::max(1, size.width() / d->m_cache.width()); + int scaleY = std::max(1, size.height() / d->m_cache.height()); + if (dimensions() == TwoDimensions) { + scaleX = scaleY = std::min(scaleX, scaleY); + } + + QImage out(d->m_cache.width() * scaleX, d->m_cache.height() * scaleY, d->m_cache.format()); + QPainter p(&out); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + p.drawImage(out.rect(), d->m_cache, d->m_cache.rect()); + return out; +} + +void Barcode::setData(const QString &data) +{ + if (d) { + d->m_data = data; + d->m_cache = QImage(); + } +} + +void Barcode::setData(const QByteArray &data) +{ + d->m_data = data; + d->m_cache = QImage(); +} + +QSizeF Barcode::minimumSize() const +{ + d->recompute(); + return d->m_cache.size(); +} + +QSizeF Barcode::preferredSize(qreal devicePixelRatio) const +{ + d->recompute(); + return d->preferredSize(devicePixelRatio); +} + +QColor Barcode::backgroundColor() const +{ + return d->m_background; +} + +QColor Barcode::foregroundColor() const +{ + return d->m_foreground; +} + +void Barcode::setBackgroundColor(const QColor &backgroundcolor) +{ + if (backgroundcolor != backgroundColor()) { + d->m_background = backgroundcolor; + d->m_cache = QImage(); + } +} + +void Barcode::setForegroundColor(const QColor &foregroundcolor) +{ + if (foregroundcolor != foregroundColor()) { + d->m_foreground = foregroundcolor; + d->m_cache = QImage(); + } +} + +Barcode::Dimensions Barcode::dimensions() const +{ + return d->m_dimension; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/barcode.h b/local/recipes/kde/kf6-prison/source/src/lib/barcode.h new file mode 100644 index 00000000..b2f480c6 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/barcode.h @@ -0,0 +1,155 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + SPDX-FileCopyrightText: 2023 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_BARCODE_H +#define PRISON_BARCODE_H + +#include "prison_export.h" + +#include "prison.h" +#include + +#include + +class QByteArray; +class QColor; +class QImage; +class QSizeF; +class QString; + +namespace Prison +{ + +class AbstractBarcodePrivate; + +/** + * A barcode generator for a fixed barcode format. + * + * @note This replaces Prison::createBarcode and AbstractBarcode* from KF5. + * You can create Barcode instances directly now and specify the format in its + * constructor. + * Rather than checking for createBarcode returning a @c nullptr, check whether + * the format is not Prison::Null. + * + * @since 6.0 + */ +class PRISON_EXPORT Barcode +{ +public: + Barcode(Barcode &&); + ~Barcode(); + Barcode &operator=(Barcode &&); + + /** Barcode format of this barcode generator. */ + Prison::BarcodeType format() const; + + /** + * Textual content encoded in this barcode. + * This returns an empty QString if binary content is set. + * @see byteArrayData() + */ + QString data() const; + /** + * Binary data encoded in this barcode. + * This returns an empty QByteArray if textual content is set. + * @see data() + * @since 5.85 + */ + QByteArray byteArrayData() const; + /** + * Sets textual data to be drawn as a barcode. + * Only use this function if your content is textual, use the QByteArray overload + * when your content contains non-textual binary content. + * Calling this function does not do any repaints of anything, they are + * your own responsibility. + * @param data textual barcode content + */ + void setData(const QString &data); + /** + * Sets binary data to be drawn as a barcode. + * Prefer the QString overload if your content is purely textual, to reduce + * the risk of encoding issues for non-ASCII content. + * Calling this function does not do any repaints of anything, they are + * your own responsibility. + * @param data binary barcode content + * @since 5.85 + */ + void setData(const QByteArray &data); + /** + * Creates a image with a barcode on + * @return QImage with a barcode on, trying to match the requested \param size + * + * If one of the dimensions of @param size is smaller than the matching dimension in \ref minimumSize, + * a null QImage will be returned + */ + QImage toImage(const QSizeF &size); + + /** + * The minimal amount of pixels needed to represent this barcode without loss of information. + * That is, the size of the barcode image if each line or dot is just one pixel wide. + * On normal screens that is not enough for barcode scanners to reliably detect the barcode + * though. + * @see preferredSize + */ + QSizeF minimumSize() const; + + /** + * The recommended size for this barcode when shown on a screen. + * This is typically significantly larger than trueMinimumSize() so that + * barcode scanners tend to reliably detect the code. As this depends + * on the physical resolution of the output, you have to pass the device + * pixel ration of the output screen here. + * @param devicePixelRatio The device pixel ratio of the screen this is shown on. + * @see trueMinimumSize + * @since 5.69 + */ + QSizeF preferredSize(qreal devicePixelRatio) const; + + /** + * @return the foreground color (by default black) to be used for the barcode. + */ + QColor foregroundColor() const; + /** + * @return the background color (by default white) to be used for the barcode. + */ + QColor backgroundColor() const; + /** + * sets the foreground color + * @param foregroundcolor - the new foreground color + */ + void setForegroundColor(const QColor &foregroundcolor); + /** + * sets the background color + * @param backgroundcolor - the new background color + */ + void setBackgroundColor(const QColor &backgroundcolor); + + /** Dimensions of the barcode. */ + enum Dimensions : uint8_t { + NoDimensions, ///< Null barcode. + OneDimension, ///< One-dimensional barcode. + TwoDimensions, ///< 2D matrix code. + }; + + /** Returns the amount of dimensions of the barcode. */ + Dimensions dimensions() const; + + /** Create a new barcode generator. + * + * If a format is requested that is not supported by the current build + * due to missing/disabled optional dependencies, Barcode::format() will + * return Prison::Null. + */ + static std::optional create(Prison::BarcodeType type); + +private: + friend class AbstractBarcodePrivate; + explicit Barcode(std::unique_ptr &&d); + std::unique_ptr d; +}; +} + +#endif diff --git a/local/recipes/kde/kf6-prison/source/src/lib/barcodeutil.cpp b/local/recipes/kde/kf6-prison/source/src/lib/barcodeutil.cpp new file mode 100644 index 00000000..47a21747 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/barcodeutil.cpp @@ -0,0 +1,29 @@ +/* + SPDX-FileCopyrightText: 2020 Laurent Montel + + SPDX-License-Identifier: MIT +*/ +#include "barcodeutil_p.h" + +#include + +using namespace Prison; + +QList BarCodeUtil::barSequence(const char *str) +{ + Q_ASSERT(strlen(str) == 9); // this is a internal helper tool, only called with fixed strings in here, all 9 chars long + QList ret; + for (int i = 0; i < 9; i++) { + ret.append(str[i] == '1'); + Q_ASSERT(str[i] == '0' || str[i] == '1'); + } + return ret; +} + +QByteArray BarCodeUtil::asLatin1ByteArray(const QVariant &data) +{ + if (data.typeId() == QMetaType::QString) { + return data.toString().toLatin1(); + } + return data.toByteArray(); +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/barcodeutil_p.h b/local/recipes/kde/kf6-prison/source/src/lib/barcodeutil_p.h new file mode 100644 index 00000000..f03b53e0 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/barcodeutil_p.h @@ -0,0 +1,20 @@ +/* + SPDX-FileCopyrightText: 2020 Laurent Montel + + SPDX-License-Identifier: MIT +*/ + +#ifndef BARCODEUTIL_H +#define BARCODEUTIL_H +#include +namespace Prison +{ +namespace BarCodeUtil +{ +QList barSequence(const char *str); + +QByteArray asLatin1ByteArray(const QVariant &data); +} +} + +#endif // BARCODEUTIL_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/bitvector.cpp b/local/recipes/kde/kf6-prison/source/src/lib/bitvector.cpp new file mode 100644 index 00000000..aaf46490 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/bitvector.cpp @@ -0,0 +1,110 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "bitvector_p.h" + +using namespace Prison; + +BitVector::BitVector() = default; +BitVector::~BitVector() = default; + +void BitVector::appendLSB(int data, int bits) +{ + for (int i = 0; i < bits; ++i) { + appendBit(data & (1 << i)); + } +} + +void BitVector::appendMSB(int data, int bits) +{ + for (int i = bits - 1; i >= 0; --i) { + appendBit(data & (1 << i)); + } +} + +void BitVector::appendBit(bool bit) +{ + const auto subIdx = m_size % 8; + if (subIdx == 0) { + m_data.append('\0'); + } + if (bit) { + m_data.data()[m_data.size() - 1] |= (1 << subIdx); + } + ++m_size; +} + +void BitVector::append(const BitVector &other) +{ + for (int i = 0; i < other.size(); ++i) { + appendBit(other.at(i)); + } +} + +bool BitVector::at(int index) const +{ + const auto majIdx = index / 8; + const auto minIdx = index % 8; + return (m_data.at(majIdx) & (1 << minIdx)) >> minIdx; +} + +void BitVector::clear() +{ + m_data.clear(); + m_size = 0; +} + +void BitVector::reserve(int size) +{ + m_data.reserve((size / 8) + 1); +} + +int BitVector::size() const +{ + return m_size; +} + +int BitVector::valueAtMSB(int index, int size) const +{ + int res = 0; + for (int i = 0; i < size; ++i) { + res = res << 1; + res |= (at(index + i) ? 1 : 0); + } + return res; +} + +BitVector::iterator BitVector::begin() const +{ + iterator it; + it.m_index = 0; + it.m_vector = this; + return it; +} + +BitVector::iterator BitVector::end() const +{ + iterator it; + it.m_index = m_size; + it.m_vector = this; + return it; +} + +bool BitVector::operator==(const BitVector &other) const +{ + return m_size == other.m_size && m_data == other.m_data; +} + +bool BitVector::operator!=(const Prison::BitVector &other) const +{ + return m_size != other.m_size || m_data != other.m_data; +} + +QDebug operator<<(QDebug dbg, const Prison::BitVector &v) +{ + dbg << v.m_data.toHex(); + return dbg; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/bitvector_p.h b/local/recipes/kde/kf6-prison/source/src/lib/bitvector_p.h new file mode 100644 index 00000000..59412ea2 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/bitvector_p.h @@ -0,0 +1,78 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_BITVECTOR_P_H +#define PRISON_BITVECTOR_P_H + +#include +#include + +namespace Prison +{ +class BitVector; +} +QDebug operator<<(QDebug dbg, const Prison::BitVector &v); + +namespace Prison +{ +/** Vector for working with a set of bits without byte alignment. */ +class BitVector +{ +public: + BitVector(); + ~BitVector(); + + class iterator + { + public: + inline bool operator!=(const iterator &other) + { + return m_index != other.m_index; + } + inline bool operator*() const + { + return m_vector->at(m_index); + } + inline iterator operator++() + { + ++m_index; + return *this; + } + + private: + friend class BitVector; + const BitVector *m_vector; + int m_index; + }; + + /** Append the lowest @p bits of @p data with the least significant bit first. */ + void appendLSB(int data, int bits); + /** Append the lowest @p bits of @p data with the most significant bit first. */ + void appendMSB(int data, int bits); + void appendBit(bool bit); + void append(const BitVector &other); + /** Returns the bit at index @p index. */ + bool at(int index) const; + void clear(); + void reserve(int size); + int size() const; + /** Returns the value starting at @p index of size @p size. */ + int valueAtMSB(int index, int size) const; + iterator begin() const; + iterator end() const; + + bool operator==(const BitVector &other) const; + bool operator!=(const BitVector &other) const; + +private: + friend QDebug(::operator<<)(QDebug dbg, const Prison::BitVector &v); + QByteArray m_data; + int m_size = 0; +}; + +} + +#endif // PRISON_BITVECTOR_P_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/code128barcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/code128barcode.cpp new file mode 100644 index 00000000..a81d0e2d --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/code128barcode.cpp @@ -0,0 +1,336 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "code128barcode_p.h" + +#include "barcodeutil_p.h" +#include "bitvector_p.h" +#include "prison_debug.h" + +#include +#include + +using namespace Prison; + +enum { + SymbolSize = 11, + StopPatternSize = 13, + StopPattern = 108, + QuietZone = 10, +}; + +enum CodeSet : uint8_t { + CodeSetA = 0, + CodeSetB = 1, + CodeSetC = 2, + CodeSetUnknown = 3, +}; + +enum CodeSetOp : uint8_t { + None = 255, + StartA = 103, + StartB = 104, + StartC = 105, + Shift = 98, + LatchA = 101, + LatchB = 100, + LatchC = 99, +}; + +Code128Barcode::Code128Barcode() + : AbstractBarcodePrivate(Barcode::OneDimension) +{ +} +Code128Barcode::~Code128Barcode() = default; + +QImage Code128Barcode::paintImage() +{ + const auto bits = encode(BarCodeUtil::asLatin1ByteArray(m_data)); + const auto width = bits.size() + 2 * QuietZone; + + QImage img(width, 1, QImage::Format_ARGB32); + img.fill(m_background); + QPainter p(&img); + for (int i = 0; i < bits.size(); ++i) { + if (bits.at(i)) { + img.setPixel(QuietZone + i, 0, m_foreground.rgb()); + } + } + + return img; +} + +// Code 128 symbol table +static const uint16_t code128_symbols[] = { + 0b11011001100, // 0 + 0b11001101100, + 0b11001100110, + 0b10010011000, + 0b10010001100, + 0b10001001100, + 0b10011001000, + 0b10011000100, + 0b10001100100, + 0b11001001000, + 0b11001000100, // 10 + 0b11000100100, + 0b10110011100, + 0b10011011100, + 0b10011001110, + 0b10111001100, + 0b10011101100, + 0b10011100110, + 0b11001110010, + 0b11001011100, + 0b11001001110, // 20 + 0b11011100100, + 0b11001110100, + 0b11101101110, + 0b11101001100, + 0b11100101100, + 0b11100100110, + 0b11101100100, + 0b11100110100, + 0b11100110010, + 0b11011011000, // 30 + 0b11011000110, + 0b11000110110, + 0b10100011000, + 0b10001011000, + 0b10001000110, + 0b10110001000, + 0b10001101000, + 0b10001100010, + 0b11010001000, + 0b11000101000, // 40 + 0b11000100010, + 0b10110111000, + 0b10110001110, + 0b10001101110, + 0b10111011000, + 0b10111000110, + 0b10001110110, + 0b11101110110, + 0b11010001110, + 0b11000101110, // 50 + 0b11011101000, + 0b11011100010, + 0b11011101110, + 0b11101011000, + 0b11101000110, + 0b11100010110, + 0b11101101000, + 0b11101100010, + 0b11100011010, + 0b11101111010, // 60 + 0b11001000010, + 0b11110001010, + 0b10100110000, + 0b10100001100, + 0b10010110000, + 0b10010000110, + 0b10000101100, + 0b10000100110, + 0b10110010000, + 0b10110000100, // 70 + 0b10011010000, + 0b10011000010, + 0b10000110100, + 0b10000110010, + 0b11000010010, + 0b11001010000, + 0b11110111010, + 0b11000010100, + 0b10001111010, + 0b10100111100, // 80 + 0b10010111100, + 0b10010011110, + 0b10111100100, + 0b10011110100, + 0b10011110010, + 0b11110100100, + 0b11110010100, + 0b11110010010, + 0b11011011110, + 0b11011110110, // 90 + 0b11110110110, + 0b10101111000, + 0b10100011110, + 0b10001011110, + 0b10111101000, + 0b10111100010, + 0b11110101000, + 0b11110100010, + 0b10111011110, + 0b10111101110, // 100 + 0b11101011110, + 0b11110101110, + 0b11010000100, + 0b11010010000, + 0b11010011100, + 0b11000111010, + 0b11010111000, + 0b1100011101011, +}; + +static uint8_t symbolForCharacter(const QByteArray &data, int index, CodeSet set) +{ + const auto c1 = data.at(index); + switch (set) { + case CodeSetA: + return (c1 < ' ') ? c1 + 64 : c1 - ' '; + case CodeSetB: + return c1 - ' '; + case CodeSetC: { + const auto c2 = data.at(index + 1); + return ((c1 - '0') * 10) + c2 - '0'; + } + case CodeSetUnknown: + Q_UNREACHABLE(); + } + + Q_UNREACHABLE(); + return {}; +} + +struct CodeSetChange { + CodeSet set; + CodeSetOp symbol; +}; + +static bool isInCodeSetA(char c) +{ + return c <= 95; +} + +static bool isInCodeSetB(char c) +{ + // ### this does not consider FNC4 high byte encoding + return c >= 32; +} + +static CodeSetChange opForData(const QByteArray &data, int index, CodeSet currentSet) +{ + // determine if Code C makes sense at this point + int codeC = 0; + for (int i = index; i < data.size(); ++i, ++codeC) { + if (data.at(i) < '0' || data.at(i) > '9') { + break; + } + } + if (currentSet == CodeSetC && codeC >= 2) { // already in C + return {CodeSetC, None}; + } + if (codeC >= 6 // that's always good enough + || (index == 0 && codeC >= 4) // beginning of data + || (index + codeC == data.size() && codeC >= 4) // end of data + || (codeC == data.size() && codeC == 2) // 2 ... + || (codeC == data.size() && codeC == 4)) // ... or 4 as the entire data + { + return currentSet == CodeSetUnknown ? CodeSetChange{CodeSetC, StartC} : CodeSetChange{CodeSetC, LatchC}; + } + + // if we are in Code A or Code B, check if we need to switch for the next char + // this is a shortcut to prevent the below more extensive search from making this O(n²) in the common case + if ((currentSet == CodeSetA && isInCodeSetA(data.at(index))) || (currentSet == CodeSetB && isInCodeSetB(data.at(index)))) { + return {currentSet, None}; + } + + // we need to switch to A or B, select which one, and select whether to use start, shift or latch + const auto nextA = isInCodeSetA(data.at(index)); + const auto nextB = isInCodeSetB(data.at(index)); + + // count how many following characters we could encode in A or B + int countA = 0; + for (int i = index + 1; i < data.size(); ++i, ++countA) { + if (!isInCodeSetA(data.at(i))) { + break; + } + } + int countB = 0; + for (int i = index + 1; i < data.size(); ++i, ++countB) { + if (!isInCodeSetB(data.at(i))) { + break; + } + } + + // select how we want to switch to Code A or Code B, biased to B as that's the more useful one in general + switch (currentSet) { + case CodeSetUnknown: + // if we are at the start, take whichever code will get us further, or the only one that works + if (nextA && nextB) { + return countA > countB ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB}; + } + return nextA ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB}; + case CodeSetC: + // same for Code C + if (nextA && nextB) { + return countA > countB ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB}; + } + return nextA ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB}; + case CodeSetA: + // switch or latch to B? + return CodeSetChange{CodeSetB, countB >= countA ? LatchB : Shift}; + case CodeSetB: + // switch or latch to A? + return CodeSetChange{CodeSetA, countA > countB ? LatchA : Shift}; + } + + Q_UNREACHABLE(); + return CodeSetChange{currentSet, None}; +} + +BitVector Code128Barcode::encode(const QByteArray &data) const +{ + BitVector v; + if (data.isEmpty()) { + return v; + } + + // determine code set for start + const auto op = opForData(data, 0, CodeSetUnknown); + auto currentSet = op.set; + + // write start code + qCDebug(Log) << "start symbol:" << op.symbol << code128_symbols[op.symbol]; + v.appendMSB(code128_symbols[op.symbol], SymbolSize); + + uint32_t checksum = op.symbol; + uint32_t checksumWeight = 1; + + for (int i = 0; i < data.size(); i += currentSet == CodeSetC ? 2 : 1) { + if (static_cast(data.at(i)) > 127) { // FNC4 encoding not implemented yet + continue; + } + + // perform code switch if needed + const auto op = opForData(data, i, currentSet); + if (op.symbol != None) { + qCDebug(Log) << "op symbol:" << op.symbol << code128_symbols[op.symbol]; + v.appendMSB(code128_symbols[op.symbol], SymbolSize); + checksum += op.symbol * checksumWeight++; + } + + // encode current symbol + const auto symbol = symbolForCharacter(data, i, op.set); + qCDebug(Log) << "data symbol:" << symbol << code128_symbols[symbol]; + v.appendMSB(code128_symbols[symbol], SymbolSize); + checksum += symbol * checksumWeight++; + + // update current code set + if (op.symbol != Shift) { + currentSet = op.set; + } + } + + // encode checksum + qCDebug(Log) << "checksum:" << checksum << code128_symbols[checksum % 103]; + v.appendMSB(code128_symbols[checksum % 103], SymbolSize); + + // add stop pattern + v.appendMSB(code128_symbols[StopPattern], StopPatternSize); + return v; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/code128barcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/code128barcode_p.h new file mode 100644 index 00000000..b8402f59 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/code128barcode_p.h @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_CODE128BARCODE_H +#define PRISON_CODE128BARCODE_H + +#include "abstractbarcode_p.h" + +class Code128BarcodeTest; + +namespace Prison +{ +class BitVector; + +/** Code 128 barcode + * @see https://en.wikipedia.org/wiki/Code_128 + */ +class Code128Barcode : public AbstractBarcodePrivate +{ +public: + Code128Barcode(); + ~Code128Barcode() override; + +protected: + QImage paintImage() override; + +private: + friend class ::Code128BarcodeTest; + BitVector encode(const QByteArray &data) const; +}; + +} + +#endif // PRISON_CODE128BARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/code39barcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/code39barcode.cpp new file mode 100644 index 00000000..538001b1 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/code39barcode.cpp @@ -0,0 +1,171 @@ +/* + SPDX-FileCopyrightText: 2011 Geoffry Song + + SPDX-License-Identifier: MIT +*/ + +#include "barcodeutil_p.h" +#include "code39barcode_p.h" +#include + +using namespace Prison; + +static QList sequenceForChar(ushort c) +{ + switch (QChar::toUpper(c)) { + case '0': + return BarCodeUtil::barSequence("000110100"); + case '1': + return BarCodeUtil::barSequence("100100001"); + case '2': + return BarCodeUtil::barSequence("001100001"); + case '3': + return BarCodeUtil::barSequence("101100000"); + case '4': + return BarCodeUtil::barSequence("000110001"); + case '5': + return BarCodeUtil::barSequence("100110000"); + case '6': + return BarCodeUtil::barSequence("001110000"); + case '7': + return BarCodeUtil::barSequence("000100101"); + case '8': + return BarCodeUtil::barSequence("100100100"); + case '9': + return BarCodeUtil::barSequence("001100100"); + case 'A': + return BarCodeUtil::barSequence("100001001"); + case 'B': + return BarCodeUtil::barSequence("001001001"); + case 'C': + return BarCodeUtil::barSequence("101001000"); + case 'D': + return BarCodeUtil::barSequence("000011001"); + case 'E': + return BarCodeUtil::barSequence("100011000"); + case 'F': + return BarCodeUtil::barSequence("001011000"); + case 'G': + return BarCodeUtil::barSequence("000001101"); + case 'H': + return BarCodeUtil::barSequence("100001100"); + case 'I': + return BarCodeUtil::barSequence("001001100"); + case 'J': + return BarCodeUtil::barSequence("000011100"); + case 'K': + return BarCodeUtil::barSequence("100000011"); + case 'L': + return BarCodeUtil::barSequence("001000011"); + case 'M': + return BarCodeUtil::barSequence("101000010"); + case 'N': + return BarCodeUtil::barSequence("000010011"); + case 'O': + return BarCodeUtil::barSequence("100010010"); + case 'P': + return BarCodeUtil::barSequence("001010010"); + case 'Q': + return BarCodeUtil::barSequence("000000111"); + case 'R': + return BarCodeUtil::barSequence("100000110"); + case 'S': + return BarCodeUtil::barSequence("001000110"); + case 'T': + return BarCodeUtil::barSequence("000010110"); + case 'U': + return BarCodeUtil::barSequence("110000001"); + case 'V': + return BarCodeUtil::barSequence("011000001"); + case 'W': + return BarCodeUtil::barSequence("111000000"); + case 'X': + return BarCodeUtil::barSequence("010010001"); + case 'Y': + return BarCodeUtil::barSequence("110010000"); + case 'Z': + return BarCodeUtil::barSequence("011010000"); + case '-': + return BarCodeUtil::barSequence("010000101"); + case '.': + return BarCodeUtil::barSequence("110000100"); + case ' ': + return BarCodeUtil::barSequence("011000100"); + case '$': + return BarCodeUtil::barSequence("010101000"); + case '/': + return BarCodeUtil::barSequence("010100010"); + case '+': + return BarCodeUtil::barSequence("010001010"); + case '%': + return BarCodeUtil::barSequence("000101010"); + default: + return QList(); // unknown character + } +} + +Code39Barcode::Code39Barcode() + : AbstractBarcodePrivate(Barcode::OneDimension) +{ +} +Code39Barcode::~Code39Barcode() = default; + +QImage Code39Barcode::paintImage() +{ + QList barcode; + // convert text into sequences of wide/narrow bars + { + // the guard sequence that goes on each end + const QList endSequence = BarCodeUtil::barSequence("010010100"); + barcode += endSequence; + barcode += false; + // translate the string + const auto str = BarCodeUtil::asLatin1ByteArray(m_data); + for (int i = 0; i < str.size(); i++) { + QList b = sequenceForChar(str.at(i)); + if (!b.empty()) { + barcode += b; + barcode += false; // add a narrow space between each character + } + } + // ending guard + barcode += endSequence; + } + + /* + calculate integer bar widths that fit inside `size' + each character has 6 narrow bars and 3 wide bars and there is a narrow bar between characters + restrictions: + *) smallWidth * 2 <= largeWidth <= smallWidth * 3 + - in other words, the ratio largeWidth:smallWidth is between 3:1 and 2:1 + *) wide * largeWidth + narrow * smallWidth <= size.width() + - the barcode has to fit within the given size + */ + const int wide = barcode.count(true); + const int narrow = barcode.count(false); + // wide bar width + const int largeWidth = 2; + // narrow bar width + const int smallWidth = 1; + Q_ASSERT(largeWidth > smallWidth); + + const int quietZoneWidth = 10 * smallWidth; + + // one line of the result image + QList line; + line.reserve(wide * largeWidth + narrow * smallWidth + 2 * quietZoneWidth); + line.insert(0, quietZoneWidth, m_background.rgba()); + for (int i = 0; i < barcode.size(); i++) { + const QRgb color = (((i & 1) == 0) ? m_foreground : m_background).rgba(); // alternate between foreground and background color + const int width = barcode.at(i) ? largeWidth : smallWidth; + for (int j = 0; j < width; j++) { + line.append(color); + } + } + line.insert(line.size(), quietZoneWidth, m_background.rgba()); + + // build the complete barcode + QImage ret(line.size(), 1, QImage::Format_ARGB32); + memcpy(ret.scanLine(0), line.data(), line.size() * sizeof(QRgb)); + return ret; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/code39barcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/code39barcode_p.h new file mode 100644 index 00000000..e4bfed99 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/code39barcode_p.h @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: 2011 Geoffry Song + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_CODE39BARCODE_H +#define PRISON_CODE39BARCODE_H + +#include "abstractbarcode_p.h" + +namespace Prison +{ +/** + * Code 39 Barcode generator + */ +class Code39Barcode : public Prison::AbstractBarcodePrivate +{ +public: + /** + * creates a Code 39 generator + */ + Code39Barcode(); + ~Code39Barcode() override; + +protected: + /** + * This function generates the barcode + * @return QImage containing a barcode, trying to approximate the requested sizes, or a null QImage if it can't be painted within requested size + */ + QImage paintImage() override; +}; +} // namespace + +#endif // PRISON_CODE39BARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/code93barcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/code93barcode.cpp new file mode 100644 index 00000000..cedd7f92 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/code93barcode.cpp @@ -0,0 +1,665 @@ +/* + SPDX-FileCopyrightText: 2011 Geoffry Song + + SPDX-License-Identifier: MIT +*/ + +#include "barcodeutil_p.h" +#include "code93barcode_p.h" +#include + +using namespace Prison; + +// returns a list of 9 bar colors, where `true' means foreground and `false' means background color +static QList sequenceForID(int id) +{ + switch (id) { + case 0: + return BarCodeUtil::barSequence("100010100"); // 0-9 + case 1: + return BarCodeUtil::barSequence("101001000"); + case 2: + return BarCodeUtil::barSequence("101000100"); + case 3: + return BarCodeUtil::barSequence("101000010"); + case 4: + return BarCodeUtil::barSequence("100101000"); + case 5: + return BarCodeUtil::barSequence("100100100"); + case 6: + return BarCodeUtil::barSequence("100100010"); + case 7: + return BarCodeUtil::barSequence("101010000"); + case 8: + return BarCodeUtil::barSequence("100010010"); + case 9: + return BarCodeUtil::barSequence("100001010"); + case 10: + return BarCodeUtil::barSequence("110101000"); // A-Z + case 11: + return BarCodeUtil::barSequence("110100100"); + case 12: + return BarCodeUtil::barSequence("110100010"); + case 13: + return BarCodeUtil::barSequence("110010100"); + case 14: + return BarCodeUtil::barSequence("110010010"); + case 15: + return BarCodeUtil::barSequence("110001010"); + case 16: + return BarCodeUtil::barSequence("101101000"); + case 17: + return BarCodeUtil::barSequence("101100100"); + case 18: + return BarCodeUtil::barSequence("101100010"); + case 19: + return BarCodeUtil::barSequence("100110100"); + case 20: + return BarCodeUtil::barSequence("100011010"); + case 21: + return BarCodeUtil::barSequence("101011000"); + case 22: + return BarCodeUtil::barSequence("101001100"); + case 23: + return BarCodeUtil::barSequence("101000110"); + case 24: + return BarCodeUtil::barSequence("100101100"); + case 25: + return BarCodeUtil::barSequence("100010110"); + case 26: + return BarCodeUtil::barSequence("110110100"); + case 27: + return BarCodeUtil::barSequence("110110010"); + case 28: + return BarCodeUtil::barSequence("110101100"); + case 29: + return BarCodeUtil::barSequence("110100110"); + case 30: + return BarCodeUtil::barSequence("110010110"); + case 31: + return BarCodeUtil::barSequence("110011010"); + case 32: + return BarCodeUtil::barSequence("101101100"); + case 33: + return BarCodeUtil::barSequence("101100110"); + case 34: + return BarCodeUtil::barSequence("100110110"); + case 35: + return BarCodeUtil::barSequence("100111010"); + case 36: + return BarCodeUtil::barSequence("100101110"); // - + case 37: + return BarCodeUtil::barSequence("111010100"); // . + case 38: + return BarCodeUtil::barSequence("111010010"); // space + case 39: + return BarCodeUtil::barSequence("111001010"); // $ + case 40: + return BarCodeUtil::barSequence("101101110"); // / + case 41: + return BarCodeUtil::barSequence("101110110"); // + + case 42: + return BarCodeUtil::barSequence("110101110"); // $ + case 43: + return BarCodeUtil::barSequence("100100110"); // ($) + case 44: + return BarCodeUtil::barSequence("111011010"); // (%) + case 45: + return BarCodeUtil::barSequence("111010110"); // (/) + case 46: + return BarCodeUtil::barSequence("100110010"); // (+) + case 47: + return BarCodeUtil::barSequence("101011110"); // stop sequence + default: + // unknown ID... shouldn't happen + qWarning("Code93Barcode::sequenceForID called with unknown ID"); + return QList(); + } +} + +// returns the list of IDs that represent a character +static QList codesForChar(uint c) +{ + QList ret; + switch (c) { + case 0: + ret += 44; + ret += 30; + break; + case 1: + ret += 43; + ret += 10; + break; + case 2: + ret += 43; + ret += 11; + break; + case 3: + ret += 43; + ret += 12; + break; + case 4: + ret += 43; + ret += 13; + break; + case 5: + ret += 43; + ret += 14; + break; + case 6: + ret += 43; + ret += 15; + break; + case 7: + ret += 43; + ret += 16; + break; + case 8: + ret += 43; + ret += 17; + break; + case 9: + ret += 43; + ret += 18; + break; + case 10: + ret += 43; + ret += 19; + break; + case 11: + ret += 43; + ret += 20; + break; + case 12: + ret += 43; + ret += 21; + break; + case 13: + ret += 43; + ret += 22; + break; + case 14: + ret += 43; + ret += 23; + break; + case 15: + ret += 43; + ret += 24; + break; + case 16: + ret += 43; + ret += 25; + break; + case 17: + ret += 43; + ret += 26; + break; + case 18: + ret += 43; + ret += 27; + break; + case 19: + ret += 43; + ret += 28; + break; + case 20: + ret += 43; + ret += 29; + break; + case 21: + ret += 43; + ret += 30; + break; + case 22: + ret += 43; + ret += 31; + break; + case 23: + ret += 43; + ret += 32; + break; + case 24: + ret += 43; + ret += 33; + break; + case 25: + ret += 43; + ret += 34; + break; + case 26: + ret += 43; + ret += 35; + break; + case 27: + ret += 44; + ret += 10; + break; + case 28: + ret += 44; + ret += 11; + break; + case 29: + ret += 44; + ret += 12; + break; + case 30: + ret += 44; + ret += 13; + break; + case 31: + ret += 44; + ret += 14; + break; + case 32: + ret += 38; + break; + case 33: + ret += 45; + ret += 10; + break; + case 34: + ret += 45; + ret += 11; + break; + case 35: + ret += 45; + ret += 12; + break; + case 36: + ret += 39; + break; + case 37: + ret += 42; + break; + case 38: + ret += 45; + ret += 15; + break; + case 39: + ret += 45; + ret += 16; + break; + case 40: + ret += 45; + ret += 17; + break; + case 41: + ret += 45; + ret += 18; + break; + case 42: + ret += 45; + ret += 19; + break; + case 43: + ret += 41; + break; + case 44: + ret += 45; + ret += 21; + break; + case 45: + ret += 36; + break; + case 46: + ret += 37; + break; + case 47: + ret += 40; + break; + case 48: + ret += 0; + break; + case 49: + ret += 1; + break; + case 50: + ret += 2; + break; + case 51: + ret += 3; + break; + case 52: + ret += 4; + break; + case 53: + ret += 5; + break; + case 54: + ret += 6; + break; + case 55: + ret += 7; + break; + case 56: + ret += 8; + break; + case 57: + ret += 9; + break; + case 58: + ret += 45; + ret += 35; + break; + case 59: + ret += 44; + ret += 15; + break; + case 60: + ret += 44; + ret += 16; + break; + case 61: + ret += 44; + ret += 17; + break; + case 62: + ret += 44; + ret += 18; + break; + case 63: + ret += 44; + ret += 19; + break; + case 64: + ret += 44; + ret += 31; + break; + case 65: + ret += 10; + break; + case 66: + ret += 11; + break; + case 67: + ret += 12; + break; + case 68: + ret += 13; + break; + case 69: + ret += 14; + break; + case 70: + ret += 15; + break; + case 71: + ret += 16; + break; + case 72: + ret += 17; + break; + case 73: + ret += 18; + break; + case 74: + ret += 19; + break; + case 75: + ret += 20; + break; + case 76: + ret += 21; + break; + case 77: + ret += 22; + break; + case 78: + ret += 23; + break; + case 79: + ret += 24; + break; + case 80: + ret += 25; + break; + case 81: + ret += 26; + break; + case 82: + ret += 27; + break; + case 83: + ret += 28; + break; + case 84: + ret += 29; + break; + case 85: + ret += 30; + break; + case 86: + ret += 31; + break; + case 87: + ret += 32; + break; + case 88: + ret += 33; + break; + case 89: + ret += 34; + break; + case 90: + ret += 35; + break; + case 91: + ret += 44; + ret += 20; + break; + case 92: + ret += 44; + ret += 21; + break; + case 93: + ret += 44; + ret += 22; + break; + case 94: + ret += 44; + ret += 23; + break; + case 95: + ret += 44; + ret += 24; + break; + case 96: + ret += 44; + ret += 32; + break; + case 97: + ret += 46; + ret += 10; + break; + case 98: + ret += 46; + ret += 11; + break; + case 99: + ret += 46; + ret += 12; + break; + case 100: + ret += 46; + ret += 13; + break; + case 101: + ret += 46; + ret += 14; + break; + case 102: + ret += 46; + ret += 15; + break; + case 103: + ret += 46; + ret += 16; + break; + case 104: + ret += 46; + ret += 17; + break; + case 105: + ret += 46; + ret += 18; + break; + case 106: + ret += 46; + ret += 19; + break; + case 107: + ret += 46; + ret += 20; + break; + case 108: + ret += 46; + ret += 21; + break; + case 109: + ret += 46; + ret += 22; + break; + case 110: + ret += 46; + ret += 23; + break; + case 111: + ret += 46; + ret += 24; + break; + case 112: + ret += 46; + ret += 25; + break; + case 113: + ret += 46; + ret += 26; + break; + case 114: + ret += 46; + ret += 27; + break; + case 115: + ret += 46; + ret += 28; + break; + case 116: + ret += 46; + ret += 29; + break; + case 117: + ret += 46; + ret += 30; + break; + case 118: + ret += 46; + ret += 31; + break; + case 119: + ret += 46; + ret += 32; + break; + case 120: + ret += 46; + ret += 33; + break; + case 121: + ret += 46; + ret += 34; + break; + case 122: + ret += 46; + ret += 35; + break; + case 123: + ret += 44; + ret += 25; + break; + case 124: + ret += 44; + ret += 26; + break; + case 125: + ret += 44; + ret += 27; + break; + case 126: + ret += 44; + ret += 28; + break; + case 127: + ret += 44; + ret += 29; + break; + } + return ret; // return an empty list for a non-ascii character code +} + +// calculate a checksum +static int checksum(const QList &codes, int wrap) +{ + int check = 0; + for (int i = 0; i < codes.size(); i++) { + // weight goes from 1 to wrap, right-to-left, then repeats + const int weight = (codes.size() - i - 1) % wrap + 1; + check += codes.at(i) * weight; + } + return check % 47; +} + +Code93Barcode::Code93Barcode() + : AbstractBarcodePrivate(Barcode::OneDimension) +{ +} +Code93Barcode::~Code93Barcode() = default; + +QImage Code93Barcode::paintImage() +{ + QList barcode; + // convert text into sequences of fg/bg bars + { + // translate the string into a code sequence + QList codes; + const auto str = BarCodeUtil::asLatin1ByteArray(m_data); + for (int i = 0; i < str.size(); i++) { + codes += codesForChar(str.at(i)); + } + + // calculate checksums + codes.append(checksum(codes, 20)); // "C" checksum + codes.append(checksum(codes, 15)); // "K" checksum: includes previous checksum + + // now generate the barcode + // the guard sequence that goes on each end + const QList endSequence = sequenceForID(47); + barcode += endSequence; + // translate codes into bars + for (int i = 0; i < codes.size(); i++) { + barcode += sequenceForID(codes.at(i)); + } + // ending guard + barcode += endSequence; + // termination bar + barcode += true; + } + + const int barWidth = 1; + const int quietZoneWidth = 10 * barWidth; + + // build one line of the result image + QList line; + line.reserve(barWidth * barcode.size() + 2 * quietZoneWidth); + line.insert(0, quietZoneWidth, m_background.rgba()); + for (int i = 0; i < barcode.size(); i++) { + const QRgb color = (barcode.at(i) ? m_foreground : m_background).rgba(); + for (int j = 0; j < barWidth; j++) { + line.append(color); + } + } + line.insert(line.size(), quietZoneWidth, m_background.rgba()); + + // build the complete barcode + QImage ret(line.size(), 1, QImage::Format_ARGB32); + memcpy(ret.scanLine(0), line.data(), line.size() * sizeof(QRgb)); + return ret; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/code93barcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/code93barcode_p.h new file mode 100644 index 00000000..13793a57 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/code93barcode_p.h @@ -0,0 +1,33 @@ +/* + SPDX-FileCopyrightText: 2011 Geoffry Song + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_CODE93BARCODE_H +#define PRISON_CODE93BARCODE_H + +#include "abstractbarcode_p.h" + +namespace Prison +{ +/** + * Code 93 Barcode generator + */ +class Code93Barcode : public Prison::AbstractBarcodePrivate +{ +public: + /** + * creates a Code 93 generator + */ + Code93Barcode(); + ~Code93Barcode() override; + /** + * This function generates the barcode + * @return QImage containing a barcode, trying to approximate the requested sizes + */ + QImage paintImage() override; +}; +} // namespace + +#endif // PRISON_CODE39BARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/config-prison.h.cmake b/local/recipes/kde/kf6-prison/source/src/lib/config-prison.h.cmake new file mode 100644 index 00000000..9ae18e65 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/config-prison.h.cmake @@ -0,0 +1,13 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_CONFIG_H +#define PRISON_CONFIG_H + +#cmakedefine01 HAVE_DMTX +#cmakedefine01 HAVE_ZXING + +#endif diff --git a/local/recipes/kde/kf6-prison/source/src/lib/datamatrixbarcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/datamatrixbarcode.cpp new file mode 100644 index 00000000..5f48339a --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/datamatrixbarcode.cpp @@ -0,0 +1,78 @@ +/* + SPDX-FileCopyrightText: 2010-2014 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#include "datamatrixbarcode_p.h" +#include +using namespace Prison; + +DataMatrixBarcode::DataMatrixBarcode() + : AbstractBarcodePrivate(Barcode::TwoDimensions) +{ +} +DataMatrixBarcode::~DataMatrixBarcode() = default; + +QImage DataMatrixBarcode::paintImage() +{ + const auto data = m_data.toString(); + if (data.size() > 1200) { + return QImage(); + } + + DmtxEncode *enc = dmtxEncodeCreate(); + dmtxEncodeSetProp(enc, DmtxPropPixelPacking, DmtxPack32bppRGBX); + dmtxEncodeSetProp(enc, DmtxPropModuleSize, 1); + dmtxEncodeSetProp(enc, DmtxPropMarginSize, 2); + + QByteArray trimmedData(data.trimmed().toUtf8()); + DmtxPassFail result = dmtxEncodeDataMatrix(enc, trimmedData.length(), reinterpret_cast(trimmedData.data())); + if (result == DmtxFail) { + dmtxEncodeDestroy(&enc); + return QImage(); + } + Q_ASSERT(enc->image->width == enc->image->height); + + QImage ret; + + if (m_foreground == Qt::black && m_background == Qt::white) { + QImage tmp(enc->image->pxl, enc->image->width, enc->image->height, QImage::Format_ARGB32); + // we need to copy, because QImage generated from a char pointer requires the + // char pointer to be kept around forever, and manually deleted. + ret = tmp.copy(); + } else { + if (enc->image->width > 0) { + int size = enc->image->width * enc->image->height * 4; + uchar *img = new uchar[size]; + QByteArray background(4, '\0'); + background[3] = qAlpha(m_background.rgba()); + background[2] = qRed(m_background.rgba()); + background[1] = qGreen(m_background.rgba()); + background[0] = qBlue(m_background.rgba()); + QByteArray foreground(4, '\0'); + foreground[3] = qAlpha(m_foreground.rgba()); + foreground[2] = qRed(m_foreground.rgba()); + foreground[1] = qGreen(m_foreground.rgba()); + foreground[0] = qBlue(m_foreground.rgba()); + for (int i = 1; i < size; i += 4) { + QByteArray color; + if (enc->image->pxl[i] == 0x00) { + color = foreground; + } else { + color = background; + } + for (int j = 0; j < 4; j++) { + img[i - 1 + j] = color[j]; + } + } + QImage tmp(img, enc->image->width, enc->image->height, QImage::Format_ARGB32); + // we need to copy, because QImage generated from a char pointer requires the + // char pointer to be kept around forever, and manually deleted. + ret = tmp.copy(); + delete[] img; + } + } + dmtxEncodeDestroy(&enc); + return ret; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/datamatrixbarcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/datamatrixbarcode_p.h new file mode 100644 index 00000000..60ba441b --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/datamatrixbarcode_p.h @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_DATAMATRIXBARCODE_H +#define PRISON_DATAMATRIXBARCODE_H + +#include "abstractbarcode_p.h" +#include "prison_export.h" + +namespace Prison +{ +/** + * This is a Datamatrix barcode generator that uses libdmtx + * for the actual generation of barcodes. + */ +class DataMatrixBarcode : public Prison::AbstractBarcodePrivate +{ +public: + /** + * creates a datamatrixbarcode generator + */ + DataMatrixBarcode(); + ~DataMatrixBarcode() override; + +protected: + /** + * This is the function doing the actual work in generating the barcode + * @return QImage containing a DataMatrix, trying to approximate the requested sizes + */ + QImage paintImage() override; +}; +} + +#endif // PRISON_DATAMATRIXBARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/mecard.cpp b/local/recipes/kde/kf6-prison/source/src/lib/mecard.cpp new file mode 100644 index 00000000..a195c994 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/mecard.cpp @@ -0,0 +1,148 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + SPDX-FileCopyrightText: 2023 Kai Uwe Broulik + SPDX-License-Identifier: MIT +*/ + +#include "mecard.h" + +#include + +using namespace Prison; + +class Prison::MeCardPrivate +{ +public: + QStringView header; + struct Element { + QStringView key; + QStringList values; + bool operator<(QStringView other) const; + }; + std::vector elements; +}; + +bool MeCardPrivate::Element::operator<(QStringView other) const +{ + return key < other; +} + +MeCard::MeCard() + : d(new MeCardPrivate()) +{ +} + +MeCard::MeCard(MeCard &&other) noexcept + : d(std::move(other.d)) +{ + other.d.reset(); +} + +MeCard &MeCard::operator=(MeCard &&other) noexcept +{ + std::swap(d, other.d); + return *this; +} + +MeCard::~MeCard() = default; + +std::optional MeCard::parse(const QString &data) +{ + const auto idx = data.indexOf(QLatin1Char(':')); + if (idx <= 0 || idx >= data.size() - 1) { + return std::nullopt; + } + + MeCard m; + m.d->header = QStringView(data).left(idx); + + auto remaining = QStringView(data).mid(idx + 1); + while (remaining.size() > 0) { + const auto keyIdx = remaining.indexOf(QLatin1Char(':')); + if (keyIdx <= 0 || keyIdx + 2 >= remaining.size()) { + break; + } + + QString value; + auto elemIdx = keyIdx + 1; + bool inQuote = false; + for (; elemIdx < remaining.size() - 1; ++elemIdx) { + auto c = remaining.at(elemIdx); + if (elemIdx == (keyIdx + 1) && c == QLatin1Char('"')) { // leading quote + inQuote = true; + continue; + } + if (inQuote && c == QLatin1Char('"') && remaining.at(elemIdx + 1) == QLatin1Char(';')) { // trailing quote + break; + } + if (c == QLatin1Char(';')) { // end of element + break; + } + if (c == QLatin1Char('\\')) { // quoted character + ++elemIdx; + c = remaining.at(elemIdx); + } + value.push_back(c); + } + + const auto key = remaining.left(keyIdx); + auto it = std::lower_bound(m.d->elements.begin(), m.d->elements.end(), key); + if (it == m.d->elements.end()) { + m.d->elements.push_back(MeCardPrivate::Element()); + it = std::prev(m.d->elements.end()); + } else if ((*it).key != key) { + it = m.d->elements.insert(it, MeCardPrivate::Element()); + } + (*it).key = key; + (*it).values.push_back(value); + + remaining = remaining.mid(elemIdx + 1); + } + + if (m.d->elements.empty()) { + return std::nullopt; + } + + return m; +} + +QString MeCard::header() const +{ + return d->header.toString(); +} + +QStringView MeCard::headerView() const +{ + return d->header; +} + +QString MeCard::value(QStringView key) const +{ + const auto it = std::lower_bound(d->elements.begin(), d->elements.end(), key); + if (it != d->elements.end() && (*it).key == key && (*it).values.size() == 1) { + return (*it).values.at(0); + } + return {}; +} + +QStringList MeCard::values(QStringView key) const +{ + const auto it = std::lower_bound(d->elements.begin(), d->elements.end(), key); + if (it != d->elements.end() && (*it).key == key) { + return (*it).values; + } + return {}; +} + +QVariantMap MeCard::toVariantMap() const +{ + QVariantMap map; + for (const auto &element : std::as_const(d->elements)) { + if (element.values.size() > 1) { + map.insert(element.key.toString(), element.values); + } else { + map.insert(element.key.toString(), element.values.at(0)); + } + } + return map; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/mecard.h b/local/recipes/kde/kf6-prison/source/src/lib/mecard.h new file mode 100644 index 00000000..edadec97 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/mecard.h @@ -0,0 +1,92 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + SPDX-FileCopyrightText: 2023 Kai Uwe Broulik + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_MECARD_H +#define PRISON_MECARD_H + +#include +#include +#include + +#include "prison_export.h" + +#include +#include + +namespace Prison +{ + +/** Parser for the MeCard format. + * This was originally used for a more compact vCard representation, but today + * is mostly relevant for Wifi configuration QR codes. + * @see https://en.wikipedia.org/wiki/MeCard_(QR_code) + * @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 + * @since 5.101 + */ +class PRISON_EXPORT MeCard +{ +public: + /** + * Move constructor + */ + MeCard(MeCard &&other) noexcept; + /** + * Move assignment + */ + MeCard &operator=(MeCard &&other) noexcept; + /** + * Destructor + */ + ~MeCard(); + + /** + * Parse the given string + * @param data The string to parse + * @return A MeCard, if parsing was successful, a nullopt otherwise. + */ + static std::optional parse(const QString &data); + + /** + * Get the MeCard header. + * + * If you just want to identify the card, + * use headerView() instead. + */ + QString header() const; + /** + * Get the MeCard header as a string view. + * + * Useful for identifying the type of card. + */ + QStringView headerView() const; + /** + * Get the value for a given key. + * + * Convenience method for getting the first value + * if only one value is expected. + */ + QString value(QStringView key) const; + /** + * Get the list of values for a given key. + * @return The list of values for the given key + */ + QStringList values(QStringView key) const; + + /** + * Get the parsed data as QVariantMap. + */ + QVariantMap toVariantMap() const; + +private: + explicit MeCard(); + + friend class MeCardPrivate; + std::unique_ptr d; +}; + +} // namespace Prison + +#endif // PRISON_MECARD_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/pdf417barcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/pdf417barcode.cpp new file mode 100644 index 00000000..ae88c014 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/pdf417barcode.cpp @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "pdf417barcode_p.h" +#include "zxingutil_p.h" + +#include +#include + +#include + +using namespace Prison; + +Pdf417Barcode::Pdf417Barcode() + : AbstractBarcodePrivate(Barcode::TwoDimensions) +{ +} + +QImage Pdf417Barcode::paintImage() +{ + try { + ZXing::MultiFormatWriter writer(ZXing::BarcodeFormat::PDF417); + // ISO/IEC 15438:2006(E) §5.8.3 Quiet Zone + writer.setMargin(2); + if (m_data.userType() == QMetaType::QByteArray) { + writer.setEncoding(ZXing::CharacterSet::BINARY); + } + // aspect ratio 4 is hard-coded in ZXing + const auto matrix = writer.encode(ZXingUtil::toStdWString(m_data), 4, 1); + return ZXingUtil::toImage(matrix, m_foreground, m_background); + } catch (const std::invalid_argument &e) { + }; // input too large + return {}; +} + +QSizeF Pdf417Barcode::preferredSize(qreal devicePixelRatio) const +{ + return m_cache.size() * (devicePixelRatio < 2 ? 2 : 1); +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/pdf417barcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/pdf417barcode_p.h new file mode 100644 index 00000000..644962cd --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/pdf417barcode_p.h @@ -0,0 +1,30 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_PDF417BARCODE_H +#define PRISON_PDF417BARCODE_H + +#include "abstractbarcode_p.h" + +namespace Prison +{ +/** PDF417 barcode. + * @see https://en.wikipedia.org/wiki/PDF417 + * @see ISO/IEC 15438 + */ +class Pdf417Barcode : public AbstractBarcodePrivate +{ +public: + explicit Pdf417Barcode(); + +protected: + QImage paintImage() override; + QSizeF preferredSize(qreal devicePixelRatio) const override; +}; + +} + +#endif // PRISON_PDF417BARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/prison.h b/local/recipes/kde/kf6-prison/source/src/lib/prison.h new file mode 100644 index 00000000..a83e38e3 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/prison.h @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_PRISON_H +#define PRISON_PRISON_H + +#include "prison_export.h" + +/** + * @namespace Prison + * + * Provides classes and methods for generating barcodes. + */ +namespace Prison +{ +/** + * possible supported barcode types + */ +enum BarcodeType { + /** QRCode 2d barcode */ + QRCode, + /** DataMatrix 2d barcode */ + DataMatrix, + /** Aztec 2d barcode */ + Aztec, + /** Code39 barcode */ + Code39, + /** Code93 barcode */ + Code93, + /** Code 128 barcode */ + Code128, + /** PDF417 barcode */ + PDF417, + /** EAN13 barcode */ + EAN13, +}; +} + +#endif // PRISON_PRISON_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/qrcodebarcode.cpp b/local/recipes/kde/kf6-prison/source/src/lib/qrcodebarcode.cpp new file mode 100644 index 00000000..11066f25 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/qrcodebarcode.cpp @@ -0,0 +1,108 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#include "qrcodebarcode_p.h" +#include + +#include + +using namespace Prison; + +using QRcode_ptr = std::unique_ptr; +using QRinput_ptr = std::unique_ptr; + +QRCodeBarcode::QRCodeBarcode() + : AbstractBarcodePrivate(Barcode::TwoDimensions) +{ +} +QRCodeBarcode::~QRCodeBarcode() = default; + +static void qrEncodeString(QRcode_ptr &code, const QByteArray &data) +{ + // try decreasing ECC levels, in case the higher levels result in overflowing the maximum content size + for (auto ecc : {QR_ECLEVEL_Q, QR_ECLEVEL_M, QR_ECLEVEL_L}) { + code.reset(QRcode_encodeString(data.constData(), 0, ecc, QR_MODE_8, true)); + if (code) { + break; + } + } +} + +QImage QRCodeBarcode::paintImage() +{ + QRcode_ptr code(nullptr, &QRcode_free); + QRinput_ptr input(nullptr, &QRinput_free); + if (m_data.typeId() == QMetaType::QString) { + const QByteArray trimmedData(m_data.toString().trimmed().toUtf8()); + qrEncodeString(code, trimmedData); + } else { + const auto b = m_data.toByteArray(); + const auto isReallyBinary = std::any_of(b.begin(), b.end(), [](unsigned char c) { + return std::iscntrl(c) && !std::isspace(c); + }); + // prefer encodeString whenever possible, as that selects the more efficient encoding + // automatically, otherwise we end up needlessly in the binary encoding unconditionally + if (isReallyBinary) { + input.reset(QRinput_new()); + QRinput_append(input.get(), QR_MODE_8, b.size(), reinterpret_cast(b.constData())); + code.reset(QRcode_encodeInput(input.get())); + } else { + qrEncodeString(code, b); + } + } + + if (!code) { + return QImage(); + } + const int margin = 4; + /*32 bit colors, 8 bit pr byte*/ + uchar *img = new uchar[4 * sizeof(char *) * (2 * margin + code->width) * (2 * margin * +code->width)]; + uchar *p = img; + QByteArray background; + background.resize(4); + background[3] = qAlpha(m_background.rgba()); + background[2] = qRed(m_background.rgba()); + background[1] = qGreen(m_background.rgba()); + background[0] = qBlue(m_background.rgba()); + QByteArray foreground; + foreground.resize(4); + foreground[3] = qAlpha(m_foreground.rgba()); + foreground[2] = qRed(m_foreground.rgba()); + foreground[1] = qGreen(m_foreground.rgba()); + foreground[0] = qBlue(m_foreground.rgba()); + for (int row = 0; row < code->width + 2 * margin; row++) { + for (int col = 0; col < code->width + 2 * margin; col++) { + if (row < margin || row >= (code->width + margin) || col < margin || col >= (code->width + margin)) { + /*4 bytes for color*/ + for (int i = 0; i < 4; i++) { + *p = background[i]; + p++; + } + } else { + int c = (row - margin) * code->width + (col - margin); + /*it is bit 1 that is the interesting bit for us from libqrencode*/ + if (code->data[c] & 1) { + /*4 bytes for color*/ + for (int i = 0; i < 4; i++) { + *p = foreground[i]; + p++; + } + } else { + /*4 bytes for color*/ + for (int i = 0; i < 4; i++) { + *p = background[i]; + p++; + } + } + } + } + } + + const auto result = + QImage(img, code->width + 2 * margin, code->width + 2 * margin, QImage::Format_ARGB32).copy(); // deep copy as we are going to delete img + delete[] img; + return result; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/qrcodebarcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/qrcodebarcode_p.h new file mode 100644 index 00000000..af80ee1c --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/qrcodebarcode_p.h @@ -0,0 +1,34 @@ +/* + SPDX-FileCopyrightText: 2010-2014 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_QRCODEBARCODE_H +#define PRISON_QRCODEBARCODE_H + +#include "abstractbarcode_p.h" + +namespace Prison +{ +/** + * QRCode Barcode generator ; uses libqrencode to do the actual encoding + * of the barcode. + */ +class QRCodeBarcode : public AbstractBarcodePrivate +{ +public: + /** + * creates a QRCode generator + */ + QRCodeBarcode(); + ~QRCodeBarcode() override; + /** + * This is the function doing the actual work in generating the barcode + * @return QImage containing a QRCode, trying to approximate the requested sizes + */ + QImage paintImage() override; +}; +} // namespace + +#endif // PRISON_QRCODEBARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/reedsolomon.cpp b/local/recipes/kde/kf6-prison/source/src/lib/reedsolomon.cpp new file mode 100644 index 00000000..47b1ec70 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/reedsolomon.cpp @@ -0,0 +1,91 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "bitvector_p.h" +#include "reedsolomon_p.h" + + +#include + +using namespace Prison; + +// See https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders + +static int highestBit(int n) +{ + int i = 0; + while (n >= (1 << i)) { + ++i; + } + return i - 1; +} + +ReedSolomon::ReedSolomon(int polynom, int symbolCount) + : m_symCount(symbolCount) +{ + m_symSize = highestBit(polynom); + + // calculate the log/alog tables + const auto logmod = (1 << m_symSize) - 1; + m_logTable.reset(new int[logmod + 1]); + m_antiLogTable.reset(new int[logmod]); + + for (int p = 1, v = 0; v < logmod; v++) { + m_antiLogTable[v] = p; + m_logTable[p] = v; + p <<= 1; + if (p & (1 << m_symSize)) { + p ^= polynom; + } + } + + // compute the encoding polynom + m_polynom.reset(new int[m_symCount + 1]); + m_polynom[0] = 1; + for (int i = 1; i <= m_symCount; ++i) { + m_polynom[i] = 1; + for (int k = i - 1; k > 0; --k) { + if (m_polynom[k]) { + m_polynom[k] = m_antiLogTable[(m_logTable[m_polynom[k]] + i) % logmod]; + } + m_polynom[k] ^= m_polynom[k - 1]; + } + m_polynom[0] = m_antiLogTable[(m_logTable[m_polynom[0]] + i) % logmod]; + } +} + +ReedSolomon::~ReedSolomon() = default; + +BitVector ReedSolomon::encode(const BitVector &input) const +{ + std::unique_ptr result(new int[m_symCount]); + for (int i = 0; i < m_symCount; ++i) { + result[i] = 0; + } + + const auto logmod = (1 << m_symSize) - 1; + for (int i = 0; i < input.size() / m_symSize; i++) { + auto m = result[m_symCount - 1] ^ input.valueAtMSB(i * m_symSize, m_symSize); + for (int k = m_symCount - 1; k > 0; --k) { + if (m && m_polynom[k]) { + result[k] = result[k - 1] ^ m_antiLogTable[(m_logTable[m] + m_logTable[m_polynom[k]]) % logmod]; + } else { + result[k] = result[k - 1]; + } + } + if (m && m_polynom[0]) { + result[0] = m_antiLogTable[(m_logTable[m] + m_logTable[m_polynom[0]]) % logmod]; + } else { + result[0] = 0; + } + } + + BitVector v; + for (int i = m_symCount - 1; i >= 0; --i) { + v.appendMSB(result[i], m_symSize); + } + return v; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/reedsolomon_p.h b/local/recipes/kde/kf6-prison/source/src/lib/reedsolomon_p.h new file mode 100644 index 00000000..59044059 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/reedsolomon_p.h @@ -0,0 +1,51 @@ +/* + SPDX-FileCopyrightText: 2017 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_REEDSOLOMON_P_H +#define PRISON_REEDSOLOMON_P_H + +#include + +namespace Prison +{ +class BitVector; + +/** Reed Solomon checksum generator. */ +class ReedSolomon +{ +public: + enum GF { + GF16 = 0x13, + GF64 = 0x43, + GF256 = 0x12d, + GF1024 = 0x409, + GF4096 = 0x1069, + }; + + /** Initialize a Reed Solomon encoder with the Galois Field + * described by the bit pattern of @p polynom, for generating + * @p symbolCount error correction symbols. + */ + explicit ReedSolomon(int polynom, int symbolCount); + ReedSolomon(const ReedSolomon &) = delete; + ~ReedSolomon(); + + /** Encode the content of @p input and return the resulting + * code words. + */ + BitVector encode(const BitVector &input) const; + +private: + std::unique_ptr m_logTable; + std::unique_ptr m_antiLogTable; + std::unique_ptr m_polynom; + int m_symCount = 0; + int m_symSize = 0; +}; + +} + +#endif // PRISON_REEDSOLOMON_P_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/zxingonedbarcode_p.h b/local/recipes/kde/kf6-prison/source/src/lib/zxingonedbarcode_p.h new file mode 100644 index 00000000..5676e6e5 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/zxingonedbarcode_p.h @@ -0,0 +1,44 @@ +/* + SPDX-FileCopyrightText: 2023 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_ZXINGONEDBARCODE_H +#define PRISON_ZXINGONEDBARCODE_H + +#include "abstractbarcode_p.h" +#include "zxingutil_p.h" + +#include +#include + +#include + +namespace Prison +{ +/** Generic support for ZXing 1D barcodes. */ +template +class ZXingOneDBarcode : public AbstractBarcodePrivate +{ +public: + explicit inline ZXingOneDBarcode() + : AbstractBarcodePrivate(Barcode::OneDimension) + { + } + +protected: + inline QImage paintImage() override + { + try { + ZXing::MultiFormatWriter writer(Format); + const auto matrix = writer.encode(ZXingUtil::toStdWString(m_data), 1, 1); + return ZXingUtil::toImage(matrix, m_foreground, m_background); + } catch (const std::invalid_argument &e) { + }; // input too large or incompatible + return {}; + } +}; + +} + +#endif // PRISON_PDF417BARCODE_H diff --git a/local/recipes/kde/kf6-prison/source/src/lib/zxingutil.cpp b/local/recipes/kde/kf6-prison/source/src/lib/zxingutil.cpp new file mode 100644 index 00000000..ca2ddd6c --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/zxingutil.cpp @@ -0,0 +1,45 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "zxingutil_p.h" + +#include +#include + +#include + +using namespace Prison; + +std::wstring ZXingUtil::toStdWString(const QVariant &data) +{ + if (data.userType() == QMetaType::QString) { + return data.toString().toStdWString(); + } + if (data.userType() == QMetaType::QByteArray) { + const auto b = data.toByteArray(); + std::wstring ws; + ws.reserve(b.size()); + // ensure we explicitly copy unsigned bytes here, ie. each byte ends up in the least significant byte of + // the std::wstring. If we end up converting to a signed value inbetween, values > 127 end up negative and + // will be wrongly represented in the std::wstring + for (uint8_t c : b) { + ws.push_back(c); + } + return ws; + } + + return {}; +} + +QImage ZXingUtil::toImage(const ZXing::BitMatrix &matrix, const QColor &foreground, const QColor &background) +{ + QImage image(matrix.width(), matrix.height(), QImage::Format_ARGB32); + for (int y = 0; y < matrix.height(); ++y) { + for (int x = 0; x < matrix.width(); ++x) { + image.setPixel(x, y, matrix.get(x, y) ? foreground.rgb() : background.rgb()); + } + } + return image; +} diff --git a/local/recipes/kde/kf6-prison/source/src/lib/zxingutil_p.h b/local/recipes/kde/kf6-prison/source/src/lib/zxingutil_p.h new file mode 100644 index 00000000..ca420ea8 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/lib/zxingutil_p.h @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2021 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_ZXINGUTILS_P_H +#define PRISON_ZXINGUTILS_P_H + +#include + +namespace ZXing +{ +class BitMatrix; +} + +class QColor; +class QImage; +class QVariant; + +namespace Prison +{ +/** Utilities for interfacing with ZXing. */ +namespace ZXingUtil +{ +/** Convert barcode content (string or byte array) to the input + * format expected by ZXing. + */ +std::wstring toStdWString(const QVariant &data); + +/** Convert the bitmatrix output of ZXing to a QImage. */ +QImage toImage(const ZXing::BitMatrix &matrix, const QColor &foreground, const QColor &background); +} + +} + +#endif diff --git a/local/recipes/kde/kf6-prison/source/src/quick/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/src/quick/CMakeLists.txt new file mode 100644 index 00000000..b3eb778e --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/quick/CMakeLists.txt @@ -0,0 +1,9 @@ +ecm_add_qml_module(prisonquickplugin URI org.kde.prison VERSION 1.0 DEPENDENCIES QtQuick GENERATE_PLUGIN_SOURCE) + +target_sources(prisonquickplugin PRIVATE + barcodequickitem.cpp + barcodequickitem.h +) +target_link_libraries(prisonquickplugin PRIVATE Qt6::Quick KF6::Prison) + +ecm_finalize_qml_module(prisonquickplugin) diff --git a/local/recipes/kde/kf6-prison/source/src/quick/barcodequickitem.cpp b/local/recipes/kde/kf6-prison/source/src/quick/barcodequickitem.cpp new file mode 100644 index 00000000..20691b7c --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/quick/barcodequickitem.cpp @@ -0,0 +1,180 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#include "barcodequickitem.h" + +#include +#include +#include + +using namespace Prison; + +BarcodeQuickItem::BarcodeQuickItem(QQuickItem *parent) + : QQuickPaintedItem(parent) +{ +} + +BarcodeQuickItem::~BarcodeQuickItem() = default; + +QVariant BarcodeQuickItem::content() const +{ + return m_content; +} + +void BarcodeQuickItem::setContent(const QVariant &content) +{ + if (m_content == content) { + return; + } + m_content = content; + Q_EMIT contentChanged(); + updateBarcode(); +} + +QJSValue BarcodeQuickItem::barcodeType() const +{ + if (m_type) { + return static_cast(m_type.value()); + } + return QJSValue(); +} + +void BarcodeQuickItem::setBarcodeType(const QJSValue &type) +{ + if (!type.isNumber()) { + if (m_type) { + m_type.reset(); + } else { + return; + } + + } else { + auto enumType = static_cast(type.toInt()); + if (enumType == m_type) { + return; + } + m_type = enumType; + } + Q_EMIT barcodeTypeChanged(); + m_barcode.reset(); + updateBarcode(); +} + +QColor BarcodeQuickItem::foregroundColor() const +{ + return m_fgColor; +} + +void BarcodeQuickItem::setForegroundColor(const QColor &color) +{ + if (m_fgColor == color) { + return; + } + m_fgColor = color; + Q_EMIT foregroundColorChanged(); + updateBarcode(); +} + +QColor BarcodeQuickItem::backgroundColor() const +{ + return m_bgColor; +} + +void BarcodeQuickItem::setBackgroundColor(const QColor &color) +{ + if (m_bgColor == color) { + return; + } + m_bgColor = color; + Q_EMIT backgroundColorChanged(); + updateBarcode(); +} + +BarcodeQuickItem::Dimensions Prison::BarcodeQuickItem::dimensions() const +{ + if (m_barcode) + return static_cast(m_barcode->dimensions()); + return BarcodeQuickItem::Dimensions::NoDimensions; +} + +void BarcodeQuickItem::paint(QPainter *painter) +{ + if (!m_barcode) { + return; + } + + const auto w_max = std::max(minimumWidth(), width()); + const auto h_max = std::max(minimumHeight(), height()); + const auto img = m_barcode->toImage(QSizeF(w_max, h_max)); + const auto x = (w_max - img.width()) / 2; + const auto y = (h_max - img.height()) / 2; + painter->setRenderHint(QPainter::SmoothPixmapTransform, false); + painter->drawImage(QRectF(x, y, img.width(), img.height()), img, img.rect()); +} + +void BarcodeQuickItem::componentComplete() +{ + QQuickPaintedItem::componentComplete(); + updateBarcode(); +} + +qreal BarcodeQuickItem::minimumHeight() const +{ + return m_barcode ? m_barcode->minimumSize().height() : 0; +} + +qreal BarcodeQuickItem::minimumWidth() const +{ + return m_barcode ? m_barcode->minimumSize().width() : 0; +} + +bool BarcodeQuickItem::isEmpty() const +{ + switch (m_content.userType()) { + case QMetaType::QString: + return m_content.toString().isEmpty(); + case QMetaType::QByteArray: + return m_content.toByteArray().isEmpty(); + default: + break; + } + return true; +} + +void BarcodeQuickItem::updateBarcode() +{ + if (!isComponentComplete()) { + return; + } + + if (isEmpty() || !m_type) { + m_barcode.reset(); + update(); + Q_EMIT dimensionsChanged(); + return; + } + if (!m_barcode) { + m_barcode = Prison::Barcode::create(m_type.value()); + } + if (!m_barcode) { + return; + } + + if (m_content.userType() == QMetaType::QString) { + m_barcode->setData(m_content.toString()); + } else { + m_barcode->setData(m_content.toByteArray()); + } + m_barcode->setForegroundColor(m_fgColor); + m_barcode->setBackgroundColor(m_bgColor); + const auto size = m_barcode->preferredSize(QGuiApplication::primaryScreen()->devicePixelRatio()); + setImplicitSize(size.width(), size.height()); + + update(); + Q_EMIT dimensionsChanged(); +} + +#include "moc_barcodequickitem.cpp" diff --git a/local/recipes/kde/kf6-prison/source/src/quick/barcodequickitem.h b/local/recipes/kde/kf6-prison/source/src/quick/barcodequickitem.h new file mode 100644 index 00000000..df991c65 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/quick/barcodequickitem.h @@ -0,0 +1,101 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_BARCODEQUICKITEM_H +#define PRISON_BARCODEQUICKITEM_H + +#include + +#include +#include + +#include + +namespace Prison +{ + +class BarcodeQuickItem : public QQuickPaintedItem +{ + Q_OBJECT + QML_ELEMENT + QML_NAMED_ELEMENT(Barcode) + Q_PROPERTY(QVariant content READ content WRITE setContent NOTIFY contentChanged) + Q_PROPERTY(QJSValue barcodeType READ barcodeType WRITE setBarcodeType NOTIFY barcodeTypeChanged) + Q_PROPERTY(QColor foregroundColor READ foregroundColor WRITE setForegroundColor NOTIFY foregroundColorChanged) + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged) + Q_PROPERTY(Dimensions dimensions READ dimensions NOTIFY dimensionsChanged) + /** + * @see Prison::Barcode::minimumSize() + * @since 5.69 + */ + Q_PROPERTY(qreal minimumHeight READ minimumHeight NOTIFY implicitHeightChanged) + /** + * @see Prison::Barcode::minimumSize() + * @since 5.69 + */ + Q_PROPERTY(qreal minimumWidth READ minimumWidth NOTIFY implicitWidthChanged) + +public: + enum BarcodeType { + QRCode = Prison::QRCode, + DataMatrix = Prison::DataMatrix, + Aztec = Prison::Aztec, + Code39 = Prison::Code39, + Code93 = Prison::Code93, + Code128 = Prison::Code128, + PDF417 = Prison::PDF417, + EAN13 = Prison::EAN13, + }; + Q_ENUM(BarcodeType) + explicit BarcodeQuickItem(QQuickItem *parent = nullptr); + ~BarcodeQuickItem() override; + + QVariant content() const; + void setContent(const QVariant &data); + + QJSValue barcodeType() const; + void setBarcodeType(const QJSValue &type); + + QColor foregroundColor() const; + void setForegroundColor(const QColor &color); + QColor backgroundColor() const; + void setBackgroundColor(const QColor &color); + + enum Dimensions { + NoDimensions, + OneDimension, + TwoDimensions, + }; + Q_ENUM(Dimensions) + Dimensions dimensions() const; + + void paint(QPainter *painter) override; + void componentComplete() override; + + qreal minimumHeight() const; + qreal minimumWidth() const; + +Q_SIGNALS: + void contentChanged(); + void barcodeTypeChanged(); + void foregroundColorChanged(); + void backgroundColorChanged(); + void dimensionsChanged(); + +private: + bool isEmpty() const; + void updateBarcode(); + + QVariant m_content; + std::optional m_barcode; + QColor m_fgColor = Qt::black; + QColor m_bgColor = Qt::white; + std::optional m_type; +}; + +} + +#endif // PRISON_BARCODEQUICKITEM_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner-quick/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/src/scanner-quick/CMakeLists.txt new file mode 100644 index 00000000..3fc5b64f --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner-quick/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2022 Volker Krause +# SPDX-License-Identifier: BSD-3-Clause + +ecm_add_qml_module(prisonscannerquickplugin URI "org.kde.prison.scanner" VERSION 1.0 GENERATE_PLUGIN_SOURCE DEPENDENCIES QtMultimedia) +target_sources(prisonscannerquickplugin PRIVATE types.h) +target_link_libraries(prisonscannerquickplugin PRIVATE Qt6::Quick KF6::PrisonScanner) +ecm_finalize_qml_module(prisonscannerquickplugin DESTINATION ${KDE_INSTALL_QMLDIR}) diff --git a/local/recipes/kde/kf6-prison/source/src/scanner-quick/types.h b/local/recipes/kde/kf6-prison/source/src/scanner-quick/types.h new file mode 100644 index 00000000..519b9435 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner-quick/types.h @@ -0,0 +1,30 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include +#include +#include + +#include +#include + +struct VideoScannerForeign { + Q_GADGET + QML_NAMED_ELEMENT(VideoScanner) + QML_FOREIGN(Prison::VideoScanner) +}; + +namespace FormatForeign +{ +Q_NAMESPACE +QML_NAMED_ELEMENT(Format) +QML_FOREIGN_NAMESPACE(Prison::Format) +}; + +struct ScanResultForeign { + Q_GADGET + QML_VALUE_TYPE(scanResult) + QML_FOREIGN(Prison::ScanResult) +}; diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/src/scanner/CMakeLists.txt new file mode 100644 index 00000000..37bce09b --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/CMakeLists.txt @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2022 Volker Krause +# SPDX-License-Identifier: BSD-3-Clause + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-prison-scanner.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-prison-scanner.h) + +add_library(KF6PrisonScanner) +add_library(KF6::PrisonScanner ALIAS KF6PrisonScanner) + +qt_extract_metatypes(KF6PrisonScanner) + +set_target_properties(KF6PrisonScanner PROPERTIES + VERSION ${PRISON_VERSION} + SOVERSION ${PRISON_SOVERSION} + EXPORT_NAME PrisonScanner +) + +target_sources(KF6PrisonScanner PRIVATE + format.cpp + imagescanner.cpp + scanresult.cpp + videoscanner.cpp + videoscannerframe.cpp + videoscannerworker.cpp +) + +kde_source_files_enable_exceptions(videoscannerworker.cpp imagescanner.cpp scanresult.cpp) + +ecm_generate_export_header(KF6PrisonScanner + BASE_NAME PrisonScanner + GROUP_BASE_NAME KF + VERSION ${KF_VERSION} + USE_VERSION_HEADER + VERSION_BASE_NAME Prison + DEPRECATED_BASE_VERSION 0 + EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} +) + +target_include_directories(KF6PrisonScanner + INTERFACE + "$" + "$" # module version header + PUBLIC + "$" # module version header +) +target_link_libraries(KF6PrisonScanner + PUBLIC + Qt6::Multimedia + PRIVATE + Qt6::Core + ZXing::ZXing +) + +install(TARGETS KF6PrisonScanner EXPORT KF6PrisonTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) + +ecm_generate_headers(PrisonScanner_CamelCase_HEADERS + HEADER_NAMES + Format + ImageScanner + ScanResult + VideoScanner + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/Prison + REQUIRED_HEADERS PrisonScanner_HEADERS +) + +install( + FILES + ${PrisonScanner_CamelCase_HEADERS} + ${PrisonScanner_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/prisonscanner_export.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/PrisonScanner/Prison + COMPONENT Devel +) + +if(BUILD_QCH) + ecm_add_qch( + KF6PrisonScanner_QCH + NAME PrisonScanner + BASE_NAME KF6PrisonScanner + VERSION ${KF_VERSION} + ORG_DOMAIN org.kde + SOURCES # using only public headers, to cover only public API + ${PrisonScanner_HEADERS} + MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" + LINK_QCHS + Qt6Multimedia_QCH + INCLUDE_DIRS + ${CMAKE_CURRENT_BINARY_DIR} + BLANK_MACROS + PRISONSCANNER_EXPORT + PRISONSCANNER_DEPRECATED + PRISONSCANNER_DEPRECATED_EXPORT + "PRISONSCANNER_DEPRECATED_VERSION(x, y, t)" + "PRISONSCANNER_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)" + TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + COMPONENT Devel + ) +endif() diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/config-prison-scanner.h.in b/local/recipes/kde/kf6-prison/source/src/scanner/config-prison-scanner.h.in new file mode 100644 index 00000000..fe3855e2 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/config-prison-scanner.h.in @@ -0,0 +1,14 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef CONFIG_PRISON_SCANNER_H +#define CONFIG_PRISON_SCANNER_H + +#define ZXING_VERSION_MAJOR @ZXing_VERSION_MAJOR@ +#define ZXING_VERSION_MINOR @ZXing_VERSION_MINOR@ +#define ZXING_VERSION_PATCH @ZXing_VERSION_PATCH@ +#define ZXING_VERSION ((@ZXing_VERSION_MAJOR@<<16)|(@ZXing_VERSION_MINOR@<<8)|(@ZXing_VERSION_PATCH@)) + +#endif // CONFIG_PRISON_SCANNER_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/format.cpp b/local/recipes/kde/kf6-prison/source/src/scanner/format.cpp new file mode 100644 index 00000000..d1278f1e --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/format.cpp @@ -0,0 +1,55 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "format_p.h" + +using namespace Prison; + +struct format_map_t { + ZXing::BarcodeFormat zxFormat; + Prison::Format::BarcodeFormat format; +}; + +static constexpr const format_map_t format_map[] = { + {ZXing::BarcodeFormat::None, Format::NoFormat}, + {ZXing::BarcodeFormat::Aztec, Format::Aztec}, + {ZXing::BarcodeFormat::Codabar, Format::Codabar}, + {ZXing::BarcodeFormat::Code39, Format::Code39}, + {ZXing::BarcodeFormat::Code93, Format::Code93}, + {ZXing::BarcodeFormat::Code128, Format::Code128}, + {ZXing::BarcodeFormat::DataBar, Format::DataBar}, + {ZXing::BarcodeFormat::DataBarExpanded, Format::DataBarExpanded}, + {ZXing::BarcodeFormat::DataMatrix, Format::DataMatrix}, + {ZXing::BarcodeFormat::EAN8, Format::EAN8}, + {ZXing::BarcodeFormat::EAN13, Format::EAN13}, + {ZXing::BarcodeFormat::ITF, Format::ITF}, + {ZXing::BarcodeFormat::MaxiCode, Format::MaxiCode}, + {ZXing::BarcodeFormat::PDF417, Format::PDF417}, + {ZXing::BarcodeFormat::QRCode, Format::QRCode}, + {ZXing::BarcodeFormat::UPCA, Format::UPCA}, + {ZXing::BarcodeFormat::UPCE, Format::UPCE}, +}; + +ZXing::BarcodeFormats Format::toZXing(Format::BarcodeFormats formats) +{ + ZXing::BarcodeFormats f; + for (auto m : format_map) { + if (m.format & formats) { + f |= m.zxFormat; + } + } + return f; +} + +Format::BarcodeFormat Format::toFormat(ZXing::BarcodeFormat format) +{ + const auto it = std::find_if(std::begin(format_map), std::end(format_map), [format](auto m) { + return m.zxFormat == format; + }); + + return it != std::end(format_map) ? (*it).format : Format::NoFormat; +} + +#include "moc_format.cpp" diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/format.h b/local/recipes/kde/kf6-prison/source/src/scanner/format.h new file mode 100644 index 00000000..7ee213cb --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/format.h @@ -0,0 +1,54 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_FORMAT_H +#define PRISON_FORMAT_H + +#include "prisonscanner_export.h" + +#include +#include + +namespace Prison +{ + +/** + * Barcode formats detectable by Prison::VideoScanner. + * + * @see Prison::ScanResult. + * @since 5.94 + */ +namespace Format +{ +Q_NAMESPACE_EXPORT(PRISONSCANNER_EXPORT) +/** Barcode formats. */ +enum BarcodeFormat { + NoFormat = 0, + Aztec = 1, + Codabar = 2, + Code39 = 4, + Code93 = 8, + Code128 = 16, + DataBar = 32, + DataBarExpanded = 64, + DataMatrix = 128, + EAN8 = 256, + EAN13 = 512, + ITF = 1024, + MaxiCode = 2048, + PDF417 = 4096, + QRCode = 8192, + UPCA = 16384, + UPCE = 32768, +}; +Q_ENUM_NS(BarcodeFormat) + +Q_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) +Q_FLAG_NS(BarcodeFormats) +Q_DECLARE_OPERATORS_FOR_FLAGS(BarcodeFormats) +} +} + +#endif // PRISON_FORMAT_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/format_p.h b/local/recipes/kde/kf6-prison/source/src/scanner/format_p.h new file mode 100644 index 00000000..0cbc0acf --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/format_p.h @@ -0,0 +1,24 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_FORMAT_P_H +#define PRISON_FORMAT_P_H + +#include "format.h" + +#include + +namespace Prison +{ +namespace Format +{ + +ZXing::BarcodeFormats toZXing(BarcodeFormats formats); +BarcodeFormat toFormat(ZXing::BarcodeFormat format); + +} +} + +#endif // PRISON_FORMAT_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner.cpp b/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner.cpp new file mode 100644 index 00000000..054b8b42 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner.cpp @@ -0,0 +1,105 @@ +/* + SPDX-FileCopyrightText: 2024 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "config-prison-scanner.h" + +#include "imagescanner.h" +#include "format.h" +#include "format_p.h" +#include "imagescanner_p.h" +#include "scanresult.h" +#include "scanresult_p.h" + +#include + +#define ZX_USE_UTF8 1 +#include +#include +#include + +using namespace Prison; + +ZXing::Result ImageScanner::readBarcode(const QImage &image, Format::BarcodeFormats formats) +{ + ZXing::DecodeHints hints; + hints.setFormats(formats == Format::NoFormat ? ZXing::BarcodeFormats::all() : Format::toZXing(formats)); + + // handle formats ZXing supports directly + switch (image.format()) { + case QImage::Format_Invalid: +#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0) + return ZXing::Result(ZXing::DecodeStatus::FormatError); +#else + return {}; +#endif + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + break; // needs conversion + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::XRGB, (int)image.bytesPerLine()}, hints); + case QImage::Format_RGB16: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_RGB666: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_RGB555: + case QImage::Format_ARGB8555_Premultiplied: + break; // needs conversion + case QImage::Format_RGB888: + return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::RGB, (int)image.bytesPerLine()}, hints); + case QImage::Format_RGB444: + case QImage::Format_ARGB4444_Premultiplied: + break; // needs conversion + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888: + case QImage::Format_RGBA8888_Premultiplied: + return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::RGBX, (int)image.bytesPerLine()}, hints); + case QImage::Format_BGR30: + case QImage::Format_A2BGR30_Premultiplied: + case QImage::Format_RGB30: + case QImage::Format_A2RGB30_Premultiplied: + break; // needs conversion + case QImage::Format_Alpha8: +#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0) + return ZXing::Result(ZXing::DecodeStatus::FormatError); +#else + return {}; +#endif + case QImage::Format_Grayscale8: + return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::Lum, (int)image.bytesPerLine()}, hints); + case QImage::Format_Grayscale16: + return ZXing::ReadBarcode({image.bits() + 1, image.width(), image.height(), ZXing::ImageFormat::Lum, (int)image.bytesPerLine(), 1}, hints); + case QImage::Format_RGBX64: + case QImage::Format_RGBA64: + case QImage::Format_RGBA64_Premultiplied: + break; // needs conversion + case QImage::Format_BGR888: + return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::BGR, (int)image.bytesPerLine()}, hints); + case QImage::Format_RGBX16FPx4: + case QImage::Format_RGBA16FPx4: + case QImage::Format_RGBA16FPx4_Premultiplied: + case QImage::Format_RGBX32FPx4: + case QImage::Format_RGBA32FPx4: + case QImage::Format_RGBA32FPx4_Premultiplied: + break; // needs conversion + + case QImage::NImageFormats: // silence warnings +#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0) + return ZXing::Result(ZXing::DecodeStatus::FormatError); +#else + return {}; +#endif + } + + const auto converted = image.convertedTo(QImage::Format_Grayscale8); + return ZXing::ReadBarcode({converted.bits(), converted.width(), converted.height(), ZXing::ImageFormat::Lum, (int)converted.bytesPerLine()}, hints); +} + +ScanResult ImageScanner::scan(const QImage &image, Format::BarcodeFormats formats) +{ + return ScanResultPrivate::fromZXingResult(ImageScanner::readBarcode(image, formats)); +} diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner.h b/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner.h new file mode 100644 index 00000000..d7daa29a --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner.h @@ -0,0 +1,48 @@ +/* + SPDX-FileCopyrightText: 2024 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_IMAGESCANNER_H +#define PRISON_IMAGESCANNER_H + +#include "format.h" +#include "prisonscanner_export.h" + +class QImage; + +namespace Prison +{ + +class ScanResult; + +/** Scans a still image for barcodes. + * + * @since 6.3 + */ +namespace ImageScanner +{ + +/** Scan @p image for a barcode. + * This method is synchronous and expensive. + * For use in the main thread running this on a secondary thread is strongly recommended + * when processing larger images. + * + * @code + * QtConcurrent::run([&img]() { return ImageScanner::scan(img); }).then([](const ScanResult &result) { + * ... + * }); + * @endcode + * + * @param image The image to scan for barcodes, in any format. + * @param formats The barcode formats to look for. By default all supported formats + * are searched, limiting this improves performance and can improve result quality. + * + * @since 6.3 + */ +[[nodiscard]] PRISONSCANNER_EXPORT ScanResult scan(const QImage &image, Format::BarcodeFormats formats = {}); + +} +} + +#endif // PRISON_IMAGESCANNER_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner_p.h b/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner_p.h new file mode 100644 index 00000000..9f763574 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/imagescanner_p.h @@ -0,0 +1,27 @@ +/* + SPDX-FileCopyrightText: 2024 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_IMAGESCANNER_P_H +#define PRISON_IMAGESCANNER_P_H + +#include "format.h" + +#define ZX_USE_UTF8 1 +#include + +class QImage; + +namespace Prison +{ + +namespace ImageScanner +{ + +[[nodiscard]] ZXing::Result readBarcode(const QImage &image, Format::BarcodeFormats formats); + +} +} + +#endif // PRISON_IMAGESCANNER_P_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/scanresult.cpp b/local/recipes/kde/kf6-prison/source/src/scanner/scanresult.cpp new file mode 100644 index 00000000..61820c24 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/scanresult.cpp @@ -0,0 +1,127 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "config-prison-scanner.h" + +#include "format_p.h" +#include "scanresult.h" +#include "scanresult_p.h" + +#define ZX_USE_UTF8 1 +#include + +using namespace Prison; + +ScanResult::ScanResult() + : d(new ScanResultPrivate) +{ +} + +ScanResult::ScanResult(const ScanResult &) = default; +ScanResult::~ScanResult() = default; +ScanResult &ScanResult::operator=(const ScanResult &) = default; + +bool ScanResult::operator==(const ScanResult &other) const +{ + return d->content == other.d->content && d->boundingRect == other.d->boundingRect && d->format == other.d->format; +} + +bool ScanResult::hasContent() const +{ + return !d->content.isNull(); +} + +QVariant ScanResult::content() const +{ + return d->content; +} + +bool ScanResult::hasText() const +{ + return d->content.userType() == QMetaType::QString; +} + +QString ScanResult::text() const +{ + return hasText() ? d->content.toString() : QString(); +} + +bool ScanResult::hasBinaryData() const +{ + return d->content.userType() == QMetaType::QByteArray; +} + +QByteArray ScanResult::binaryData() const +{ + return hasBinaryData() ? d->content.toByteArray() : QByteArray(); +} + +Format::BarcodeFormat ScanResult::format() const +{ + return d->format; +} + +QRect ScanResult::boundingRect() const +{ + return d->boundingRect; +} + +ScanResult ScanResultPrivate::fromZXingResult(const ZXing::Result &zxRes, const QTransform &transform) +{ + ScanResult res; + if (!zxRes.isValid()) { + return res; + } + +#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0) + // distinguish between binary and text content + const auto hasWideChars = std::any_of(zxRes.text().begin(), zxRes.text().end(), [](auto c) { + return c > 255; + }); + const auto hasControlChars = std::any_of(zxRes.text().begin(), zxRes.text().end(), [](auto c) { + return c < 0x20 && c != 0x0a && c != 0x0d; + }); + if (hasWideChars || !hasControlChars) { + res.d->content = QString::fromStdString(ZXing::TextUtfEncoding::ToUtf8(zxRes.text())); + } else { + QByteArray b; + b.resize(zxRes.text().size()); + std::copy(zxRes.text().begin(), zxRes.text().end(), b.begin()); + res.d->content = b; + } +#else + if (zxRes.contentType() == ZXing::ContentType::Text) { + res.d->content = QString::fromStdString(zxRes.text()); + } else { + QByteArray b; + b.resize(zxRes.bytes().size()); + std::copy(zxRes.bytes().begin(), zxRes.bytes().end(), b.begin()); + res.d->content = b; + } +#endif + + // determine the bounding rect + // the cooridinates we get from ZXing are a polygon, we need to determine the + // bounding rect manually from its coordinates + const auto p = zxRes.position(); + int x1 = std::numeric_limits::max(); + int y1 = std::numeric_limits::max(); + int x2 = std::numeric_limits::min(); + int y2 = std::numeric_limits::min(); + for (int i = 0; i < 4; ++i) { + x1 = std::min(x1, p[i].x); + y1 = std::min(y1, p[i].y); + x2 = std::max(x2, p[i].x); + y2 = std::max(y2, p[i].y); + } + res.d->boundingRect = QRect(QPoint(x1, y1), QPoint(x2, y2)); + + // apply frame transformations to the bounding rect + res.d->boundingRect = transform.mapRect(res.d->boundingRect); + res.d->format = Format::toFormat(zxRes.format()); + return res; +} + +#include "moc_scanresult.cpp" diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/scanresult.h b/local/recipes/kde/kf6-prison/source/src/scanner/scanresult.h new file mode 100644 index 00000000..6536d1d2 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/scanresult.h @@ -0,0 +1,90 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_SCANRESULT_H +#define PRISON_SCANRESULT_H + +#include "format.h" +#include "prisonscanner_export.h" + +#include +#include +#include +#include + +namespace Prison +{ + +class ScanResultPrivate; + +/** Result of a barcode scan attempt. + * + * A scan result consists of the barcode content (which can be text or + * binary data), the barcode format and the position in the video frame + * the barcode was detected. + * + * @since 5.94 + */ +class PRISONSCANNER_EXPORT ScanResult +{ + Q_GADGET + Q_PROPERTY(bool hasContent READ hasContent) + Q_PROPERTY(QVariant content READ content) + + Q_PROPERTY(bool hasText READ hasText) + Q_PROPERTY(QString text READ text) + + Q_PROPERTY(bool hasBinaryData READ hasBinaryData) + Q_PROPERTY(QByteArray binaryData READ binaryData) + + Q_PROPERTY(Prison::Format::BarcodeFormat format READ format) + Q_PROPERTY(QRect boundingRect READ boundingRect) + +public: + explicit ScanResult(); + ScanResult(const ScanResult &); + ~ScanResult(); + ScanResult &operator=(const ScanResult &); + + bool operator==(const ScanResult &other) const; + + /** Returns @c true if a barcode has been found. */ + bool hasContent() const; + /** The barcode content, either a QString or a QByteArray. */ + QVariant content() const; + + /** Returns @c true if the found barcode contained a textual payload. */ + bool hasText() const; + /** + * Returns the textual barcode content, if the content was text rather than binary data, + * otherwise returns an empty string. + */ + QString text() const; + + /** Returns @c true if the found barcode contained a binary data payload. */ + bool hasBinaryData() const; + /** + * Returns the binary data content, if the content was binary data rather than text, + * otherwise returns an empty QByteArray. + */ + QByteArray binaryData() const; + + /** The format of the detected barcode. */ + Format::BarcodeFormat format() const; + + /** The bounding rectangle of the detected barcode in source coordinates. + * @note When using this to display an overlay in a view finder, this needs + * to be mapped to item coordinates. + */ + QRect boundingRect() const; + +private: + friend class ScanResultPrivate; + QExplicitlySharedDataPointer d; +}; + +} + +#endif // PRISON_SCANRESULT_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/scanresult_p.h b/local/recipes/kde/kf6-prison/source/src/scanner/scanresult_p.h new file mode 100644 index 00000000..7d8fe044 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/scanresult_p.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_SCANRESULT_P_H +#define PRISON_SCANRESULT_P_H + +#include "scanresult.h" + +#include +#include + +#define ZX_USE_UTF8 1 +#include + +namespace Prison +{ + +class ScanResultPrivate : public QSharedData +{ +public: + [[nodiscard]] static ScanResult fromZXingResult(const ZXing::Result &zxRes, const QTransform &transform = QTransform()); + + QVariant content; + QRect boundingRect; + Format::BarcodeFormat format = Format::NoFormat; +}; + +} +#endif // PRISON_SCANRESULT_P_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/videoscanner.cpp b/local/recipes/kde/kf6-prison/source/src/scanner/videoscanner.cpp new file mode 100644 index 00000000..cb636dc1 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/videoscanner.cpp @@ -0,0 +1,137 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "videoscanner.h" +#include "videoscannerframe_p.h" +#include "videoscannerworker_p.h" + +#include + +using namespace Prison; + +namespace Prison +{ +class VideoScannerPrivate +{ +public: + void newFrame(const QVideoFrame &videoFrame, bool verticallyFlipped); + void setResult(VideoScanner *q, const ScanResult &result); + + QVideoSink *m_sink = nullptr; + VideoScannerThread m_thread; + VideoScannerWorker m_worker; + QByteArray m_frameDataBuffer; // reused memory when we have to copy frame data + ScanResult m_result; + QVariant m_previousContent; + Format::BarcodeFormats m_formats = Format::NoFormat; + bool m_workerBusy = false; +}; +} + +void VideoScannerPrivate::newFrame(const QVideoFrame &videoFrame, bool verticallyFlipped) +{ + // NOTE: this runs in the render thread + if (!m_workerBusy && videoFrame.isValid()) { + m_workerBusy = true; + + VideoScannerFrame frame(videoFrame, verticallyFlipped, m_formats); + // check if we are only allowed to access this in the render thread + if (frame.copyRequired()) { + frame.map(); + if (frame.needsConversion()) { + frame.convertToImage(); + } else { + frame.copyFrameData(m_frameDataBuffer); + } + frame.unmap(); + } + + Q_EMIT m_worker.scanFrameRequest(frame); + } +} + +void VideoScannerPrivate::setResult(VideoScanner *q, const ScanResult &result) +{ + m_workerBusy = false; + if (m_result == result) { + return; + } + + m_result = result; + Q_EMIT q->resultChanged(result); + + if (m_previousContent == result.content()) { + return; + } + m_previousContent = result.content(); + Q_EMIT q->resultContentChanged(result); +} + +VideoScanner::VideoScanner(QObject *parent) + : QObject(parent) + , d(new VideoScannerPrivate) +{ + d->m_worker.moveToThread(&d->m_thread); + connect( + &d->m_worker, + &VideoScannerWorker::result, + this, + [this](const ScanResult &result) { + d->setResult(this, result); + }, + Qt::QueuedConnection); + + d->m_thread.setObjectName(QStringLiteral("Prison Barcode Scanner Worker")); + d->m_thread.start(); +} + +VideoScanner::~VideoScanner() +{ + d->m_thread.quit(); + d->m_thread.wait(); +} + +ScanResult VideoScanner::result() const +{ + return d->m_result; +} + +Format::BarcodeFormats VideoScanner::formats() const +{ + return d->m_formats; +} + +void VideoScanner::setFormats(Format::BarcodeFormats formats) +{ + if (d->m_formats == formats) { + return; + } + + d->m_formats = formats; + Q_EMIT formatsChanged(); +} + +QVideoSink *VideoScanner::videoSink() const +{ + return d->m_sink; +} + +void VideoScanner::setVideoSink(QVideoSink *sink) +{ + if (d->m_sink == sink) { + return; + } + + if (d->m_sink) { + disconnect(d->m_sink, nullptr, this, nullptr); + } + d->m_sink = sink; + connect(d->m_sink, &QVideoSink::videoFrameChanged, this, [this](const QVideoFrame &frame) { + d->newFrame(frame, frame.surfaceFormat().scanLineDirection() == QVideoFrameFormat::BottomToTop); + }); + Q_EMIT videoSinkChanged(); +} + +#include "moc_videoscanner.cpp" diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/videoscanner.h b/local/recipes/kde/kf6-prison/source/src/scanner/videoscanner.h new file mode 100644 index 00000000..1abb5743 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/videoscanner.h @@ -0,0 +1,85 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_VIDEOSCANNER_H +#define PRISON_VIDEOSCANNER_H + +#include "prisonscanner_export.h" +#include "scanresult.h" + +#include +#include + +#include + +namespace Prison +{ + +class VideoScannerPrivate; + +/** Scans a live video feed for barcodes. + * + * In Qt5 this can be added as a video filter to a VideoOutput element. + * In Qt6 this can be connected to a QVideoSink object. + * + * @since 5.94 + */ +class PRISONSCANNER_EXPORT VideoScanner : public QObject +{ + Q_OBJECT + Q_PROPERTY(Prison::ScanResult result READ result NOTIFY resultChanged) + Q_PROPERTY(Prison::Format::BarcodeFormats formats READ formats WRITE setFormats NOTIFY formatsChanged) + + Q_PROPERTY(QVideoSink *videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged) + +public: + explicit VideoScanner(QObject *parent = nullptr); + ~VideoScanner(); + + /** The latest result of the barcode scan. */ + ScanResult result() const; + + /** The barcode formats the scanner should look for. + * By default all supported formats are enabled. + */ + Format::BarcodeFormats formats() const; + /** + * Sets the barcode formats to detect. + * @param formats can be OR'ed values from Format::BarcodeFormats. + */ + void setFormats(Format::BarcodeFormats formats); + + /** The video sink being scanned for barcodes. */ + QVideoSink *videoSink() const; + /** Sets the video sink to scan for barcodes. */ + void setVideoSink(QVideoSink *sink); + +Q_SIGNALS: + /** Emitted whenever we get a new scan result, as long as any + * property of the result changes. On a live video feed this can + * be very frequently due to the changes of the position of the detected + * barcode. This is therefore useful e.g. for marking the position + * of the detected barcode. + * @see resultContentChanged + */ + void resultChanged(const Prison::ScanResult &scanResult); + + /** Emitted when a barcode with a new content has been detected, but + * not when merely the position of a barcode changes in the video stream. + * This is useful e.g. for continuously scanning multiple codes in one go. + * @see resultChanged + */ + void resultContentChanged(const Prison::ScanResult &scanResult); + + void formatsChanged(); + void videoSinkChanged(); + +private: + std::unique_ptr d; +}; + +} + +#endif // PRISON_VIDEOSCANNER_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerframe.cpp b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerframe.cpp new file mode 100644 index 00000000..ec65e91d --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerframe.cpp @@ -0,0 +1,142 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "videoscannerframe_p.h" + +#include + +using namespace Prison; + +VideoScannerFrame::VideoScannerFrame() = default; + +VideoScannerFrame::VideoScannerFrame(const QVideoFrame &frame, bool isVerticallyFlipped, Format::BarcodeFormats formats) + : m_frame(frame) + , m_formats(formats) + , m_verticallyFlipped(isVerticallyFlipped) +{ +} + +VideoScannerFrame::~VideoScannerFrame() = default; + +int VideoScannerFrame::width() const +{ + return m_frame.width(); +} + +int VideoScannerFrame::height() const +{ + return m_frame.height(); +} + +int VideoScannerFrame::bytesPerLine() const +{ + return m_frame.bytesPerLine(0); +} + +QVideoFrameFormat::PixelFormat VideoScannerFrame::pixelFormat() const +{ + return m_frame.pixelFormat(); +} + +void VideoScannerFrame::map() +{ + if (!m_frameData && m_image.isNull()) { + m_frame.map(QVideoFrame::ReadOnly); + } +} + +void VideoScannerFrame::unmap() +{ + if (m_frame.isMapped()) { + m_frame.unmap(); + } +} + +const uint8_t *VideoScannerFrame::bits() const +{ + if (m_frameData) { + return m_frameData; + } + if (!m_image.isNull()) { + return m_image.bits(); + } + + Q_ASSERT(m_frame.isMapped()); + return m_frame.bits(0); +} + +bool VideoScannerFrame::copyRequired() const +{ + return m_frame.handleType() == QVideoFrame::RhiTextureHandle; +} + +void VideoScannerFrame::copyFrameData(QByteArray &buffer) +{ + Q_ASSERT(m_frame.isMapped()); + + const auto size = frameDataSize(); + if (buffer.size() != size) { + buffer.resize(size); + } + std::memcpy(buffer.data(), m_frame.bits(0), size); + m_frameData = reinterpret_cast(buffer.constData()); +} + +int VideoScannerFrame::frameDataSize() const +{ + Q_ASSERT(m_frame.isMapped()); + + switch (m_frame.pixelFormat()) { + case QVideoFrameFormat::Format_YUV420P: + case QVideoFrameFormat::Format_YUV422P: + case QVideoFrameFormat::Format_YV12: + case QVideoFrameFormat::Format_NV12: + case QVideoFrameFormat::Format_NV21: + case QVideoFrameFormat::Format_IMC1: + case QVideoFrameFormat::Format_IMC2: + case QVideoFrameFormat::Format_IMC3: + case QVideoFrameFormat::Format_IMC4: + return m_frame.mappedBytes(0) / 2; + default: + return m_frame.mappedBytes(0); + } +} + +bool VideoScannerFrame::needsConversion() const +{ + switch (m_frame.pixelFormat()) { + case QVideoFrameFormat::Format_Jpeg: + case QVideoFrameFormat::Format_SamplerExternalOES: + case QVideoFrameFormat::Format_SamplerRect: + return true; + default: + return false; + } +} + +void VideoScannerFrame::convertToImage() +{ + if (!m_image.isNull()) { + return; + } + + Q_ASSERT(m_frame.isMapped()); + m_image = m_frame.toImage(); +} + +QImage VideoScannerFrame::image() const +{ + return m_image; +} + +bool VideoScannerFrame::isVerticallyFlipped() const +{ + return m_verticallyFlipped; +} + +Format::BarcodeFormats VideoScannerFrame::formats() const +{ + return m_formats; +} diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerframe_p.h b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerframe_p.h new file mode 100644 index 00000000..d5518096 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerframe_p.h @@ -0,0 +1,108 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_VIDEOSCANNERFRAME_P_H +#define PRISON_VIDEOSCANNERFRAME_P_H + +#include "format.h" + +#include +#include +#include + +namespace Prison +{ + +/** + * A single frame from a video feed handed over to the barcode scanner worker thread. + * + * This abstracts three possible states: + * - direct data access to the original frame data, when possible + * - access to an internal copy of at least the luminescence part of the raw frame data + * if access is only possible in the render thread and the raw format is consumeable + * by ZXing + * - a decoded 8bit grayscale QImage of the frame content, if the raw frame data is only + * accessible in the render thread and the native frame format is not consumeable by + * ZXing directly + * + * @internal + */ +class VideoScannerFrame +{ +public: + explicit VideoScannerFrame(); + explicit VideoScannerFrame(const QVideoFrame &frame, bool verticallyFlipped, Format::BarcodeFormats formats); + ~VideoScannerFrame(); + + int width() const; + int height() const; + int bytesPerLine() const; + QVideoFrameFormat::PixelFormat pixelFormat() const; + + /** Map/unmap the frame if needed, ie. if we don't already have a copy. */ + void map(); + void unmap(); + + /** Raw frame data, either zero copy or from an internal buffer + * if we were forced to copy. + * Possibly truncated to just the subset that contains all the luminescence + * channel, e.g. for planar formats. + */ + const uint8_t *bits() const; + + /** Decides based on the input frame format whether an immediate + * copy in the reader thread is necessary. + */ + bool copyRequired() const; + /** Copy the required subset of the frame data to our internal buffer. + * @note Requires the frame to be mapped. + */ + void copyFrameData(QByteArray &buffer); + + /** Returns whether we have a format that ZXing cannot consume without + * prior conversion. These are the formats we need to let Qt convert + * to a QImage first, and in case frame data access is only allowed in + * the render thread, this also has to happen there. + */ + bool needsConversion() const; + /** Convert to grayscale QImage. + * @note Requires the frame to be mapped. + */ + void convertToImage(); + + /** Returns the frame as QImage. + * Result is only valid after a call to convertToImage(). + */ + QImage image() const; + + /** The requested barcode formats. */ + Format::BarcodeFormats formats() const; + + /** Returns @c true if the raw frame data is vertically flipped compared + * to how it's displayed. This doesn't impact barcode detection as such, + * but it requires corresponding adjustments to the coordinates at which + * a barcode has been detected. + */ + bool isVerticallyFlipped() const; + +private: + /** The amount of data to copy. This can be less than the entire frame + * size for planar formats. + * @note Requires the frame to be mapped. + */ + int frameDataSize() const; + + QVideoFrame m_frame; + const uint8_t *m_frameData = nullptr; + QImage m_image; + Format::BarcodeFormats m_formats = {}; + bool m_verticallyFlipped = false; +}; + +} + +Q_DECLARE_METATYPE(Prison::VideoScannerFrame) + +#endif // PRISON_VIDEOSCANNERFRAME_P_H diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerworker.cpp b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerworker.cpp new file mode 100644 index 00000000..57a7ec90 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerworker.cpp @@ -0,0 +1,123 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#include "config-prison-scanner.h" +#include "format_p.h" +#include "imagescanner_p.h" +#include "scanresult.h" +#include "scanresult_p.h" +#include "videoscannerframe_p.h" +#include "videoscannerworker_p.h" + +#include +#include +#include + +#define ZX_USE_UTF8 1 +#include + +using namespace Prison; + +VideoScannerWorker::VideoScannerWorker(QObject *parent) + : QObject(parent) +{ + connect(this, &VideoScannerWorker::scanFrameRequest, this, &VideoScannerWorker::slotScanFrame, Qt::QueuedConnection); +} + +void VideoScannerWorker::slotScanFrame(VideoScannerFrame frame) +{ +#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0) + ZXing::Result zxRes(ZXing::DecodeStatus::FormatError); +#else + ZXing::Result zxRes; +#endif + ZXing::DecodeHints hints; + hints.setFormats(frame.formats() == Format::NoFormat ? ZXing::BarcodeFormats::all() : Format::toZXing(frame.formats())); + + frame.map(); + switch (frame.pixelFormat()) { + case QVideoFrameFormat::Format_Invalid: // already checked before we get here + break; + // formats ZXing can consume directly + case QVideoFrameFormat::Format_ARGB8888: + case QVideoFrameFormat::Format_ARGB8888_Premultiplied: + case QVideoFrameFormat::Format_XRGB8888: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::XRGB, frame.bytesPerLine()}, hints); + break; + case QVideoFrameFormat::Format_BGRA8888: + case QVideoFrameFormat::Format_BGRA8888_Premultiplied: + case QVideoFrameFormat::Format_BGRX8888: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::BGRX, frame.bytesPerLine()}, hints); + break; + case QVideoFrameFormat::Format_ABGR8888: + case QVideoFrameFormat::Format_XBGR8888: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::XBGR, frame.bytesPerLine()}, hints); + break; + case QVideoFrameFormat::Format_RGBA8888: + case QVideoFrameFormat::Format_RGBX8888: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::RGBX, frame.bytesPerLine()}, hints); + break; + case QVideoFrameFormat::Format_AYUV: + case QVideoFrameFormat::Format_AYUV_Premultiplied: + zxRes = ZXing::ReadBarcode({frame.bits() + 1, frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 4}, hints); + break; + case QVideoFrameFormat::Format_YUV420P: + case QVideoFrameFormat::Format_YUV422P: + case QVideoFrameFormat::Format_YV12: + case QVideoFrameFormat::Format_NV12: + case QVideoFrameFormat::Format_NV21: + case QVideoFrameFormat::Format_IMC1: + case QVideoFrameFormat::Format_IMC2: + case QVideoFrameFormat::Format_IMC3: + case QVideoFrameFormat::Format_IMC4: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine()}, hints); + break; + case QVideoFrameFormat::Format_UYVY: + zxRes = ZXing::ReadBarcode({frame.bits() + 1, frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 2}, hints); + break; + case QVideoFrameFormat::Format_YUYV: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 2}, hints); + break; + case QVideoFrameFormat::Format_Y8: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine()}, hints); + break; + case QVideoFrameFormat::Format_Y16: + zxRes = ZXing::ReadBarcode({frame.bits() + 1, frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 1}, hints); + break; + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: + case QVideoFrameFormat::Format_YUV420P10: + zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 1}, hints); + break; + + // formats needing conversion before ZXing can consume them + case QVideoFrameFormat::Format_Jpeg: + case QVideoFrameFormat::Format_SamplerExternalOES: + case QVideoFrameFormat::Format_SamplerRect: + frame.convertToImage(); + zxRes = ImageScanner::readBarcode(frame.image(), frame.formats()); + break; + } + frame.unmap(); + + // process scan result + if (zxRes.isValid()) { + QTransform t; + if (frame.isVerticallyFlipped()) { + t.scale(1, -1); + t.translate(0, -frame.height()); + } + Q_EMIT result(ScanResultPrivate::fromZXingResult(zxRes, t)); + } else { + Q_EMIT result(ScanResult()); + } +} + +void VideoScannerThread::run() +{ + exec(); +} + +#include "moc_videoscannerworker_p.cpp" diff --git a/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerworker_p.h b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerworker_p.h new file mode 100644 index 00000000..359792d0 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/scanner/videoscannerworker_p.h @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_VIDEOSCANNERWORKER_H +#define PRISON_VIDEOSCANNERWORKER_H + +#include "scanresult.h" + +#include +#include +#include + +namespace Prison +{ + +class VideoScannerFrame; + +/** Contains the actual barcode detecting/decoding work, + * to be run in a secondary thread. + */ +class VideoScannerWorker : public QObject +{ + Q_OBJECT +public: + explicit VideoScannerWorker(QObject *parent = nullptr); + +Q_SIGNALS: + void scanFrameRequest(const VideoScannerFrame &frame); + void result(const Prison::ScanResult &result); + +public Q_SLOTS: + void slotScanFrame(VideoScannerFrame frame); +}; + +/** Thread for executing the VideoScannerWorker. */ +class VideoScannerThread : public QThread +{ +public: + void run() override; +}; + +} + +#endif // PRISON_VIDEOSCANNERWORKER_H diff --git a/local/recipes/kde/kf6-prison/source/src/tools/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/src/tools/CMakeLists.txt new file mode 100644 index 00000000..8d135647 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/tools/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(prison-datamatrix prison-datamatrix.cpp) +target_link_libraries(prison-datamatrix KF6::Prison) diff --git a/local/recipes/kde/kf6-prison/source/src/tools/prison-datamatrix.cpp b/local/recipes/kde/kf6-prison/source/src/tools/prison-datamatrix.cpp new file mode 100644 index 00000000..016314cb --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/src/tools/prison-datamatrix.cpp @@ -0,0 +1,94 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#include + +#include +#include +#include + +#include +#include +#include + +void error(const QString &error, const QString &errormessage) +{ + QTextStream str(stdout); + str << error << ": " << errormessage << '\n'; + str.flush(); + exit(0); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QString size; + QString outputfile; + QString outputformat; + QStringList arguments = app.arguments(); + + arguments.takeFirst(); // Program (argv[0]). + while (!arguments.isEmpty()) { + QString argument = arguments.takeFirst(); + if (argument == QLatin1String("--")) { + break; // rest is data + } else if (argument == QLatin1String("--size") || argument == QLatin1String("-s")) { + size = arguments.takeFirst(); + } else if (argument == QLatin1String("--outputfile") || argument == QLatin1String("--output-file") || argument == QLatin1String("-o")) { + outputfile = arguments.takeFirst(); + } else if (argument == QLatin1String("--output-format") || argument == QLatin1String("--output-format") || argument == QLatin1String("-f")) { + outputformat = arguments.takeFirst(); + } else if (argument.startsWith(QLatin1String("-"))) { + error(QStringLiteral("unknown argument"), argument); + } else { + break; + } + } + + if (outputformat.isEmpty()) { + outputformat = QStringLiteral("png"); + } + + if (!QImageWriter::supportedImageFormats().contains(outputformat.toLocal8Bit())) { + error(QStringLiteral("unsupported output format"), outputformat); + } + + if (outputfile.isEmpty()) { + error(QStringLiteral("outputfile is missing"), QString()); + } + + bool ok = false; + int intsize = size.toInt(&ok); + if (!ok) { + error(QStringLiteral("size not a int"), size); + } + if (intsize < 10) { + error(QStringLiteral("needs a larger output size"), size); + } + + QString data = arguments.join(QLatin1Char(' ')); + if (data.size() == 0) { + QTextStream in(stdin); + data = in.readAll(); + if (data.size() == 0) { + error(QStringLiteral("No data, neither on commandline nor on stdin"), QString()); + } + } + + auto barcode = Prison::Barcode::create(Prison::DataMatrix); + if (!barcode) { + error(QStringLiteral("unsupported barcode type"), QString()); + } + + barcode->setData(data); + QImage result = barcode->toImage(QSizeF(intsize, intsize)); + QImageWriter w(outputfile, outputformat.toLocal8Bit()); + if (!w.write(result)) { + error(QStringLiteral("writing failed"), w.errorString()); + } + return EXIT_SUCCESS; +} diff --git a/local/recipes/kde/kf6-prison/source/tests/CMakeLists.txt b/local/recipes/kde/kf6-prison/source/tests/CMakeLists.txt new file mode 100644 index 00000000..b88fc722 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(prison-test) +target_sources(prison-test PRIVATE prisontest.cpp barcodeexamplewidget.cpp main.cpp) +target_link_libraries(prison-test Qt6::Widgets KF6::Prison) diff --git a/local/recipes/kde/kf6-prison/source/tests/barcode.qml b/local/recipes/kde/kf6-prison/source/tests/barcode.qml new file mode 100644 index 00000000..0922fcc7 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/barcode.qml @@ -0,0 +1,67 @@ +/* + SPDX-FileCopyrightText: 2018 Volker Krause + + SPDX-License-Identifier: MIT +*/ + +import QtQuick 2.0 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 +import org.kde.prison 1.0 as Prison +Rectangle { + width: 640 + height: 320 + color: "lightsteelblue" + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + TextField { + id: contentEdit + Layout.fillWidth: true + text: "KF5::Prison - The KDE barcode generation framework." + } + ComboBox { + id: typeCombobox + model: [ "QRCode", "DataMatrix", "Aztec", "Code39", "Code93", "Code128", "PDF417", "EAN13" ] + currentIndex: 3 + } + Button { + text: "undef" + onClicked: barcode.barcodeType = undefined + } + } + + Prison.Barcode { + id: barcode + Layout.fillWidth: true + Layout.fillHeight: true + content: contentEdit.text + barcodeType: typeCombobox.currentIndex +// foregroundColor: "red" +// backgroundColor: "green" + } + + RowLayout { + Label { + text: "1D: " + (barcode.dimensions == Prison.Barcode.OneDimension) + } + Label { + text: "2D: " + (barcode.dimensions == 2) + } + Label { + text: "Min size: " + barcode.minimumWidth + "x" + barcode.minimumHeight + } + } + Prison.Barcode { + id: nullbarcode + Layout.fillWidth: true + Layout.fillHeight: true + content: contentEdit.text + Component.onCompleted: { + console.log(nullbarcode.barcodeType) + } + } + } +} diff --git a/local/recipes/kde/kf6-prison/source/tests/barcodeexamplewidget.cpp b/local/recipes/kde/kf6-prison/source/tests/barcodeexamplewidget.cpp new file mode 100644 index 00000000..714e759d --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/barcodeexamplewidget.cpp @@ -0,0 +1,89 @@ +/* + SPDX-FileCopyrightText: 2010-2014 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#include "barcodeexamplewidget.h" +// Qt +#include +#include +#include +#include +#include +#include + +using namespace Prison; + +BarcodeExampleWidget::BarcodeExampleWidget(std::optional barcode, QWidget *parent) + : QWidget(parent) + , m_barcode(std::move(barcode)) +{ + if (!barcode) { + qDebug() << "unsupported barcode, showing a black square"; + } +} + +BarcodeExampleWidget::BarcodeExampleWidget(BarcodeType barcode, QWidget *parent) + : BarcodeExampleWidget(Barcode::create(barcode), parent) +{ +} + +void BarcodeExampleWidget::setData(const QString &data) +{ + if (m_barcode) { + m_barcode->setData(data); + updateGeometry(); + repaint(); + } +} + +void BarcodeExampleWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + if (m_barcode) { + QRect targetrect = rect(); + QImage image = m_barcode->toImage(targetrect.size()); + if (!image.isNull()) { + QRectF rect(targetrect.left() + targetrect.width() / 2 - image.size().width() / 2, + targetrect.top() + targetrect.height() / 2 - image.size().height() / 2, + targetrect.size().width(), + targetrect.height()); + painter.drawImage(rect.topLeft(), image, image.rect()); + } else { + painter.fillRect(QRectF(QPointF(0, 0), size()), Qt::cyan); + } + } else { + painter.fillRect(QRectF(QPointF(0, 0), size()), Qt::black); + } + QWidget::paintEvent(event); +} + +void BarcodeExampleWidget::resizeEvent(QResizeEvent *event) +{ + updateGeometry(); + repaint(); + QWidget::resizeEvent(event); +} + +void BarcodeExampleWidget::mousePressEvent(QMouseEvent *event) +{ + if (m_barcode && event->buttons() & Qt::LeftButton) { + QMimeData *data = new QMimeData(); + data->setImageData(m_barcode->toImage(rect().size())); + QDrag *drag = new QDrag(this); + drag->setMimeData(data); + drag->exec(); + } else { + QWidget::mousePressEvent(event); + } +} + +QSize BarcodeExampleWidget::minimumSizeHint() const +{ + if (m_barcode) + return m_barcode->preferredSize(QGuiApplication::primaryScreen()->devicePixelRatio()).toSize(); + return QSize(10, 10); +} + +BarcodeExampleWidget::~BarcodeExampleWidget() = default; diff --git a/local/recipes/kde/kf6-prison/source/tests/barcodeexamplewidget.h b/local/recipes/kde/kf6-prison/source/tests/barcodeexamplewidget.h new file mode 100644 index 00000000..9f87508c --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/barcodeexamplewidget.h @@ -0,0 +1,60 @@ +/* + SPDX-FileCopyrightText: 2010-2014 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#ifndef PRISON_BARCODEWIDGET_H +#define PRISON_BARCODEWIDGET_H + +#include + +#include + +/** + * QWidget with a barcode on + */ +class BarcodeExampleWidget : public QWidget +{ +public: + BarcodeExampleWidget(std::optional barcode, QWidget *parent = nullptr); + /** + * Creates a barcode widget with 'barcode' as barcode generator + * @param barcode The barcode generator for this widget. Takes ownership over the barcode generator + * @param parent the parent in QWidget hierarchy + */ + BarcodeExampleWidget(Prison::BarcodeType barcode, QWidget *parent = nullptr); + ~BarcodeExampleWidget() override; + /** + * sets the data shown to data, and triggers a repaint and resize if needed + * @param data QString holding the data to be shown + */ + void setData(const QString &data); + /** + * Reimplementation + * @return minimumSizeHint for this widget + */ + QSize minimumSizeHint() const override; + +protected: + /** + * paintEvent + * @param event QPaintEvent + */ + void paintEvent(QPaintEvent *event) override; + /** + * resizeEvent + * @param event QResizeEvent + */ + void resizeEvent(QResizeEvent *event) override; + /** + * enables drag from the barcodewidget + * @param event QMouseEvent + */ + void mousePressEvent(QMouseEvent *event) override; + +private: + std::optional m_barcode; +}; + +#endif // PRISON_BARCODEWIDGET_H diff --git a/local/recipes/kde/kf6-prison/source/tests/main.cpp b/local/recipes/kde/kf6-prison/source/tests/main.cpp new file mode 100644 index 00000000..c72caab3 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/main.cpp @@ -0,0 +1,10 @@ +#include "prisontest.h" +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + main_window foo; + foo.show(); + return app.exec(); +} diff --git a/local/recipes/kde/kf6-prison/source/tests/prisontest.cpp b/local/recipes/kde/kf6-prison/source/tests/prisontest.cpp new file mode 100644 index 00000000..77b53530 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/prisontest.cpp @@ -0,0 +1,101 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Sune Vuorela + + SPDX-License-Identifier: MIT +*/ + +#include "prisontest.h" + +#include "barcodeexamplewidget.h" +// Prison +#include +// Qt +#include +#include +#include +#include +#include + +void main_window::data_changed() +{ + QString result = m_lineedit->text(); + m_dmw->setData(result); + m_qrw->setData(result); + m_39w->setData(result); + m_93w->setData(result); + m_dmcolor->setData(result); + m_qrcolor->setData(result); + m_39color->setData(result); + m_93color->setData(result); + m_nullw->setData(result); +} + +main_window::main_window() +{ + QHBoxLayout *lay = new QHBoxLayout(); + m_lineedit = new QLineEdit(this); + QPushButton *but = new QPushButton(this); + connect(but, &QPushButton::clicked, this, &main_window::data_changed); + lay->addWidget(m_lineedit); + lay->addWidget(but); + + QVBoxLayout *mainlay = new QVBoxLayout(this); + + m_dmw = new BarcodeExampleWidget(Prison::DataMatrix, this); + m_qrw = new BarcodeExampleWidget(Prison::QRCode, this); + m_39w = new BarcodeExampleWidget(Prison::Code39, this); + m_93w = new BarcodeExampleWidget(Prison::Code93, this); + { + auto dmcolorcode = Prison::Barcode::create(Prison::DataMatrix); + if (dmcolorcode) { + dmcolorcode->setForegroundColor(Qt::red); + dmcolorcode->setBackgroundColor(Qt::darkBlue); + m_dmcolor = new BarcodeExampleWidget(std::move(dmcolorcode), this); + } + } + { + auto qrcolorcode = Prison::Barcode::create(Prison::QRCode); + if (qrcolorcode) { + qrcolorcode->setForegroundColor(Qt::red); + qrcolorcode->setBackgroundColor(Qt::darkBlue); + } + m_qrcolor = new BarcodeExampleWidget(std::move(qrcolorcode), this); + } + { + auto c39colorcode = Prison::Barcode::create(Prison::Code39); + if (c39colorcode) { + c39colorcode->setForegroundColor(Qt::red); + c39colorcode->setBackgroundColor(Qt::darkBlue); + } + m_39color = new BarcodeExampleWidget(std::move(c39colorcode), this); + } + { + auto c93colorcode = Prison::Barcode::create(Prison::Code93); + if (c93colorcode) { + c93colorcode->setForegroundColor(Qt::red); + c93colorcode->setBackgroundColor(Qt::darkBlue); + } + m_93color = new BarcodeExampleWidget(std::move(c93colorcode), this); + } + + m_nullw = new BarcodeExampleWidget(std::nullopt, this); + + QSplitter *splitter = new QSplitter(Qt::Vertical); + splitter->addWidget(m_dmw); + splitter->addWidget(m_qrw); + splitter->addWidget(m_39w); + splitter->addWidget(m_93w); + splitter->addWidget(m_dmcolor); + splitter->addWidget(m_qrcolor); + splitter->addWidget(m_39color); + splitter->addWidget(m_93color); + splitter->addWidget(m_nullw); + + mainlay->addLayout(lay); + mainlay->addWidget(splitter); + + m_lineedit->setText(QStringLiteral("AOEUIAOEUIAOEUI")); + data_changed(); +} + +#include "moc_prisontest.cpp" diff --git a/local/recipes/kde/kf6-prison/source/tests/prisontest.h b/local/recipes/kde/kf6-prison/source/tests/prisontest.h new file mode 100644 index 00000000..a60b33e2 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/prisontest.h @@ -0,0 +1,30 @@ +#ifndef prison_H +#define prison_H + +#include + +class BarcodeExampleWidget; + +class QLineEdit; +class main_window : public QWidget +{ + Q_OBJECT +public: + main_window(); +public Q_SLOTS: + void data_changed(); + +private: + QLineEdit *m_lineedit = nullptr; + BarcodeExampleWidget *m_dmw = nullptr; + BarcodeExampleWidget *m_qrw = nullptr; + BarcodeExampleWidget *m_39w = nullptr; + BarcodeExampleWidget *m_93w = nullptr; + BarcodeExampleWidget *m_dmcolor = nullptr; + BarcodeExampleWidget *m_qrcolor = nullptr; + BarcodeExampleWidget *m_39color = nullptr; + BarcodeExampleWidget *m_93color = nullptr; + BarcodeExampleWidget *m_nullw = nullptr; +}; + +#endif // prison_H diff --git a/local/recipes/kde/kf6-prison/source/tests/scanner-qt6.qml b/local/recipes/kde/kf6-prison/source/tests/scanner-qt6.qml new file mode 100644 index 00000000..7a2be296 --- /dev/null +++ b/local/recipes/kde/kf6-prison/source/tests/scanner-qt6.qml @@ -0,0 +1,48 @@ +/* + SPDX-FileCopyrightText: 2022 Volker Krause + SPDX-License-Identifier: MIT +*/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtMultimedia 6.2 +import org.kde.prison.scanner 1.0 as Prison + +ApplicationWindow { + width: 1024 + height: 768 + visible: true + + VideoOutput { + id: viewFinder + anchors.fill: parent + } + + Prison.VideoScanner { + id: scanner + formats: Prison.Format.QRCode | Prison.Format.Aztec + onResultChanged: console.log(result.text, result.format); + videoSink: viewFinder.videoSink + } + + CaptureSession { + camera: Camera { + id: camera + active: true + function onErrorOccurred(error, errorString) { + console.log("Camera error: " + camera.errorString) + } + } + videoOutput: viewFinder + } + + Rectangle { + color: "#80ff0000" + x: viewFinder.contentRect.x + scanner.result.boundingRect.x / viewFinder.sourceRect.width * viewFinder.contentRect.width + y: viewFinder.contentRect.y + scanner.result.boundingRect.y / viewFinder.sourceRect.height * viewFinder.contentRect.height + width: scanner.result.boundingRect.width / viewFinder.sourceRect.width * viewFinder.contentRect.width + height: scanner.result.boundingRect.height / viewFinder.sourceRect.height * viewFinder.contentRect.height + } +} + diff --git a/local/recipes/system/driver-params/source/src/main.rs b/local/recipes/system/driver-params/source/src/main.rs index 50a74da3..ed3e5384 100644 --- a/local/recipes/system/driver-params/source/src/main.rs +++ b/local/recipes/system/driver-params/source/src/main.rs @@ -17,7 +17,7 @@ use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_ACCMODE, O_RDONLY}; use syscall::schemev2::NewFdFlags; use syscall::Stat; -const SCHEME_NAME: &str = "sys/driver"; +const SCHEME_NAME: &str = "driver-params"; const SCHEME_ROOT_ID: usize = 1; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -563,7 +563,8 @@ fn notify_scheme_ready( syscall::CallFlags::FD, &[], ) - .map_err(|err| format!("driver-params: failed to notify init that scheme is ready: {err}")) + .map_err(|err| format!("driver-params: failed to notify init that scheme is ready: {err}"))?; + Ok(()) } #[cfg(target_os = "redox")] diff --git a/local/recipes/system/redbear-acmd/source/src/main.rs b/local/recipes/system/redbear-acmd/source/src/main.rs index 496972c7..33557b4c 100644 --- a/local/recipes/system/redbear-acmd/source/src/main.rs +++ b/local/recipes/system/redbear-acmd/source/src/main.rs @@ -1,23 +1,22 @@ -use log::{info, warn, LevelFilter}; +use log::{info, LevelFilter}; use std::fs; use std::time::Duration; - struct StderrLogger; impl log::Log for StderrLogger { fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info } fn log(&self, r: &log::Record) { eprintln!("[{}] redbear-acmd: {}", r.level(), r.args()); } fn flush(&self) {} } - -fn scan_and_create() -> usize { +fn scan() -> usize { let mut n = 0; let _ = fs::create_dir_all("/dev"); if let Ok(dir) = fs::read_dir("/scheme/usb") { - for entry in dir.flatten() { - if let Ok(config) = fs::read_to_string(entry.path().join("config")) { - if config.contains("class=0a") || config.contains("CDC ACM") { - let tty = format!("/dev/ttyACM{}", n); - let _ = fs::write(&tty, &[]); + for e in dir.flatten() { + if let Ok(c) = fs::read_to_string(e.path().join("config")) { + if c.contains("class=0a") || c.contains("CDC ACM") { + let tgt = e.path(); + let lnk = format!("/dev/ttyACM{}", n); + let _ = std::os::unix::fs::symlink(&tgt, &lnk); n += 1; } } @@ -25,14 +24,9 @@ fn scan_and_create() -> usize { } n } - fn main() { log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); info!("redbear-acmd: USB CDC ACM serial daemon"); - loop { - let n = scan_and_create(); - if n > 0 { info!("redbear-acmd: {} ttyACM device(s)", n); } - std::thread::sleep(Duration::from_secs(5)); - } + loop { let c = scan(); if c > 0 { info!("redbear-acmd: {} ttyACM symlink(s)", c); } std::thread::sleep(Duration::from_secs(5)); } } diff --git a/local/recipes/system/redbear-ecmd/source/src/main.rs b/local/recipes/system/redbear-ecmd/source/src/main.rs index a7d48dcf..ac2bf6b2 100644 --- a/local/recipes/system/redbear-ecmd/source/src/main.rs +++ b/local/recipes/system/redbear-ecmd/source/src/main.rs @@ -1,23 +1,22 @@ -use log::{info, warn, LevelFilter}; +use log::{info, LevelFilter}; use std::fs; use std::time::Duration; - struct StderrLogger; impl log::Log for StderrLogger { fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info } fn log(&self, r: &log::Record) { eprintln!("[{}] redbear-ecmd: {}", r.level(), r.args()); } fn flush(&self) {} } - -fn scan_and_create() -> usize { +fn scan() -> usize { let mut n = 0; let _ = fs::create_dir_all("/dev/net"); if let Ok(dir) = fs::read_dir("/scheme/usb") { - for entry in dir.flatten() { - if let Ok(config) = fs::read_to_string(entry.path().join("config")) { - if config.contains("class=02") && (config.contains("subclass=06") || config.contains("subclass=0d")) { - let dev = format!("/dev/net/usb{}", n); - let _ = fs::write(&dev, &[]); + for e in dir.flatten() { + if let Ok(c) = fs::read_to_string(e.path().join("config")) { + if c.contains("class=02") && (c.contains("subclass=06") || c.contains("subclass=0d")) { + let tgt = e.path(); + let lnk = format!("/dev/net/usb{}", n); + let _ = std::os::unix::fs::symlink(&tgt, &lnk); n += 1; } } @@ -25,14 +24,9 @@ fn scan_and_create() -> usize { } n } - fn main() { log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); info!("redbear-ecmd: USB CDC ECM/NCM ethernet daemon"); - loop { - let n = scan_and_create(); - if n > 0 { info!("redbear-ecmd: {} usb net device(s)", n); } - std::thread::sleep(Duration::from_secs(5)); - } + loop { let c = scan(); if c > 0 { info!("redbear-ecmd: {} usb net symlink(s)", c); } std::thread::sleep(Duration::from_secs(5)); } } diff --git a/local/recipes/system/redbear-usbaudiod/source/src/main.rs b/local/recipes/system/redbear-usbaudiod/source/src/main.rs index 00cf9017..bce8b84e 100644 --- a/local/recipes/system/redbear-usbaudiod/source/src/main.rs +++ b/local/recipes/system/redbear-usbaudiod/source/src/main.rs @@ -1,23 +1,22 @@ -use log::{info, warn, LevelFilter}; +use log::{info, LevelFilter}; use std::fs; use std::time::Duration; - struct StderrLogger; impl log::Log for StderrLogger { fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info } fn log(&self, r: &log::Record) { eprintln!("[{}] redbear-usbaudiod: {}", r.level(), r.args()); } fn flush(&self) {} } - -fn scan_and_create() -> usize { +fn scan() -> usize { let mut n = 0; let _ = fs::create_dir_all("/dev/audio"); if let Ok(dir) = fs::read_dir("/scheme/usb") { - for entry in dir.flatten() { - if let Ok(config) = fs::read_to_string(entry.path().join("config")) { - if config.contains("class=01") { - let dev = format!("/dev/audio/usb{}", n); - let _ = fs::write(&dev, &[]); + for e in dir.flatten() { + if let Ok(c) = fs::read_to_string(e.path().join("config")) { + if c.contains("class=01") { + let tgt = e.path(); + let lnk = format!("/dev/audio/usb{}", n); + let _ = std::os::unix::fs::symlink(&tgt, &lnk); n += 1; } } @@ -25,14 +24,9 @@ fn scan_and_create() -> usize { } n } - fn main() { log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); info!("redbear-usbaudiod: USB Audio Class daemon"); - loop { - let n = scan_and_create(); - if n > 0 { info!("redbear-usbaudiod: {} usb audio device(s)", n); } - std::thread::sleep(Duration::from_secs(5)); - } + loop { let c = scan(); if c > 0 { info!("redbear-usbaudiod: {} usb audio symlink(s)", c); } std::thread::sleep(Duration::from_secs(5)); } } diff --git a/local/scripts/fetch-firmware.sh b/local/scripts/fetch-firmware.sh index 9bd68cbe..5aadc5b3 100755 --- a/local/scripts/fetch-firmware.sh +++ b/local/scripts/fetch-firmware.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Fetch bounded GPU firmware blobs from linux-firmware repository. -# AMD remains the larger set; Intel support here is intentionally limited to -# display-critical DMC blobs for the current bounded startup manifest. +# Fetch bounded firmware blobs from linux-firmware repository. +# AMD support remains GPU-focused; Intel support now includes display, Wi-Fi, +# and Bluetooth subsets for current Red Bear bring-up milestones. set -euo pipefail @@ -10,27 +10,36 @@ LINUX_FIRMWARE_REPO="https://git.kernel.org/pub/scm/linux/kernel/git/firmware/li TEMP_DIR=$(mktemp -d) VENDOR="amd" SUBSET="all" +COPIED_COUNT=0 usage() { cat </dev/null | wc -l) firmware blobs" + COPIED_COUNT=${#selected_blobs[@]} + echo "Copied $COPIED_COUNT firmware blobs" echo "=== Verifying firmware selection ===" if [ "$SUBSET" = "rdna" ]; then @@ -222,60 +232,84 @@ copy_intel_dmc_firmware() { rm -f "$FIRMWARE_DIR"/*.bin cp -v "${selected_blobs[@]}" "$FIRMWARE_DIR/" + COPIED_COUNT=${#selected_blobs[@]} + echo "Copied $COPIED_COUNT Intel DMC firmware blobs" +} - cat > "$FIRMWARE_DIR/MANIFEST.txt" <<'MANIFEST' -# Intel GPU Firmware for Red Bear OS (bounded startup slice) -# Source: linux-firmware (https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git) -# Scope: display-critical DMC blobs only -# -# This subset is intentionally bounded to startup/display proof for current Intel DRM work. -# It does NOT include GuC/HuC/GSC runtime/render/media firmware. -# -# Current bounded candidates: -# - adlp_dmc.bin / adlp_dmc_ver2_16.bin -# - tgl_dmc.bin / tgl_dmc_ver2_12.bin -# - dg2_dmc.bin / dg2_dmc_ver2_06.bin -# - mtl_dmc.bin -MANIFEST +copy_intel_wifi_firmware() { + echo "Copying Intel Wi-Fi firmware blobs..." - echo "Copied ${#selected_blobs[@]} Intel DMC firmware blobs" + shopt -s nullglob + local ucode_blobs=("$TEMP_DIR/linux-firmware/"iwlwifi-*.ucode) + local pnvm_blobs=("$TEMP_DIR/linux-firmware/"iwlwifi-*.pnvm) + local selected_blobs=("${ucode_blobs[@]}" "${pnvm_blobs[@]}") + + if [ "${#ucode_blobs[@]}" -eq 0 ]; then + echo "ERROR: No Intel Wi-Fi .ucode blobs were found" + exit 1 + fi + if [ "${#pnvm_blobs[@]}" -eq 0 ]; then + echo "ERROR: No Intel Wi-Fi .pnvm blobs were found" + exit 1 + fi + + rm -f "$FIRMWARE_DIR"/iwlwifi-*.ucode "$FIRMWARE_DIR"/iwlwifi-*.pnvm + cp -v "${selected_blobs[@]}" "$FIRMWARE_DIR/" + COPIED_COUNT=${#selected_blobs[@]} + echo "Copied $COPIED_COUNT Intel Wi-Fi firmware blobs" + shopt -u nullglob +} + +copy_intel_bluetooth_firmware() { + echo "Copying Intel Bluetooth firmware blobs..." + + if [ ! -d "$TEMP_DIR/linux-firmware/intel" ]; then + echo "ERROR: intel firmware directory not found in linux-firmware" + exit 1 + fi + + shopt -s nullglob + local sfi_blobs=("$TEMP_DIR/linux-firmware/intel/"ibt-*.sfi) + local ddc_blobs=("$TEMP_DIR/linux-firmware/intel/"ibt-*.ddc) + local selected_blobs=("${sfi_blobs[@]}" "${ddc_blobs[@]}") + + if [ "${#sfi_blobs[@]}" -eq 0 ]; then + echo "ERROR: No Intel Bluetooth .sfi blobs were found" + exit 1 + fi + if [ "${#ddc_blobs[@]}" -eq 0 ]; then + echo "ERROR: No Intel Bluetooth .ddc blobs were found" + exit 1 + fi + + rm -f "$FIRMWARE_DIR"/ibt-*.sfi "$FIRMWARE_DIR"/ibt-*.ddc + cp -v "${selected_blobs[@]}" "$FIRMWARE_DIR/" + COPIED_COUNT=${#selected_blobs[@]} + echo "Copied $COPIED_COUNT Intel Bluetooth firmware blobs" + shopt -u nullglob } case "$VENDOR" in amd) copy_amd_firmware - echo "=== Creating firmware manifest ===" - cat > "$FIRMWARE_DIR/MANIFEST.txt" << 'MANIFEST' -# AMD GPU Firmware for Red Bear OS -# Source: linux-firmware (https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git) -# License: Various — see linux-firmware WHENCE file for details -# -# Required for: RDNA2 (gfx10.3), RDNA3 (gfx11) -# Minimum set for basic display output: -# - PSP SOS + TA (security processor) -# - GC ME/PFP/CE/MEC (graphics/compute) -# - SDMA (DMA engine) -# - DMCUB (Display Microcontroller) -# -# Key files for RDNA2 (Navi 21/22/23/24, gfx10.3): -# psp_13_0_*_sos.bin, gc_10_3_*.bin, sdma_5_*.bin, dcn_3_*.bin -# -# Key files for RDNA3 (Navi 31/32/33, gfx11): -# psp_13_*_sos.bin, gc_11_0_*.bin, sdma_6_*.bin, dcn_3_1_*.bin -MANIFEST - echo "$FIRMWARE_DIR/MANIFEST.txt created" ;; intel) - copy_intel_dmc_firmware + case "$SUBSET" in + dmc) copy_intel_dmc_firmware ;; + wifi) copy_intel_wifi_firmware ;; + bluetooth) copy_intel_bluetooth_firmware ;; + esac ;; esac +echo "NOTE: MANIFEST.txt is now generated by firmware-loader (--generate-manifest or daemon startup)." + # Summary echo "" echo "=== Firmware blobs installed ===" ls -la "$FIRMWARE_DIR/" | head -20 echo "..." -echo "Total: $(ls "$FIRMWARE_DIR/"*.bin 2>/dev/null | wc -l) blobs" +echo "Copied: $COPIED_COUNT files" echo "" echo "WARNING: These firmware blobs are third-party upstream firmware." echo "They are NOT open source. Verify your license compliance." diff --git a/local/scripts/test-ehci-qemu.sh b/local/scripts/test-ehci-qemu.sh new file mode 100755 index 00000000..706c4b82 --- /dev/null +++ b/local/scripts/test-ehci-qemu.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +IMAGE="${1:-$REPO_ROOT/build/x86_64/redbear-mini/harddrive.img}" +[ ! -f "$IMAGE" ] && { echo "Build first: CI=1 make all CONFIG_NAME=redbear-mini"; exit 1; } +OUT=$(mktemp); trap "rm -f $OUT" EXIT +timeout 60 qemu-system-x86_64 -M q35 -m 2048 -enable-kvm -device usb-ehci,id=ehci -device usb-tablet \ + -drive file="$IMAGE",format=raw,if=none,id=disk -device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 \ + -serial file:"$OUT" -display none -no-reboot 2>/dev/null || true +pass=0; total=0 +grep -qi "ehci\|EHCI" "$OUT" && { echo "[PASS] EHCI driver loaded"; pass=$((pass+1)); } || echo "[FAIL] EHCI not detected" +total=$((total+1)) +grep -q "scheme" "$OUT" && { echo "[PASS] scheme activity"; pass=$((pass+1)); } || echo "[WARN] scheme not confirmed" +total=$((total+1)) +echo "EHCI: $pass/$total checks passed" +[ $pass -eq $total ] && exit 0 || exit 1 diff --git a/recipes/core/base/P5-init-daemon-panic-hardening.patch b/recipes/core/base/P5-init-daemon-panic-hardening.patch new file mode 120000 index 00000000..9f80f92c --- /dev/null +++ b/recipes/core/base/P5-init-daemon-panic-hardening.patch @@ -0,0 +1 @@ +../../../local/patches/base/P5-init-daemon-panic-hardening.patch \ No newline at end of file diff --git a/recipes/core/base/drivers/acpid/src/acpi.rs b/recipes/core/base/drivers/acpid/src/acpi.rs index 94a1eb17..3521bfc7 100644 --- a/recipes/core/base/drivers/acpid/src/acpi.rs +++ b/recipes/core/base/drivers/acpid/src/acpi.rs @@ -136,9 +136,10 @@ impl Sdt { let header = match plain::from_bytes::(&slice) { Ok(header) => header, Err(plain::Error::TooShort) => return Err(InvalidSdtError::InvalidSize), - Err(plain::Error::BadAlignment) => panic!( - "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!" - ), + Err(plain::Error::BadAlignment) => { + log::error!("acpid: plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)] - internal inconsistency"); + return Err(InvalidSdtError::InvalidSize); + } }; if header.length() != slice.len() { diff --git a/recipes/core/base/drivers/acpid/src/main.rs b/recipes/core/base/drivers/acpid/src/main.rs index 059254b3..8f99f2ea 100644 --- a/recipes/core/base/drivers/acpid/src/main.rs +++ b/recipes/core/base/drivers/acpid/src/main.rs @@ -28,9 +28,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { log::info!("acpid start"); - let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") - .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") - .into(); + let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") { + Ok(data) => data.into(), + Err(err) => { + log::error!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {}", err); + std::process::exit(1); + } + }; if rxsdt_raw_data.is_empty() { log::info!("System doesn't use ACPI"); @@ -38,7 +42,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { std::process::exit(0); } - let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); + let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) { + Ok(sdt) => sdt, + Err(err) => { + log::error!("acpid: failed to parse [RX]SDT: {:?}", err); + std::process::exit(1); + } + }; let mut thirty_two_bit; let mut sixty_four_bit; @@ -64,7 +74,10 @@ fn daemon(daemon: daemon::Daemon) -> ! { &mut sixty_four_bit as &mut dyn Iterator } - _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), + _ => { + log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT"); + std::process::exit(1); + } }; let region_handlers: Vec<(RegionSpace, Box)> = vec![ @@ -75,49 +88,84 @@ fn daemon(daemon: daemon::Daemon) -> ! { // TODO: I/O permission bitmap? #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3"); + if let Err(err) = common::acquire_port_io_rights() { + log::error!("acpid: failed to set I/O privilege level to Ring 3: {:?}", err); + std::process::exit(1); + } - let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") - .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); + let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") { + Ok(f) => f, + Err(err) => { + log::error!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {}", err); + std::process::exit(1); + } + }; - let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue"); - let socket = Socket::nonblock().expect("acpid: failed to create disk scheme"); + let mut event_queue = match RawEventQueue::new() { + Ok(q) => q, + Err(err) => { + log::error!("acpid: failed to create event queue: {:?}", err); + std::process::exit(1); + } + }; + let socket = match Socket::nonblock() { + Ok(s) => s, + Err(err) => { + log::error!("acpid: failed to create scheme socket: {:?}", err); + std::process::exit(1); + } + }; let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket); let mut handler = Blocking::new(&socket, 16); - event_queue + if let Err(err) = event_queue .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) - .expect("acpid: failed to register shutdown pipe for event queue"); - event_queue + { + log::error!("acpid: failed to register shutdown pipe for event queue: {:?}", err); + std::process::exit(1); + } + if let Err(err) = event_queue .subscribe(socket.inner().raw(), 1, EventFlags::READ) - .expect("acpid: failed to register scheme socket for event queue"); + { + log::error!("acpid: failed to register scheme socket for event queue: {:?}", err); + std::process::exit(1); + } - register_sync_scheme(&socket, "acpi", &mut scheme) - .expect("acpid: failed to register acpi scheme to namespace"); + if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) { + log::error!("acpid: failed to register acpi scheme to namespace: {:?}", err); + std::process::exit(1); + } daemon.ready(); - libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); + if let Err(err) = libredox::call::setrens(0, 0) { + log::error!("acpid: failed to enter null namespace: {}", err); + std::process::exit(1); + } let mut mounted = true; while mounted { - let Some(event) = event_queue - .next() - .transpose() - .expect("acpid: failed to read event file") - else { - break; + let event = match event_queue.next().transpose() { + Ok(Some(ev)) => ev, + Ok(None) => break, + Err(err) => { + log::error!("acpid: failed to read event file: {:?}", err); + break; + } }; if event.fd == socket.inner().raw() { loop { - match handler - .process_requests_nonblocking(&mut scheme) - .expect("acpid: failed to process requests") - { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, + match handler.process_requests_nonblocking(&mut scheme) { + Ok(flow) => match flow { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => break, + }, + Err(err) => { + log::error!("acpid: failed to process requests: {:?}", err); + break; + } } } } else if event.fd == shutdown_pipe.as_raw_fd() as usize { diff --git a/recipes/core/base/drivers/pcid-spawner/recipe.toml b/recipes/core/base/drivers/pcid-spawner/recipe.toml new file mode 100644 index 00000000..61e62e08 --- /dev/null +++ b/recipes/core/base/drivers/pcid-spawner/recipe.toml @@ -0,0 +1,8 @@ +[source] +path = "../../source/drivers/pcid-spawner" + +[build] +template = "cargo" + +[package] +dependencies = ["base"] diff --git a/recipes/core/base/drivers/pcid/src/main.rs b/recipes/core/base/drivers/pcid/src/main.rs index 61cd9a78..cad33114 100644 --- a/recipes/core/base/drivers/pcid/src/main.rs +++ b/recipes/core/base/drivers/pcid/src/main.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; -use log::{debug, info, trace, warn}; +use log::{debug, error, info, trace, warn}; use pci_types::capability::PciCapability; use pci_types::{ Bar as TyBar, CommandRegister, EndpointHeader, HeaderType, PciAddress, @@ -259,17 +259,25 @@ fn daemon(daemon: daemon::Daemon) -> ! { Ok(register_pci) => { let access_id = scheme.access(); - let access_fd = socket + let access_fd = match socket .create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) - .expect("failed to issue this resource"); - let access_bytes = access_fd.to_ne_bytes(); - let _ = register_pci - .call_wo( + { + Ok(fd) => Some(fd), + Err(err) => { + warn!("pcid: failed to issue acpi resource fd: {:?}", err); + None + } + }; + if let Some(access_fd) = access_fd { + let access_bytes = access_fd.to_ne_bytes(); + if let Err(err) = register_pci.call_wo( &access_bytes, syscall::CallFlags::WRITE | syscall::CallFlags::FD, &[], - ) - .expect("failed to send pci_fd to acpid"); + ) { + warn!("pcid: failed to send pci_fd to acpid: {:?}", err); + } + } } Err(err) => { if err.errno() == libredox::errno::ENODEV { @@ -304,14 +312,17 @@ fn daemon(daemon: daemon::Daemon) -> ! { } debug!("Enumeration complete, now starting pci scheme"); - register_sync_scheme(&socket, "pci", &mut scheme) - .expect("failed to register pci scheme to namespace"); + if let Err(err) = register_sync_scheme(&socket, "pci", &mut scheme) { + error!("pcid: failed to register pci scheme to namespace: {:?}", err); + std::process::exit(1); + } let _ = daemon.ready(); - handler - .process_requests_blocking(scheme) - .expect("pcid: failed to process requests"); + handler.process_requests_blocking(scheme).unwrap_or_else(|err| { + error!("pcid: failed to process requests: {:?}", err); + std::process::exit(1); + }); } fn scan_device( diff --git a/recipes/core/base/init/src/main.rs b/recipes/core/base/init/src/main.rs index 5682cf44..72c97f53 100644 --- a/recipes/core/base/init/src/main.rs +++ b/recipes/core/base/init/src/main.rs @@ -166,19 +166,29 @@ fn main() { } }; for entry in entries { + let Some(file_name) = entry.file_name().and_then(|n| n.to_str()) else { + eprintln!("init: skipping entry with invalid filename: {}", entry.display()); + continue; + }; scheduler.schedule_start_and_report_errors( &mut unit_store, - UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), + UnitId(file_name.to_owned()), ); } }; scheduler.step(&mut unit_store, &mut init_config); - libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); + if let Err(err) = libredox::call::setrens(0, 0) { + eprintln!("init: failed to enter null namespace: {}", err); + return; + } loop { let mut status = 0; - libredox::call::waitpid(0, &mut status, 0).unwrap(); + match libredox::call::waitpid(0, &mut status, 0) { + Ok(()) => {} + Err(err) => eprintln!("init: waitpid error: {}", err), + } } } diff --git a/recipes/core/base/init/src/scheduler.rs b/recipes/core/base/init/src/scheduler.rs index d42a4e57..333e0e20 100644 --- a/recipes/core/base/init/src/scheduler.rs +++ b/recipes/core/base/init/src/scheduler.rs @@ -1,7 +1,16 @@ use std::collections::VecDeque; +use std::io::Read; +use std::os::fd::AsRawFd; +use std::os::unix::process::CommandExt; +use std::process::Command; +use std::time::Duration; +use std::{env, io}; use crate::InitConfig; -use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; +use crate::service::ServiceType; +use crate::unit::{RestartPolicy, UnitId, UnitKind, UnitStore}; + +const MAX_DEPENDENCY_WAIT_RETRIES: u32 = 1000; pub struct Scheduler { pending: VecDeque, @@ -10,10 +19,12 @@ pub struct Scheduler { struct Job { unit: UnitId, kind: JobKind, + dep_retries: u32, } enum JobKind { Start, + Restart { backoff: Duration }, } impl Scheduler { @@ -50,37 +61,97 @@ impl Scheduler { self.pending.push_back(Job { unit: unit_id, kind: JobKind::Start, + dep_retries: 0, }); } } pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) { 'a: loop { - let Some(job) = self.pending.pop_front() else { + let Some(mut job) = self.pending.pop_front() else { return; }; match job.kind { JobKind::Start => { - let unit = unit_store.unit_mut(&job.unit); + let unit = unit_store.unit(&job.unit); + let timeout_secs = unit.info.dependency_timeout_secs; + let mut deps_pending = false; for dep in &unit.info.requires_weak { for pending_job in &self.pending { if &pending_job.unit == dep { - self.pending.push_back(job); - continue 'a; + deps_pending = true; + break; } } + if deps_pending { + break; + } } - run(unit, init_config); + if deps_pending { + if timeout_secs > 0 { + job.dep_retries += 1; + let max_retries = timeout_secs * 100; // ~10ms per retry + if job.dep_retries > max_retries as u32 { + eprintln!( + "init: {}: dependency timeout after {}s, failing", + job.unit.0, timeout_secs + ); + continue; + } + } else if job.dep_retries >= MAX_DEPENDENCY_WAIT_RETRIES { + eprintln!( + "init: {}: dependency wait exceeded {} retries, failing", + job.unit.0, MAX_DEPENDENCY_WAIT_RETRIES + ); + continue; + } + job.dep_retries += 1; + self.pending.push_back(job); + continue 'a; + } + + if let Err(restart) = run(unit_store, &job.unit, init_config) { + if let Some(backoff) = restart { + self.pending.push_back(Job { + unit: job.unit.clone(), + kind: JobKind::Restart { backoff }, + dep_retries: 0, + }); + } + } + } + JobKind::Restart { backoff } => { + std::thread::sleep(backoff); + let next_backoff = (backoff * 2).min(Duration::from_secs(60)); + if let Err(restart) = run(unit_store, &job.unit, init_config) { + if let Some(_next) = restart { + self.pending.push_back(Job { + unit: job.unit, + kind: JobKind::Restart { + backoff: next_backoff, + }, + dep_retries: 0, + }); + } + } } } } } } -fn run(unit: &mut Unit, config: &mut InitConfig) { +fn run( + unit_store: &UnitStore, + unit_id: &UnitId, + config: &mut InitConfig, +) -> Result<(), Option> { + let unit = unit_store.unit(unit_id); + + let restart_policy = unit.info.restart; + match &unit.kind { UnitKind::LegacyScript { script } => { for cmd in script.clone() { @@ -89,11 +160,12 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { } cmd.run(config); } + Ok(()) } UnitKind::Service { service } => { if config.skip_cmd.contains(&service.cmd) { eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); - return; + return Ok(()); } if config.log_debug { eprintln!( @@ -102,7 +174,44 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { service.cmd, ); } - service.spawn(&config.envs); + + let mut command = Command::new(&service.cmd); + command.args(&service.args); + command.env_clear(); + for env in &service.inherit_envs { + if let Some(value) = env::var_os(env) { + command.env(env, value); + } + } + command.envs(config.envs.iter().map(|(k, v)| (k.as_str(), v.as_os_str()))); + + let (read_pipe, write_pipe) = match io::pipe() { + Ok(p) => p, + Err(err) => { + eprintln!("init: pipe failed for {}: {}", service.cmd, err); + return Err(restart_signal(restart_policy)); + } + }; + + let write_fd: std::os::fd::OwnedFd = write_pipe.into(); + unsafe { + command.env("INIT_NOTIFY", format!("{}", write_fd.as_raw_fd())); + command.pre_exec(move || { + if unsafe { libc::fcntl(write_fd.as_raw_fd(), libc::F_SETFD, 0) } == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + }); + } + + let status = service_spawn_status(read_pipe, command, &service.type_, &service.cmd); + + match status { + SpawnStatus::Success => Ok(()), + SpawnStatus::Failed => Err(restart_signal(restart_policy)), + SpawnStatus::Async => Ok(()), + } } UnitKind::Target {} => { if config.log_debug { @@ -111,6 +220,113 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { unit.info.description.as_ref().unwrap_or(&unit.id.0), ); } + Ok(()) } } } + +enum SpawnStatus { + Success, + Failed, + Async, +} + +fn restart_signal(policy: RestartPolicy) -> Option { + match policy { + RestartPolicy::No => None, + RestartPolicy::OnFailure | RestartPolicy::Always => Some(Duration::from_secs(1)), + } +} + +fn service_spawn_status( + mut read_pipe: impl Read + AsRawFd, + mut command: Command, + service_type: &ServiceType, + cmd: &str, +) -> SpawnStatus { + let mut child = match command.spawn() { + Ok(child) => child, + Err(err) => { + eprintln!("init: failed to execute {}: {}", cmd, err); + return SpawnStatus::Failed; + } + }; + + match service_type { + ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { + Ok(()) => SpawnStatus::Success, + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { + eprintln!("init: {cmd} exited without notifying readiness"); + SpawnStatus::Failed + } + Err(err) => { + eprintln!("init: failed to wait for {cmd}: {err}"); + SpawnStatus::Failed + } + }, + ServiceType::Scheme(scheme) => { + let scheme = scheme.clone(); + let mut new_fd = usize::MAX; + let res = loop { + match syscall::call_ro( + read_pipe.as_raw_fd() as usize, + unsafe { plain::as_mut_bytes(&mut new_fd) }, + syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER, + &[], + ) { + Err(syscall::Error { + errno: syscall::EINTR, + }) => continue, + Ok(0) => break SpawnStatus::Failed, + Ok(1) => break SpawnStatus::Success, + Ok(n) => { + eprintln!("init: incorrect amount of fds {n} returned from {cmd}"); + break SpawnStatus::Failed; + } + Err(err) => { + eprintln!("init: failed to wait for {cmd}: {err}"); + break SpawnStatus::Failed; + } + } + }; + + if matches!(res, SpawnStatus::Success) { + match libredox::call::getns() { + Ok(current_namespace_fd) => { + if let Err(err) = libredox::call::register_scheme_to_ns( + current_namespace_fd, + &scheme, + new_fd, + ) { + eprintln!("init: scheme registration failed for {cmd}: {err}"); + return SpawnStatus::Failed; + } + } + Err(err) => { + eprintln!("init: getns failed for {cmd}: {err}"); + return SpawnStatus::Failed; + } + } + } + res + } + ServiceType::Oneshot => { + drop(read_pipe); + match child.wait() { + Ok(exit_status) => { + if !exit_status.success() { + eprintln!("init: {cmd} failed with {exit_status}"); + SpawnStatus::Failed + } else { + SpawnStatus::Success + } + } + Err(err) => { + eprintln!("init: failed to wait for {cmd}: {err}"); + SpawnStatus::Failed + } + } + } + ServiceType::OneshotAsync => SpawnStatus::Async, + } +} diff --git a/recipes/core/base/init/src/unit.rs b/recipes/core/base/init/src/unit.rs index 98053cb2..414b92d1 100644 --- a/recipes/core/base/init/src/unit.rs +++ b/recipes/core/base/init/src/unit.rs @@ -125,6 +125,25 @@ pub struct UnitInfo { pub condition_architecture: Option>, // FIXME replace this with hwd reading from the devicetree pub condition_board: Option>, + /// Restart policy for the service (only applies to Service units) + #[serde(default)] + pub restart: RestartPolicy, + /// Maximum time in seconds to wait for dependencies before failing (0 = no timeout) + #[serde(default)] + pub dependency_timeout_secs: u64, +} + +/// Restart policy for managed services +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)] +#[serde(rename_all = "kebab-case")] +pub enum RestartPolicy { + /// Never restart the service (default) + #[default] + No, + /// Restart on failure (non-zero exit or crash) + OnFailure, + /// Always restart (on any exit) + Always, } fn true_bool() -> bool { @@ -190,6 +209,8 @@ impl Unit { requires_weak: script.1, condition_architecture: None, condition_board: None, + restart: RestartPolicy::No, + dependency_timeout_secs: 0, }, kind: UnitKind::LegacyScript { script: script.0 }, }); diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 293c8dda..45a07d2d 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -5,9 +5,16 @@ patches = [ "P0-daemon-fix-init-notify-unwrap.patch", "P0-workspace-add-bootstrap.patch", "P0-bootstrap-workspace-fix.patch", + "P2-daemon-ready-graceful.patch", "P2-i2c-gpio-ucsi-drivers.patch", "P3-pcid-bind-scheme.patch", + "P3-pcid-aer-scheme.patch", + "P3-pcid-uevent-format-fix.patch", "P3-acpi-wave12-hardening.patch", + "P3-acpi-power-dmi.patch", + "P3-xhci-device-hardening.patch", + "P5-init-supervisor-restart.patch", + "P5-init-daemon-panic-hardening.patch", ] [build] diff --git a/recipes/core/kernel/recipe.toml b/recipes/core/kernel/recipe.toml index 264a631b..902725f9 100644 --- a/recipes/core/kernel/recipe.toml +++ b/recipes/core/kernel/recipe.toml @@ -1,6 +1,6 @@ [source] git = "https://gitlab.redox-os.org/redox-os/kernel.git" -patches = ["redox.patch", "P0-canary.patch", "P1-memory-map-overflow.patch", "../../../local/patches/kernel/P4-supplementary-groups.patch", "../../../local/patches/kernel/P4-s3-suspend-resume.patch", "../../../local/patches/kernel/P5-sched-policy-context.patch", "../../../local/patches/kernel/P5-sched-rt-policy.patch", "../../../local/patches/kernel/P5-proc-setschedpolicy.patch", "../../../local/patches/kernel/P5-scheme-sched-id.patch", "../../../local/patches/kernel/P5-context-mod-sched.patch", "../../../local/patches/kernel/P6-vruntime-context.patch", "../../../local/patches/kernel/P6-percpu-runqueues.patch", "../../../local/patches/kernel/P6-futex-sharding.patch", "../../../local/patches/kernel/P6-vruntime-switch.patch", "../../../local/patches/kernel/P7-cache-affine-context.patch", "../../../local/patches/kernel/P7-cache-affine-switch.patch", "../../../local/patches/kernel/P7-proc-setname.patch", "../../../local/patches/kernel/P7-proc-setpriority.patch", "../../../local/patches/kernel/P8-futex-requeue.patch", "../../../local/patches/kernel/P8-futex-pi.patch", "../../../local/patches/kernel/P8-futex-robust.patch", "../../../local/patches/kernel/P8-percpu-wiring.patch", "../../../local/patches/kernel/P8-percpu-sched.patch", "../../../local/patches/kernel/P9-proc-lock-ordering.patch", "../../../local/patches/kernel/P9-futex-pi-cas-fix.patch"] +patches = ["redox.patch", "P0-canary.patch", "P1-memory-map-overflow.patch", "../../../local/patches/kernel/P4-supplementary-groups.patch", "../../../local/patches/kernel/P4-s3-suspend-resume.patch", "../../../local/patches/kernel/P4-scheme-failure-modes.patch", "../../../local/patches/kernel/P5-sched-policy-context.patch", "../../../local/patches/kernel/P5-sched-rt-policy.patch", "../../../local/patches/kernel/P5-proc-setschedpolicy.patch", "../../../local/patches/kernel/P5-scheme-sched-id.patch", "../../../local/patches/kernel/P5-context-mod-sched.patch", "../../../local/patches/kernel/P5-boot-path-hardening.patch", "../../../local/patches/kernel/P6-vruntime-context.patch", "../../../local/patches/kernel/P6-percpu-runqueues.patch", "../../../local/patches/kernel/P6-futex-sharding.patch", "../../../local/patches/kernel/P6-vruntime-switch.patch", "../../../local/patches/kernel/P7-cache-affine-context.patch", "../../../local/patches/kernel/P7-cache-affine-switch.patch", "../../../local/patches/kernel/P7-proc-setname.patch", "../../../local/patches/kernel/P7-proc-setpriority.patch", "../../../local/patches/kernel/P8-futex-requeue.patch", "../../../local/patches/kernel/P8-futex-pi.patch", "../../../local/patches/kernel/P8-futex-robust.patch", "../../../local/patches/kernel/P8-percpu-wiring.patch", "../../../local/patches/kernel/P8-percpu-sched.patch", "../../../local/patches/kernel/P9-proc-lock-ordering.patch", "../../../local/patches/kernel/P9-futex-pi-cas-fix.patch", "../../../local/patches/kernel/P1-boot-path-diagnostics.patch"] [build] template = "custom" diff --git a/recipes/libs/glib/recipe.toml b/recipes/libs/glib/recipe.toml index c76d7560..b43eae26 100644 --- a/recipes/libs/glib/recipe.toml +++ b/recipes/libs/glib/recipe.toml @@ -17,7 +17,7 @@ dependencies = [ script = """ DYNAMIC_INIT cookbook_meson \ - -Ddefault_library=shared \ + -Ddefault_library=static \ -Dxattr=false \ -Dc_args="['-I${COOKBOOK_SYSROOT}/include','-Wno-error=implicit-function-declaration']" """ diff --git a/recipes/libs/libiconv/recipe.toml b/recipes/libs/libiconv/recipe.toml index b91224c1..604d3af6 100644 --- a/recipes/libs/libiconv/recipe.toml +++ b/recipes/libs/libiconv/recipe.toml @@ -85,6 +85,7 @@ do done export CPP="${GNU_TARGET}-gcc -E" +export CFLAGS+=" -fPIC" # Force cross mode in the shipped top-level configure and keep the rest of the # generated shell structure intact. diff --git a/recipes/tools/gettext/recipe.toml b/recipes/tools/gettext/recipe.toml index af6676d1..9d8ce459 100644 --- a/recipes/tools/gettext/recipe.toml +++ b/recipes/tools/gettext/recipe.toml @@ -23,7 +23,13 @@ dependencies = [ "libiconv" ] script = """ -DYNAMIC_STATIC_INIT +COOKBOOK_CONFIGURE_FLAGS=( + --host="${GNU_TARGET}" + --prefix="/usr" + --disable-shared + --enable-static +) +export CFLAGS+=" -fPIC" COOKBOOK_CONFIGURE_FLAGS+=( ac_cv_have_decl_program_invocation_name=no gt_cv_locale_fr=false diff --git a/recipes/wip/qt/qtbase/recipe.toml b/recipes/wip/qt/qtbase/recipe.toml index 3d5e17c0..be7e8388 100644 --- a/recipes/wip/qt/qtbase/recipe.toml +++ b/recipes/wip/qt/qtbase/recipe.toml @@ -133,7 +133,7 @@ EOF -o "${COOKBOOK_SYSROOT}/lib/libredbear-qt-strtold-compat.so" mkdir -p "${COOKBOOK_STAGE}/usr/lib" cp -f "${COOKBOOK_SYSROOT}/lib/libredbear-qt-strtold-compat.so" "${COOKBOOK_STAGE}/usr/lib/" -export LDFLAGS="${LDFLAGS} -Wl,--no-as-needed -L${COOKBOOK_SYSROOT}/lib -lredbear-qt-strtold-compat -lc" +export LDFLAGS="${LDFLAGS} -Wl,--no-as-needed -L${COOKBOOK_SYSROOT}/lib -lredbear-qt-strtold-compat -lffi -lc" export CFLAGS="${CFLAGS} -fcf-protection=none" export CXXFLAGS="${CXXFLAGS} -fcf-protection=none" @@ -154,7 +154,36 @@ GLES3_EOF fi done -# ============================================================ +mkdir -p "${COOKBOOK_SYSROOT}/include/netinet" +if [ ! -f "${COOKBOOK_SYSROOT}/include/netinet/in6_pktinfo_compat.h" ]; then + cat > "${COOKBOOK_SYSROOT}/include/netinet/in6_pktinfo_compat.h" <<'PKTINFO_EOF' +#ifndef REDBEAR_IN6_PKTINFO_COMPAT_H +#define REDBEAR_IN6_PKTINFO_COMPAT_H +#include +#ifndef IPV6_RECVPKTINFO +#define IPV6_RECVPKTINFO 46 +#endif +#ifndef IPV6_PKTINFO +#define IPV6_PKTINFO 50 +#endif +#ifndef in6_pktinfo +struct in6_pktinfo { + struct in6_addr ipi6_addr; + unsigned int ipi6_ifindex; +}; +#endif +#endif +PKTINFO_EOF +fi +python - <<'PY' +import os +from pathlib import Path +path = Path(os.environ["COOKBOOK_SOURCE"]) / "src/network/socket/qnativesocketengine_unix.cpp" +text = path.read_text() +if 'in6_pktinfo_compat' not in text: + text = '#include \\n' + text + path.write_text(text) +PY # Step 1: Build Qt host tools (moc, rcc, uic) on the host # These are needed for cross-compilation — Qt6 generates code # with these tools during the target build. @@ -618,8 +647,10 @@ PY cmake "${COOKBOOK_SOURCE}" \ -GNinja \ -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ - -DCMAKE_SHARED_LINKER_FLAGS="-lc" \ - -DCMAKE_EXE_LINKER_FLAGS="-lc" \ + -DCMAKE_SHARED_LINKER_FLAGS="-lc -lffi" \ + -DCMAKE_EXE_LINKER_FLAGS="-lc -lffi" \ + -DCMAKE_C_STANDARD_LIBRARIES="-lffi" \ + -DCMAKE_CXX_STANDARD_LIBRARIES="-lffi" \ -DQT_HOST_PATH="${HOST_BUILD}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=Release \ diff --git a/recipes/wip/qt/qtwayland/recipe.toml b/recipes/wip/qt/qtwayland/recipe.toml index e8fa97c1..026ad0d4 100644 --- a/recipes/wip/qt/qtwayland/recipe.toml +++ b/recipes/wip/qt/qtwayland/recipe.toml @@ -13,6 +13,8 @@ dependencies = [ script = """ DYNAMIC_INIT +export LDFLAGS="${LDFLAGS} -lffi" + HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build" # Clean stale CMake cache @@ -81,7 +83,10 @@ cmake "${COOKBOOK_SOURCE}" \ -DFEATURE_wayland_client=ON \ -DFEATURE_wayland_compositor=OFF \ -DWaylandScanner_EXECUTABLE=/usr/bin/wayland-scanner \ - -DCMAKE_TRY_COMPILE_OUTPUT_SIZE=64000 \ + -DCMAKE_SHARED_LINKER_FLAGS="-lc -lffi" \ + -DCMAKE_EXE_LINKER_FLAGS="-lc -lffi" \ + -DCMAKE_C_STANDARD_LIBRARIES="-lffi" \ + -DCMAKE_CXX_STANDARD_LIBRARIES="-lffi" \ -Wno-dev cmake --build . -j"${COOKBOOK_MAKE_JOBS}" diff --git a/recipes/wip/wayland/libwayland/recipe.toml b/recipes/wip/wayland/libwayland/recipe.toml index 9213fdef..b61a0bb4 100644 --- a/recipes/wip/wayland/libwayland/recipe.toml +++ b/recipes/wip/wayland/libwayland/recipe.toml @@ -14,8 +14,25 @@ dependencies = [ "libxml2", ] script = """ -DYNAMIC_INIT -cookbook_meson -Ddocumentation=false -Dtests=false -Ddtd_validation=false -Dc_args=-Wno-error +COOKBOOK_CONFIGURE_FLAGS=( + --host="${GNU_TARGET}" + --prefix="/usr" + --disable-shared + --enable-static +) +COOKBOOK_MESON_FLAGS=( + --buildtype release + --wrap-mode nofallback + -Ddefault_library=static + -Dprefix=/usr +) +cookbook_meson -Ddocumentation=false -Dtests=false -Ddtd_validation=false -Dscanner=false -Dc_args=-Wno-error + +for pc in "${COOKBOOK_STAGE}/usr/lib/pkgconfig/wayland-client.pc" "${COOKBOOK_STAGE}/usr/lib/pkgconfig/wayland-server.pc"; do + if [ -f "$pc" ]; then + sed -i 's/^Libs: /Libs: -lffi /' "$pc" + fi +done """ [package] diff --git a/src/cook/fetch.rs b/src/cook/fetch.rs index 129a5358..0440d2ec 100644 --- a/src/cook/fetch.rs +++ b/src/cook/fetch.rs @@ -797,6 +797,14 @@ pub(crate) fn fetch_cargo( source_dir = source_dir.join(cargopath); } + // Canonicalize source_dir so that relative path dependencies in Cargo.toml + // resolve correctly when the recipe directory is a symlink (e.g. recipes/system/foo -> local/recipes/system/foo). + // Without canonicalization, cargo resolves relative paths from the symlink location, + // which may have a different depth than the real path, causing path resolution failures. + if let Ok(canonical) = source_dir.canonicalize() { + source_dir = canonical; + } + let local_redoxer = Path::new("target/release/cookbook_redbear_redoxer"); let mut command = if is_redox() && !local_redoxer.is_file() { Command::new("cookbook_redbear_redoxer")