Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e78f881
Embed the TV's Chromium (libcbe) in a native app
Aug 22, 2026
c951872
web/cbe: document the JS bridge, and add the injection API it needs
Aug 22, 2026
1f98ebf
web/cbe: measure the show/hide cycle, and the two-app alternative
Aug 22, 2026
2bcecc7
web/hybrid: SDL2 and a web view in one process, swapping views
Aug 22, 2026
b17e623
Ignore .mcp.json
Aug 22, 2026
43888c6
web/hybrid: leave the web view through Close(), not a title change
Aug 22, 2026
51c978a
web/: correct why webOS 3 is out, and say what "webOS 4" rests on
Aug 22, 2026
25bb23e
web/cbe: say what webOS 1 and 2 have instead of libcbe
Aug 22, 2026
bac513c
web/: the upper bound is 4.9, not 5
Aug 22, 2026
1f14d59
web/: scope by major release, and note a -verify false negative
Aug 22, 2026
9a6531e
web/libcbe: name the delegate slots from the OSE SDK's own header
Aug 22, 2026
3ec4d28
web/hybrid: fix the white screen on the second visit, and a segfault
Aug 22, 2026
de783ee
web/hybrid: draw the native view with Nuklear on GLES2
Aug 22, 2026
66db4d5
web/hybrid: carry data both ways, not just the switch
Aug 22, 2026
d9a5b55
web/hybrid: make it a sign-in flow, which is the actual use case
Aug 22, 2026
f5526c1
web/hybrid: verify the remote key path, and ask for Back properly
Aug 22, 2026
c4bf7d3
web/hybrid: Back and Exit are scancodes, not keysyms
Aug 22, 2026
456c741
web/hybrid: record that the web view starts with nothing focused
Aug 22, 2026
83364fe
web/cbe-webos3: the webOS 3 build - ABI proven, window handover not
Aug 23, 2026
158fade
web/cbe-webos3: chase the window handover, and write down the dead ends
Aug 23, 2026
f28530f
web/cbe-webos3: narrow the window failure to a state the compositor i…
Aug 23, 2026
b08b575
web/cbe-webos3: close two more doors on the window handover
Aug 23, 2026
633a15d
web/cbe-webos3: PlatformDelegate reached, and it is not the answer ei…
Aug 23, 2026
675b26b
web/cbe-webos3: register with SAM, the way SDL-webOS does
Aug 23, 2026
237437e
web/cbe-webos3: it is not a windowing bug - no frame is ever committed
Aug 23, 2026
31d9913
web/cbe-webos3: fix the GPU command buffer, and diff against a workin…
Aug 24, 2026
c0959aa
web/cbe-webos3: switches are not the answer; no accelerated surface i…
Aug 24, 2026
a4b4718
web/cbe-webos3: root cause - SetWidgetState(SHOW) is a stub on webOS 3
Aug 24, 2026
c13ccbc
web/cbe-webos3: there IS a standalone embedder on webOS 3 - two corre…
Aug 24, 2026
5b1f99d
web/cbe-webos3: bisect the browser's config down to one fatal switch
Aug 24, 2026
cedf37c
web/cbe-webos3: libcbe crashes inside its own logger without fontconfig
Aug 24, 2026
73dd4c5
web/cbe-webos3: why the wayland backend cannot work - two platforms, …
Aug 24, 2026
97ca5fb
web/cbe-webos3: attach=0 IS the bug - the earlier comparison was wrong
Aug 24, 2026
200dc1b
web/cbe-webos3: localise the gap to browser-side compositing
Aug 24, 2026
5e9640d
web/cbe-webos3: the GetActiveWebContents stub is a red herring
Aug 24, 2026
b30f97f
web/cbe-webos3: SetWidgetState(SHOW) was never the blocker
Aug 24, 2026
2b0fd7c
web/cbe-webos3: mark it parked, and say where to go instead
Aug 24, 2026
dc8ee5a
web/cbe-webos6: map the webOS 6+ ABI; sample starts but is not right yet
Aug 24, 2026
33d627d
web/cbe-webos6: working - a stubbed slot with a return value was the bug
Aug 24, 2026
fa9cb09
web/neva: the published API, working on webOS 6
Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@
/assets/*.pcm
/assets/*.ac3

# Claude Code's per-project MCP server config: machine-local, since it carries
# absolute paths to whatever tooling the checkout happens to sit next to.
/.mcp.json

.DS_Store
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ if (TARGET_WEBOS)
add_subdirectory(media/smp)
add_subdirectory(media/ndl)
add_subdirectory(media/lgnc)
add_subdirectory(web/libcbe)
add_subdirectory(web/cbe)
add_subdirectory(web/cbe-webos3)
add_subdirectory(web/cbe-webos6)
add_subdirectory(web/neva)
add_subdirectory(web/hybrid)
endif ()
39 changes: 32 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
Small, readable sample apps for LG webOS native homebrew. Each one does a single thing and
is meant to be read top to bottom, without a framework in the way.

The media samples feed raw elementary streams straight into the TV's hardware decoder, with
SDL2 owning the window and the remote control. There are two entirely different ways to
reach that decoder, and all of them get samples: **starfish-media-pipeline**
(`libplayerAPIs`), **NDL**, and **LGNC** (the LG NetCast Open API).
Most of them are media samples: they feed raw elementary streams straight into the TV's
hardware decoder, with SDL2 owning the window and the remote control. There are three
entirely different ways to reach that decoder, and all of them get samples:
**starfish-media-pipeline** (`libplayerAPIs`), **NDL**, and **LGNC** (the LG NetCast Open
API).

The `web/` samples are the odd ones out. They link `libcbe.so` - the TV's own Chromium, the
engine every web app on the box already runs inside - and put a real web view in a *native*
app, with no WAM and no web app package. There is no SDK for that library; the headers were
reconstructed from firmware symbol tables and from the vtables of WAM's own subclasses, and
`web/cbe/README.md` writes down how. `web/hybrid` uses that to solve a real problem: a
native app that needs the user to sign in on somebody else's web page, and needs the token
that comes back in the redirect URL, without shipping a browser to do it. It runs SDL2 with
a Nuklear UI and the web view in one process - `WebOSMain()` never returns, so SDL is pumped
from Chromium's message loop rather than its own - and reads the answer straight out of the
navigation.

## What is here

Expand All @@ -21,6 +33,13 @@ media/
esplayer/ libndl-directmedia2, NDL_Esplayer* - webOS 2.x to 3.4
directmedia/ libNDL_directmedia, NDL_Direct* - webOS 3.5+, built for API v1 and v2
lgnc/ liblgncopenapi, LGNC_DIRECT* - webOS 1 to 4, one binary for all of them
web/
libcbe/ reconstructed libcbe headers and the link stub, shared by both
cbe/ the smallest thing that puts a web page on screen
cbe-webos3/ the same, against webOS 3's older and different libcbe
cbe-webos6/ and again for webOS 6+, where the API changes a third time
neva/ the same, through neva_app_runtime - the API LG published
hybrid/ a native sign-in flow: SDL2 + Nuklear, a web login page, one process
```

The same two files play through all three stacks. Comparing the three `main.c` files is the
Expand Down Expand Up @@ -104,11 +123,12 @@ and skips everything else.

### Icons

`assets/icons/<name>.png` carries white artwork on transparency - the API name, a play
mark, and which variant it is - and no colour at all. Each sample passes a Material 500 colour to `webos_add_ipk`, which
`assets/icons/<name>.png` carries white artwork on transparency - the API name, a mark (a
play triangle for the media samples, a globe for the web ones), and which variant it is -
and no colour at all. Each sample passes a Material 500 colour to `webos_add_ipk`, which
writes it to both `iconColor` and `bgColor` in `appinfo.json`, and webOS paints that behind
the glyph. Families are grouped by hue: blues for starfish, greens for NDL, orange for
LGNC.
LGNC, blue and purple for the web samples.

Both fields are set deliberately: `bgColor` is the tile background, while `iconColor` fills
behind the icon itself - without it the launcher's default shows through the glyph's
Expand Down Expand Up @@ -181,6 +201,11 @@ hard to diagnose from the TV side.
| `media/smp/webos5` | 5+ | **verified on hardware** - 65UP7560 (webOS 6.5.2) and OLED77C5 (webOS 10.3.1): exported window accepted, full load / play / feed / EOS / unload, 300 video + 470 audio units on both. Those runs predate the `Play()` ordering fix, which all SMP samples share - re-run pending |
| `media/ndl/directmedia` (v2) | 5+ | **verified on hardware** - 65UP7560 (webOS 6.5.2) and OLED77C5 (webOS 10.3.1): 300 video + 469 PCM chunks on both |
| `media/ndl/directmedia` (v1) | 3.5 - 4.x | built and symbol-verified, needs a 2017-2019 set to test |
| `web/hybrid` | webOS 4.0 | **verified on hardware** - 49LK5900, webOS 4.4.3: the sign-in flow runs end to end - native panel, web login form, and back with the username read out of the intercepted redirect URL. The OK key is verified too, injected with `com.webos.service.networkinput/test/sendKeyCode`; leaving the web view by remote is not, since keys go to whichever window is up |
| `web/neva` | webOS 6+ | **verified on hardware** - 65UP7560, starfish 6.5.2: page loads and renders full-screen, app takes the foreground. The one API here with published upstream headers, and `-verify` clean to 11.2 |
| `web/cbe-webos6` | webOS 6 | **verified on hardware** - 65UP7560, starfish 6.5.2: the page loads and renders full-screen and the app takes the foreground. A third shape of the API - 61-slot delegate, 92-byte `WebViewBase`, `WebOSMain` as a class |
| `web/cbe-webos3` | webOS 3 | **unfinished, parked** - 43UH6100, starfish 3.4.0: the reconstructed webOS 3 ABI is right and the page loads and paints, with every delegate callback firing, but the window never reaches the screen - LSM keeps the previous app foreground. webOS 3 has no `Activate()` and the equivalent has not been found |
| `web/cbe` | webOS 4.0 | **verified on hardware** - 49LK5900, webOS 4.4.3: the window registers with LSM and SAM as the foreground card, and a display capture shows the page rendered full-screen at 1920x1080. Input and lifecycle are not implemented |
| `media/smp/webos1` | 1.x | not written yet - and there is no webOS 1 hardware here to validate it against, so it would ship untestable |

## What a `Play()` that returns true does not tell you
Expand Down
Binary file added assets/icons/web-cbe-webos3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/web-cbe-webos6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/web-cbe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/web-hybrid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/web-neva.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 32 additions & 12 deletions assets/make-icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
each sample sets to its own Material 500 value (the COLOR argument to webos_add_ipk). The
PNG carries only white artwork on transparency:

SMP <- which media API
|> <- play mark
SMP <- which API
|> <- play mark, or a globe for the web samples
w4 <- which variant of it

Colour alone was not enough to tell eight tiles apart, and the two lines say which API and
Expand All @@ -21,7 +21,7 @@
SIZE = 80
SS = 8 # supersample, for clean edges

# target name (minus the "media-" prefix) -> (API, variant)
# target name (minus the "media-" prefix) -> (API, variant[, mark])
ICONS = {
"smp-acb-webos2": ("SMP", "w2"),
"smp-acb-webos3": ("SMP", "w3"),
Expand All @@ -31,6 +31,11 @@
"ndl-directmedia-v1": ("NDL", "DM1"),
"ndl-directmedia-v2": ("NDL", "DM2"),
"lgnc": ("LGNC", "1-4"),
"web-cbe": ("CBE", "w4", "globe"),
"web-cbe-webos3": ("CBE", "w3", "globe"),
"web-cbe-webos6": ("CBE", "w6+", "globe"),
"web-neva": ("NEVA", "w6+", "globe"),
"web-hybrid": ("SDL+", "w4", "globe"),
}


Expand All @@ -40,7 +45,26 @@ def centred(d, text, font, cy, n):
text, font=font, fill=(255, 255, 255, 255))


def make(path, api, variant):
def play_mark(d, cx, cy, h):
w = h * 0.87
d.polygon([(cx - w / 2, cy - h / 2), (cx - w / 2, cy + h / 2), (cx + w / 2, cy)],
fill=(255, 255, 255, 255))


def globe_mark(d, cx, cy, h):
"""A ring with one meridian and one parallel - enough to read as "web" at 80px."""
r = h / 2
t = max(2, int(h * 0.075))
d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=(255, 255, 255, 255), width=t)
d.ellipse([cx - r * 0.45, cy - r, cx + r * 0.45, cy + r],
outline=(255, 255, 255, 255), width=t)
d.line([cx - r, cy, cx + r, cy], fill=(255, 255, 255, 255), width=t)


MARKS = {"play": play_mark, "globe": globe_mark}


def make(path, api, variant, mark="play"):
n = SIZE * SS
img = Image.new("RGBA", (n, n), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
Expand All @@ -49,11 +73,7 @@ def make(path, api, variant):
centred(d, api, ImageFont.truetype(FONT, int(n * (0.19 if len(api) > 3 else 0.23))),
n * 0.16, n)

cx, cy = n * 0.53, n * 0.50
h = n * 0.30
w = h * 0.87
d.polygon([(cx - w / 2, cy - h / 2), (cx - w / 2, cy + h / 2), (cx + w / 2, cy)],
fill=(255, 255, 255, 255))
MARKS[mark](d, n * 0.53 if mark == "play" else n * 0.50, n * 0.50, n * 0.30)

centred(d, variant, ImageFont.truetype(FONT, int(n * 0.21)), n * 0.85, n)

Expand All @@ -64,7 +84,7 @@ def make(path, api, variant):
here = os.path.dirname(os.path.abspath(__file__))
out_dir = os.path.join(here, "icons")
os.makedirs(out_dir, exist_ok=True)
for name, (api, variant) in ICONS.items():
for name, spec in ICONS.items():
out = os.path.join(out_dir, f"{name}.png")
make(out, api, variant)
print(f"{out} {api}/{variant}")
make(out, *spec)
print(f"{out} {spec[0]}/{spec[1]}")
1 change: 1 addition & 0 deletions cmake/appinfo.json.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"title": "@IPK_TITLE@",
"icon": "icon.png",
"iconColor": "@IPK_COLOR@",
"noSplashOnLaunch": true,
"bgColor": "@IPK_COLOR@"
}
33 changes: 33 additions & 0 deletions web/cbe-webos3/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# web/cbe, built against webOS 3's libcbe.
#
# Not a flag flip. Between webOS 3 and 4 the library changed its string ABI *and*
# its API: WebViewBase takes its size in the constructor with no Initialize(),
# WebAppWindowBase has no InitWindow(), and the delegate has a different slot
# order with LoadStarted carrying a URL where webOS 4 grew a separate
# DidStartNavigation. So this is a sibling sample rather than a build variant,
# the way media/smp/acb and media/smp/webos5 are siblings.
#
# Verified against a 43UH6100 running starfish 3.4.0.

find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB REQUIRED glib-2.0)

add_executable(web-cbe-webos3 main.cpp luna_register.c)
target_include_directories(web-cbe-webos3 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
target_include_directories(web-cbe-webos3 SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS})
target_link_directories(web-cbe-webos3 PRIVATE ${GLIB_LIBRARY_DIRS})
# cbe-stub-webos3 carries the include path, -fno-rtti and the old string ABI.
target_link_libraries(web-cbe-webos3 PRIVATE cbe-stub-webos3 ${GLIB_LIBRARIES} ${CMAKE_DL_LIBS})
target_compile_definitions(web-cbe-webos3 PRIVATE APP_LOG_NAME="web-cbe-webos3")

set_target_properties(web-cbe-webos3 PROPERTIES
SKIP_BUILD_RPATH TRUE
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "")

webos_add_ipk(
TARGET web-cbe-webos3
APPID org.webosbrew.sample.web.cbe3
TITLE "CBE WebView (webOS 3)"
COLOR "#26A69A"
WEBOS_VERSIONS ">=3.4, <4")
Loading