name='happ'
version=3.3.6
release=4
summary='User-friendly GUI proxy client for xray-core'
summary_ru='Удобный GUI-прокси-клиент для xray-core'
group='Networking/Other'
desc='Happ is a GUI client for xray-core with TUN/VPN mode via sing-box, anti-censorship tools, QR code import and subscription management.'
desc_ru='Happ — GUI-клиент для xray-core с режимом TUN/VPN через sing-box, средствами обхода блокировок, импортом QR-кодов и управлением подписками.'
homepage='https://happ.su/'
maintainer='FlyFrog LLC <support@happ.su>'

architectures=('amd64' 'arm64')

license=('Custom')
nonfree=1
nonfree_msg='Happ является проприетарным приложением FlyFrog LLC. Использование клиента, подписок и сетевых функций регулируется условиями Happ.'
nonfree_url='https://happ.su/license'

provides=()
replaces=('happ')
conflicts=()

# auto_req отключен — Qt-приложение поставляется с bundled библиотеками
auto_reqprov_method="dirty"
auto_req=0
auto_prov=0

deps=()
deps_debian=('libc6' 'libssl3 | libssl3t64')
deps_ubuntu=('libc6' 'libssl3 | libssl3t64')
deps_fedora=('glibc' 'openssl-libs')
deps_arch=('glibc' 'openssl')
deps_opensuse=('glibc' 'libopenssl1_1')
deps_altlinux=('glibc' 'libssl3')

build_deps=('binutils')
build_deps_debian=('binutils' 'python3')
build_deps_ubuntu=("${build_deps_debian[@]}")
build_deps_fedora=('binutils' 'python3')
build_deps_arch=('binutils' 'python')
build_deps_opensuse=('binutils' 'python3')
build_deps_altlinux=('binutils' 'python3')

sources=(
	"https://github.com/Happ-proxy/happ-desktop/releases/download/${version}/Happ.linux.x64.deb?~archive=false&~name=happ.deb"
	'local:///patch-tray-icons.py'
	'local:///happ-launcher'
	'local:///tray-idle.svg'
	'local:///tray-connected.svg'
	'local:///LICENSE'
)

sources_arm64=(
	"https://github.com/Happ-proxy/happ-desktop/releases/download/${version}/Happ.linux.arm64.deb?~archive=false&~name=happ.deb"
	'local:///patch-tray-icons.py'
	'local:///happ-launcher'
	'local:///tray-idle.svg'
	'local:///tray-connected.svg'
	'local:///LICENSE'
)

checksums=(
	'a7dac51277387bfe1049b1ad40f40f2e74af233a5eab020b5be1a622effc46a4'
	'5b0106dda286b039d6bdce45208f44ea3eb7a2deb4e00455190ce7a07177dde0'
	'fa4d741a4e2df82b0a19291e2c7bad4a8753a1e59237b2bfe5fef7de8c22efd2'
	'af47a069497a8063b9028549dc6db111776589818c6165c7a46cb8b25a822ead'
	'7e105f63e4535676e15e4d613426d6af843376a09fd25674bbbf367778668ae5'
	'5f0fbc1b0b0e8399c8a04425512168e3dd87960381e85a7cb62a41d55b054b0f'
)

checksums_arm64=(
	'a4d3d6dcab1db61db23cdf0f86bce736014887262b570927befe48cb49f14aa6'
	'5b0106dda286b039d6bdce45208f44ea3eb7a2deb4e00455190ce7a07177dde0'
	'fa4d741a4e2df82b0a19291e2c7bad4a8753a1e59237b2bfe5fef7de8c22efd2'
	'af47a069497a8063b9028549dc6db111776589818c6165c7a46cb8b25a822ead'
	'7e105f63e4535676e15e4d613426d6af843376a09fd25674bbbf367778668ae5'
	'5f0fbc1b0b0e8399c8a04425512168e3dd87960381e85a7cb62a41d55b054b0f'
)

scripts=(
	['postinstall']='postinstall.sh'
	['postremove']='postremove.sh'
)

