From 200e2bfc0be69ea0b3207e4c67934a10a59666a7 Mon Sep 17 00:00:00 2001 From: MoHadiShibli <142665133+MoHadiShibli@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:19:38 +0300 Subject: [PATCH] Show who's signed in on each controller, and an Invite QR code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the PS4 reports a user signed in on a controller, the service asks for the user's name and adds it to the status as `user`. The PS4 may not have finished signing the user in by then, so a failed look-up is retried every half second for five seconds. The tile shows the name in place of "Controller N" (the ring keeps the number), and the controller screen reads "Controller 1 · Alex". Names are kept out of the log, since people post logs in bug reports, and they go out cleaned: quotes and backslashes escaped, and control characters or anything that isn't valid UTF-8 replaced, because a browser drops the whole WebSocket on a text frame that isn't valid UTF-8. An Invite panel, opened from a button next to the settings or from the menu, shows the page's address as a QR code for friends to scan. The console makes the code with the QR encoder the app already ships, from the address the page reached it on, and sends the module grid; the page draws it as SVG, so it still needs no libraries. The gamepad picker's tooltip had the same "Controller 1 · Controller 1" doubling the controller screen had; it now names the user too. --- CHANGELOG.md | 8 ++- Makefile | 8 ++- README.md | 3 ++ SECURITY.md | 3 +- client/index.html | 92 ++++++++++++++++++++++++++++--- docs/images/controller.png | Bin 1391291 -> 1396662 bytes docs/images/desktop.png | Bin 547463 -> 550384 bytes docs/images/home.png | Bin 1168068 -> 1175357 bytes docs/playing.md | 8 ++- docs/protocol.md | 8 ++- include/c4f_sce.h | 2 + include/c4f_vda.h | 4 ++ src/vda.c | 11 ++++ src/web.c | 108 +++++++++++++++++++++++++++++++++++-- tests/frontend_test.cjs | 10 ++++ tests/test_recovery.py | 2 +- tests/test_web.py | 45 +++++++++++++++- tests/web_stub.c | 15 ++++++ 18 files changed, 308 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8244a6a..4ebbe58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,13 @@ Rumble and the light bar. gamepad that can't be rumbled says so on its row. - The page glows in the controller's light-bar colour, the player colour the PS4 gives each user or whatever the game sets, brightened from the quarter strength the PS4 uses. +- Each controller shows the name of the PS4 user signed in on it. +- An **Invite** button shows the page's address as a QR code, so friends can scan it and join. The console + makes the code, so the page still needs no libraries. - The page says "Choose user on PS4" rather than "on TV": not everyone plays on a TV. -- The status reports the light bar's colour in `color`, and the service sends rumble as - `{"method": "v", "params": [pad, large, small]}`. See [docs/protocol.md](docs/protocol.md). +- Protocol: the status reports the light bar's colour in `color` and the signed-in user in `user`; the + service sends rumble as `{"method": "v", "params": [pad, large, small]}`; a new `invite` method returns the + QR code. See [docs/protocol.md](docs/protocol.md). ## 1.0.0 — 2026-10-05 diff --git a/Makefile b/Makefile index 9cfee1a..4f1a19e 100644 --- a/Makefile +++ b/Makefile @@ -29,11 +29,11 @@ ifdef C4F_PROBE OBJDIR := build/probe endif -OBJECTS := $(SOURCES:src/%.c=$(OBJDIR)/%.o) $(OBJDIR)/client.o $(OBJDIR)/assets.o +OBJECTS := $(SOURCES:src/%.c=$(OBJDIR)/%.o) $(OBJDIR)/client.o $(OBJDIR)/assets.o $(OBJDIR)/qrcodegen.o CFLAGS += -std=gnu11 -Wall -Wextra -Wpointer-arith -g -O2 CFLAGS += -MMD -MP -CFLAGS += -Iinclude -Ivendor/jsmn -DC4F_VERSION='"$(VERSION)"' $(EXTRA_CFLAGS) +CFLAGS += -Iinclude -Ivendor/jsmn -Ivendor/qrcodegen -DC4F_VERSION='"$(VERSION)"' $(EXTRA_CFLAGS) LDFLAGS += -lScePad -lSceUserService -ldl -lpthread .PHONY: all clean @@ -54,6 +54,10 @@ $(OBJDIR)/%.o: src/%.c VERSION | $(OBJDIR) build/client.c: client/index.html tools/embed_client.py | build python3 tools/embed_client.py $< $@ +# The page's Invite QR code (MIT, the same copy the app uses). +$(OBJDIR)/qrcodegen.o: vendor/qrcodegen/qrcodegen.c | $(OBJDIR) + $(CC) $(CFLAGS) -c $< -o $@ + $(OBJDIR)/client.o: build/client.c | $(OBJDIR) $(CC) $(CFLAGS) -c $< -o $@ diff --git a/README.md b/README.md index de97c79..5423c9d 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ sees a real controller, so it works wherever a DualShock does: ## Features - **Up to four controllers**, from as many phones, tablets or computers as you like. +- **See who's playing.** Each controller shows the name of the PS4 user signed in on it. +- **Invite friends with a QR code.** One button on the page shows its address as a QR code: friends scan it + and they're in. - **A full DualShock 4 on the screen**: analog sticks, a two-finger touchpad, L3/R3, PS, Share and Options. Move, resize or hide any button with the layout editor; each device keeps its own layout. - **The gamepads you already have.** Connect one to the phone or PC and give it a controller. One computer diff --git a/SECURITY.md b/SECURITY.md index 9eb503d..ec2738e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,7 +16,8 @@ can reach your PS4 over the network can: - take a free controller and play, on the home screen, at sign-in and in games; - press PS and Share, which reach the system menus; -- stop Control4Free, which disconnects every controller. +- stop Control4Free, which disconnects every controller; +- see the names of the users signed in on the controllers. They cannot sign in as one of your users without someone at the PS4 choosing that user on the PS4's own screen, and they cannot see your screen. diff --git a/client/index.html b/client/index.html index 3ebef53..49020c2 100644 --- a/client/index.html +++ b/client/index.html @@ -924,6 +924,18 @@ .options-menu .actions { display: grid; gap: 10px; margin-top: 16px; } .menu-close { position: absolute; top: max(14px, env(safe-area-inset-top)); right: 14px; } + /* Invite: the page's address as a QR code, in a panel opened on demand */ + .invite-qr { + width: min(280px, 100%); + aspect-ratio: 1; + margin: 20px auto 14px; + border-radius: 14px; + background: #fff; + overflow: hidden; + } + .invite-qr svg { display: block; width: 100%; height: 100%; } + .invite-address { text-align: center; font-size: 18px; letter-spacing: .02em; word-break: break-all; user-select: all; } + /* ---------- Notification ---------- */ .toast { @@ -987,6 +999,9 @@ Connecting
dIE7v6DDQSS^ZJ*=I;Cvrm3Md6cnbM
zhMot)CFj0Q8W{*Os^!dl=yk^X5v}9Xb7utc?W<;E5;Hh|B~cCl?|8yK;!7&{5y0Sp
z@0NOw`Vl;xiJYO%33g>k{ZJub>9>%=ViOlk^j&^RFUtazpa$Q86Satz2x@=n7O>Ki
zdMvf6FYWvr;z9XQ$PVhAf5~kz967l1eI@uQ7G<@$;zSPL5egH1WO1jlx8cP~&Nl=D
zuQcDHPZ!Z@=q&)yv}TJjSJ ZecDle7I8%K?3?}Pg?VN k!=Gbl{7U9XTHG<*!ngu8-tb@two2 |C5KwAT`tWfT2d~nEE3ayOMUgONU!Jl%_Jbbw|Og5`}sL7U9
znh7CX;NDd(+vfc;4nG;pNP7A`8WDY&nT9ViEKJ;aI(8hM1-&5I?p30fk`{_#&ZLPn
z+FAit31Qfq+^~6Bbq{Tc3w)!^WJLAo0=J{vum#HwB-V{%+pj$-^=A+{xXYzGC86(Y
zN86J>F>+|yGJN0{y?3I_QmEhfZP`gt>N88f7M?qTU}i4LVVN0mFn-C|vd7EZ(*>7R
z44DB>X5Vuoj)E(%&Jv>F3vj<8Qp}g=WIO@mnrYL_Sz0wob82H|jdJ4n?h}I~jtM11
z2LAA!?+#KDFAyeMnA8#)D)0URyuZGw?pepk73(W3|DMgfZTo?)NJ=l}t!wG#!fB4|
zG}K{CouH;3An
A`JJOH92?Ciwvpuw|f
z@OPRL(OBekiVO@roC=nR2A5-c?kNKdp3$xFd6PS6qQv(I4zjJJ>(F`9fCN+#!2L%?
z@cgG^Dm-#7Kr?{I1u2~;D8?EkVwY8C@pL3GMX!*;1e>7rrQb-FX>k8?1Y#K51cnoB
zc82qmlP4l)A*)5Gc8I~3e%FRo0$$dh#WL*gqrUHK-pi@JTn&wVMPp}@R8LZhLs(6#
zteu7OoJ(?Ip13L8F)xpB53VtKO6QT1E*zM+xlGdzgfHTDViz8@rqMFOqhR&4(o!;Y{Qb1_)hYA3n)DCjy>;{_^o
zNPz$EV&!}=+Luu1uE~%?j3OIq6mj3>r!Ot3r2zH
bv|OQu6QMKk5yTf1lA0xc|V7n2-FozNwh)?0^gp6QBD#)Y{~l4&Y(K{95RT
zwGN`dgGF}Zk|~>m=0l;2H!*3)2q`+F?
%8O@fhT$smgnZHRkuKnMk
ze2rI{+I^2VuWZ|+31B^dqZZ*NAy4HnG7EMXs=?Jn`y){B!!n9W&qb3SzVl|2AfK;T
z;ZxED%7}EKdlOM1Yc3idV?DP_aPz#>=uSU^*%o2>`Zq(gl_2K#%vhK3@9yz&rzrZE
zakW#>f8LE7^b*ksg4cLNBaJqim7HXwf1<>0OMElTJGc2D69G`ynOBg7gtfIVW_deG
zOySF@jYq!J`o?(q_jpXi_z7tJcuiJ9