Compare commits

...

6 Commits

Author SHA1 Message Date
fufesou
80a5865db3 macOS update: restore LaunchAgent in GUI session and isolate temp update dir by euid (#14434)
* fix(update): macos, load agent

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(update): macos, isolate temp update dir by euid

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact(update): macos script

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-01 20:06:04 +08:00
MichaIng
9cb6f38aea packaging: deb: remove obsolete Python version check (#14429)
It was used to conditionally install a Python module in the past. But that is not the case anymore since https://github.com/rustdesk/rustdesk/commit/37dbfcc. Now the check is obsolete.

Due to `set -e`, the check leads to a package configuration failure if Python is not installed, which however otherwise is not needed for RustDesk.

The commit includes an indentation fix and trailing space removal.

Signed-off-by: MichaIng <micha@dietpi.com>
2026-03-01 18:05:19 +08:00
fufesou
cd7e3e4505 fix(update): macos, input password (#14430)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-01 15:19:07 +08:00
fufesou
1833cb0655 fix(update): revert check (#14424)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-02-28 18:17:26 +08:00
fufesou
e4208aa9cf fix(update): revert check (#14423)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-02-28 16:33:54 +08:00
Amirhosein Akhlaghpoor
bb3501a4f9 ui: scale wheel lines on Windows/Linux to Mac (#14395)
* input: accelerate wheel bursts on Windows->Mac

- boost fast wheel bursts without affecting single-step scrolls\n- use dominant-axis smooth detection and velocity gate\n- reset wheel timestamp on enter/leave\n- enforce single-axis scrolling\n- extract/tune Sciter wheel accel thresholds

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>

* input: clarify wheel burst tuning

- add comments on acceleration rules and units\n- apply burst accel on Windows/Linux to macOS\n- reset wheel timing on enter/leave

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>

* input: align wheel burst velocity thresholds

- match Flutter velocity gate with Sciter

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>

* input: restore flutter wheel velocity threshold

- keep burst threshold at 0.002 delta/us

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>

---------

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
2026-02-28 10:56:25 +08:00
8 changed files with 147 additions and 84 deletions

View File

@@ -3938,9 +3938,7 @@ void earlyAssert() {
void checkUpdate() { void checkUpdate() {
if (!isWeb) { if (!isWeb) {
final isWindowsInstalled = isWindows && bind.mainIsInstalled(); if (!bind.isCustomClient()) {
final shouldCheckUpdate = isWindowsInstalled || !bind.isCustomClient();
if (shouldCheckUpdate) {
platformFFI.registerEventHandler( platformFFI.registerEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
(Map<String, dynamic> evt) async { (Map<String, dynamic> evt) async {

View File

@@ -430,12 +430,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} }
Widget buildHelpCards(String updateUrl) { Widget buildHelpCards(String updateUrl) {
final isWindowsInstalled = isWindows && bind.mainIsInstalled(); if (!bind.isCustomClient() &&
if (updateUrl.isNotEmpty && updateUrl.isNotEmpty &&
!isCardClosed && !isCardClosed &&
(isWindowsInstalled || bind.mainUriPrefixSync().contains('rustdesk')) {
(!bind.isCustomClient() &&
bind.mainUriPrefixSync().contains('rustdesk')))) {
final isToUpdate = (isWindows || isMacOS) && bind.mainIsInstalled(); final isToUpdate = (isWindows || isMacOS) && bind.mainIsInstalled();
String btnText = isToUpdate ? 'Update' : 'Download'; String btnText = isToUpdate ? 'Update' : 'Download';
GestureTapCallback onPressed = () async { GestureTapCallback onPressed = () async {

View File

@@ -365,6 +365,16 @@ class InputModel {
final isPhysicalMouse = false.obs; final isPhysicalMouse = false.obs;
int _lastButtons = 0; int _lastButtons = 0;
Offset lastMousePos = Offset.zero; Offset lastMousePos = Offset.zero;
int _lastWheelTsUs = 0;
// Wheel acceleration thresholds.
static const int _wheelAccelFastThresholdUs = 40000; // 40ms
static const int _wheelAccelMediumThresholdUs = 80000; // 80ms
static const double _wheelBurstVelocityThreshold =
0.002; // delta units per microsecond
// Wheel burst acceleration (empirical tuning).
// Applies only to fast, non-smooth bursts to preserve single-step scrolling.
// Flutter uses microseconds for dt, so velocity is in delta/us.
// Relative mouse mode (for games/3D apps). // Relative mouse mode (for games/3D apps).
final relativeMouseMode = false.obs; final relativeMouseMode = false.obs;
@@ -964,6 +974,7 @@ class InputModel {
toReleaseRawKeys.release(handleRawKeyEvent); toReleaseRawKeys.release(handleRawKeyEvent);
_pointerMovedAfterEnter = false; _pointerMovedAfterEnter = false;
_pointerInsideImage = enter; _pointerInsideImage = enter;
_lastWheelTsUs = 0;
// Fix status // Fix status
if (!enter) { if (!enter) {
@@ -1407,17 +1418,44 @@ class InputModel {
if (isViewOnly) return; if (isViewOnly) return;
if (isViewCamera) return; if (isViewCamera) return;
if (e is PointerScrollEvent) { if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx.toInt(); final rawDx = e.scrollDelta.dx;
var dy = e.scrollDelta.dy.toInt(); final rawDy = e.scrollDelta.dy;
final dominantDelta = rawDx.abs() > rawDy.abs() ? rawDx.abs() : rawDy.abs();
final isSmooth = dominantDelta < 1;
final nowUs = DateTime.now().microsecondsSinceEpoch;
final dtUs = _lastWheelTsUs == 0 ? 0 : nowUs - _lastWheelTsUs;
_lastWheelTsUs = nowUs;
int accel = 1;
if (!isSmooth &&
dtUs > 0 &&
dtUs <= _wheelAccelMediumThresholdUs &&
(isWindows || isLinux) &&
peerPlatform == kPeerPlatformMacOS) {
final velocity = dominantDelta / dtUs;
if (velocity >= _wheelBurstVelocityThreshold) {
if (dtUs < _wheelAccelFastThresholdUs) {
accel = 3;
} else {
accel = 2;
}
}
}
var dx = rawDx.toInt();
var dy = rawDy.toInt();
if (rawDx.abs() > rawDy.abs()) {
dy = 0;
} else {
dx = 0;
}
if (dx > 0) { if (dx > 0) {
dx = -1; dx = -accel;
} else if (dx < 0) { } else if (dx < 0) {
dx = 1; dx = accel;
} }
if (dy > 0) { if (dy > 0) {
dy = -1; dy = -accel;
} else if (dy < 0) { } else if (dy < 0) {
dy = 1; dy = accel;
} }
bind.sessionSendMouse( bind.sessionSendMouse(
sessionId: sessionId, sessionId: sessionId,

View File

@@ -12,9 +12,7 @@ if [ "$1" = configure ]; then
if [ -e /etc/systemd/system/rustdesk.service ]; then if [ -e /etc/systemd/system/rustdesk.service ]; then
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1 rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1
fi fi
version=$(python3 -V 2>&1 | grep -Po '(?<=Python )(.+)') mkdir -p /usr/lib/systemd/system/
parsedVersion=$(echo "${version//./}")
mkdir -p /usr/lib/systemd/system/
cp /usr/share/rustdesk/files/systemd/rustdesk.service /usr/lib/systemd/system/rustdesk.service cp /usr/share/rustdesk/files/systemd/rustdesk.service /usr/lib/systemd/system/rustdesk.service
# try fix error in Ubuntu 18.04 # try fix error in Ubuntu 18.04
# Failed to reload rustdesk.service: Unit rustdesk.service is not loaded properly: Exec format error. # Failed to reload rustdesk.service: Unit rustdesk.service is not loaded properly: Exec format error.

View File

@@ -940,9 +940,7 @@ pub fn is_modifier(evt: &KeyEvent) -> bool {
} }
pub fn check_software_update() { pub fn check_software_update() {
let is_windows_installed = cfg!(target_os = "windows") && is_installed(); if is_custom_client() {
let should_check_update = is_windows_installed || !is_custom_client();
if !should_check_update {
return; return;
} }
let opt = LocalConfig::get_option(keys::OPTION_ENABLE_CHECK_UPDATE); let opt = LocalConfig::get_option(keys::OPTION_ENABLE_CHECK_UPDATE);

View File

@@ -42,9 +42,16 @@ static PRIVILEGES_SCRIPTS_DIR: Dir =
include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts"); include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts");
static mut LATEST_SEED: i32 = 0; static mut LATEST_SEED: i32 = 0;
// Using a fixed temporary directory for updates is preferable to #[inline]
// using one that includes the custom client name. fn get_update_temp_dir() -> PathBuf {
const UPDATE_TEMP_DIR: &str = "/tmp/.rustdeskupdate"; let euid = unsafe { hbb_common::libc::geteuid() };
Path::new("/tmp").join(format!(".rustdeskupdate-{}", euid))
}
#[inline]
fn get_update_temp_dir_string() -> String {
get_update_temp_dir().to_string_lossy().into_owned()
}
/// Global mutex to serialize CoreGraphics cursor operations. /// Global mutex to serialize CoreGraphics cursor operations.
/// This prevents race conditions between cursor visibility (hide depth tracking) /// This prevents race conditions between cursor visibility (hide depth tracking)
@@ -279,24 +286,12 @@ fn update_daemon_agent(agent_plist_file: String, update_source_dir: String, sync
Err(e) => { Err(e) => {
log::error!("run osascript failed: {}", e); log::error!("run osascript failed: {}", e);
} }
Ok(status) if !status.success() => {
log::warn!("run osascript failed with status: {}", status);
}
_ => { _ => {
let installed = std::path::Path::new(&agent_plist_file).exists(); let installed = std::path::Path::new(&agent_plist_file).exists();
log::info!("Agent file {} installed: {}", &agent_plist_file, installed); log::info!("Agent file {} installed: {}", &agent_plist_file, installed);
if installed {
// Unload first, or load may not work if already loaded.
// We hope that the load operation can immediately trigger a start.
std::process::Command::new("launchctl")
.args(&["unload", "-w", &agent_plist_file])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok();
let status = std::process::Command::new("launchctl")
.args(&["load", "-w", &agent_plist_file])
.status();
log::info!("launch server, status: {:?}", &status);
}
} }
} }
}; };
@@ -415,7 +410,9 @@ pub fn set_cursor_pos(x: i32, y: i32) -> bool {
let _guard = match CG_CURSOR_MUTEX.try_lock() { let _guard = match CG_CURSOR_MUTEX.try_lock() {
Ok(guard) => guard, Ok(guard) => guard,
Err(std::sync::TryLockError::WouldBlock) => { Err(std::sync::TryLockError::WouldBlock) => {
log::error!("[BUG] set_cursor_pos: CG_CURSOR_MUTEX is already held - potential deadlock!"); log::error!(
"[BUG] set_cursor_pos: CG_CURSOR_MUTEX is already held - potential deadlock!"
);
debug_assert!(false, "Re-entrant call to set_cursor_pos detected"); debug_assert!(false, "Re-entrant call to set_cursor_pos detected");
return false; return false;
} }
@@ -822,7 +819,8 @@ pub fn quit_gui() {
#[inline] #[inline]
pub fn try_remove_temp_update_dir(dir: Option<&str>) { pub fn try_remove_temp_update_dir(dir: Option<&str>) {
let target_path = Path::new(dir.unwrap_or(UPDATE_TEMP_DIR)); let target_path_buf = dir.map(PathBuf::from).unwrap_or_else(get_update_temp_dir);
let target_path = target_path_buf.as_path();
if target_path.exists() { if target_path.exists() {
std::fs::remove_dir_all(target_path).ok(); std::fs::remove_dir_all(target_path).ok();
} }
@@ -851,32 +849,33 @@ pub fn update_me() -> ResultType<()> {
if is_installed_daemon && !is_service_stopped { if is_installed_daemon && !is_service_stopped {
let agent = format!("{}_server.plist", crate::get_full_name()); let agent = format!("{}_server.plist", crate::get_full_name());
let agent_plist_file = format!("/Library/LaunchAgents/{}", agent); let agent_plist_file = format!("/Library/LaunchAgents/{}", agent);
std::process::Command::new("launchctl")
.args(&["unload", "-w", &agent_plist_file])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok();
update_daemon_agent(agent_plist_file, app_dir, true); update_daemon_agent(agent_plist_file, app_dir, true);
} else { } else {
// `kill -9` may not work without "administrator privileges" // `kill -9` may not work without "administrator privileges"
let update_body = format!( let update_body = r#"
r#" on run {app_name, cur_pid, app_dir, user_name}
do shell script " set app_bundle to "/Applications/" & app_name & ".app"
pgrep -x '{app_name}' | grep -v {pid} | xargs kill -9 && rm -rf '/Applications/{app_name}.app' && ditto '{app_dir}' '/Applications/{app_name}.app' && chown -R {user}:staff '/Applications/{app_name}.app' && xattr -r -d com.apple.quarantine '/Applications/{app_name}.app' set app_bundle_q to quoted form of app_bundle
" with prompt "{app_name} wants to update itself" with administrator privileges set app_dir_q to quoted form of app_dir
"#, set user_name_q to quoted form of user_name
app_name = app_name,
pid = std::process::id(), set kill_others to "pids=$(pgrep -x '" & app_name & "' | grep -vx " & cur_pid & " || true); if [ -n \"$pids\" ]; then echo \"$pids\" | xargs kill -9 || true; fi;"
app_dir = app_dir, set copy_files to "rm -rf " & app_bundle_q & " && ditto " & app_dir_q & " " & app_bundle_q & " && chown -R " & user_name_q & ":staff " & app_bundle_q & " && (xattr -r -d com.apple.quarantine " & app_bundle_q & " || true);"
user = get_active_username() set sh to "set -e;" & kill_others & copy_files
);
match Command::new("osascript") do shell script sh with prompt app_name & " wants to update itself" with administrator privileges
end run
"#;
let active_user = get_active_username();
let status = Command::new("osascript")
.arg("-e") .arg("-e")
.arg(update_body) .arg(update_body)
.status() .arg(app_name.to_string())
{ .arg(std::process::id().to_string())
.arg(app_dir)
.arg(active_user)
.status();
match status {
Ok(status) if !status.success() => { Ok(status) if !status.success() => {
log::error!("osascript execution failed with status: {}", status); log::error!("osascript execution failed with status: {}", status);
} }
@@ -897,25 +896,28 @@ pgrep -x '{app_name}' | grep -v {pid} | xargs kill -9 && rm -rf '/Applications/{
} }
pub fn update_from_dmg(dmg_path: &str) -> ResultType<()> { pub fn update_from_dmg(dmg_path: &str) -> ResultType<()> {
let update_temp_dir = get_update_temp_dir_string();
println!("Starting update from DMG: {}", dmg_path); println!("Starting update from DMG: {}", dmg_path);
extract_dmg(dmg_path, UPDATE_TEMP_DIR)?; extract_dmg(dmg_path, &update_temp_dir)?;
println!("DMG extracted"); println!("DMG extracted");
update_extracted(UPDATE_TEMP_DIR)?; update_extracted(&update_temp_dir)?;
println!("Update process started"); println!("Update process started");
Ok(()) Ok(())
} }
pub fn update_to(_file: &str) -> ResultType<()> { pub fn update_to(_file: &str) -> ResultType<()> {
update_extracted(UPDATE_TEMP_DIR)?; let update_temp_dir = get_update_temp_dir_string();
update_extracted(&update_temp_dir)?;
Ok(()) Ok(())
} }
pub fn extract_update_dmg(file: &str) { pub fn extract_update_dmg(file: &str) {
let update_temp_dir = get_update_temp_dir_string();
let mut evt: HashMap<&str, String> = let mut evt: HashMap<&str, String> =
HashMap::from([("name", "extract-update-dmg".to_string())]); HashMap::from([("name", "extract-update-dmg".to_string())]);
match extract_dmg(file, UPDATE_TEMP_DIR) { match extract_dmg(file, &update_temp_dir) {
Ok(_) => { Ok(_) => {
log::info!("Extracted dmg file to {}", UPDATE_TEMP_DIR); log::info!("Extracted dmg file to {}", update_temp_dir);
} }
Err(e) => { Err(e) => {
evt.insert("err", e.to_string()); evt.insert("err", e.to_string());

View File

@@ -1,18 +1,25 @@
on run {daemon_file, agent_file, user, cur_pid, source_dir} on run {daemon_file, agent_file, user, cur_pid, source_dir}
set unload_service to "launchctl unload -w /Library/LaunchDaemons/com.carriez.RustDesk_service.plist || true;" set agent_plist to "/Library/LaunchAgents/com.carriez.RustDesk_server.plist"
set daemon_plist to "/Library/LaunchDaemons/com.carriez.RustDesk_service.plist"
set app_bundle to "/Applications/RustDesk.app"
set kill_others to "pgrep -x 'RustDesk' | grep -v " & cur_pid & " | xargs kill -9;" set resolve_uid to "uid=$(id -u " & quoted form of user & " 2>/dev/null || true);"
set unload_agent to "if [ -n \"$uid\" ]; then launchctl bootout gui/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl bootout user/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl unload -w " & quoted form of agent_plist & " || true; else launchctl unload -w " & quoted form of agent_plist & " || true; fi;"
set unload_service to "launchctl unload -w " & daemon_plist & " || true;"
set kill_others to "pids=$(pgrep -x 'RustDesk' | grep -vx " & cur_pid & " || true); if [ -n \"$pids\" ]; then echo \"$pids\" | xargs kill -9 || true; fi;"
set copy_files to "rm -rf /Applications/RustDesk.app && ditto " & source_dir & " /Applications/RustDesk.app && chown -R " & quoted form of user & ":staff /Applications/RustDesk.app && xattr -r -d com.apple.quarantine /Applications/RustDesk.app;" set copy_files to "(rm -rf " & quoted form of app_bundle & " && ditto " & quoted form of source_dir & " " & quoted form of app_bundle & " && chown -R " & quoted form of user & ":staff " & quoted form of app_bundle & " && (xattr -r -d com.apple.quarantine " & quoted form of app_bundle & " || true)) || exit 1;"
set sh1 to "echo " & quoted form of daemon_file & " > /Library/LaunchDaemons/com.carriez.RustDesk_service.plist && chown root:wheel /Library/LaunchDaemons/com.carriez.RustDesk_service.plist;" set write_daemon_plist to "echo " & quoted form of daemon_file & " > " & daemon_plist & " && chown root:wheel " & daemon_plist & ";"
set write_agent_plist to "echo " & quoted form of agent_file & " > " & agent_plist & " && chown root:wheel " & agent_plist & ";"
set load_service to "launchctl load -w " & daemon_plist & ";"
set agent_label_cmd to "agent_label=$(basename " & quoted form of agent_plist & " .plist);"
set bootstrap_agent to "if [ -n \"$uid\" ]; then launchctl bootstrap gui/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl bootstrap user/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl load -w " & quoted form of agent_plist & " || true; else launchctl load -w " & quoted form of agent_plist & " || true; fi;"
set kickstart_agent to "if [ -n \"$uid\" ]; then launchctl kickstart -k gui/$uid/$agent_label 2>/dev/null || launchctl kickstart -k user/$uid/$agent_label 2>/dev/null || true; fi;"
set load_agent to agent_label_cmd & bootstrap_agent & kickstart_agent
set sh2 to "echo " & quoted form of agent_file & " > /Library/LaunchAgents/com.carriez.RustDesk_server.plist && chown root:wheel /Library/LaunchAgents/com.carriez.RustDesk_server.plist;" set sh to "set -e;" & resolve_uid & unload_agent & unload_service & kill_others & copy_files & write_daemon_plist & write_agent_plist & load_service & load_agent
set sh3 to "launchctl load -w /Library/LaunchDaemons/com.carriez.RustDesk_service.plist;"
set sh to unload_service & kill_others & copy_files & sh1 & sh2 & sh3
do shell script sh with prompt "RustDesk wants to update itself" with administrator privileges do shell script sh with prompt "RustDesk wants to update itself" with administrator privileges
end run end run

View File

@@ -142,6 +142,14 @@ function resetWheel() {
} }
var INERTIA_ACCELERATION = 30; var INERTIA_ACCELERATION = 30;
var WHEEL_ACCEL_VELOCITY_THRESHOLD = 5000;
var WHEEL_ACCEL_DT_FAST = 0.04;
var WHEEL_ACCEL_DT_MEDIUM = 0.08;
var WHEEL_ACCEL_VALUE_FAST = 3;
var WHEEL_ACCEL_VALUE_MEDIUM = 2;
// Wheel burst acceleration (empirical tuning).
// Applies only on fast, non-smooth wheel bursts to keep single-step scroll unchanged.
// Sciter uses seconds for dt, so velocity is in delta/sec.
// not good, precision not enough to simulate acceleration effect, // not good, precision not enough to simulate acceleration effect,
// seems have to use pixel based rather line based delta // seems have to use pixel based rather line based delta
@@ -237,12 +245,28 @@ function handler.onMouse(evt)
// mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"]; // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"];
mask = 3; mask = 3;
{ {
var (dx, dy) = evt.wheelDeltas; var now = getTime();
if (dx > 0) dx = 1; var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0;
else if (dx < 0) dx = -1; var (raw_dx, raw_dy) = evt.wheelDeltas;
if (dy > 0) dy = 1; var dx = 0;
else if (dy < 0) dy = -1; var dy = 0;
if (Math.abs(dx) > Math.abs(dy)) { var abs_dx = Math.abs(raw_dx);
var abs_dy = Math.abs(raw_dy);
var dominant = abs_dx > abs_dy ? abs_dx : abs_dy;
var is_smooth = dominant < 1;
var accel = 1;
if (!is_smooth && dt > 0 && (is_win || is_linux) && get_peer_platform() == "Mac OS") {
var velocity = dominant / dt;
if (velocity >= WHEEL_ACCEL_VELOCITY_THRESHOLD) {
if (dt < WHEEL_ACCEL_DT_FAST) accel = WHEEL_ACCEL_VALUE_FAST;
else if (dt < WHEEL_ACCEL_DT_MEDIUM) accel = WHEEL_ACCEL_VALUE_MEDIUM;
}
}
if (raw_dx > 0) dx = accel;
else if (raw_dx < 0) dx = -accel;
if (raw_dy > 0) dy = accel;
else if (raw_dy < 0) dy = -accel;
if (abs_dx > abs_dy) {
dy = 0; dy = 0;
} else { } else {
dx = 0; dx = 0;
@@ -253,8 +277,6 @@ function handler.onMouse(evt)
wheel_delta_y = acc_wheel_delta_y.toInteger(); wheel_delta_y = acc_wheel_delta_y.toInteger();
acc_wheel_delta_x -= wheel_delta_x; acc_wheel_delta_x -= wheel_delta_x;
acc_wheel_delta_y -= wheel_delta_y; acc_wheel_delta_y -= wheel_delta_y;
var now = getTime();
var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0;
if (dt > 0) { if (dt > 0) {
var vx = dx / dt; var vx = dx / dt;
var vy = dy / dt; var vy = dy / dt;
@@ -297,11 +319,13 @@ function handler.onMouse(evt)
entered = true; entered = true;
stdout.println("enter"); stdout.println("enter");
handler.enter(handler.get_keyboard_mode()); handler.enter(handler.get_keyboard_mode());
last_wheel_time = 0;
return keyboard_enabled; return keyboard_enabled;
case Event.MOUSE_LEAVE: case Event.MOUSE_LEAVE:
entered = false; entered = false;
stdout.println("leave"); stdout.println("leave");
handler.leave(handler.get_keyboard_mode()); handler.leave(handler.get_keyboard_mode());
last_wheel_time = 0;
if (is_left_down && get_peer_platform() == "Android") { if (is_left_down && get_peer_platform() == "Android") {
is_left_down = false; is_left_down = false;
handler.send_mouse((1 << 3) | 2, 0, 0, evt.altKey, handler.send_mouse((1 << 3) | 2, 0, 0, evt.altKey,