package() {
	cd "${srcdir}"

	mkdir -p "${srcdir}/happ-extracted"
	ar x happ.deb
	tar -xf data.tar.* -C "${srcdir}/happ-extracted"

	cp -a "${srcdir}/happ-extracted/opt" "${pkgdir}/opt"
	python3 patch-tray-icons.py \
		"${pkgdir}/opt/happ/bin/Happ" \
		tray-idle.svg \
		tray-connected.svg

	install-systemd "${srcdir}/happ-extracted/etc/systemd/system/happd.service"

	sed -i 's|^Exec=/opt/happ/bin/Happ|Exec=/usr/bin/happ|' \
		"${srcdir}/happ-extracted/usr/share/applications/Happ.desktop"
	install-desktop "${srcdir}/happ-extracted/usr/share/applications/Happ.desktop"

	python3 - \
		"${srcdir}/happ-extracted/usr/share/icons/hicolor/256x256/apps/happ.png" \
		"${pkgdir}/usr/share/icons/hicolor/512x512/apps/happ.png" <<'PY'
import math
import struct
import sys
import zlib
from pathlib import Path

PNG_SIG = b"\x89PNG\r\n\x1a\n"


def read_png(path):
    data = Path(path).read_bytes()
    if not data.startswith(PNG_SIG):
        raise ValueError("not a PNG")

    pos = len(PNG_SIG)
    width = height = bit_depth = color_type = None
    idat = bytearray()
    while pos < len(data):
        length = struct.unpack(">I", data[pos:pos + 4])[0]
        pos += 4
        chunk_type = data[pos:pos + 4]
        pos += 4
        chunk = data[pos:pos + length]
        pos += length + 4
        if chunk_type == b"IHDR":
            width, height, bit_depth, color_type, _, _, _ = struct.unpack(
                ">IIBBBBB", chunk
            )
        elif chunk_type == b"IDAT":
            idat.extend(chunk)
        elif chunk_type == b"IEND":
            break

    if bit_depth != 8 or color_type not in (2, 6):
        raise ValueError("unsupported PNG format")

    channels = 4 if color_type == 6 else 3
    raw = zlib.decompress(bytes(idat))
    stride = width * channels
    rows = []
    prev = [0] * stride
    index = 0

    for _ in range(height):
        filter_type = raw[index]
        index += 1
        encoded = list(raw[index:index + stride])
        index += stride
        row = [0] * stride

        for x, value in enumerate(encoded):
            left = row[x - channels] if x >= channels else 0
            up = prev[x]
            upper_left = prev[x - channels] if x >= channels else 0

            if filter_type == 0:
                decoded = value
            elif filter_type == 1:
                decoded = (value + left) & 255
            elif filter_type == 2:
                decoded = (value + up) & 255
            elif filter_type == 3:
                decoded = (value + ((left + up) // 2)) & 255
            elif filter_type == 4:
                predictor = left + up - upper_left
                distances = (
                    abs(predictor - left),
                    abs(predictor - up),
                    abs(predictor - upper_left),
                )
                decoded = (value + (left, up, upper_left)[distances.index(min(distances))]) & 255
            else:
                raise ValueError("unsupported PNG filter")

            row[x] = decoded

        prev = row
        rows.append(row)

    pixels = []
    for row in rows:
        out_row = []
        for x in range(width):
            offset = x * channels
            if channels == 4:
                out_row.append(tuple(row[offset:offset + 4]))
            else:
                out_row.append((row[offset], row[offset + 1], row[offset + 2], 255))
        pixels.append(out_row)

    return width, height, pixels


def write_png(path, width, height, pixels):
    def chunk(chunk_type, payload):
        crc = zlib.crc32(chunk_type + payload) & 0xFFFFFFFF
        return struct.pack(">I", len(payload)) + chunk_type + payload + struct.pack(">I", crc)

    raw = bytearray()
    for y in range(height):
        raw.append(0)
        for x in range(width):
            raw.extend(bytes(pixels[y][x]))

    ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)
    Path(path).parent.mkdir(parents=True, exist_ok=True)
    Path(path).write_bytes(
        PNG_SIG + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9))
        + chunk(b"IEND", b"")
    )


