Skip to content

Commit 9b6aacb

Browse files
Release v1.1.10 — Windows-style OSK, gamepad shortcuts, vmouse scroll.
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5024759 commit 9b6aacb

10 files changed

Lines changed: 376 additions & 189 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.19)
22
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
33

4-
set(MOONLIGHT_VERSION "1.1.9")
4+
set(MOONLIGHT_VERSION "1.1.10")
55

66
project(moonlight VERSION ${MOONLIGHT_VERSION} LANGUAGES C CXX)
77

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ Unofficial fork of [Moonlight TV](https://github.com/mariotaku/moonlight-tv) for
4040
| Gesture | Action |
4141
|---------|--------|
4242
| **Hold Select/Back 4s** | Toggle pinned performance stats |
43-
| **Hold Start 4s** | Toggle virtual mouse (enable in Settings → Input first) |
43+
| **Y / Triangle** (virtual mouse on) | Open on-screen keyboard |
4444

45-
Full keyboard: stream overlay menu, or Magic Remote **BLUE** (no gamepad chord — avoids conflict with in-game RB+RS). Virtual mouse: right stick = cursor, left stick = scroll, LT/RT = mouse buttons.
45+
Full keyboard: stream overlay, Magic Remote **BLUE**, or gamepad **Y** while virtual mouse is active. With the keyboard open: **Y** = Space, **LT** = abc/`&123`, **LB/RB** = Left/Right. Virtual mouse: stream overlay button (or enable in Settings → Input to start enabled); right stick = cursor, left stick = scroll, LT/RT = mouse buttons.
4646

4747
Details, hotkey layout, and stats field reference: [webOS build guide](docs/BUILD_WEBOS.md).
4848

scripts/webos/docker_build_inner.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
set -e
33

44
install_apt_packages() {
5-
apt-get update -qq && apt-get install -y -qq cmake gawk curl git build-essential ca-certificates wget file
5+
# Docker Desktop on Windows often resolves Ubuntu mirrors to broken IPv6; force IPv4.
6+
apt-get -o Acquire::ForceIPv4=true update -qq && \
7+
apt-get -o Acquire::ForceIPv4=true install -y -qq cmake gawk curl git build-essential ca-certificates wget file
68
}
79

810
if ! install_apt_packages > /dev/null 2>&1; then

src/app/lvgl/input/lv_drv_sdl_key.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,37 @@ static void sdl_input_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
113113
data->continue_reading = true;
114114
} else if (SDL_PeepEvents(&e, 1, SDL_GETEVENT, SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERDEVICEREMAPPED) > 0) {
115115
bool handled_modal = false;
116-
if (ui_modal_consumes_input()
116+
/* LT edge while soft keyboard is open → toggle abc / &123 */
117+
static bool kbd_lt_held = false;
118+
if (!streaming_soft_keyboard_shown()) {
119+
kbd_lt_held = false;
120+
}
121+
if (streaming_soft_keyboard_shown() && e.type == SDL_CONTROLLERAXISMOTION
122+
&& e.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) {
123+
bool down = e.caxis.value > 16000;
124+
if (down != kbd_lt_held) {
125+
kbd_lt_held = down;
126+
if (down) {
127+
soft_keyboard_gamepad_shortcut(SOFT_KBD_GP_TOGGLE_LAYER, true);
128+
}
129+
}
130+
handled_modal = true; /* swallow LT while keyboard is open */
131+
} else if (ui_modal_consumes_input()
117132
&& (e.type == SDL_CONTROLLERBUTTONDOWN || e.type == SDL_CONTROLLERBUTTONUP)) {
118133
bool pressed = (e.cbutton.state == SDL_PRESSED);
119134
NAVKEY navkey = navkey_from_sdl(&e, &pressed);
120135
if (streaming_soft_keyboard_shown() && app->session != NULL) {
121136
stream_input_t *si = session_get_input(app->session);
122137
short vk = 0;
123138
bool close_kbd = false;
139+
/* LB / RB → Left / Right (Windows OSK badges) */
140+
if (e.cbutton.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
141+
soft_keyboard_gamepad_shortcut(SOFT_KBD_GP_ARROW_LEFT, pressed);
142+
handled_modal = true;
143+
} else if (e.cbutton.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
144+
soft_keyboard_gamepad_shortcut(SOFT_KBD_GP_ARROW_RIGHT, pressed);
145+
handled_modal = true;
146+
} else {
124147
switch (navkey) {
125148
case NAVKEY_UP:
126149
case NAVKEY_DOWN:
@@ -153,6 +176,7 @@ static void sdl_input_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
153176
}
154177
handled_modal = true;
155178
}
179+
}
156180
} else if (streaming_soft_keyboard_shown()) {
157181
switch (navkey) {
158182
case NAVKEY_UP:

src/app/stream/input/session_gamepad.c

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
#define QUIT_BUTTONS (PLAY_FLAG | BACK_FLAG | LB_FLAG | RB_FLAG)
1717
/** Hold Select (Back) this long to toggle pinned performance stats (Artemis-style). */
1818
#define GAMEPAD_HOLD_STATS_MS 4000
19-
/** Hold Start (Play) this long to toggle virtual mouse (Artemis-style). */
20-
#define GAMEPAD_HOLD_VMOUSE_MS 4000
2119

2220
static bool quit_combo_pressed = false;
2321

24-
/** Hold timers only detect shortcuts; Select/Start are always forwarded to the host. */
22+
/** Hold timer only detects Select→stats shortcut; Select is still forwarded to the host. */
2523
static SDL_TimerID stats_hold_timer = 0;
26-
static SDL_TimerID vmouse_hold_timer = 0;
2724

2825
static void release_buttons(stream_input_t *input, app_gamepad_state_t *gamepad);
2926

@@ -42,14 +39,10 @@ static void stream_input_send_buttons(stream_input_t *input, app_gamepad_state_t
4239

4340
static void cancel_stats_hold(void);
4441

45-
static void cancel_vmouse_hold(void);
46-
4742
static void cancel_all_holds(void);
4843

4944
static Uint32 stats_hold_timer_cb(Uint32 interval, void *param);
5045

51-
static Uint32 vmouse_hold_timer_cb(Uint32 interval, void *param);
52-
5346
static bool stream_input_gamepad_sends_moonlight(const stream_input_t *input,
5447
const app_gamepad_state_t *gamepad) {
5548
(void) input;
@@ -66,6 +59,15 @@ void stream_input_handle_cbutton(stream_input_t *input, const SDL_ControllerButt
6659
if (gamepad == NULL) {
6760
return;
6861
}
62+
63+
/* Physical Y/Triangle opens the soft keyboard only while virtual mouse is active. */
64+
if (event->type == SDL_CONTROLLERBUTTONDOWN && event->button == SDL_CONTROLLER_BUTTON_Y
65+
&& session_input_is_vmouse_active(&input->vmouse)) {
66+
cancel_all_holds();
67+
bus_pushevent(USER_OPEN_SOFT_KEYBOARD, NULL, NULL);
68+
return;
69+
}
70+
6971
int button = 0;
7072
switch (event->button) {
7173
case SDL_CONTROLLER_BUTTON_A:
@@ -154,27 +156,15 @@ void stream_input_handle_cbutton(stream_input_t *input, const SDL_ControllerButt
154156
return;
155157
}
156158

157-
/* Select/Start always go to the host. Hold timers only arm client shortcuts. */
159+
/* Select always goes to the host. Hold timer only arms the stats shortcut. */
158160
if (event->type == SDL_CONTROLLERBUTTONDOWN && button == BACK_FLAG) {
159-
cancel_vmouse_hold();
160161
cancel_stats_hold();
161162
stats_hold_timer = SDL_AddTimer(GAMEPAD_HOLD_STATS_MS, stats_hold_timer_cb, NULL);
162163
} else if (event->type == SDL_CONTROLLERBUTTONUP && button == BACK_FLAG) {
163164
cancel_stats_hold();
164-
} else if (event->type == SDL_CONTROLLERBUTTONDOWN && button == PLAY_FLAG) {
165-
cancel_stats_hold();
166-
cancel_vmouse_hold();
167-
vmouse_hold_timer = SDL_AddTimer(GAMEPAD_HOLD_VMOUSE_MS, vmouse_hold_timer_cb, NULL);
168-
} else if (event->type == SDL_CONTROLLERBUTTONUP && button == PLAY_FLAG) {
169-
cancel_vmouse_hold();
170-
} else {
165+
} else if (button != BACK_FLAG) {
171166
/* Mixing other buttons with a pending hold cancels the shortcut. */
172-
if (button != BACK_FLAG) {
173-
cancel_stats_hold();
174-
}
175-
if (button != PLAY_FLAG) {
176-
cancel_vmouse_hold();
177-
}
167+
cancel_stats_hold();
178168
}
179169

180170
stream_input_send_buttons(input, gamepad);
@@ -467,16 +457,8 @@ static void cancel_stats_hold(void) {
467457
}
468458
}
469459

470-
static void cancel_vmouse_hold(void) {
471-
if (vmouse_hold_timer) {
472-
SDL_RemoveTimer(vmouse_hold_timer);
473-
vmouse_hold_timer = 0;
474-
}
475-
}
476-
477460
static void cancel_all_holds(void) {
478461
cancel_stats_hold();
479-
cancel_vmouse_hold();
480462
}
481463

482464
static Uint32 stats_hold_timer_cb(Uint32 interval, void *param) {
@@ -488,15 +470,6 @@ static Uint32 stats_hold_timer_cb(Uint32 interval, void *param) {
488470
return 0;
489471
}
490472

491-
static Uint32 vmouse_hold_timer_cb(Uint32 interval, void *param) {
492-
(void) interval;
493-
(void) param;
494-
vmouse_hold_timer = 0;
495-
bus_pushevent(USER_TOGGLE_VMOUSE, NULL, NULL);
496-
commons_log_info("Input", "Start held %dms — toggle virtual mouse", GAMEPAD_HOLD_VMOUSE_MS);
497-
return 0;
498-
}
499-
500473
static void release_buttons(stream_input_t *input, app_gamepad_state_t *gamepad) {
501474
gamepad->buttons = 0;
502475
gamepad->leftTrigger = 0;

src/app/stream/input/session_virt_mouse.c

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44
#include <SDL_stdinc.h>
55
#include <math.h>
66

7-
#define VMOUSE_SCROLL_SENSITIVITY 4.0
7+
/** Tuned for smooth but usable left-stick scrolling (not page jumps). */
8+
#define VMOUSE_SCROLL_SENSITIVITY 1.85
9+
#define VMOUSE_SCROLL_DEADZONE 5500
10+
#define VMOUSE_SCROLL_MAX_TICK 58
11+
#define VMOUSE_SCROLL_DIVIDER 14.0
812

913
static short calc_mouse_movement(short axis);
1014

1115
static short calc_scroll_delta(short axis);
1216

17+
static short clamp_scroll_tick(double value);
18+
1319
static Uint32 vmouse_timer_callback(Uint32 interval, void *param);
1420

1521
void session_input_set_vmouse_active(session_input_vmouse_t *vmouse, bool active) {
@@ -83,23 +89,30 @@ static Uint32 vmouse_timer_callback(Uint32 interval, void *param) {
8389
}
8490

8591
if (vmouse->state.scroll_x || vmouse->state.scroll_y) {
86-
double scroll_x = vmouse->state.scroll_x;
87-
double scroll_y = vmouse->state.scroll_y;
88-
double abs_sx = SDL_fabs(scroll_x), abs_sy = SDL_fabs(scroll_y);
89-
if (scroll_y != 0) {
90-
LiSendHighResScrollEvent((short) (abs_sy > 1 ? -scroll_y : -scroll_y / abs_sy));
92+
double scroll_x = vmouse->state.scroll_x / VMOUSE_SCROLL_DIVIDER;
93+
double scroll_y = vmouse->state.scroll_y / VMOUSE_SCROLL_DIVIDER;
94+
short sy = clamp_scroll_tick(scroll_y);
95+
short sx = clamp_scroll_tick(scroll_x);
96+
if (sy != 0) {
97+
/* Positive high-res scroll = wheel up / content moves down naturally with stick-up. */
98+
LiSendHighResScrollEvent(sy);
9199
}
92-
if (scroll_x != 0) {
93-
LiSendHighResHScrollEvent((short) (abs_sx > 1 ? scroll_x : scroll_x / abs_sx));
100+
if (sx != 0) {
101+
LiSendHighResHScrollEvent(sx);
94102
}
95103
}
96104

97-
double activity = SDL_max(SDL_max(abs_x, abs_y),
98-
SDL_max(SDL_fabs(vmouse->state.scroll_x), SDL_fabs(vmouse->state.scroll_y)));
105+
double move_activity = SDL_max(abs_x, abs_y);
106+
double scroll_activity = SDL_max(SDL_fabs(vmouse->state.scroll_x), SDL_fabs(vmouse->state.scroll_y))
107+
/ VMOUSE_SCROLL_DIVIDER;
108+
double activity = SDL_max(move_activity, scroll_activity);
99109
if (activity <= 0) {
100110
return 0;
101111
}
102-
return SDL_max(5, SDL_min(5 / activity, 20));
112+
if (move_activity <= 0 && scroll_activity > 0) {
113+
return (Uint32) SDL_max(12, SDL_min(28, (int) (18 / SDL_max(0.25, scroll_activity))));
114+
}
115+
return (Uint32) SDL_max(5, SDL_min(5 / activity, 20));
103116
}
104117

105118
static short calc_mouse_movement(short axis) {
@@ -110,11 +123,35 @@ static short calc_mouse_movement(short axis) {
110123
}
111124

112125
static short calc_scroll_delta(short axis) {
113-
if (axis == 0) {
126+
short abs_axis = (short) (axis > 0 ? axis : -axis);
127+
if (abs_axis < VMOUSE_SCROLL_DEADZONE) {
128+
return 0;
129+
}
130+
double t = (abs_axis - VMOUSE_SCROLL_DEADZONE) / (32767.0 - VMOUSE_SCROLL_DEADZONE);
131+
if (t > 1.0) {
132+
t = 1.0;
133+
}
134+
double curved = t * t * VMOUSE_SCROLL_SENSITIVITY;
135+
short mag = (short) (curved * 160.0);
136+
if (mag < 1) {
137+
mag = 1;
138+
}
139+
return (short) (axis > 0 ? mag : -mag);
140+
}
141+
142+
static short clamp_scroll_tick(double value) {
143+
if (value == 0) {
114144
return 0;
115145
}
116-
double normalized = axis / 32767.0;
117-
double scaled = normalized * (VMOUSE_SCROLL_SENSITIVITY / 4.0) * 2.0;
118-
double curved = (scaled >= 0 ? 1.0 : -1.0) * pow(SDL_fabs(scaled), 3.0);
119-
return (short) (curved * 32767.0);
146+
short v = (short) value;
147+
if (v == 0) {
148+
v = value > 0 ? 1 : -1;
149+
}
150+
if (v > VMOUSE_SCROLL_MAX_TICK) {
151+
return VMOUSE_SCROLL_MAX_TICK;
152+
}
153+
if (v < -VMOUSE_SCROLL_MAX_TICK) {
154+
return (short) -VMOUSE_SCROLL_MAX_TICK;
155+
}
156+
return v;
120157
}

src/app/stream/session.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ bool session_start_input(session_t *session) {
153153
}
154154
#endif
155155
session_input_started(&session->input);
156+
if (session->config.vmouse) {
157+
session_input_set_vmouse_active(&session->input.vmouse, true);
158+
}
156159
return true;
157160
}
158161

@@ -165,7 +168,7 @@ bool session_has_input(session_t *session) {
165168
}
166169

167170
void session_toggle_vmouse(session_t *session) {
168-
bool value = session->config.vmouse && !session_input_is_vmouse_active(&session->input.vmouse);
171+
bool value = !session_input_is_vmouse_active(&session->input.vmouse);
169172
session_input_set_vmouse_active(&session->input.vmouse, value);
170173
}
171174

src/app/ui/settings/panes/input.pane.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,18 @@ static lv_obj_t *create_obj(lv_fragment_t *self, lv_obj_t *container) {
8181
false);
8282

8383
pref_checkbox(view, locstr("Virtual mouse"), &app_configuration->virtual_mouse, false);
84-
pref_desc_label(view, locstr("Hold Start for 4 seconds during streaming to toggle virtual mouse "
85-
"(enable here first). Right stick moves the cursor, left stick scrolls, "
86-
"LT/RT are left/right mouse buttons."),
84+
pref_desc_label(view, locstr("When enabled, virtual mouse starts active at the beginning of a stream. "
85+
"Toggle anytime from the stream overlay Virtual Mouse button. "
86+
"Right stick moves the cursor, left stick scrolls, LT/RT are left/right mouse buttons."),
8787
false);
8888

8989
pane->swap_abxy_toggle = pref_checkbox(view, locstr("Swap ABXY buttons"), &app_configuration->swap_abxy, false);
9090
pref_desc_label(view, locstr("Swap A/B and X/Y gamepad buttons. Useful when you prefer Nintendo-like layouts."),
9191
false);
9292

9393
pref_desc_label(view, locstr("Hold Select/Back for 4 seconds during streaming to pin or unpin performance stats. "
94-
"Open the on-screen keyboard from the stream overlay or Magic Remote BLUE."),
94+
"Open the on-screen keyboard from the stream overlay, Magic Remote BLUE, "
95+
"or gamepad Y while virtual mouse is active."),
9596
false);
9697

9798
#if FEATURE_INPUT_EVMOUSE

0 commit comments

Comments
 (0)