From abaa23950588f768c141da53933ae35659a0304a Mon Sep 17 00:00:00 2001 From: Oskari Alaranta Date: Thu, 12 Feb 2026 02:39:24 +0200 Subject: [PATCH] Add basic MIT-SHM support Supports only Attach, Detach and PutImage but those seem to be enough for firefox and chromium --- xbanan/Base.cpp | 81 +++--------- xbanan/Base.h | 2 + xbanan/CMakeLists.txt | 2 + xbanan/ExtSHM.cpp | 283 ++++++++++++++++++++++++++++++++++++++++++ xbanan/Image.cpp | 88 +++++++++++++ xbanan/Image.h | 28 +++++ 6 files changed, 423 insertions(+), 61 deletions(-) create mode 100644 xbanan/ExtSHM.cpp create mode 100644 xbanan/Image.cpp create mode 100644 xbanan/Image.h diff --git a/xbanan/Base.cpp b/xbanan/Base.cpp index 93b24ac..0ebc004 100644 --- a/xbanan/Base.cpp +++ b/xbanan/Base.cpp @@ -1,5 +1,6 @@ #include "Definitions.h" #include "Extensions.h" +#include "Image.h" #include "Keymap.h" #include "Utils.h" @@ -331,7 +332,7 @@ static void invalidate_window_recursive(WINDOW wid, int32_t x, int32_t y, int32_ ); } -static void invalidate_window(WINDOW wid, int32_t x, int32_t y, int32_t w, int32_t h) +void invalidate_window(WINDOW wid, int32_t x, int32_t y, int32_t w, int32_t h) { ASSERT(wid != g_root.windowId); @@ -2720,66 +2721,24 @@ BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet) break; } - uint8_t bpp = 0; - for (const auto& format : g_formats) - if (format.depth == request.depth) - bpp = format.bitsPerPixel; - ASSERT(bpp && 32 % bpp == 0); - - uint32_t (*get_pixel)(int32_t x, int32_t y, uint8_t bpp, uint32_t width, const uint32_t* data) = nullptr; - - switch (request.format) - { - case XYBitmap: - ASSERT(request.depth == 1); - [[fallthrough]]; - case ZPixmap: - if (bpp == 32) - { - get_pixel = - [](int32_t x, int32_t y, uint8_t bpp, uint32_t width, const uint32_t* data) -> uint32_t - { - (void)bpp; - return data[y * width + x]; - }; - } - else - { - get_pixel = - [](int32_t x, int32_t y, uint8_t bpp, uint32_t width, const uint32_t* data) -> uint32_t - { - const auto bits_per_scanline = (bpp * width + 31) / 32 * 32; - const auto bit_offset = y * bits_per_scanline + x * bpp; - const auto dword = bit_offset / 32; - const auto shift = bit_offset % 32; - const auto mask = (1u << bpp) - 1; - return (data[dword] >> shift) & mask; - }; - } - break; - default: - dwarnln("PutImage unsupported format {}, depth {}", request.format, request.depth); - break; - } - - if (get_pixel != nullptr) - { - const auto* in_data_u32 = packet.as_span().data(); - auto* out_data_u32 = out_data.as_span().data(); - - const auto min_x = BAN::Math::max(0, -request.dstX); - const auto min_y = BAN::Math::max(0, -request.dstY); - - const auto max_x = BAN::Math::min(request.width, out_w - request.dstX); - const auto max_y = BAN::Math::min(request.height, out_h - request.dstY); - - for (int32_t y = min_y; y < max_y; y++) - { - const auto row_off = (request.dstY + y) * out_w; - for (int32_t x = min_x; x < max_x; x++) - out_data_u32[row_off + (request.dstX + x)] = get_pixel(x, y, bpp, request.width, in_data_u32); - } - } + put_image({ + .out_data = out_data.data(), + .out_x = request.dstX, + .out_y = request.dstY, + .out_w = out_w, + .out_h = out_h, + .out_depth = out_depth, + .in_data = packet.data(), + .in_x = 0, + .in_y = 0, + .in_w = request.width, + .in_h = request.height, + .in_depth = request.depth, + .w = request.width, + .h = request.height, + .left_pad = request.leftPad, + .format = request.format, + }); if (object.type == Object::Type::Window) invalidate_window(request.drawable, request.dstX, request.dstY, request.width, request.height); diff --git a/xbanan/Base.h b/xbanan/Base.h index 037fb05..edd0213 100644 --- a/xbanan/Base.h +++ b/xbanan/Base.h @@ -4,3 +4,5 @@ BAN::ErrorOr setup_client_conneciton(Client& client_info, const xConnClientPrefix& client_prefix); BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet); + +void invalidate_window(WINDOW wid, int32_t x, int32_t y, int32_t w, int32_t h); diff --git a/xbanan/CMakeLists.txt b/xbanan/CMakeLists.txt index a7376af..1398213 100644 --- a/xbanan/CMakeLists.txt +++ b/xbanan/CMakeLists.txt @@ -4,6 +4,8 @@ set(SOURCES Extensions.cpp ExtBigReg.cpp ExtRANDR.cpp + ExtSHM.cpp + Image.cpp Keymap.cpp ) diff --git a/xbanan/ExtSHM.cpp b/xbanan/ExtSHM.cpp new file mode 100644 index 0000000..cb6a414 --- /dev/null +++ b/xbanan/ExtSHM.cpp @@ -0,0 +1,283 @@ +#include "Base.h" +#include "Extensions.h" +#include "Image.h" +#include "Utils.h" + +#include +#include + +#include +#include +#include +#include + +struct ShmSegment +{ + void* addr; +}; + +static BAN::HashMap s_shm_segments; + +static BYTE s_shm_event_base; +static BYTE s_shm_error_base; + +static bool is_local_socket(int socket) +{ + sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getpeername(socket, reinterpret_cast(&addr), &addr_len) == -1) + return false; + + switch (addr.ss_family) + { + case AF_UNIX: + return true; + case AF_INET: + { + const auto* addr_in = reinterpret_cast(&addr); + const auto ipv4 = ntohl(addr_in->sin_addr.s_addr); + return (ipv4 & IN_CLASSA_NET) == IN_LOOPBACKNET; + } + case AF_INET6: + { + const auto* addr_in6 = reinterpret_cast(&addr); + return IN6_IS_ADDR_LOOPBACK(&addr_in6->sin6_addr); + } + } + + return false; +} + +static BAN::ErrorOr extension_shm(Client& client_info, BAN::ConstByteSpan packet) +{ + struct DrawableInfo + { + BAN::ByteSpan data; + CARD32 w, h; + CARD8 depth; + }; + + const uint8_t major_opcode = packet[0]; + const uint8_t minor_opcode = packet[1]; + + const auto get_drawable = + [&client_info, minor_opcode, major_opcode](WINDOW drawable) -> BAN::ErrorOr + { + auto it = g_objects.find(drawable); + if (it == g_objects.end() || (it->value->type != Object::Type::Window && it->value->type != Object::Type::Pixmap)) + { + xError error { + .type = X_Error, + .errorCode = BadDrawable, + .sequenceNumber = client_info.sequence, + .resourceID = drawable, + .minorCode = minor_opcode, + .majorCode = major_opcode, + }; + TRY(encode(client_info.output_buffer, error)); + return BAN::Error::from_errno(ENOENT); + } + + return *it->value; + }; + + const auto get_drawable_info = + [](Object& object) -> DrawableInfo + { + DrawableInfo info; + + switch (object.type) + { + case Object::Type::Window: + { + auto& window = object.object.get(); + auto& texture = window.texture(); + + info.data = { reinterpret_cast(texture.pixels().data()), texture.pixels().size() * 4 }; + info.w = texture.width(); + info.h = texture.height(); + info.depth = window.depth; + + break; + } + case Object::Type::Pixmap: + { + auto& pixmap = object.object.get(); + + info.data = pixmap.data.span(); + info.w = pixmap.width; + info.h = pixmap.height; + info.depth = pixmap.depth; + + break; + } + default: + ASSERT_NOT_REACHED(); + } + + return info; + }; + + switch (packet[1]) + { + case X_ShmQueryVersion: + { + dprintln("ShmQueryVersion"); + + xShmQueryVersionReply reply { + .type = X_Reply, + .sharedPixmaps = is_local_socket(client_info.fd), + .sequenceNumber = client_info.sequence, + .length = 0, + .majorVersion = 1, + .minorVersion = 2, + .uid = static_cast(getuid()), + .gid = static_cast(getgid()), + .pixmapFormat = ZPixmap, + }; + TRY(encode(client_info.output_buffer, reply)); + + break; + } + case X_ShmAttach: + { + auto request = decode(packet).value(); + + dprintln("ShmAttach"); + dprintln(" shmseg: {}", request.shmseg); + dprintln(" shmid: {}", request.shmid); + dprintln(" readOnly: {}", request.readOnly); + + void* addr = shmat(request.shmid, nullptr, request.readOnly ? SHM_RDONLY : 0); + ASSERT(addr != (void*)-1); + + TRY(s_shm_segments.insert(request.shmseg, { + .addr = addr, + })); + + xShmCompletionEvent event { + .type = static_cast(s_shm_event_base + ShmCompletion), + .sequenceNumber = client_info.sequence, + .drawable = 0, + .shmseg = request.shmseg, + .offset = 0, + }; + TRY(encode(client_info.output_buffer, event)); + + break; + } + case X_ShmDetach: + { + auto request = decode(packet).value(); + + dprintln("ShmDetach"); + dprintln(" shmseg: {}", request.shmseg); + + auto it = s_shm_segments.find(request.shmseg); + if (it != s_shm_segments.end()) + { + shmdt(it->value.addr); + s_shm_segments.remove(it); + } + else + { + xError error { + .type = X_Error, + .errorCode = static_cast(s_shm_error_base + BadShmSeg), + .sequenceNumber = client_info.sequence, + .resourceID = request.shmseg, + .minorCode = minor_opcode, + .majorCode = major_opcode, + }; + TRY(encode(client_info.output_buffer, error)); + } + + break; + } + case X_ShmPutImage: + { + auto request = decode(packet).value(); + +#if 0 + dprintln("ShmPutImage"); + dprintln(" drawable: {}", request.drawable); + dprintln(" gc: {}", request.gc); + dprintln(" totalWidth: {}", request.totalWidth); + dprintln(" totalHeight: {}", request.totalHeight); + dprintln(" srcX: {}", request.srcX); + dprintln(" srcY: {}", request.srcY); + dprintln(" srcWidth: {}", request.srcWidth); + dprintln(" srcHeight: {}", request.srcHeight); + dprintln(" dstX: {}", request.dstX); + dprintln(" dstY: {}", request.dstY); + dprintln(" depth: {}", request.depth); + dprintln(" format: {}", request.format); + dprintln(" sendEvent: {}", request.sendEvent); + dprintln(" shmseg: {}", request.shmseg); + dprintln(" offset: {}", request.offset); +#endif + + auto& shm_segment = s_shm_segments[request.shmseg]; + + auto& object = TRY_REF(get_drawable(request.drawable)); + auto [out_data, out_w, out_h, out_depth] = get_drawable_info(object); + + put_image({ + .out_data = out_data.data(), + .out_x = request.dstX, + .out_y = request.dstY, + .out_w = out_w, + .out_h = out_h, + .out_depth = out_depth, + .in_data = (const uint8_t*)shm_segment.addr + request.offset, + .in_x = request.srcX, + .in_y = request.srcY, + .in_w = request.totalWidth, + .in_h = request.totalHeight, + .in_depth = request.depth, + .w = request.srcWidth, + .h = request.srcHeight, + .left_pad = 0, + .format = request.format, + }); + + if (object.type == Object::Type::Window) + invalidate_window(request.drawable, request.dstX, request.dstY, request.srcWidth, request.srcHeight); + + if (request.sendEvent) + { + xShmCompletionEvent event { + .type = static_cast(s_shm_event_base + ShmCompletion), + .sequenceNumber = client_info.sequence, + .drawable = request.drawable, + .shmseg = request.shmseg, + .offset = request.offset, + }; + TRY(encode(client_info.output_buffer, event)); + } + + break; + } + default: + dwarnln("unsupported shm minor opcode {}", packet[1]); + break; + } + + return {}; +} + +static struct SHMInstaller +{ + SHMInstaller() + { + install_extension(SHMNAME, ShmNumberEvents, ShmNumberErrors, extension_shm); + for (const auto& extension : g_extensions) + { + if (extension.name != SHMNAME) + continue; + s_shm_event_base = extension.event_base; + s_shm_error_base = extension.error_base; + break; + } + } +} installer; diff --git a/xbanan/Image.cpp b/xbanan/Image.cpp new file mode 100644 index 0000000..5cd39de --- /dev/null +++ b/xbanan/Image.cpp @@ -0,0 +1,88 @@ +#include "Definitions.h" +#include "Image.h" + +#include + +void put_image(const PutImageInfo& info) +{ + uint8_t in_bpp = 0; + for (const auto& format : g_formats) + if (format.depth == info.in_depth) + in_bpp = format.bitsPerPixel; + ASSERT(in_bpp && 32 % in_bpp == 0); + + const auto min_x = BAN::Math::max(0, -info.out_x); + const auto min_y = BAN::Math::max(0, -info.out_y); + + const auto max_x = BAN::Math::min(info.w, info.out_w - info.out_x); + const auto max_y = BAN::Math::min(info.h, info.out_h - info.out_y); + + auto* out_data_u32 = static_cast(info.out_data); + const auto* in_data_u32 = static_cast(info.in_data); + + if (!(info.format == XYBitmap || info.in_depth == info.out_depth)) + { + dwarnln("format {}, in depth {}, out depth {}", info.format, info.in_depth, info.out_depth); + ASSERT_NOT_REACHED(); + } + + switch (info.format) + { + case XYBitmap: + ASSERT(info.in_depth == 1); + [[fallthrough]]; + case XYPixmap: + { + const auto dwords_per_plane = (info.left_pad + info.in_w + 31) / 32; + for (int32_t y = min_y; y < max_y; y++) + { + const auto dst_off = (info.out_y + y) * info.out_w + info.out_x; + for (int32_t x = min_x; x < max_x; x++) + { + const auto bit_index = info.left_pad + (info.in_y + y) * info.in_w + (info.in_x + x); + const auto dword = bit_index / 32; + const auto bit_mask = 1u << (bit_index % 32); + uint32_t pixel = 0; + for (size_t i = 0; i < info.in_depth; i++) + if (in_data_u32[dword + i * dwords_per_plane] & bit_mask) + pixel |= 1u << i; + out_data_u32[dst_off + x] = pixel; + } + } + break; + } + case ZPixmap: + { + ASSERT(info.left_pad == 0); + if (in_bpp == 32) + { + const auto bytes_per_row = (max_x - min_x) * 4; + for (int32_t y = min_y; y < max_y; y++) + { + const auto dst_off = (info.out_y + y) * info.out_w + (info.out_x + min_x); + const auto src_off = (info. in_y + y) * info. in_w + (info. in_x + min_y); + memcpy(&out_data_u32[dst_off], &in_data_u32[src_off], bytes_per_row); + } + } + else + { + const auto pixel_mask = (1u << info.in_depth) - 1; + const auto bits_per_scanline = (info.in_w * in_bpp + 31) / 32 * 32; + for (int32_t y = min_y; y < max_y; y++) + { + const auto dst_off = (info.out_y + y) * info.out_w + info.out_x; + for (int32_t x = min_x; x < max_x; x++) + { + const auto bit_offset = (info.in_y + y) * bits_per_scanline + (info.in_x + x) * in_bpp; + const auto dword = bit_offset / 32; + const auto shift = bit_offset % 32; + out_data_u32[dst_off + x] = (in_data_u32[dword] >> shift) & pixel_mask; + } + } + } + break; + } + default: + ASSERT_NOT_REACHED(); + } +} diff --git a/xbanan/Image.h b/xbanan/Image.h new file mode 100644 index 0000000..58e44c6 --- /dev/null +++ b/xbanan/Image.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +struct PutImageInfo +{ + void* out_data; + int32_t out_x; + int32_t out_y; + uint32_t out_w; + uint32_t out_h; + uint8_t out_depth; + + const void* in_data; + int32_t in_x; + int32_t in_y; + uint32_t in_w; + uint32_t in_h; + uint8_t in_depth; + + uint32_t w; + uint32_t h; + + uint32_t left_pad; + uint8_t format; +}; + +void put_image(const PutImageInfo& info);