commit
7a925b214d
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -1159,14 +1159,38 @@ dependencies = [
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core 0.10.2",
|
||||
"darling_macro 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
"strsim 0.9.3",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1183,13 +1207,24 @@ dependencies = [
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core 0.10.2",
|
||||
"quote 1.0.21",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.13.4",
|
||||
"quote 1.0.21",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
@ -1389,6 +1424,18 @@ dependencies = [
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_setters"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1cf41b4580a37cca5ef2ada2cc43cf5d6be3983f4522e83010d67ab6925e84b"
|
||||
dependencies = [
|
||||
"darling 0.10.2",
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "detect-desktop-environment"
|
||||
version = "0.2.0"
|
||||
@ -3585,7 +3632,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.13.4",
|
||||
"proc-macro-crate 1.2.1",
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
@ -4944,6 +4991,7 @@ dependencies = [
|
||||
"winreg 0.10.1",
|
||||
"winres",
|
||||
"wol-rs",
|
||||
"xrandr-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5469,6 +5517,12 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@ -6965,6 +7019,16 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "xrandr-parser"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5af43ba661cee58bd86b9f81a899e45a15ac7f42fa4401340f73c0c2950030c1"
|
||||
dependencies = [
|
||||
"derive_setters",
|
||||
"serde 1.0.149",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
@ -121,6 +121,7 @@ mouce = { git="https://github.com/fufesou/mouce.git" }
|
||||
evdev = { git="https://github.com/fufesou/evdev" }
|
||||
dbus = "0.9"
|
||||
dbus-crossroads = "0.5"
|
||||
xrandr-parser = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.11"
|
||||
|
@ -1814,3 +1814,19 @@ class DraggableNeverScrollableScrollPhysics extends ScrollPhysics {
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
}
|
||||
|
||||
Widget futureBuilder(
|
||||
{required Future? future, required Widget Function(dynamic data) hasData}) {
|
||||
return FutureBuilder(
|
||||
future: future,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return hasData(snapshot.data!);
|
||||
} else {
|
||||
if (snapshot.hasError) {
|
||||
debugPrint(snapshot.error.toString());
|
||||
}
|
||||
return Container();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -319,7 +319,7 @@ class _GeneralState extends State<_General> {
|
||||
bind.mainSetOption(key: 'audio-input', value: device);
|
||||
}
|
||||
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
||||
if (Platform.isWindows) {
|
||||
devices.insert(0, 'System Sound');
|
||||
@ -346,7 +346,7 @@ class _GeneralState extends State<_General> {
|
||||
}
|
||||
|
||||
Widget record(BuildContext context) {
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
String customDirectory =
|
||||
await bind.mainGetOption(key: 'video-save-directory');
|
||||
String defaultDirectory = await bind.mainDefaultVideoSaveDirectory();
|
||||
@ -399,7 +399,7 @@ class _GeneralState extends State<_General> {
|
||||
}
|
||||
|
||||
Widget language() {
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
String langs = await bind.mainGetLangs();
|
||||
String lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
|
||||
return {'langs': langs, 'lang': lang};
|
||||
@ -487,7 +487,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
|
||||
Widget _permissions(context, bool stopService) {
|
||||
bool enabled = !locked;
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
return await bind.mainGetOption(key: 'access-mode');
|
||||
}(), hasData: (data) {
|
||||
String accessMode = data! as String;
|
||||
@ -744,7 +744,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
return [
|
||||
_OptionCheckBox(context, 'Enable Direct IP Access', 'direct-server',
|
||||
update: update, enabled: !locked),
|
||||
_futureBuilder(
|
||||
futureBuilder(
|
||||
future: () async {
|
||||
String enabled = await bind.mainGetOption(key: 'direct-server');
|
||||
String port = await bind.mainGetOption(key: 'direct-access-port');
|
||||
@ -805,7 +805,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
|
||||
Widget whitelist() {
|
||||
bool enabled = !locked;
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
return await bind.mainGetOption(key: 'whitelist');
|
||||
}(), hasData: (data) {
|
||||
RxBool hasWhitelist = (data as String).isNotEmpty.obs;
|
||||
@ -931,7 +931,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
|
||||
server(bool enabled) {
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
return await bind.mainGetOptions();
|
||||
}(), hasData: (data) {
|
||||
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
||||
@ -1366,7 +1366,7 @@ class _About extends StatefulWidget {
|
||||
class _AboutState extends State<_About> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _futureBuilder(future: () async {
|
||||
return futureBuilder(future: () async {
|
||||
final license = await bind.mainGetLicense();
|
||||
final version = await bind.mainGetVersion();
|
||||
final buildDate = await bind.mainGetBuildDate();
|
||||
@ -1500,7 +1500,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
|
||||
bool enabled = true,
|
||||
Icon? checkedIcon,
|
||||
bool? fakeValue}) {
|
||||
return _futureBuilder(
|
||||
return futureBuilder(
|
||||
future: bind.mainGetOption(key: key),
|
||||
hasData: (data) {
|
||||
bool value = option2bool(key, data.toString());
|
||||
@ -1633,22 +1633,6 @@ Widget _SubLabeledWidget(BuildContext context, String label, Widget child,
|
||||
).marginOnly(left: _kContentHSubMargin);
|
||||
}
|
||||
|
||||
Widget _futureBuilder(
|
||||
{required Future? future, required Widget Function(dynamic data) hasData}) {
|
||||
return FutureBuilder(
|
||||
future: future,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return hasData(snapshot.data!);
|
||||
} else {
|
||||
if (snapshot.hasError) {
|
||||
debugPrint(snapshot.error.toString());
|
||||
}
|
||||
return Container();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _lock(
|
||||
bool locked,
|
||||
String label,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -270,6 +270,7 @@ class FfiModel with ChangeNotifier {
|
||||
parent.target?.canvasModel.updateViewStyle();
|
||||
}
|
||||
parent.target?.recordingModel.onSwitchDisplay();
|
||||
handleResolutions(peerId, evt["resolutions"]);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -437,10 +438,35 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
Map<String, dynamic> features = json.decode(evt['features']);
|
||||
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
||||
handleResolutions(peerId, evt["resolutions"]);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
handleResolutions(String id, dynamic resolutions) {
|
||||
try {
|
||||
final List<dynamic> dynamicArray = jsonDecode(resolutions as String);
|
||||
List<Resolution> arr = List.empty(growable: true);
|
||||
for (int i = 0; i < dynamicArray.length; i++) {
|
||||
var width = dynamicArray[i]["width"];
|
||||
var height = dynamicArray[i]["height"];
|
||||
if (width is int && width > 0 && height is int && height > 0) {
|
||||
arr.add(Resolution(width, height));
|
||||
}
|
||||
}
|
||||
arr.sort((a, b) {
|
||||
if (b.width != a.width) {
|
||||
return b.width - a.width;
|
||||
} else {
|
||||
return b.height - a.height;
|
||||
}
|
||||
});
|
||||
_pi.resolutions = arr;
|
||||
} catch (e) {
|
||||
debugPrint("Failed to parse resolutions:$e");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the peer info synchronization event based on [evt].
|
||||
handleSyncPeerInfo(Map<String, dynamic> evt, String peerId) async {
|
||||
if (evt['displays'] != null) {
|
||||
@ -458,6 +484,9 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
_pi.displays = newDisplays;
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) {
|
||||
_display = _pi.displays[_pi.currentDisplay];
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@ -1532,6 +1561,17 @@ class Display {
|
||||
}
|
||||
}
|
||||
|
||||
class Resolution {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
Resolution(this.width, this.height);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Resolution($width,$height)';
|
||||
}
|
||||
}
|
||||
|
||||
class Features {
|
||||
bool privacyMode = false;
|
||||
}
|
||||
@ -1545,6 +1585,7 @@ class PeerInfo {
|
||||
int currentDisplay = 0;
|
||||
List<Display> displays = [];
|
||||
Features features = Features();
|
||||
List<Resolution> resolutions = [];
|
||||
}
|
||||
|
||||
const canvasKey = 'canvas';
|
||||
|
@ -90,6 +90,7 @@ message PeerInfo {
|
||||
int32 conn_id = 8;
|
||||
Features features = 9;
|
||||
SupportedEncoding encoding = 10;
|
||||
SupportedResolutions resolutions = 11;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
@ -416,6 +417,13 @@ message Cliprdr {
|
||||
}
|
||||
}
|
||||
|
||||
message Resolution {
|
||||
int32 width = 1;
|
||||
int32 height = 2;
|
||||
}
|
||||
|
||||
message SupportedResolutions { repeated Resolution resolutions = 1; }
|
||||
|
||||
message SwitchDisplay {
|
||||
int32 display = 1;
|
||||
sint32 x = 2;
|
||||
@ -423,6 +431,7 @@ message SwitchDisplay {
|
||||
int32 width = 4;
|
||||
int32 height = 5;
|
||||
bool cursor_embedded = 6;
|
||||
SupportedResolutions resolutions = 7;
|
||||
}
|
||||
|
||||
message PermissionInfo {
|
||||
@ -597,6 +606,7 @@ message Misc {
|
||||
bool portable_service_running = 20;
|
||||
SwitchSidesRequest switch_sides_request = 21;
|
||||
SwitchBack switch_back = 22;
|
||||
Resolution change_resolution = 24;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{x11, common::TraitCapturer};
|
||||
use crate::{common::TraitCapturer, x11};
|
||||
use std::{io, ops, time::Duration};
|
||||
|
||||
pub struct Capturer(x11::Capturer);
|
||||
@ -90,6 +90,6 @@ impl Display {
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
"".to_owned()
|
||||
self.0.name()
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ pub struct Display {
|
||||
default: bool,
|
||||
rect: Rect,
|
||||
root: xcb_window_t,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
@ -25,12 +26,14 @@ impl Display {
|
||||
default: bool,
|
||||
rect: Rect,
|
||||
root: xcb_window_t,
|
||||
name: String,
|
||||
) -> Display {
|
||||
Display {
|
||||
server,
|
||||
default,
|
||||
rect,
|
||||
root,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,4 +55,8 @@ impl Display {
|
||||
pub fn root(&self) -> xcb_window_t {
|
||||
self.root
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,21 @@ extern "C" {
|
||||
) -> xcb_randr_monitor_info_iterator_t;
|
||||
|
||||
pub fn xcb_randr_monitor_info_next(i: *mut xcb_randr_monitor_info_iterator_t);
|
||||
|
||||
pub fn xcb_get_atom_name(
|
||||
c: *mut xcb_connection_t,
|
||||
atom: xcb_atom_t,
|
||||
) -> xcb_get_atom_name_cookie_t;
|
||||
|
||||
pub fn xcb_get_atom_name_reply(
|
||||
c: *mut xcb_connection_t,
|
||||
cookie: xcb_get_atom_name_cookie_t,
|
||||
e: *mut *mut xcb_generic_error_t,
|
||||
) -> *const xcb_get_atom_name_reply_t;
|
||||
|
||||
pub fn xcb_get_atom_name_name(reply: *const xcb_get_atom_name_request_t) -> *const u8;
|
||||
|
||||
pub fn xcb_get_atom_name_name_length(reply: *const xcb_get_atom_name_reply_t) -> i32;
|
||||
}
|
||||
|
||||
pub const XCB_IMAGE_FORMAT_Z_PIXMAP: u8 = 2;
|
||||
@ -78,6 +93,9 @@ pub type xcb_timestamp_t = u32;
|
||||
pub type xcb_colormap_t = u32;
|
||||
pub type xcb_shm_seg_t = u32;
|
||||
pub type xcb_drawable_t = u32;
|
||||
pub type xcb_get_atom_name_cookie_t = u32;
|
||||
pub type xcb_get_atom_name_reply_t = u32;
|
||||
pub type xcb_get_atom_name_request_t = xcb_get_atom_name_reply_t;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct xcb_setup_t {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
||||
@ -64,6 +65,7 @@ impl Iterator for DisplayIter {
|
||||
if inner.rem != 0 {
|
||||
unsafe {
|
||||
let data = &*inner.data;
|
||||
let name = get_atom_name(self.server.raw(), data.name);
|
||||
|
||||
let display = Display::new(
|
||||
self.server.clone(),
|
||||
@ -75,6 +77,7 @@ impl Iterator for DisplayIter {
|
||||
h: data.height,
|
||||
},
|
||||
root,
|
||||
name,
|
||||
);
|
||||
|
||||
xcb_randr_monitor_info_next(inner);
|
||||
@ -91,3 +94,30 @@ impl Iterator for DisplayIter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_atom_name(conn: *mut xcb_connection_t, atom: xcb_atom_t) -> String {
|
||||
let empty = "".to_owned();
|
||||
if atom == 0 {
|
||||
return empty;
|
||||
}
|
||||
unsafe {
|
||||
let mut e: xcb_generic_error_t = std::mem::zeroed();
|
||||
let reply = xcb_get_atom_name_reply(
|
||||
conn,
|
||||
xcb_get_atom_name(conn, atom),
|
||||
&mut ((&mut e) as *mut xcb_generic_error_t) as _,
|
||||
);
|
||||
if reply == std::ptr::null() {
|
||||
return empty;
|
||||
}
|
||||
let length = xcb_get_atom_name_name_length(reply);
|
||||
let name = xcb_get_atom_name_name(reply);
|
||||
let mut v = vec![0u8; length as _];
|
||||
std::ptr::copy_nonoverlapping(name as _, v.as_mut_ptr(), length as _);
|
||||
libc::free(reply as *mut _);
|
||||
if let Ok(s) = CString::new(v) {
|
||||
return s.to_string_lossy().to_string();
|
||||
}
|
||||
empty
|
||||
}
|
||||
}
|
||||
|
@ -480,6 +480,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
features.insert("privacy_mode", 0);
|
||||
}
|
||||
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
|
||||
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
|
||||
*self.peer_info.write().unwrap() = pi.clone();
|
||||
self.push_event(
|
||||
"peer_info",
|
||||
@ -492,6 +493,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
("version", &pi.version),
|
||||
("features", &features),
|
||||
("current_display", &pi.current_display.to_string()),
|
||||
("resolutions", &resolutions),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -529,6 +531,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
|
||||
fn switch_display(&self, display: &SwitchDisplay) {
|
||||
let resolutions = serialize_resolutions(&display.resolutions.resolutions);
|
||||
self.push_event(
|
||||
"switch_display",
|
||||
vec![
|
||||
@ -548,6 +551,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
("resolutions", &resolutions),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -861,6 +865,27 @@ pub fn set_cur_session_id(id: String) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_resolutions(resolutions: &Vec<Resolution>) -> String {
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct ResolutionSerde {
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
let mut v = vec![];
|
||||
resolutions
|
||||
.iter()
|
||||
.map(|r| {
|
||||
v.push(ResolutionSerde {
|
||||
width: r.width,
|
||||
height: r.height,
|
||||
})
|
||||
})
|
||||
.count();
|
||||
serde_json::ser::to_string(&v).unwrap_or("".to_string())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
pub fn session_get_rgba_size(id: *const char) -> usize {
|
||||
|
@ -529,7 +529,13 @@ pub fn session_switch_sides(id: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_size(_id: String, _width: i32, _height: i32) {
|
||||
pub fn session_change_resolution(id: String, width: i32, height: i32) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.change_resolution(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_size(_id: String, _width: i32, _height: i32) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_id) {
|
||||
session.set_size(_width, _height);
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "停止语音通话"),
|
||||
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r,或者在卡片选项里选择强制走中继连接。"),
|
||||
("Reconnect", "重连"),
|
||||
("Codec", "编解码"),
|
||||
("Resolution", "分辨率"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "Sprachanruf beenden"),
|
||||
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."),
|
||||
("Reconnect", "Erneut verbinden"),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "Detener llamada de voz"),
|
||||
("relay_hint_tip", "Puede que no sea posible conectar directamente. Puedes tratar de conectar a través de relay. \nAdicionalmente, si quieres usar relay en el primer intento, puedes añadir el sufijo \"/r\" a la ID o seleccionar la opción \"Conectar siempre a través de relay\" en la tarjeta del par."),
|
||||
("Reconnect", "Reconectar"),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "توقف تماس صوتی"),
|
||||
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
|
||||
("Reconnect", "اتصال مجدد"),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "Interrompi la chiamata vocale"),
|
||||
("relay_hint_tip", "Se non è possibile connettersi direttamente, si può provare a farlo tramite relay.\nInoltre, se si desidera utilizzare il relay al primo tentativo, è possibile aggiungere il suffisso \"/r\" all'ID o selezionare l'opzione \"Collegati sempre tramite relay\" nella scheda peer."),
|
||||
("Reconnect", "Riconnetti"),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "Stop spraakoproep"),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "Завершить голосовой вызов"),
|
||||
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
|
||||
("Reconnect", "Переподключить"),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", "停止語音聊天"),
|
||||
("relay_hint_tip", "可能無法直連,可以嘗試中繼連接。 \n另外,如果想直接使用中繼連接,可以在ID後面添加/r,或者在卡片選項裡選擇強制走中繼連接。"),
|
||||
("Reconnect", "重連"),
|
||||
("Codec", "編解碼"),
|
||||
("Resolution", "分辨率"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::{CursorData, ResultType};
|
||||
use hbb_common::libc::{c_char, c_int, c_long, c_void};
|
||||
pub use hbb_common::platform::linux::*;
|
||||
use hbb_common::{allow_err, bail, log};
|
||||
use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
path::PathBuf,
|
||||
@ -10,6 +10,7 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use xrandr_parser::Parser;
|
||||
|
||||
type Xdo = *const c_void;
|
||||
|
||||
@ -641,3 +642,55 @@ pub fn get_double_click_time() -> u32 {
|
||||
double_click_time
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
let mut v = vec![];
|
||||
let mut parser = Parser::new();
|
||||
if parser.parse().is_ok() {
|
||||
if let Ok(connector) = parser.get_connector(name) {
|
||||
if let Ok(resolutions) = &connector.available_resolutions() {
|
||||
for r in resolutions {
|
||||
if let Ok(width) = r.horizontal.parse::<i32>() {
|
||||
if let Ok(height) = r.vertical.parse::<i32>() {
|
||||
let resolution = Resolution {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
};
|
||||
if !v.contains(&resolution) {
|
||||
v.push(resolution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
||||
let mut parser = Parser::new();
|
||||
parser.parse().map_err(|e| anyhow!(e))?;
|
||||
let connector = parser.get_connector(name).map_err(|e| anyhow!(e))?;
|
||||
let r = connector.current_resolution();
|
||||
let width = r.horizontal.parse::<i32>()?;
|
||||
let height = r.vertical.parse::<i32>()?;
|
||||
Ok(Resolution {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
|
||||
std::process::Command::new("xrandr")
|
||||
.args(vec![
|
||||
"--output",
|
||||
name,
|
||||
"--mode",
|
||||
&format!("{}x{}", width, height),
|
||||
])
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -40,3 +40,114 @@ extern "C" float BackingScaleFactor() {
|
||||
if (s) return [s backingScaleFactor];
|
||||
return 1;
|
||||
}
|
||||
|
||||
// https://github.com/jhford/screenresolution/blob/master/cg_utils.c
|
||||
// https://github.com/jdoupe/screenres/blob/master/setgetscreen.m
|
||||
|
||||
extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) {
|
||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
||||
if (allModes == NULL) {
|
||||
return false;
|
||||
}
|
||||
*numModes = CFArrayGetCount(allModes);
|
||||
CFRelease(allModes);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, uint32_t max, uint32_t *numModes) {
|
||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
||||
if (allModes == NULL) {
|
||||
return false;
|
||||
}
|
||||
*numModes = CFArrayGetCount(allModes);
|
||||
for (int i = 0; i < *numModes && i < max; i++) {
|
||||
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
||||
widths[i] = (uint32_t)CGDisplayModeGetWidth(mode);
|
||||
heights[i] = (uint32_t)CGDisplayModeGetHeight(mode);
|
||||
}
|
||||
CFRelease(allModes);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t *height) {
|
||||
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
|
||||
if (mode == NULL) {
|
||||
return false;
|
||||
}
|
||||
*width = (uint32_t)CGDisplayModeGetWidth(mode);
|
||||
*height = (uint32_t)CGDisplayModeGetHeight(mode);
|
||||
CGDisplayModeRelease(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t bitDepth(CGDisplayModeRef mode) {
|
||||
size_t depth = 0;
|
||||
CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(mode);
|
||||
// my numerical representation for kIO16BitFloatPixels and kIO32bitFloatPixels
|
||||
// are made up and possibly non-sensical
|
||||
if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO32BitFloatPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 96;
|
||||
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO64BitDirectPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 64;
|
||||
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO16BitFloatPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 48;
|
||||
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 32;
|
||||
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 30;
|
||||
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 16;
|
||||
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive)) {
|
||||
depth = 8;
|
||||
}
|
||||
CFRelease(pixelEncoding);
|
||||
return depth;
|
||||
}
|
||||
|
||||
bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
|
||||
CGError rc;
|
||||
CGDisplayConfigRef config;
|
||||
rc = CGBeginDisplayConfiguration(&config);
|
||||
if (rc != kCGErrorSuccess) {
|
||||
return false;
|
||||
}
|
||||
rc = CGConfigureDisplayWithDisplayMode(config, display, mode, NULL);
|
||||
if (rc != kCGErrorSuccess) {
|
||||
return false;
|
||||
}
|
||||
rc = CGCompleteDisplayConfiguration(config, kCGConfigureForSession);
|
||||
if (rc != kCGErrorSuccess) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height)
|
||||
{
|
||||
bool ret = false;
|
||||
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
|
||||
if (currentMode == NULL) {
|
||||
return ret;
|
||||
}
|
||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
||||
if (allModes == NULL) {
|
||||
CGDisplayModeRelease(currentMode);
|
||||
return ret;
|
||||
}
|
||||
int numModes = CFArrayGetCount(allModes);
|
||||
CGDisplayModeRef bestMode = NULL;
|
||||
for (int i = 0; i < numModes; i++) {
|
||||
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
||||
if (width == CGDisplayModeGetWidth(mode) &&
|
||||
height == CGDisplayModeGetHeight(mode) &&
|
||||
bitDepth(currentMode) == bitDepth(mode) &&
|
||||
CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode)) {
|
||||
ret = setDisplayToMode(display, mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CGDisplayModeRelease(currentMode);
|
||||
CFRelease(allModes);
|
||||
return ret;
|
||||
}
|
@ -17,7 +17,7 @@ use core_graphics::{
|
||||
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
|
||||
window::{kCGWindowName, kCGWindowOwnerPID},
|
||||
};
|
||||
use hbb_common::{allow_err, bail, log};
|
||||
use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use scrap::{libc::c_void, quartz::ffi::*};
|
||||
@ -34,6 +34,16 @@ extern "C" {
|
||||
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
||||
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
||||
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
|
||||
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
|
||||
fn MacGetModes(
|
||||
display: u32,
|
||||
widths: *mut u32,
|
||||
heights: *mut u32,
|
||||
max: u32,
|
||||
numModes: *mut u32,
|
||||
) -> BOOL;
|
||||
fn MacGetMode(display: u32, width: *mut u32, height: *mut u32) -> BOOL;
|
||||
fn MacSetMode(display: u32, width: u32, height: u32) -> BOOL;
|
||||
}
|
||||
|
||||
pub fn is_process_trusted(prompt: bool) -> bool {
|
||||
@ -594,3 +604,64 @@ pub fn handle_application_should_open_untitled_file() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
let mut v = vec![];
|
||||
if let Ok(display) = name.parse::<u32>() {
|
||||
let mut num = 0;
|
||||
unsafe {
|
||||
if YES == MacGetModeNum(display, &mut num) {
|
||||
let (mut widths, mut heights) = (vec![0; num as _], vec![0; num as _]);
|
||||
let mut realNum = 0;
|
||||
if YES
|
||||
== MacGetModes(
|
||||
display,
|
||||
widths.as_mut_ptr(),
|
||||
heights.as_mut_ptr(),
|
||||
num,
|
||||
&mut realNum,
|
||||
)
|
||||
{
|
||||
if realNum <= num {
|
||||
for i in 0..realNum {
|
||||
let resolution = Resolution {
|
||||
width: widths[i as usize] as _,
|
||||
height: heights[i as usize] as _,
|
||||
..Default::default()
|
||||
};
|
||||
if !v.contains(&resolution) {
|
||||
v.push(resolution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
||||
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
|
||||
unsafe {
|
||||
let (mut width, mut height) = (0, 0);
|
||||
if NO == MacGetMode(display, &mut width, &mut height) {
|
||||
bail!("MacGetMode failed");
|
||||
}
|
||||
Ok(Resolution {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
|
||||
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
|
||||
unsafe {
|
||||
if NO == MacSetMode(display, width as _, height as _) {
|
||||
bail!("MacSetMode failed");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -74,5 +74,13 @@ mod tests {
|
||||
assert!(!get_cursor_pos().is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[test]
|
||||
fn test_resolution() {
|
||||
let name = r"\\.\DISPLAY1";
|
||||
println!("current:{:?}", current_resolution(name));
|
||||
println!("change:{:?}", change_resolution(name, 2880, 1800));
|
||||
println!("resolutions:{:?}", resolutions(name));
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ use crate::license::*;
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
config::{self, Config},
|
||||
log, sleep, timeout, tokio,
|
||||
log,
|
||||
message_proto::Resolution,
|
||||
sleep, timeout, tokio,
|
||||
};
|
||||
use std::io::prelude::*;
|
||||
use std::{
|
||||
@ -1784,3 +1786,89 @@ pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
unsafe {
|
||||
let mut dm: DEVMODEW = std::mem::zeroed();
|
||||
let wname = wide_string(name);
|
||||
let len = if wname.len() <= dm.dmDeviceName.len() {
|
||||
wname.len()
|
||||
} else {
|
||||
dm.dmDeviceName.len()
|
||||
};
|
||||
std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len);
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
let mut v = vec![];
|
||||
let mut num = 0;
|
||||
loop {
|
||||
if EnumDisplaySettingsW(NULL as _, num, &mut dm) == 0 {
|
||||
break;
|
||||
}
|
||||
let r = Resolution {
|
||||
width: dm.dmPelsWidth as _,
|
||||
height: dm.dmPelsHeight as _,
|
||||
..Default::default()
|
||||
};
|
||||
if !v.contains(&r) {
|
||||
v.push(r);
|
||||
}
|
||||
num += 1;
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
||||
unsafe {
|
||||
let mut dm: DEVMODEW = std::mem::zeroed();
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
let wname = wide_string(name);
|
||||
if EnumDisplaySettingsW(wname.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 {
|
||||
bail!(
|
||||
"failed to get currrent resolution, errno={}",
|
||||
GetLastError()
|
||||
);
|
||||
}
|
||||
let r = Resolution {
|
||||
width: dm.dmPelsWidth as _,
|
||||
height: dm.dmPelsHeight as _,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
|
||||
unsafe {
|
||||
let mut dm: DEVMODEW = std::mem::zeroed();
|
||||
if FALSE == EnumDisplaySettingsW(NULL as _, ENUM_CURRENT_SETTINGS, &mut dm) {
|
||||
bail!("EnumDisplaySettingsW failed, errno={}", GetLastError());
|
||||
}
|
||||
let wname = wide_string(name);
|
||||
let len = if wname.len() <= dm.dmDeviceName.len() {
|
||||
wname.len()
|
||||
} else {
|
||||
dm.dmDeviceName.len()
|
||||
};
|
||||
std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len);
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
dm.dmPelsWidth = width as _;
|
||||
dm.dmPelsHeight = height as _;
|
||||
dm.dmFields = DM_PELSHEIGHT | DM_PELSWIDTH;
|
||||
let res = ChangeDisplaySettingsExW(
|
||||
wname.as_ptr(),
|
||||
&mut dm,
|
||||
NULL as _,
|
||||
CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_RESET,
|
||||
NULL,
|
||||
);
|
||||
if res != DISP_CHANGE_SUCCESSFUL {
|
||||
bail!(
|
||||
"ChangeDisplaySettingsExW failed, res={}, errno={}",
|
||||
res,
|
||||
GetLastError()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ pub struct Connection {
|
||||
#[cfg(windows)]
|
||||
portable: PortableState,
|
||||
from_switch: bool,
|
||||
origin_resolution: HashMap<String, Resolution>,
|
||||
voice_call_request_timestamp: Option<NonZeroI64>,
|
||||
audio_input_device_before_voice_call: Option<String>,
|
||||
}
|
||||
@ -228,6 +229,7 @@ impl Connection {
|
||||
#[cfg(windows)]
|
||||
portable: Default::default(),
|
||||
from_switch: false,
|
||||
origin_resolution: Default::default(),
|
||||
audio_sender: None,
|
||||
voice_call_request_timestamp: None,
|
||||
audio_input_device_before_voice_call: None,
|
||||
@ -533,6 +535,8 @@ impl Connection {
|
||||
conn.post_conn_audit(json!({
|
||||
"action": "close",
|
||||
}));
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
conn.reset_resolution();
|
||||
ALIVE_CONNS.lock().unwrap().retain(|&c| c != id);
|
||||
if let Some(s) = conn.server.upgrade() {
|
||||
s.write().unwrap().remove_connection(&conn.inner);
|
||||
@ -881,6 +885,16 @@ impl Connection {
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
pi.resolutions = Some(SupportedResolutions {
|
||||
resolutions: video_service::get_current_display_name()
|
||||
.map(|name| crate::platform::resolutions(&name))
|
||||
.unwrap_or(vec![]),
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut sub_service = false;
|
||||
if self.file_transfer.is_some() {
|
||||
@ -1597,6 +1611,26 @@ impl Connection {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::ChangeResolution(r)) => {
|
||||
if self.keyboard {
|
||||
if let Ok(name) = video_service::get_current_display_name() {
|
||||
if let Ok(current) = crate::platform::current_resolution(&name) {
|
||||
if let Err(e) = crate::platform::change_resolution(
|
||||
&name,
|
||||
r.width as _,
|
||||
r.height as _,
|
||||
) {
|
||||
log::error!("change resolution failed:{:?}", e);
|
||||
} else {
|
||||
if !self.origin_resolution.contains_key(&name) {
|
||||
self.origin_resolution.insert(name, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
@ -1937,6 +1971,20 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn reset_resolution(&self) {
|
||||
self.origin_resolution
|
||||
.iter()
|
||||
.map(|(name, r)| {
|
||||
if let Err(e) =
|
||||
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
||||
{
|
||||
log::error!("change resolution failed:{:?}", e);
|
||||
}
|
||||
})
|
||||
.count();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
|
@ -356,7 +356,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
||||
let (ndisplay, current, display) = get_current_display()?;
|
||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||
log::debug!(
|
||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
|
||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
|
||||
ndisplay,
|
||||
current,
|
||||
&origin,
|
||||
@ -364,6 +364,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
||||
height,
|
||||
num_cpus::get_physical(),
|
||||
num_cpus::get(),
|
||||
display.name(),
|
||||
);
|
||||
|
||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||
@ -501,6 +502,14 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
cursor_embedded: capture_cursor_embedded(),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
resolutions: Some(SupportedResolutions {
|
||||
resolutions: get_current_display_name()
|
||||
.map(|name| crate::platform::resolutions(&name))
|
||||
.unwrap_or(vec![]),
|
||||
..SupportedResolutions::default()
|
||||
})
|
||||
.into(),
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
@ -992,6 +1001,10 @@ pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
get_current_display_2(try_get_displays()?)
|
||||
}
|
||||
|
||||
pub fn get_current_display_name() -> ResultType<String> {
|
||||
Ok(get_current_display_2(try_get_displays()?)?.2.name())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn start_uac_elevation_check() {
|
||||
static START: Once = Once::new();
|
||||
|
@ -713,6 +713,18 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_resolution(&self, width: i32, height: i32) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_change_resolution(Resolution {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
pub fn request_voice_call(&self) {
|
||||
self.send(Data::NewVoiceCall);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user