mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-11 19:07:33 +08:00
[feat] add macOS and mobile resolution controls with disconnect restore
This commit is contained in:
1 parent
a3159b198e
commit
34b59de15d
50 files changed
+1086
-36
No files matched your search
@@ -17,6 +17,7 @@ final class BackgroundSession implements NativeSession.Listener {
|
||||
String identity = "", platform = "", clipboard = "", fileStatus = "", endReason = "";
|
||||
JSONArray displays = new JSONArray();
|
||||
RemoteVideoSettings videoSettings;
|
||||
RemoteDisplayResolutions displayResolutions=new RemoteDisplayResolutions();
|
||||
int signalState, selectedDisplay, width, height;
|
||||
boolean muted, remoteServiceAvailable, background, ended;
|
||||
boolean autoLandscape;
|
||||
@@ -55,6 +56,7 @@ final class BackgroundSession implements NativeSession.Listener {
|
||||
}
|
||||
@Override public void data(JSONObject message) {
|
||||
if (view != null) { view.data(message); return; }
|
||||
displayResolutions.receive(message);
|
||||
if (message.optInt("type", -1) == 12 && videoSettings != null) videoSettings.receive(message);
|
||||
if (message.optInt("type", -1) == 5) {
|
||||
JSONObject service = message.optJSONObject("service_status");
|
||||
@@ -80,6 +82,7 @@ final class BackgroundSession implements NativeSession.Listener {
|
||||
if (view != null) view.fileTransfer(name, progress, sending, received);
|
||||
}
|
||||
@Override public void controlFeedback(String text) { if (view != null) view.controlFeedback(text); }
|
||||
@Override public void displayResolutionFailed(long requestId) { if(view!=null)view.displayResolutionFailed(requestId);else displayResolutions.expire(requestId); }
|
||||
@Override public void privacyChanged() { if (view != null) view.privacyChanged(); }
|
||||
@Override public void presence(JSONObject message) { if (view != null) view.presence(message); }
|
||||
@Override public void announcement(JSONObject message) { if (view != null) view.announcement(message); }
|
||||
|
||||
@@ -311,6 +311,9 @@ final class LocalizationKeys {
|
||||
IDS.put(",已保存密码", R.string.text_ba9a792d063a8c86);
|
||||
IDS.put(",接收 ", R.string.text_f4c1f9d7896760a7);
|
||||
IDS.put(",点击重试", R.string.text_42237b5ec70c26f3);
|
||||
IDS.put("被控端分辨率", R.string.text_59c9dca5568a6cd3);
|
||||
IDS.put("设备不支持", R.string.text_36e0073276c0e39f);
|
||||
IDS.put("等待电脑端确认…", R.string.text_7b3440ad0f81b811);
|
||||
}
|
||||
private LocalizationKeys() {}
|
||||
}
|
||||
@@ -93,6 +93,8 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
private RemoteVideoSettings videoSettings=new RemoteVideoSettings(1);
|
||||
private final android.os.Handler mainHandler=new android.os.Handler(android.os.Looper.getMainLooper());
|
||||
private Runnable videoSettingsTimeout;
|
||||
private RemoteDisplayResolutions displayResolutions=new RemoteDisplayResolutions();
|
||||
private Runnable displayResolutionTimeout;
|
||||
private final Set<Integer> pressed=new HashSet<>();
|
||||
private Runnable backAction;
|
||||
|
||||
@@ -349,8 +351,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
public void chord(int code,int[] modifiers){if(session==null)return;for(int modifier:modifiers)session.key(modifier,true);tapKey(code);for(int i=modifiers.length-1;i>=0;i--)session.key(modifiers[i],false);}public void shortcut(int code){MainActivity.this.shortcut(code);}public void text(String value){sendText(value);}
|
||||
public void mute(){muted=!muted;if(session!=null)session.control(2,muted?0:1);controls.setMuted(muted);}
|
||||
public void mouse(){boolean relative=!preferences.getBoolean("relativeMouse",true);preferences.edit().putBoolean("relativeMouse",relative).apply();video.setRelative(relative);controls.setRelative(relative);}
|
||||
public void displayResolution(RemoteDisplayResolutions.Mode mode){updateDisplayResolution(mode);}
|
||||
public void videoSettings(int field,int value){RemoteVideoSettings.Values old=MainActivity.this.videoSettings.selection;updateVideoSettings(new RemoteVideoSettings.Values(field==0?value:old.quality,field==1?value:old.frameRate,field==2?value:old.preference));}
|
||||
});controls.setHostPlatform(hostPlatform);controls.setRelative(preferences.getBoolean("relativeMouse",true));controls.setMuted(muted);controls.videoSettings(videoSettings);root.addView(controls,new LinearLayout.LayoutParams(-1,-1));getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
});controls.setHostPlatform(hostPlatform);controls.setRelative(preferences.getBoolean("relativeMouse",true));controls.setMuted(muted);controls.videoSettings(videoSettings);controls.displayResolutions(displayResolutions,selectedDisplay);root.addView(controls,new LinearLayout.LayoutParams(-1,-1));getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
controls.attachMouseBar(canvas,new VirtualMouseBar(ui,video.mouseInput));
|
||||
privacyChanged();
|
||||
video.remoteFocusChanged=()->{if(controls!=null)controls.resetKeyboardContext();};
|
||||
@@ -506,7 +509,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
private void tapKey(int code){if(session!=null){session.key(code,true);session.key(code,false);}}
|
||||
private void shortcut(int code){if(session!=null){int modifier=hostPlatform.equals("macos")?0x5B:0x11;session.key(modifier,true);tapKey(code);session.key(modifier,false);}}
|
||||
private void selectDisplay(){
|
||||
if(!ready)return;int count=Math.min(displays.length(),8);if(count==0){toast(L10n.tr("等待远端屏幕信息"));return;}String[] labels=new String[count];for(int i=0;i<count;i++){JSONObject display=displays.optJSONObject(i);labels[i]=(i+1)+" · "+(display==null?L10n.tr("显示器"):display.optString("name",L10n.tr("显示器")));}new AlertDialog.Builder(this).setTitle(L10n.tr("显示器")).setItems(labels,(d,w)->{if(session!=null){if(w!=selectedDisplay&&video!=null)video.resetViewport();selectedDisplay=w;session.control(4,w);if(controls!=null)controls.resetVideoStatistics();}}).show();
|
||||
if(!ready)return;int count=Math.min(displays.length(),8);if(count==0){toast(L10n.tr("等待远端屏幕信息"));return;}String[] labels=new String[count];for(int i=0;i<count;i++){JSONObject display=displays.optJSONObject(i);labels[i]=(i+1)+" · "+(display==null?L10n.tr("显示器"):display.optString("name",L10n.tr("显示器")));}new AlertDialog.Builder(this).setTitle(L10n.tr("显示器")).setItems(labels,(d,w)->{if(session!=null){if(w!=selectedDisplay&&video!=null)video.resetViewport();selectedDisplay=w;session.control(4,w);if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);if(controls!=null)controls.resetVideoStatistics();}}).show();
|
||||
}
|
||||
private void secureAttention(){
|
||||
if(!ready||session==null||!hostPlatform.equals("windows"))return;
|
||||
@@ -645,7 +648,22 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
remoteVersionCheck.reset();
|
||||
if(remoteUpdateDialog!=null){remoteUpdateDialog.dismiss();remoteUpdateDialog=null;}
|
||||
}
|
||||
private void cancelDisplayResolutionTimeout(){if(displayResolutionTimeout!=null)mainHandler.removeCallbacks(displayResolutionTimeout);displayResolutionTimeout=null;}
|
||||
private void updateDisplayResolution(RemoteDisplayResolutions.Mode mode){
|
||||
if(!ready||session==null||!hasNetworkConsent())return;
|
||||
RemoteDisplayResolutions.Request request=displayResolutions.begin(selectedDisplay,mode);if(request==null)return;
|
||||
cancelDisplayResolutionTimeout();session.displayResolution(request);
|
||||
if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);
|
||||
displayResolutionTimeout=()->{displayResolutions.expire(request.id);if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);};
|
||||
mainHandler.postDelayed(displayResolutionTimeout,10000);
|
||||
}
|
||||
@Override public void displayResolutionFailed(long requestId){
|
||||
displayResolutions.expire(requestId);
|
||||
if(!displayResolutions.pending)cancelDisplayResolutionTimeout();
|
||||
if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);
|
||||
}
|
||||
private void resetVideoSettings(){
|
||||
cancelDisplayResolutionTimeout();displayResolutions=new RemoteDisplayResolutions();
|
||||
cancelVideoSettingsTimeout();
|
||||
android.util.DisplayMetrics metrics=getResources().getDisplayMetrics();
|
||||
videoSettings=new RemoteVideoSettings(preferences.getInt("videoPreference",1),Math.max(metrics.widthPixels,metrics.heightPixels));
|
||||
@@ -658,6 +676,11 @@ 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();
|
||||
if(controls!=null)controls.displayResolutions(displayResolutions,selectedDisplay);
|
||||
if(message.optInt("type",-1)==16)return;
|
||||
}
|
||||
if(message.optInt("type",-1)==5){JSONObject service=message.optJSONObject("service_status");remoteServiceAvailable=service!=null&&service.optBoolean("available",false);return;}
|
||||
if(message.optInt("type",-1)==12){if(!ready)return;videoSettings.receive(message);if(!videoSettings.pending)cancelVideoSettingsTimeout();if(controls!=null)controls.videoSettings(videoSettings);return;}
|
||||
JSONObject info=message.optJSONObject("host_info");
|
||||
@@ -746,7 +769,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
BackgroundSession retained=backgroundSession;if(retained==null)return;
|
||||
retained.identity=localIdentity;retained.signalState=signalState;retained.platform=hostPlatform;retained.displays=displays;
|
||||
retained.muted=muted;retained.selectedDisplay=selectedDisplay;retained.remoteServiceAvailable=remoteServiceAvailable;
|
||||
retained.clipboard=remoteClipboard;retained.videoSettings=videoSettings;retained.receivedFile=receivedFile;
|
||||
retained.clipboard=remoteClipboard;retained.videoSettings=videoSettings;retained.displayResolutions=displayResolutions;retained.receivedFile=receivedFile;
|
||||
retained.width=videoWidth;retained.height=videoHeight;
|
||||
retained.autoLandscape=remoteOrientation.autoLandscape();
|
||||
}
|
||||
@@ -783,10 +806,10 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
connectedRemote=retained.remote;localIdentity=retained.identity;signalState=retained.signalState;signalError="";
|
||||
hostPlatform=retained.platform;displays=retained.displays;muted=retained.muted;selectedDisplay=retained.selectedDisplay;
|
||||
remoteServiceAvailable=retained.remoteServiceAvailable;remoteClipboard=retained.clipboard;receivedFile=retained.receivedFile;
|
||||
videoSettings=retained.videoSettings;ready=true;retained.attach(this);
|
||||
videoSettings=retained.videoSettings;displayResolutions=retained.displayResolutions;ready=true;retained.attach(this);
|
||||
if(rebuild)sessionScreen(retained.autoLandscape);
|
||||
if(retained.width>0&&retained.height>0)videoSize(retained.width,retained.height);
|
||||
if(controls!=null){controls.setHostPlatform(hostPlatform);controls.setMuted(muted);controls.videoSettings(videoSettings);controls.fileStatus(retained.fileStatus,receivedFile!=null);}
|
||||
if(controls!=null){controls.setHostPlatform(hostPlatform);controls.setMuted(muted);controls.videoSettings(videoSettings);controls.displayResolutions(displayResolutions,selectedDisplay);controls.fileStatus(retained.fileStatus,receivedFile!=null);}
|
||||
retained.setBackground(false);signaling(signalState,localIdentity);
|
||||
return true;
|
||||
}
|
||||
@@ -844,11 +867,12 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
if(!isFinishing()&&!enteredPictureInPicture&&ready&&(backgroundSession!=null||documentSession==null)&&prepareBackgroundSession(false)){
|
||||
captureBackgroundSession();backgroundSession.detach(this);backgroundSession.setBackground(true);
|
||||
mainHandler.removeCallbacks(presenceTick);recentPresence.setConnected(false,android.os.SystemClock.elapsedRealtime());announcementInbox.setConnected(false);
|
||||
cancelDisplayResolutionTimeout();if(displayResolutions.pending)displayResolutions.expire(displayResolutions.requested.id);
|
||||
cancelVideoSettingsTimeout();if(videoSettings.pending)videoSettings.expire(videoSettings.requestId);
|
||||
resetRemoteVersionCheck();if(disconnectDialog!=null)disconnectDialog.dismiss();
|
||||
}else if(documentSession==null||documentSession!=session||isFinishing()){if(session!=null)end(isFinishing()?"":L10n.tr("远程窗口已关闭,或后台保活未能启动,会话已结束"));stopSignaling();}
|
||||
super.onStop();}
|
||||
@Override protected void onDestroy(){dismissPrivacyUnlockDialog();mainHandler.removeCallbacks(documentTimeout);documentSession=null;documentToSave=null;appUpdates.setEnabled(false);pageHost.finish();resetRemoteVersionCheck();cancelVideoSettingsTimeout();previews.removeListener(previewChanged);if(privacyDialog!=null)privacyDialog.dismiss();if(previewDialog!=null)previewDialog.dismiss();announcementInbox.close();if(disconnectDialog!=null)disconnectDialog.dismiss();if(passwordDialog!=null)passwordDialog.dismiss();if(progressDialog!=null)progressDialog.dismiss();
|
||||
@Override protected void onDestroy(){dismissPrivacyUnlockDialog();mainHandler.removeCallbacks(documentTimeout);documentSession=null;documentToSave=null;appUpdates.setEnabled(false);pageHost.finish();resetRemoteVersionCheck();cancelVideoSettingsTimeout();cancelDisplayResolutionTimeout();previews.removeListener(previewChanged);if(privacyDialog!=null)privacyDialog.dismiss();if(previewDialog!=null)previewDialog.dismiss();announcementInbox.close();if(disconnectDialog!=null)disconnectDialog.dismiss();if(passwordDialog!=null)passwordDialog.dismiss();if(progressDialog!=null)progressDialog.dismiss();
|
||||
if(connectionDialog!=null)connectionDialog.dismiss();
|
||||
boolean retained=!isFinishing()&&backgroundSession!=null&&!backgroundSession.ended&&RemoteSessionService.current()==backgroundSession;
|
||||
if(backgroundSession!=null)backgroundSession.detach(this);
|
||||
|
||||
@@ -31,6 +31,7 @@ final class NativeSession implements AutoCloseable {
|
||||
default void fileTransfer(String name,double progress,boolean sending,File received) { }
|
||||
default void controlFeedback(String message) { }
|
||||
default void privacyChanged() { }
|
||||
default void displayResolutionFailed(long requestId) { }
|
||||
default void signaling(int state, String deviceId) { }
|
||||
default void presence(JSONObject message) { }
|
||||
default void announcement(JSONObject message) { }
|
||||
@@ -243,6 +244,9 @@ final class NativeSession implements AutoCloseable {
|
||||
if(!settings.valid()||requestId<0||requestId>0xffffffffL)return;
|
||||
execute(()->{if(connected&&!nVideoSettings(handle,settings.quality,settings.frameRate,settings.preference,requestId))ui(()->listener.data(settings.status(requestId,false)));});
|
||||
}
|
||||
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 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));});
|
||||
@@ -344,6 +348,7 @@ final class NativeSession implements AutoCloseable {
|
||||
private static native void nKey(long handle, int code, boolean down);
|
||||
private static native void nText(long handle,byte[] text);
|
||||
private static native void nControl(long handle, int type, int value);
|
||||
private static native boolean nDisplayResolution(long handle,int displayId,String displayKey,int width,int height,long requestId);
|
||||
private static native boolean nVideoSettings(long handle,int quality,int frameRate,int preference,long requestId);
|
||||
private static native boolean nAnnouncementRequest(long handle,byte[] data);
|
||||
private static native boolean nPresenceRequest(long handle,byte[] data);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.crossdesk.mobile;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Host-confirmed desktop modes. Requests are scoped to the native display identity. */
|
||||
final class RemoteDisplayResolutions {
|
||||
static final class Mode {
|
||||
final int width, height;
|
||||
Mode(int width,int height){this.width=width;this.height=height;}
|
||||
boolean same(Mode other){return other!=null&&width==other.width&&height==other.height;}
|
||||
String label(){return width+" × "+height;}
|
||||
}
|
||||
static final class Display {
|
||||
final int id;
|
||||
final String key;
|
||||
final Mode current;
|
||||
final List<Mode> modes=new ArrayList<>();
|
||||
Display(JSONObject value)throws Exception{
|
||||
id=(int)integer(value,"display_id",0,31);
|
||||
Object identity=value.get("display_key");
|
||||
if(!(identity instanceof String))throw new IllegalArgumentException();
|
||||
key=(String)identity;
|
||||
if(key.isEmpty()||key.getBytes(StandardCharsets.UTF_8).length>1024||key.indexOf('\0')>=0)throw new IllegalArgumentException();
|
||||
current=mode(value);
|
||||
JSONArray options=value.getJSONArray("modes");
|
||||
if(options.length()>256)throw new IllegalArgumentException();
|
||||
for(int i=0;i<options.length();i++){
|
||||
Mode item=mode(options.getJSONObject(i));
|
||||
if(!supports(item))modes.add(item);
|
||||
}
|
||||
}
|
||||
boolean supports(Mode value){for(Mode item:modes)if(item.same(value))return true;return false;}
|
||||
}
|
||||
static final class Request {
|
||||
final int displayId;
|
||||
final String displayKey;
|
||||
final Mode mode;
|
||||
final long id;
|
||||
Request(Display display,Mode mode,long id){this.displayId=display.id;this.displayKey=display.key;this.mode=mode;this.id=id;}
|
||||
boolean matches(JSONObject value)throws Exception{
|
||||
return integer(value,"request_id",1,0xffffffffL)==id&&integer(value,"display_id",0,31)==displayId
|
||||
&&displayKey.equals(value.get("display_key"))&&mode.same(mode(value));
|
||||
}
|
||||
}
|
||||
private List<Display> displays=new ArrayList<>();
|
||||
Request requested;
|
||||
boolean pending,failed;
|
||||
Display display(int id){for(Display display:displays)if(display.id==id)return display;return null;}
|
||||
Request begin(int displayId,Mode mode){
|
||||
Display display=display(displayId);
|
||||
if(pending||display==null||!display.supports(mode)||display.current.same(mode))return null;
|
||||
long next=requested==null?1:(requested.id+1)&0xffffffffL;
|
||||
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){
|
||||
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;
|
||||
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;
|
||||
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);
|
||||
}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. */ }
|
||||
}
|
||||
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));}
|
||||
private static long integer(JSONObject value,String key,long min,long max)throws Exception{
|
||||
Object raw=value.get(key);
|
||||
if(!(raw instanceof Integer)&&!(raw instanceof Long))throw new IllegalArgumentException();
|
||||
long number=((Number)raw).longValue();if(number<min||number>max)throw new IllegalArgumentException();return number;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ final class SessionControls extends FrameLayout {
|
||||
void chord(int code,int[] modifiers); void shortcut(int code); void text(String value); void mute(); void mouse();
|
||||
default boolean textEdit(int deletions,String value){return false;}
|
||||
void videoSettings(int field,int value);
|
||||
default void displayResolution(RemoteDisplayResolutions.Mode mode) { }
|
||||
void sendFile(); void saveFile(); void secureAttention();
|
||||
default void togglePrivacy() { }
|
||||
}
|
||||
@@ -36,7 +37,10 @@ final class SessionControls extends FrameLayout {
|
||||
int height=Math.max(0,getRootView().getHeight()-visible.bottom);
|
||||
keyboardInsets(height,height>ui.dp(80),0);
|
||||
}
|
||||
private TextView videoFeedback;
|
||||
private TextView videoFeedback,resolutionButton,resolutionFeedback;
|
||||
private RemoteDisplayResolutions resolutions=new RemoteDisplayResolutions();
|
||||
private int resolutionDisplay;
|
||||
private android.app.AlertDialog resolutionDialog;
|
||||
private RemotePrivacyScreen privacy=new RemotePrivacyScreen();
|
||||
private View privacyButton;
|
||||
private MobileUi.Icon privacyIcon;
|
||||
@@ -111,13 +115,14 @@ final class SessionControls extends FrameLayout {
|
||||
void resetVideoStatistics(){statistics(new RemoteNetworkStatistics.Snapshot(network.report,0,0,0,Double.NaN,network.rtt,network.remoteEncoder,network.localDecoder));}
|
||||
void statistics(RemoteNetworkStatistics.Snapshot value){network=value;renderNetwork();}
|
||||
void videoSettings(RemoteVideoSettings value){settings=value;renderVideoSettings();}
|
||||
void displayResolutions(RemoteDisplayResolutions value,int display){resolutions=value;resolutionDisplay=display;renderResolutions();}
|
||||
void privacyScreen(RemotePrivacyScreen value){privacy=value;renderPrivacy();}
|
||||
void fileStatus(String status,boolean received){transferStatus=status;hasReceivedFile=received;renderFileStatus();}
|
||||
private void renderFileStatus(){
|
||||
if(transferLabel!=null){transferLabel.setText(transferStatus);transferLabel.setVisibility(transferStatus.isEmpty()?GONE:VISIBLE);}
|
||||
if(saveFileButton!=null)saveFileButton.setVisibility(hasReceivedFile?VISIBLE:GONE);
|
||||
}
|
||||
private void closeMenu(){if(menu!=null){removeView(menu);menu=null;menuScroll=null;}if(shield!=null){removeView(shield);shield=null;}videoFeedback=transferLabel=saveFileButton=null;privacyFeedback=privacyCaption=null;privacyButton=null;privacyIcon=null;trafficCells=null;trafficRows=null;networkDetails=null;encryptionLock=encryptionOpen=null;settingRows.clear();orb.setContentDescription(L10n.tr("展开远程控制菜单"));positionMouse();}
|
||||
private void closeMenu(){if(resolutionDialog!=null){resolutionDialog.dismiss();resolutionDialog=null;}resolutionButton=resolutionFeedback=null;if(menu!=null){removeView(menu);menu=null;menuScroll=null;}if(shield!=null){removeView(shield);shield=null;}videoFeedback=transferLabel=saveFileButton=null;privacyFeedback=privacyCaption=null;privacyButton=null;privacyIcon=null;trafficCells=null;trafficRows=null;networkDetails=null;encryptionLock=encryptionOpen=null;settingRows.clear();orb.setContentDescription(L10n.tr("展开远程控制菜单"));positionMouse();}
|
||||
private void openMenu(String page){
|
||||
cancelMouseInput();
|
||||
closeMenu();menuPage=page;shield=new View(getContext());shield.setOnClickListener(v->closeMenu());addView(shield,new LayoutParams(-1,-1));
|
||||
@@ -153,6 +158,9 @@ final class SessionControls extends FrameLayout {
|
||||
}else if(page.equals("network")){
|
||||
networkPage(body);
|
||||
}else{
|
||||
TextView resolutionTitle=ui.text(L10n.tr("被控端分辨率"),12,false);resolutionTitle.setTextColor(SECONDARY);body.addView(resolutionTitle);ui.gap(body,5);
|
||||
resolutionButton=ui.action("",INK,this::chooseResolution);resolutionButton.setTextSize(13);resolutionButton.setBackground(ui.background(0xFFF0F0F2,7));body.addView(resolutionButton,ui.size(-1,34));
|
||||
resolutionFeedback=ui.text("",12,false);resolutionFeedback.setTextColor(SECONDARY);resolutionFeedback.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);body.addView(resolutionFeedback);ui.gap(body,12);renderResolutions();
|
||||
settingRows.add(new SettingRow(body,0,L10n.tr("画面质量"),new String[]{L10n.tr("低"),L10n.tr("中"),L10n.tr("高")},new int[]{0,1,2}));ui.gap(body,12);
|
||||
settingRows.add(new SettingRow(body,1,L10n.tr("画面采集帧率"),new String[]{"30 fps","60 fps"},new int[]{30,60}));ui.gap(body,12);
|
||||
settingRows.add(new SettingRow(body,2,L10n.tr("画面偏好"),RemoteVideoSettings.preferences(),new int[]{0,1,2}));renderVideoSettings();
|
||||
@@ -192,6 +200,26 @@ final class SessionControls extends FrameLayout {
|
||||
}
|
||||
void render(){group.setSelection(selectionIndex());group.setOptionsEnabled(settings.supported);}
|
||||
}
|
||||
private void renderResolutions(){
|
||||
if(resolutionButton==null)return;
|
||||
RemoteDisplayResolutions.Display display=resolutions.display(resolutionDisplay);
|
||||
boolean enabled=display!=null&&!display.modes.isEmpty()&&!resolutions.pending;
|
||||
resolutionButton.setText(display==null?L10n.tr("设备不支持"):display.current.label()+" ▾");
|
||||
resolutionButton.setEnabled(enabled);resolutionButton.setAlpha(enabled?1f:.45f);
|
||||
String feedback=resolutions.feedback();resolutionFeedback.setText(feedback);resolutionFeedback.setVisibility(feedback.isEmpty()?GONE:VISIBLE);
|
||||
}
|
||||
private void chooseResolution(){
|
||||
RemoteDisplayResolutions.Display display=resolutions.display(resolutionDisplay);
|
||||
if(display==null||resolutions.pending||display.modes.isEmpty())return;
|
||||
String[] labels=new String[display.modes.size()];int selected=-1;
|
||||
for(int i=0;i<labels.length;i++){labels[i]=display.modes.get(i).label();if(display.current.same(display.modes.get(i)))selected=i;}
|
||||
resolutionDialog=new android.app.AlertDialog.Builder(getContext()).setTitle(L10n.tr("被控端分辨率"))
|
||||
.setSingleChoiceItems(labels,selected,(dialog,index)->{
|
||||
dialog.dismiss();resolutionDialog=null;
|
||||
RemoteDisplayResolutions.Display current=resolutions.display(resolutionDisplay);
|
||||
if(current!=null&¤t.id==display.id&¤t.key.equals(display.key))actions.displayResolution(display.modes.get(index));
|
||||
}).setNegativeButton(L10n.tr("取消"),null).show();
|
||||
}
|
||||
private void renderVideoSettings(){
|
||||
if(videoFeedback!=null){String feedback=settings.feedback();videoFeedback.setText(feedback);videoFeedback.setVisibility(feedback.isEmpty()?GONE:VISIBLE);}
|
||||
for(SettingRow row:settingRows)row.render();
|
||||
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">",已保存密码"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">",接收 "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">",点击重试"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"被控端分辨率"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"设备不支持"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"等待电脑端确认…"</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">",已儲存密碼"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">",接收 "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">",點選重試"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"被控端解析度"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"裝置不支援"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"等待電腦端確認…"</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", Passwort gespeichert"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", empfangen "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">", klicken Sie auf Wiederholen"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"Auflösung des entfernten Bildschirms"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"Nicht unterstützt"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"Warten auf Bestätigung vom Computer..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", contraseña guardada"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", recibir "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">", haga clic en Reintentar"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"Resolución de la pantalla remota"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"No compatible"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"Esperando confirmación del ordenador..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", mot de passe enregistré"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", recevoir "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">", cliquez sur Réessayer"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"Résolution de l’écran distant"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"Non pris en charge"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"En attente de confirmation de l\'ordinateur..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">"、パスワードが保存されました"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">"、受け取ります "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">"、「再試行」をクリックします"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"接続先の画面解像度"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"サポートされていません"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"コンピューターからの確認を待っています..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", 비밀번호가 저장되었습니다"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", 수신 "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">", 재시도를 클릭하세요."</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"원격 화면 해상도"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"지원되지 않음"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"컴퓨터의 확인을 기다리는 중..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", senha salva"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", receba "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">", clique em Tentar novamente"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"Resolução da tela remota"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"Não suportado"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"Aguardando confirmação do computador..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", пароль сохранен"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", получить "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">"нажмите «Повторить»."</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"Разрешение удалённого экрана"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"Не поддерживается"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"Ожидание подтверждения от компьютера..."</string>
|
||||
</resources>
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="text_ba9a792d063a8c86" formatted="false">", password saved"</string>
|
||||
<string name="text_f4c1f9d7896760a7" formatted="false">", receive "</string>
|
||||
<string name="text_42237b5ec70c26f3" formatted="false">", click Retry"</string>
|
||||
<string name="text_59c9dca5568a6cd3" formatted="false">"Remote display resolution"</string>
|
||||
<string name="text_36e0073276c0e39f" formatted="false">"Unsupported"</string>
|
||||
<string name="text_7b3440ad0f81b811" formatted="false">"Waiting for confirmation from the computer..."</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user