mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-11 19:07:33 +08:00
[feat] remember remote display resolutions on mobile clients
This commit is contained in:
@@ -56,7 +56,8 @@ final class BackgroundSession implements NativeSession.Listener {
|
||||
}
|
||||
@Override public void data(JSONObject message) {
|
||||
if (view != null) { view.data(message); return; }
|
||||
displayResolutions.receive(message);
|
||||
RemoteDisplayResolutions.Request confirmed=displayResolutions.receive(message);
|
||||
if(confirmed!=null)session.rememberDisplayResolution(confirmed.displayKey,confirmed.mode);
|
||||
if (message.optInt("type", -1) == 12 && videoSettings != null) videoSettings.receive(message);
|
||||
if (message.optInt("type", -1) == 5) {
|
||||
JSONObject service = message.optJSONObject("service_status");
|
||||
|
||||
@@ -651,6 +651,10 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
private void cancelDisplayResolutionTimeout(){if(displayResolutionTimeout!=null)mainHandler.removeCallbacks(displayResolutionTimeout);displayResolutionTimeout=null;}
|
||||
private void updateDisplayResolution(RemoteDisplayResolutions.Mode mode){
|
||||
if(!ready||session==null||!hasNetworkConsent())return;
|
||||
RemoteDisplayResolutions.Display display=displayResolutions.display(selectedDisplay);
|
||||
if(!displayResolutions.pending&&display!=null&&display.supports(mode)&&display.current.same(mode)){
|
||||
session.rememberDisplayResolution(display.key,mode);return;
|
||||
}
|
||||
RemoteDisplayResolutions.Request request=displayResolutions.begin(selectedDisplay,mode);if(request==null)return;
|
||||
cancelDisplayResolutionTimeout();session.displayResolution(request);
|
||||
if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);
|
||||
@@ -677,7 +681,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
@Override public void data(JSONObject message){
|
||||
if(session==null)return;
|
||||
if(message.optInt("type",-1)==3||message.optInt("type",-1)==16){
|
||||
displayResolutions.receive(message);if(!displayResolutions.pending)cancelDisplayResolutionTimeout();
|
||||
RemoteDisplayResolutions.Request confirmed=displayResolutions.receive(message);
|
||||
if(confirmed!=null&&session!=null)session.rememberDisplayResolution(confirmed.displayKey,confirmed.mode);
|
||||
if(!displayResolutions.pending)cancelDisplayResolutionTimeout();
|
||||
if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);
|
||||
if(message.optInt("type",-1)==16)return;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ final class NativeSession implements AutoCloseable {
|
||||
private final Context context;
|
||||
private volatile Listener listener;
|
||||
private final SecretStore secrets;
|
||||
private final RemoteResolutionStore resolutionStore;
|
||||
private final String host, scope;
|
||||
private volatile String remote;
|
||||
private String password, identity = "";
|
||||
@@ -80,6 +81,7 @@ final class NativeSession implements AutoCloseable {
|
||||
this.host = host; this.port = port; this.remote = remote; this.password = password;
|
||||
scope = host.toLowerCase(java.util.Locale.ROOT) + ":" + port;
|
||||
secrets = new SecretStore(this.context);
|
||||
resolutionStore = new RemoteResolutionStore(this.context);
|
||||
documents = new SessionDocuments(this.context);
|
||||
}
|
||||
void start() {
|
||||
@@ -184,7 +186,8 @@ final class NativeSession implements AutoCloseable {
|
||||
int status = event.getInt("status");
|
||||
if (status == 1) {
|
||||
if (connected) return; // Repeated transport readiness is the same desktop session.
|
||||
connected = true; main.removeCallbacks(timeout); nReady(handle);
|
||||
connected = true; main.removeCallbacks(timeout);
|
||||
nReady(handle,resolutionStore.load(host,port,remote).toString().getBytes(StandardCharsets.UTF_8));
|
||||
audio = new AudioPlayer();
|
||||
nControl(handle, 2, 1);
|
||||
ui(() -> { listener.connected(); if(!closed){statisticsTick.run();fileTick.run();} });
|
||||
@@ -247,6 +250,9 @@ final class NativeSession implements AutoCloseable {
|
||||
void displayResolution(RemoteDisplayResolutions.Request request){
|
||||
execute(()->{if(!connected||!nDisplayResolution(handle,request.displayId,request.displayKey,request.mode.width,request.mode.height,request.id))ui(()->listener.displayResolutionFailed(request.id));});
|
||||
}
|
||||
void rememberDisplayResolution(String displayKey,RemoteDisplayResolutions.Mode mode){
|
||||
if(!closed&&connected)resolutionStore.remember(host,port,remote,displayKey,mode);
|
||||
}
|
||||
void announcementRequest(JSONObject request,String requestId){
|
||||
byte[] bytes=request.toString().getBytes(StandardCharsets.UTF_8);
|
||||
execute(()->{if(!signalReady||identity.isEmpty()||!nAnnouncementRequest(handle,bytes))ui(()->listener.announcementFailed(requestId));});
|
||||
@@ -343,7 +349,7 @@ final class NativeSession implements AutoCloseable {
|
||||
static native String nCheckMobileUpdate(String version, byte[] releaseJson);
|
||||
private static native long nCreate(NativeSession owner, String host, int port, String identity, String logs, String roots);
|
||||
private static native boolean nConnect(long handle, String identity, String remote, String password);
|
||||
private static native void nReady(long handle);
|
||||
private static native void nReady(long handle,byte[] displayResolutions);
|
||||
private static native void nPointer(long handle, float x, float y, int flag, int wheel);
|
||||
private static native void nKey(long handle, int code, boolean down);
|
||||
private static native void nText(long handle,byte[] text);
|
||||
|
||||
@@ -57,28 +57,30 @@ final class RemoteDisplayResolutions {
|
||||
requested=new Request(display,mode,next==0?1:next);pending=true;failed=false;return requested;
|
||||
}
|
||||
void expire(long id){if(pending&&requested!=null&&requested.id==id){pending=false;failed=true;}}
|
||||
void receive(JSONObject message){
|
||||
Request receive(JSONObject message){
|
||||
try{
|
||||
int type=message.getInt("type");JSONArray values;JSONObject response=null;
|
||||
if(type==3){JSONObject host=message.getJSONObject("host_info");values=host.has("display_resolutions")?host.getJSONArray("display_resolutions"):new JSONArray();}
|
||||
else if(type==16){response=message.getJSONObject("display_resolution");if(requested==null||!requested.matches(response)||!(response.get("accepted") instanceof Boolean))return;values=message.getJSONArray("display_resolutions");}
|
||||
else return;
|
||||
if(values.length()>32)return;
|
||||
else if(type==16){response=message.getJSONObject("display_resolution");if(requested==null||!requested.matches(response)||!(response.get("accepted") instanceof Boolean))return null;values=message.getJSONArray("display_resolutions");}
|
||||
else return null;
|
||||
if(values.length()>32)return null;
|
||||
List<Display> actual=new ArrayList<>();
|
||||
for(int i=0;i<values.length();i++){
|
||||
Display display=new Display(values.getJSONObject(i));
|
||||
for(Display previous:actual)if(previous.id==display.id)return;
|
||||
for(Display previous:actual)if(previous.id==display.id)return null;
|
||||
actual.add(display);
|
||||
}
|
||||
displays=actual;
|
||||
if(response!=null){
|
||||
Display display=display(requested.displayId);pending=false;
|
||||
failed=!response.getBoolean("accepted")||display==null||!display.key.equals(requested.displayKey)||!display.current.same(requested.mode);
|
||||
if(!failed)return requested;
|
||||
}else if(pending){
|
||||
Display display=display(requested.displayId);
|
||||
if(display==null||!display.key.equals(requested.displayKey)||!display.supports(requested.mode))expire(requested.id);
|
||||
}
|
||||
}catch(Exception ignored){ /* Ignore malformed or unrelated control messages. */ }
|
||||
return null;
|
||||
}
|
||||
String feedback(){return pending?L10n.tr("等待电脑端确认…"):failed?L10n.tr("调整失败,请重试。"):"";}
|
||||
private static Mode mode(JSONObject value)throws Exception{return new Mode((int)integer(value,"width",1,32768),(int)integer(value,"height",1,32768));}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.crossdesk.mobile;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Successful selections, scoped to the signaling server, device and native screen. */
|
||||
final class RemoteResolutionStore {
|
||||
private final SharedPreferences preferences;
|
||||
RemoteResolutionStore(Context context) {
|
||||
preferences=context.getSharedPreferences("remote_display_resolutions",Context.MODE_PRIVATE);
|
||||
}
|
||||
private String key(String host,int port,String remote) {
|
||||
return new JSONArray().put(host.trim().toLowerCase(Locale.ROOT)).put(port).put(remote).toString();
|
||||
}
|
||||
private static boolean validKey(String key) {
|
||||
return !key.isEmpty()&&key.getBytes(StandardCharsets.UTF_8).length<=1024&&key.indexOf('\0')<0;
|
||||
}
|
||||
private static int dimension(JSONObject value,String key)throws Exception {
|
||||
Object raw=value.get(key);
|
||||
if(!(raw instanceof Integer)&&!(raw instanceof Long))throw new IllegalArgumentException();
|
||||
long number=((Number)raw).longValue();
|
||||
if(number<1||number>32768)throw new IllegalArgumentException();
|
||||
return (int)number;
|
||||
}
|
||||
synchronized JSONArray load(String host,int port,String remote) {
|
||||
if(remote.isEmpty())return new JSONArray();
|
||||
try {
|
||||
String text=preferences.getString(key(host,port,remote),"[]");
|
||||
if(text==null||text.length()>65536)return new JSONArray();
|
||||
JSONArray values=new JSONArray(text);
|
||||
if(values.length()>32)return new JSONArray();
|
||||
HashSet<String> keys=new HashSet<>();
|
||||
JSONArray result=new JSONArray();
|
||||
for(int i=0;i<values.length();i++) {
|
||||
JSONObject value=values.getJSONObject(i);
|
||||
Object identity=value.get("display_key");
|
||||
if(!(identity instanceof String)||!validKey((String)identity)||!keys.add((String)identity))return new JSONArray();
|
||||
result.put(new JSONObject().put("display_key",identity).put("width",dimension(value,"width")).put("height",dimension(value,"height")));
|
||||
}
|
||||
return result;
|
||||
} catch(Exception ignored) { return new JSONArray(); }
|
||||
}
|
||||
synchronized void remember(String host,int port,String remote,String displayKey,RemoteDisplayResolutions.Mode mode) {
|
||||
if(remote.isEmpty()||!validKey(displayKey)||mode==null||mode.width<1||mode.width>32768||mode.height<1||mode.height>32768)return;
|
||||
try {
|
||||
JSONArray saved=load(host,port,remote),updated=new JSONArray();
|
||||
for(int i=0;i<saved.length();i++) {
|
||||
JSONObject value=saved.getJSONObject(i);
|
||||
if(!displayKey.equals(value.getString("display_key")))updated.put(value);
|
||||
}
|
||||
if(updated.length()>=32)updated.remove(0);
|
||||
updated.put(new JSONObject().put("display_key",displayKey).put("width",mode.width).put("height",mode.height));
|
||||
preferences.edit().putString(key(host,port,remote),updated.toString()).apply();
|
||||
} catch(Exception error) { Log.w("CrossDesk","Could not save remote display resolution",error); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user