def rounded_rect_coverage(px, py, size, margin, radius, samples=4):
    covered = 0
    left = top = margin
    right = bottom = size - margin

    for sy in range(samples):
        for sx in range(samples):
            x = px + (sx + 0.5) / samples
            y = py + (sy + 0.5) / samples
            if x < left or x > right or y < top or y > bottom:
                continue
            cx = min(max(x, left + radius), right - radius)
            cy = min(max(y, top + radius), bottom - radius)
            if (x - cx) ** 2 + (y - cy) ** 2 <= radius ** 2:
                covered += 1

    return covered / (samples * samples)


def sample_alpha(alpha, x, y):
    height = len(alpha)
    width = len(alpha[0])
    if x < 0 or y < 0 or x > width - 1 or y > height - 1:
        return 0.0

    x0 = int(math.floor(x))
    y0 = int(math.floor(y))
    x1 = min(x0 + 1, width - 1)
    y1 = min(y0 + 1, height - 1)
    dx = x - x0
    dy = y - y0

    top = alpha[y0][x0] * (1 - dx) + alpha[y0][x1] * dx
    bottom = alpha[y1][x0] * (1 - dx) + alpha[y1][x1] * dx
    return top * (1 - dy) + bottom * dy


src_path, dest_path = sys.argv[1], sys.argv[2]
src_width, src_height, source = read_png(src_path)
background = 43

logo_alpha = []
for y in range(src_height):
    row = []
    for x in range(src_width):
        r, g, b, a = source[y][x]
        luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
        row.append(max(0.0, min(1.0, (luminance - background) / (255 - background))) * (a / 255.0))
    logo_alpha.append(row)

xs = [x for y in range(src_height) for x in range(src_width) if logo_alpha[y][x] > 0.04]
ys = [y for y in range(src_height) for x in range(src_width) if logo_alpha[y][x] > 0.04]
left, right = min(xs), max(xs)
top, bottom = min(ys), max(ys)
crop_width = right - left + 1
crop_height = bottom - top + 1
cropped = [
    [logo_alpha[y][x] for x in range(left, right + 1)]
    for y in range(top, bottom + 1)
]

size = 512
margin = 38
radius = 116
logo_height = 292
logo_width = round(crop_width * logo_height / crop_height)
logo_x = (size - logo_width) // 2
logo_y = (size - logo_height) // 2 + 6

pixels = [[(0, 0, 0, 0) for _ in range(size)] for _ in range(size)]
for y in range(size):
    for x in range(size):
        coverage = rounded_rect_coverage(x, y, size, margin, radius)
        if coverage:
            pixels[y][x] = (43, 43, 43, round(255 * coverage))

for y in range(logo_height):
    source_y = (y + 0.5) * crop_height / logo_height - 0.5
    target_y = logo_y + y
    if target_y < 0 or target_y >= size:
        continue

    for x in range(logo_width):
        source_x = (x + 0.5) * crop_width / logo_width - 0.5
        target_x = logo_x + x
        if target_x < 0 or target_x >= size:
            continue

        alpha = sample_alpha(cropped, source_x, source_y)
        if alpha <= 0:
            continue

        dr, dg, db, da = pixels[target_y][target_x]
        da_float = da / 255.0
        out_alpha = alpha + da_float * (1 - alpha)
        out_rgb = round((255 * alpha + dr * da_float * (1 - alpha)) / out_alpha)
        pixels[target_y][target_x] = (out_rgb, out_rgb, out_rgb, round(out_alpha * 255))

write_png(dest_path, size, size, pixels)
PY

	install -Dm644 "${srcdir}/happ-extracted/usr/share/mime/packages/happ-mime.xml" \
		"${pkgdir}/usr/share/mime/packages/happ-mime.xml"

	install -Dm755 "${srcdir}/happ-launcher" "${pkgdir}/usr/bin/happ"
	install-license "${srcdir}/LICENSE" "happ/LICENSE.nivora"
}

files() {
	files-find "/opt/happ/**/*"
	files-find-binary
	files-find-desktop
	files-find-systemd
	files-find-license
	files-find \
		"/usr/share/icons/hicolor/512x512/apps/happ.png" \
		"/usr/share/mime/packages/happ-mime.xml"
}
