commit
7a925b214d
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -1159,14 +1159,38 @@ dependencies = [
|
|||||||
"zvariant",
|
"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]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.13.4",
|
||||||
"darling_macro",
|
"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]]
|
[[package]]
|
||||||
@ -1183,13 +1207,24 @@ dependencies = [
|
|||||||
"syn 1.0.105",
|
"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]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.13.4",
|
||||||
"quote 1.0.21",
|
"quote 1.0.21",
|
||||||
"syn 1.0.105",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
@ -1389,6 +1424,18 @@ dependencies = [
|
|||||||
"syn 1.0.105",
|
"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]]
|
[[package]]
|
||||||
name = "detect-desktop-environment"
|
name = "detect-desktop-environment"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -3585,7 +3632,7 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
|
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.13.4",
|
||||||
"proc-macro-crate 1.2.1",
|
"proc-macro-crate 1.2.1",
|
||||||
"proc-macro2 1.0.47",
|
"proc-macro2 1.0.47",
|
||||||
"quote 1.0.21",
|
"quote 1.0.21",
|
||||||
@ -4944,6 +4991,7 @@ dependencies = [
|
|||||||
"winreg 0.10.1",
|
"winreg 0.10.1",
|
||||||
"winres",
|
"winres",
|
||||||
"wol-rs",
|
"wol-rs",
|
||||||
|
"xrandr-parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5469,6 +5517,12 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -6965,6 +7019,16 @@ version = "0.8.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
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]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -121,6 +121,7 @@ mouce = { git="https://github.com/fufesou/mouce.git" }
|
|||||||
evdev = { git="https://github.com/fufesou/evdev" }
|
evdev = { git="https://github.com/fufesou/evdev" }
|
||||||
dbus = "0.9"
|
dbus = "0.9"
|
||||||
dbus-crossroads = "0.5"
|
dbus-crossroads = "0.5"
|
||||||
|
xrandr-parser = "0.3.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_logger = "0.11"
|
android_logger = "0.11"
|
||||||
|
@ -1814,3 +1814,19 @@ class DraggableNeverScrollableScrollPhysics extends ScrollPhysics {
|
|||||||
@override
|
@override
|
||||||
bool get allowImplicitScrolling => false;
|
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);
|
bind.mainSetOption(key: 'audio-input', value: device);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
devices.insert(0, 'System Sound');
|
devices.insert(0, 'System Sound');
|
||||||
@ -346,7 +346,7 @@ class _GeneralState extends State<_General> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget record(BuildContext context) {
|
Widget record(BuildContext context) {
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
String customDirectory =
|
String customDirectory =
|
||||||
await bind.mainGetOption(key: 'video-save-directory');
|
await bind.mainGetOption(key: 'video-save-directory');
|
||||||
String defaultDirectory = await bind.mainDefaultVideoSaveDirectory();
|
String defaultDirectory = await bind.mainDefaultVideoSaveDirectory();
|
||||||
@ -399,7 +399,7 @@ class _GeneralState extends State<_General> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget language() {
|
Widget language() {
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
String langs = await bind.mainGetLangs();
|
String langs = await bind.mainGetLangs();
|
||||||
String lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
|
String lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
|
||||||
return {'langs': langs, 'lang': lang};
|
return {'langs': langs, 'lang': lang};
|
||||||
@ -487,7 +487,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
|
|
||||||
Widget _permissions(context, bool stopService) {
|
Widget _permissions(context, bool stopService) {
|
||||||
bool enabled = !locked;
|
bool enabled = !locked;
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
return await bind.mainGetOption(key: 'access-mode');
|
return await bind.mainGetOption(key: 'access-mode');
|
||||||
}(), hasData: (data) {
|
}(), hasData: (data) {
|
||||||
String accessMode = data! as String;
|
String accessMode = data! as String;
|
||||||
@ -744,7 +744,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
return [
|
return [
|
||||||
_OptionCheckBox(context, 'Enable Direct IP Access', 'direct-server',
|
_OptionCheckBox(context, 'Enable Direct IP Access', 'direct-server',
|
||||||
update: update, enabled: !locked),
|
update: update, enabled: !locked),
|
||||||
_futureBuilder(
|
futureBuilder(
|
||||||
future: () async {
|
future: () async {
|
||||||
String enabled = await bind.mainGetOption(key: 'direct-server');
|
String enabled = await bind.mainGetOption(key: 'direct-server');
|
||||||
String port = await bind.mainGetOption(key: 'direct-access-port');
|
String port = await bind.mainGetOption(key: 'direct-access-port');
|
||||||
@ -805,7 +805,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
|
|
||||||
Widget whitelist() {
|
Widget whitelist() {
|
||||||
bool enabled = !locked;
|
bool enabled = !locked;
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
return await bind.mainGetOption(key: 'whitelist');
|
return await bind.mainGetOption(key: 'whitelist');
|
||||||
}(), hasData: (data) {
|
}(), hasData: (data) {
|
||||||
RxBool hasWhitelist = (data as String).isNotEmpty.obs;
|
RxBool hasWhitelist = (data as String).isNotEmpty.obs;
|
||||||
@ -931,7 +931,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server(bool enabled) {
|
server(bool enabled) {
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
return await bind.mainGetOptions();
|
return await bind.mainGetOptions();
|
||||||
}(), hasData: (data) {
|
}(), hasData: (data) {
|
||||||
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
// 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> {
|
class _AboutState extends State<_About> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
final license = await bind.mainGetLicense();
|
final license = await bind.mainGetLicense();
|
||||||
final version = await bind.mainGetVersion();
|
final version = await bind.mainGetVersion();
|
||||||
final buildDate = await bind.mainGetBuildDate();
|
final buildDate = await bind.mainGetBuildDate();
|
||||||
@ -1500,7 +1500,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
|
|||||||
bool enabled = true,
|
bool enabled = true,
|
||||||
Icon? checkedIcon,
|
Icon? checkedIcon,
|
||||||
bool? fakeValue}) {
|
bool? fakeValue}) {
|
||||||
return _futureBuilder(
|
return futureBuilder(
|
||||||
future: bind.mainGetOption(key: key),
|
future: bind.mainGetOption(key: key),
|
||||||
hasData: (data) {
|
hasData: (data) {
|
||||||
bool value = option2bool(key, data.toString());
|
bool value = option2bool(key, data.toString());
|
||||||
@ -1633,22 +1633,6 @@ Widget _SubLabeledWidget(BuildContext context, String label, Widget child,
|
|||||||
).marginOnly(left: _kContentHSubMargin);
|
).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(
|
Widget _lock(
|
||||||
bool locked,
|
bool locked,
|
||||||
String label,
|
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?.canvasModel.updateViewStyle();
|
||||||
}
|
}
|
||||||
parent.target?.recordingModel.onSwitchDisplay();
|
parent.target?.recordingModel.onSwitchDisplay();
|
||||||
|
handleResolutions(peerId, evt["resolutions"]);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,10 +438,35 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
Map<String, dynamic> features = json.decode(evt['features']);
|
Map<String, dynamic> features = json.decode(evt['features']);
|
||||||
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
||||||
|
handleResolutions(peerId, evt["resolutions"]);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
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].
|
/// Handle the peer info synchronization event based on [evt].
|
||||||
handleSyncPeerInfo(Map<String, dynamic> evt, String peerId) async {
|
handleSyncPeerInfo(Map<String, dynamic> evt, String peerId) async {
|
||||||
if (evt['displays'] != null) {
|
if (evt['displays'] != null) {
|
||||||
@ -458,6 +484,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
_pi.displays = newDisplays;
|
_pi.displays = newDisplays;
|
||||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||||
|
if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) {
|
||||||
|
_display = _pi.displays[_pi.currentDisplay];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
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 {
|
class Features {
|
||||||
bool privacyMode = false;
|
bool privacyMode = false;
|
||||||
}
|
}
|
||||||
@ -1545,6 +1585,7 @@ class PeerInfo {
|
|||||||
int currentDisplay = 0;
|
int currentDisplay = 0;
|
||||||
List<Display> displays = [];
|
List<Display> displays = [];
|
||||||
Features features = Features();
|
Features features = Features();
|
||||||
|
List<Resolution> resolutions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasKey = 'canvas';
|
const canvasKey = 'canvas';
|
||||||
|
@ -90,6 +90,7 @@ message PeerInfo {
|
|||||||
int32 conn_id = 8;
|
int32 conn_id = 8;
|
||||||
Features features = 9;
|
Features features = 9;
|
||||||
SupportedEncoding encoding = 10;
|
SupportedEncoding encoding = 10;
|
||||||
|
SupportedResolutions resolutions = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
@ -416,6 +417,13 @@ message Cliprdr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Resolution {
|
||||||
|
int32 width = 1;
|
||||||
|
int32 height = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SupportedResolutions { repeated Resolution resolutions = 1; }
|
||||||
|
|
||||||
message SwitchDisplay {
|
message SwitchDisplay {
|
||||||
int32 display = 1;
|
int32 display = 1;
|
||||||
sint32 x = 2;
|
sint32 x = 2;
|
||||||
@ -423,6 +431,7 @@ message SwitchDisplay {
|
|||||||
int32 width = 4;
|
int32 width = 4;
|
||||||
int32 height = 5;
|
int32 height = 5;
|
||||||
bool cursor_embedded = 6;
|
bool cursor_embedded = 6;
|
||||||
|
SupportedResolutions resolutions = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PermissionInfo {
|
message PermissionInfo {
|
||||||
@ -597,6 +606,7 @@ message Misc {
|
|||||||
bool portable_service_running = 20;
|
bool portable_service_running = 20;
|
||||||
SwitchSidesRequest switch_sides_request = 21;
|
SwitchSidesRequest switch_sides_request = 21;
|
||||||
SwitchBack switch_back = 22;
|
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};
|
use std::{io, ops, time::Duration};
|
||||||
|
|
||||||
pub struct Capturer(x11::Capturer);
|
pub struct Capturer(x11::Capturer);
|
||||||
@ -90,6 +90,6 @@ impl Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
"".to_owned()
|
self.0.name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ pub struct Display {
|
|||||||
default: bool,
|
default: bool,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
root: xcb_window_t,
|
root: xcb_window_t,
|
||||||
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
@ -25,12 +26,14 @@ impl Display {
|
|||||||
default: bool,
|
default: bool,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
root: xcb_window_t,
|
root: xcb_window_t,
|
||||||
|
name: String,
|
||||||
) -> Display {
|
) -> Display {
|
||||||
Display {
|
Display {
|
||||||
server,
|
server,
|
||||||
default,
|
default,
|
||||||
rect,
|
rect,
|
||||||
root,
|
root,
|
||||||
|
name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,4 +55,8 @@ impl Display {
|
|||||||
pub fn root(&self) -> xcb_window_t {
|
pub fn root(&self) -> xcb_window_t {
|
||||||
self.root
|
self.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,21 @@ extern "C" {
|
|||||||
) -> xcb_randr_monitor_info_iterator_t;
|
) -> xcb_randr_monitor_info_iterator_t;
|
||||||
|
|
||||||
pub fn xcb_randr_monitor_info_next(i: *mut 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;
|
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_colormap_t = u32;
|
||||||
pub type xcb_shm_seg_t = u32;
|
pub type xcb_shm_seg_t = u32;
|
||||||
pub type xcb_drawable_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)]
|
#[repr(C)]
|
||||||
pub struct xcb_setup_t {
|
pub struct xcb_setup_t {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::ffi::CString;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ impl Iterator for DisplayIter {
|
|||||||
if inner.rem != 0 {
|
if inner.rem != 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = &*inner.data;
|
let data = &*inner.data;
|
||||||
|
let name = get_atom_name(self.server.raw(), data.name);
|
||||||
|
|
||||||
let display = Display::new(
|
let display = Display::new(
|
||||||
self.server.clone(),
|
self.server.clone(),
|
||||||
@ -75,6 +77,7 @@ impl Iterator for DisplayIter {
|
|||||||
h: data.height,
|
h: data.height,
|
||||||
},
|
},
|
||||||
root,
|
root,
|
||||||
|
name,
|
||||||
);
|
);
|
||||||
|
|
||||||
xcb_randr_monitor_info_next(inner);
|
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);
|
features.insert("privacy_mode", 0);
|
||||||
}
|
}
|
||||||
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
|
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.peer_info.write().unwrap() = pi.clone();
|
||||||
self.push_event(
|
self.push_event(
|
||||||
"peer_info",
|
"peer_info",
|
||||||
@ -492,6 +493,7 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
("version", &pi.version),
|
("version", &pi.version),
|
||||||
("features", &features),
|
("features", &features),
|
||||||
("current_display", &pi.current_display.to_string()),
|
("current_display", &pi.current_display.to_string()),
|
||||||
|
("resolutions", &resolutions),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -529,6 +531,7 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn switch_display(&self, display: &SwitchDisplay) {
|
fn switch_display(&self, display: &SwitchDisplay) {
|
||||||
|
let resolutions = serialize_resolutions(&display.resolutions.resolutions);
|
||||||
self.push_event(
|
self.push_event(
|
||||||
"switch_display",
|
"switch_display",
|
||||||
vec![
|
vec![
|
||||||
@ -548,6 +551,7 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
}
|
}
|
||||||
.to_string(),
|
.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]
|
#[no_mangle]
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
pub fn session_get_rgba_size(id: *const char) -> usize {
|
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")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_id) {
|
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_id) {
|
||||||
session.set_size(_width, _height);
|
session.set_size(_width, _height);
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "停止语音通话"),
|
("Stop voice call", "停止语音通话"),
|
||||||
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r,或者在卡片选项里选择强制走中继连接。"),
|
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r,或者在卡片选项里选择强制走中继连接。"),
|
||||||
("Reconnect", "重连"),
|
("Reconnect", "重连"),
|
||||||
|
("Codec", "编解码"),
|
||||||
|
("Resolution", "分辨率"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Sprachanruf beenden"),
|
("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."),
|
("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"),
|
("Reconnect", "Erneut verbinden"),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].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"),
|
("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."),
|
("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"),
|
("Reconnect", "Reconectar"),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "توقف تماس صوتی"),
|
("Stop voice call", "توقف تماس صوتی"),
|
||||||
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
|
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
|
||||||
("Reconnect", "اتصال مجدد"),
|
("Reconnect", "اتصال مجدد"),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].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"),
|
("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."),
|
("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"),
|
("Reconnect", "Riconnetti"),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Stop spraakoproep"),
|
("Stop voice call", "Stop spraakoproep"),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Завершить голосовой вызов"),
|
("Stop voice call", "Завершить голосовой вызов"),
|
||||||
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
|
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
|
||||||
("Reconnect", "Переподключить"),
|
("Reconnect", "Переподключить"),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "停止語音聊天"),
|
("Stop voice call", "停止語音聊天"),
|
||||||
("relay_hint_tip", "可能無法直連,可以嘗試中繼連接。 \n另外,如果想直接使用中繼連接,可以在ID後面添加/r,或者在卡片選項裡選擇強制走中繼連接。"),
|
("relay_hint_tip", "可能無法直連,可以嘗試中繼連接。 \n另外,如果想直接使用中繼連接,可以在ID後面添加/r,或者在卡片選項裡選擇強制走中繼連接。"),
|
||||||
("Reconnect", "重連"),
|
("Reconnect", "重連"),
|
||||||
|
("Codec", "編解碼"),
|
||||||
|
("Resolution", "分辨率"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -454,5 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
|
("Codec", ""),
|
||||||
|
("Resolution", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{CursorData, ResultType};
|
use super::{CursorData, ResultType};
|
||||||
use hbb_common::libc::{c_char, c_int, c_long, c_void};
|
use hbb_common::libc::{c_char, c_int, c_long, c_void};
|
||||||
pub use hbb_common::platform::linux::*;
|
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::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@ -10,6 +10,7 @@ use std::{
|
|||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use xrandr_parser::Parser;
|
||||||
|
|
||||||
type Xdo = *const c_void;
|
type Xdo = *const c_void;
|
||||||
|
|
||||||
@ -641,3 +642,55 @@ pub fn get_double_click_time() -> u32 {
|
|||||||
double_click_time
|
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];
|
if (s) return [s backingScaleFactor];
|
||||||
return 1;
|
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},
|
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
|
||||||
window::{kCGWindowName, kCGWindowOwnerPID},
|
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 include_dir::{include_dir, Dir};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use scrap::{libc::c_void, quartz::ffi::*};
|
use scrap::{libc::c_void, quartz::ffi::*};
|
||||||
@ -34,6 +34,16 @@ extern "C" {
|
|||||||
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
||||||
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
||||||
fn InputMonitoringAuthStatus(_: BOOL) -> 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 {
|
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());
|
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::{
|
use hbb_common::{
|
||||||
allow_err, bail,
|
allow_err, bail,
|
||||||
config::{self, Config},
|
config::{self, Config},
|
||||||
log, sleep, timeout, tokio,
|
log,
|
||||||
|
message_proto::Resolution,
|
||||||
|
sleep, timeout, tokio,
|
||||||
};
|
};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -1784,3 +1786,89 @@ pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
|
|||||||
.spawn()?;
|
.spawn()?;
|
||||||
Ok(())
|
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)]
|
#[cfg(windows)]
|
||||||
portable: PortableState,
|
portable: PortableState,
|
||||||
from_switch: bool,
|
from_switch: bool,
|
||||||
|
origin_resolution: HashMap<String, Resolution>,
|
||||||
voice_call_request_timestamp: Option<NonZeroI64>,
|
voice_call_request_timestamp: Option<NonZeroI64>,
|
||||||
audio_input_device_before_voice_call: Option<String>,
|
audio_input_device_before_voice_call: Option<String>,
|
||||||
}
|
}
|
||||||
@ -228,6 +229,7 @@ impl Connection {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
portable: Default::default(),
|
portable: Default::default(),
|
||||||
from_switch: false,
|
from_switch: false,
|
||||||
|
origin_resolution: Default::default(),
|
||||||
audio_sender: None,
|
audio_sender: None,
|
||||||
voice_call_request_timestamp: None,
|
voice_call_request_timestamp: None,
|
||||||
audio_input_device_before_voice_call: None,
|
audio_input_device_before_voice_call: None,
|
||||||
@ -533,6 +535,8 @@ impl Connection {
|
|||||||
conn.post_conn_audit(json!({
|
conn.post_conn_audit(json!({
|
||||||
"action": "close",
|
"action": "close",
|
||||||
}));
|
}));
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
conn.reset_resolution();
|
||||||
ALIVE_CONNS.lock().unwrap().retain(|&c| c != id);
|
ALIVE_CONNS.lock().unwrap().retain(|&c| c != id);
|
||||||
if let Some(s) = conn.server.upgrade() {
|
if let Some(s) = conn.server.upgrade() {
|
||||||
s.write().unwrap().remove_connection(&conn.inner);
|
s.write().unwrap().remove_connection(&conn.inner);
|
||||||
@ -881,6 +885,16 @@ impl Connection {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into();
|
.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;
|
let mut sub_service = false;
|
||||||
if self.file_transfer.is_some() {
|
if self.file_transfer.is_some() {
|
||||||
@ -1597,6 +1611,26 @@ impl Connection {
|
|||||||
return false;
|
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)) => {
|
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) {
|
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 (ndisplay, current, display) = get_current_display()?;
|
||||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
|
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
|
||||||
ndisplay,
|
ndisplay,
|
||||||
current,
|
current,
|
||||||
&origin,
|
&origin,
|
||||||
@ -364,6 +364,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
|||||||
height,
|
height,
|
||||||
num_cpus::get_physical(),
|
num_cpus::get_physical(),
|
||||||
num_cpus::get(),
|
num_cpus::get(),
|
||||||
|
display.name(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||||
@ -501,6 +502,14 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
width: c.width as _,
|
width: c.width as _,
|
||||||
height: c.height as _,
|
height: c.height as _,
|
||||||
cursor_embedded: capture_cursor_embedded(),
|
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()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let mut msg_out = Message::new();
|
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()?)
|
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)]
|
#[cfg(windows)]
|
||||||
fn start_uac_elevation_check() {
|
fn start_uac_elevation_check() {
|
||||||
static START: Once = Once::new();
|
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) {
|
pub fn request_voice_call(&self) {
|
||||||
self.send(Data::NewVoiceCall);
|
self.send(Data::NewVoiceCall);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user