mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-19 23:29:32 +08:00
Compare commits
1430 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
914da2b86f | ||
|
|
582db9d542 | ||
|
|
9a1fd1aa4f | ||
|
|
58ddac63d2 | ||
|
|
a8eff641b2 | ||
|
|
d723c10a3b | ||
|
|
bf5abdb520 | ||
|
|
0f44de7dc3 | ||
|
|
4f1a4dc6a5 | ||
|
|
8c108065eb | ||
|
|
5fdcc748e1 | ||
|
|
bf03156dd9 | ||
|
|
964c2ed2b5 | ||
|
|
e942c80afb | ||
|
|
d7dcb5feab | ||
|
|
39d41486d6 | ||
|
|
b85526ce54 | ||
|
|
2a0c081380 | ||
|
|
1db4236f23 | ||
|
|
6749595afe | ||
|
|
e65c43a292 | ||
|
|
00b13bf918 | ||
|
|
f7f3bc8bee | ||
|
|
51c603a3a6 | ||
|
|
5fb026b8d5 | ||
|
|
f7530b16b8 | ||
|
|
db47209362 | ||
|
|
f375cbd871 | ||
|
|
0474c8fb03 | ||
|
|
c00d23846a | ||
|
|
a3d5ea8fb8 | ||
|
|
7525a6ed6a | ||
|
|
dd93416cf7 | ||
|
|
73d429d064 | ||
|
|
4bf3764b5d | ||
|
|
236687ae53 | ||
|
|
0d708f64b9 | ||
|
|
222c9de19f | ||
|
|
72fc34cd40 | ||
|
|
3f7244f23f | ||
|
|
dc24868800 | ||
|
|
1f557888f5 | ||
|
|
16db977fd8 | ||
|
|
a19d4d6686 | ||
|
|
633076ddd4 | ||
|
|
5581248a1e | ||
|
|
a4393b8f90 | ||
|
|
406d8469d8 | ||
|
|
0cf4711515 | ||
|
|
a2ba50c4ff | ||
|
|
c97cc15c0e | ||
|
|
750f1a1884 | ||
|
|
2cf83b41cc | ||
|
|
1f16b5236b | ||
|
|
dd4b5349cb | ||
|
|
46b4e21e8c | ||
|
|
3899e4e12e | ||
|
|
f016281e30 | ||
|
|
1d755c705b | ||
|
|
a0d79dd26d | ||
|
|
e6734af64e | ||
|
|
0d8e3dc24f | ||
|
|
0e44aa1ada | ||
|
|
9f40a0b490 | ||
|
|
23430bee97 | ||
|
|
04691a3b6c | ||
|
|
db0f65eedd | ||
|
|
2118b6dd7d | ||
|
|
71d7398ae7 | ||
|
|
2e16a2be56 | ||
|
|
ed24f432c3 | ||
|
|
669e8d5f8e | ||
|
|
016f4abb32 | ||
|
|
b1a946ec20 | ||
|
|
2576b46f34 | ||
|
|
a42df9a27b | ||
|
|
48102e9c53 | ||
|
|
5770aeee26 | ||
|
|
cb102a5a61 | ||
|
|
28f01784c1 | ||
|
|
29eefbcc25 | ||
|
|
8679e55f6a | ||
|
|
846db4e689 | ||
|
|
68afb89b99 | ||
|
|
804f035a87 | ||
|
|
267342e7e6 | ||
|
|
9e3633f1e4 | ||
|
|
06b3894249 | ||
|
|
d39129887b | ||
|
|
e5379bb073 | ||
|
|
c8dded1108 | ||
|
|
44e6b7dbb0 | ||
|
|
80857c22c9 | ||
|
|
6548f9f0ed | ||
|
|
58fd8780e5 | ||
|
|
0cccbc438b | ||
|
|
558e7cb5ac | ||
|
|
286181ca04 | ||
|
|
ce0fc14a8a | ||
|
|
ebfbc8ce61 | ||
|
|
fdb038c7c9 | ||
|
|
467e6dfd16 | ||
|
|
8c40c28fe0 | ||
|
|
9e25049fe8 | ||
|
|
e170153090 | ||
|
|
99e8753629 | ||
|
|
d5b0829065 | ||
|
|
2c8a60e0ea | ||
|
|
352de75fdd | ||
|
|
f7b35defc9 | ||
|
|
e471c01269 | ||
|
|
5dfaa10709 | ||
|
|
7506f94a53 | ||
|
|
a2d08fa40d | ||
|
|
159c883bf3 | ||
|
|
396d7680d3 | ||
|
|
7e93a5d3cf | ||
|
|
955c55b6cc | ||
|
|
b2b30b0cf5 | ||
|
|
6352b3a594 | ||
|
|
5be684d2e5 | ||
|
|
8dda17d546 | ||
|
|
0f5aaac1f5 | ||
|
|
35fa75f9c9 | ||
|
|
19d43b3f62 | ||
|
|
2b4a72897e | ||
|
|
cdc31b7fc7 | ||
|
|
f4ef1455c4 | ||
|
|
fe94512dd1 | ||
|
|
ae25542ce9 | ||
|
|
6c030a5230 | ||
|
|
a145d6ebcc | ||
|
|
14676e9618 | ||
|
|
72e8476ded | ||
|
|
5888b051d9 | ||
|
|
215cf73072 | ||
|
|
7f7d4a77b6 | ||
|
|
7e3a0c4ded | ||
|
|
89150317e1 | ||
|
|
f47faa548b | ||
|
|
36ed8f3f73 | ||
|
|
ca1ca21cf8 | ||
|
|
c7df539b31 | ||
|
|
e873188775 | ||
|
|
6953efc2d8 | ||
|
|
3e78aacc7e | ||
|
|
20b4ce3213 | ||
|
|
4b581c385c | ||
|
|
bdf6f5c3b9 | ||
|
|
6d0953dca4 | ||
|
|
fc0fc5ea10 | ||
|
|
f7f2d84e1f | ||
|
|
57acadd52a | ||
|
|
26cebd2aeb | ||
|
|
9a652e789d | ||
|
|
f6509e3fd6 | ||
|
|
445fe6e714 | ||
|
|
dc8a70bb26 | ||
|
|
adf9b82d6b | ||
|
|
be212b5186 | ||
|
|
59ba461d83 | ||
|
|
004c2e069c | ||
|
|
c168b7e979 | ||
|
|
79411430a5 | ||
|
|
853d655a92 | ||
|
|
2d5fa9ebbf | ||
|
|
b2deab08ab | ||
|
|
8452a17d79 | ||
|
|
19851c8a47 | ||
|
|
6b8ec6ae16 | ||
|
|
d648e67eaf | ||
|
|
ebb14af488 | ||
|
|
80afa98d66 | ||
|
|
2797a28c0d | ||
|
|
03c8d94024 | ||
|
|
c061eddf2a | ||
|
|
2e146190e1 | ||
|
|
fc110c4988 | ||
|
|
6003003228 | ||
|
|
70e5bee519 | ||
|
|
4062d1920c | ||
|
|
50b81c2356 | ||
|
|
00fe3a76c8 | ||
|
|
d11a3b9683 | ||
|
|
7934fa24a2 | ||
|
|
f0d9dee6ce | ||
|
|
3a82bddcd3 | ||
|
|
ebe044aee8 | ||
|
|
674305ce29 | ||
|
|
b9a5c6ff31 | ||
|
|
bcb0a7822a | ||
|
|
93aac0bc99 | ||
|
|
7fe58afa9c | ||
|
|
9d0f8d9886 | ||
|
|
aa7d0471db | ||
|
|
6cd9227e8d | ||
|
|
213ef58959 | ||
|
|
2fa1124752 | ||
|
|
d00a0d6c75 | ||
|
|
2e225eb84f | ||
|
|
3c57ee89c4 | ||
|
|
b6f870ac5f | ||
|
|
570ce7d28d | ||
|
|
3958090e0f | ||
|
|
99ed1b729e | ||
|
|
d16db77b52 | ||
|
|
56c176add4 | ||
|
|
42a856c24a | ||
|
|
b662090d64 | ||
|
|
29e50a5f91 | ||
|
|
7885e07eed | ||
|
|
65fd682f2a | ||
|
|
cdecbb0857 | ||
|
|
68ee781f26 | ||
|
|
0f307e7ca2 | ||
|
|
7ac49287df | ||
|
|
83faf6025b | ||
|
|
4faf5e6d3e | ||
|
|
d300596ac1 | ||
|
|
a30dc5988e | ||
|
|
e43a0da9a6 | ||
|
|
83b74c0930 | ||
|
|
0d2a39267f | ||
|
|
f87f380bb0 | ||
|
|
ab28acd709 | ||
|
|
04b53f8e1e | ||
|
|
eb0e5c2de3 | ||
|
|
8cb9b910a6 | ||
|
|
bdac79c91b | ||
|
|
88081fc3c4 | ||
|
|
6be451e9f3 | ||
|
|
64ed25879c | ||
|
|
d66e087d94 | ||
|
|
b7b0a44c52 | ||
|
|
44cba7adf7 | ||
|
|
5a8a64d284 | ||
|
|
eac6835982 | ||
|
|
7992ca4c85 | ||
|
|
ead200ea5d | ||
|
|
e146c2606f | ||
|
|
db158e1ffe | ||
|
|
72ef3c3394 | ||
|
|
e17378c6b3 | ||
|
|
0e05f5305c | ||
|
|
58f43da23e | ||
|
|
4a9e7f29da | ||
|
|
1589209567 | ||
|
|
5c6017b0a9 | ||
|
|
4246fe5b92 | ||
|
|
c441d2f03f | ||
|
|
6c21529594 | ||
|
|
6fc4253d46 | ||
|
|
7a2590d1f9 | ||
|
|
4cbbb5b64f | ||
|
|
f11104fcb5 | ||
|
|
19e8ca6c06 | ||
|
|
13005e8242 | ||
|
|
d0dde6e572 | ||
|
|
5e103a5ccb | ||
|
|
0c4e6f0a8c | ||
|
|
03a89a5cdd | ||
|
|
e22d13be1f | ||
|
|
ef9c4d96b7 | ||
|
|
bb7300a055 | ||
|
|
2d456fd1fc | ||
|
|
97108e788f | ||
|
|
b8f7259fbd | ||
|
|
2de1c62daf | ||
|
|
f5d2eff8d3 | ||
|
|
dbcf1fdb6f | ||
|
|
bd81e4d0fb | ||
|
|
80786f692c | ||
|
|
177edfea3e | ||
|
|
b4b08db778 | ||
|
|
8abf2f768c | ||
|
|
8b14cd5aea | ||
|
|
84a21d72b9 | ||
|
|
fa8e0ed27b | ||
|
|
b6e403a3fb | ||
|
|
3d368f9aca | ||
|
|
414ac950e0 | ||
|
|
34fa82f72c | ||
|
|
4d5fdc4725 | ||
|
|
1c4f28e708 | ||
|
|
7bf555ce4c | ||
|
|
bb388628a7 | ||
|
|
a2920578a1 | ||
|
|
b41a73f08d | ||
|
|
c6ace470e3 | ||
|
|
cdbe79d3c1 | ||
|
|
493ec07ff2 | ||
|
|
ec15412755 | ||
|
|
191b6d5b4e | ||
|
|
8718152dba | ||
|
|
c585db7516 | ||
|
|
515c91a407 | ||
|
|
b615029304 | ||
|
|
3b0f4b1f82 | ||
|
|
4a2bf7a0c3 | ||
|
|
79212f8ef7 | ||
|
|
c57a596156 | ||
|
|
948db1451f | ||
|
|
06aa18bfab | ||
|
|
f076afb6e1 | ||
|
|
582402f4d9 | ||
|
|
0e04ff506b | ||
|
|
e9f757dccb | ||
|
|
f02d858646 | ||
|
|
3727cd401c | ||
|
|
f0c9deca4b | ||
|
|
f41a8bc355 | ||
|
|
3100930136 | ||
|
|
9abafafcdd | ||
|
|
0d127dff3b | ||
|
|
83dbf88ffb | ||
|
|
3a6f56ebbc | ||
|
|
3e8c5d3b79 | ||
|
|
2aaca0c54a | ||
|
|
c6d587f0c7 | ||
|
|
c85682de8d | ||
|
|
9c12c3ee4e | ||
|
|
2c432dbf4d | ||
|
|
0c8643837f | ||
|
|
3fd97f7e60 | ||
|
|
5064696480 | ||
|
|
6f0fbd1088 | ||
|
|
b535722421 | ||
|
|
60e96b637d | ||
|
|
7f29c7a601 | ||
|
|
060dfedb74 | ||
|
|
2907ed6029 | ||
|
|
5649fcc9ca | ||
|
|
85ddfc0739 | ||
|
|
7c98bfd363 | ||
|
|
c23f377039 | ||
|
|
af89fb52f1 | ||
|
|
98f56fd506 | ||
|
|
88ea1ea85b | ||
|
|
2a1e33f95c | ||
|
|
2fe55e2b55 | ||
|
|
d6f5887b1d | ||
|
|
dce045461a | ||
|
|
4ab0324bc9 | ||
|
|
b2bb69fe42 | ||
|
|
ad96f9aacd | ||
|
|
9847fc5c77 | ||
|
|
3f3bca753c | ||
|
|
c223d6a87d | ||
|
|
14a428467c | ||
|
|
efc84e53ec | ||
|
|
f9ee0189f0 | ||
|
|
d90580943f | ||
|
|
f6484b4e2b | ||
|
|
0b5f48b926 | ||
|
|
f2f649319d | ||
|
|
35eaa94228 | ||
|
|
e09a050d41 | ||
|
|
38182ba020 | ||
|
|
7960b2ca10 | ||
|
|
722c789448 | ||
|
|
faa4c36956 | ||
|
|
d3a33d786d | ||
|
|
1a2d60dbfc | ||
|
|
2aa5e685b8 | ||
|
|
512c7df37d | ||
|
|
57fddf1c85 | ||
|
|
afe4206782 | ||
|
|
02bc5e3111 | ||
|
|
5a51284550 | ||
|
|
2d995826ad | ||
|
|
92f3bf2999 | ||
|
|
bfe282c2db | ||
|
|
2d0b013af3 | ||
|
|
3df938ed61 | ||
|
|
d4b6cab742 | ||
|
|
8d327af0ac | ||
|
|
f135fbee26 | ||
|
|
d65977bd59 | ||
|
|
1e0242ede9 | ||
|
|
839d3365f8 | ||
|
|
182ee8f233 | ||
|
|
24eb6f8c38 | ||
|
|
13bade262f | ||
|
|
d2ff675fbc | ||
|
|
1905a81f9a | ||
|
|
e2382a1465 | ||
|
|
ec00cd2ae0 | ||
|
|
e0fad4eaa5 | ||
|
|
882b39b067 | ||
|
|
d15a641d88 | ||
|
|
90ac8b7b0b | ||
|
|
d64afdcff1 | ||
|
|
eb58a39f57 | ||
|
|
d12352d568 | ||
|
|
217a9808b2 | ||
|
|
1b08adb178 | ||
|
|
f113fda6d4 | ||
|
|
5a6a773583 | ||
|
|
627d21a00a | ||
|
|
44e13c84bc | ||
|
|
b96067cf2e | ||
|
|
b2fe5cce5e | ||
|
|
55775adba6 | ||
|
|
0500f56163 | ||
|
|
52e5d519ab | ||
|
|
bdfbb97bf8 | ||
|
|
cff117cb40 | ||
|
|
cf71adc7e1 | ||
|
|
0582f2a168 | ||
|
|
2721af3052 | ||
|
|
f9950639e8 | ||
|
|
8f8c9ddc25 | ||
|
|
58596ad1ab | ||
|
|
b17ea991a3 | ||
|
|
45d70f73bd | ||
|
|
7a6e483171 | ||
|
|
1792dd8d2c | ||
|
|
a4ea4b138f | ||
|
|
e2c6aedddd | ||
|
|
b91a03b602 | ||
|
|
27716f077e | ||
|
|
eaa054e599 | ||
|
|
e57d07f7d6 | ||
|
|
895831f46f | ||
|
|
6b16d7ee8a | ||
|
|
45dbea57f1 | ||
|
|
6ea0dbc70f | ||
|
|
30a5041799 | ||
|
|
35083724f6 | ||
|
|
959d5dd9c2 | ||
|
|
a24a261c36 | ||
|
|
75feb90b19 | ||
|
|
fd3cb1b0eb | ||
|
|
1902134f03 | ||
|
|
cce48c5030 | ||
|
|
0654b8e427 | ||
|
|
7df10b20a4 | ||
|
|
dbde5af545 | ||
|
|
85b4f0638f | ||
|
|
94f2751902 | ||
|
|
ffdfef0adc | ||
|
|
de7652836b | ||
|
|
59913bc6f3 | ||
|
|
d77a684b64 | ||
|
|
529c075fe1 | ||
|
|
5190589653 | ||
|
|
cb9a1b4bef | ||
|
|
a0beba4e20 | ||
|
|
d794425f2c | ||
|
|
1ba11fc3ef | ||
|
|
0dfb1ae776 | ||
|
|
c8a054f997 | ||
|
|
30c4e9cf00 | ||
|
|
c9fd2fc1c2 | ||
|
|
e9e3a436eb | ||
|
|
b9bd79895f | ||
|
|
36b574a5f8 | ||
|
|
fe90372b2f | ||
|
|
d47ddac94c | ||
|
|
854c374e2f | ||
|
|
7bafe142ca | ||
|
|
14ec8c4bea | ||
|
|
e9f2a77bf6 | ||
|
|
dcb4ce8d9a | ||
|
|
2702466cdf | ||
|
|
2016000d37 | ||
|
|
be0516c06c | ||
|
|
332a3635de | ||
|
|
9df4fa6a0e | ||
|
|
ddcb9ff819 | ||
|
|
7e9f5509ca | ||
|
|
c596d75e8c | ||
|
|
a0337d399c | ||
|
|
f0f52d7244 | ||
|
|
d55d796c00 | ||
|
|
6da36b27aa | ||
|
|
b4d3fc393e | ||
|
|
c4b68280fd | ||
|
|
97aeee7172 | ||
|
|
8bf57f1293 | ||
|
|
4584cebad5 | ||
|
|
557773144b | ||
|
|
32a29a5556 | ||
|
|
9f4a844c9b | ||
|
|
82b7650458 | ||
|
|
b7121c4447 | ||
|
|
b522de3b56 | ||
|
|
eb94caf4b6 | ||
|
|
0f59e0c950 | ||
|
|
05a254c682 | ||
|
|
576ba2c745 | ||
|
|
3dafda6ace | ||
|
|
2d048a3295 | ||
|
|
f2d345e7b1 | ||
|
|
6a0db02230 | ||
|
|
93f271912e | ||
|
|
55b78727ab | ||
|
|
69b555c059 | ||
|
|
07f327d641 | ||
|
|
c69f4ba4a2 | ||
|
|
d7e8d4d5c3 | ||
|
|
679a026e72 | ||
|
|
0db180a54f | ||
|
|
6c2de53e07 | ||
|
|
af132aa62e | ||
|
|
d528fd3762 | ||
|
|
663d355a48 | ||
|
|
27112e3480 | ||
|
|
482d3ff352 | ||
|
|
a444d4af94 | ||
|
|
6f77fda6fa | ||
|
|
8f7d653e80 | ||
|
|
e474b595ad | ||
|
|
b4c6292397 | ||
|
|
4aa994827c | ||
|
|
a521ccde47 | ||
|
|
03d280054b | ||
|
|
c0a3279856 | ||
|
|
e8643632bf | ||
|
|
6d8272472a | ||
|
|
409d5b124a | ||
|
|
0c1f3d056a | ||
|
|
0e702d8693 | ||
|
|
b155cd9a5a | ||
|
|
6a281fb7ba | ||
|
|
203891c957 | ||
|
|
1365df898f | ||
|
|
a2bc02b4c5 | ||
|
|
0d01aa4eea | ||
|
|
017c73132c | ||
|
|
534bfad50f | ||
|
|
429f44eb6a | ||
|
|
346c88b661 | ||
|
|
c49853e7b4 | ||
|
|
11232c6f23 | ||
|
|
9aa788f086 | ||
|
|
c35268c493 | ||
|
|
df0ad4486e | ||
|
|
d369385657 | ||
|
|
059e067bdc | ||
|
|
050759c1c2 | ||
|
|
6d264b4394 | ||
|
|
284330ed5f | ||
|
|
49205b604c | ||
|
|
c14c27970f | ||
|
|
b1682d5794 | ||
|
|
d0b9dd9ae7 | ||
|
|
0a94b7473d | ||
|
|
587a928b84 | ||
|
|
48dbc06b29 | ||
|
|
967515a34f | ||
|
|
8e0bce4da4 | ||
|
|
01078bd7b2 | ||
|
|
a97fbb2d48 | ||
|
|
6782f92703 | ||
|
|
9096c29fef | ||
|
|
100967c57b | ||
|
|
c871af2711 | ||
|
|
faf99ffe14 | ||
|
|
58d073b516 | ||
|
|
52a4d41c6f | ||
|
|
0805b00c50 | ||
|
|
c8f8bfd6f0 | ||
|
|
1f51d37ae6 | ||
|
|
f8a4b8de51 | ||
|
|
eaa64d7b70 | ||
|
|
4480fbf787 | ||
|
|
c2287033e3 | ||
|
|
0a0fb5287a | ||
|
|
52acbd7d2c | ||
|
|
af4f84a84b | ||
|
|
633241fcee | ||
|
|
f1f3d288cd | ||
|
|
7c98da85a0 | ||
|
|
36f7d64352 | ||
|
|
a4bbcbe5ff | ||
|
|
cbb4ec5aa1 | ||
|
|
84fc5f7d67 | ||
|
|
a4d84cdc21 | ||
|
|
1a422f318c | ||
|
|
8f202fd70d | ||
|
|
dfe96eb30c | ||
|
|
3d7c6c14b3 | ||
|
|
85a4e361d2 | ||
|
|
05b2fa97d3 | ||
|
|
47d57ddf70 | ||
|
|
9521ac6adb | ||
|
|
c2703d215b | ||
|
|
f5c1133fc5 | ||
|
|
f63d81826a | ||
|
|
fa39982a8f | ||
|
|
4b52414e03 | ||
|
|
29b0a7659f | ||
|
|
e69183ce12 | ||
|
|
009c088a64 | ||
|
|
2c1f948832 | ||
|
|
b83e007405 | ||
|
|
1d3ce2c029 | ||
|
|
5ad0730a26 | ||
|
|
52f1383903 | ||
|
|
12b8cbf3e0 | ||
|
|
de44f8565d | ||
|
|
7480ead76a | ||
|
|
e96ae7a650 | ||
|
|
ae524c4d0e | ||
|
|
5adce88c37 | ||
|
|
06dda24431 | ||
|
|
fa046df923 | ||
|
|
2432eb39b1 | ||
|
|
c211a2517f | ||
|
|
73ea0a57a0 | ||
|
|
f05f86dc80 | ||
|
|
e44b25c80f | ||
|
|
bc3acc2826 | ||
|
|
007fb34ca5 | ||
|
|
46a363cce4 | ||
|
|
3047ce57a3 | ||
|
|
7b9ce072d9 | ||
|
|
4d3e3f3aed | ||
|
|
62563ad8a1 | ||
|
|
6fdce63359 | ||
|
|
fdc4d6dda9 | ||
|
|
a3b06ee83f | ||
|
|
43aa62e212 | ||
|
|
70dd3f323e | ||
|
|
803509d952 | ||
|
|
30e85c8654 | ||
|
|
7b24835c9e | ||
|
|
7aee76f5de | ||
|
|
80200a9983 | ||
|
|
79f6b5c181 | ||
|
|
dc02ce3f97 | ||
|
|
845d5a548f | ||
|
|
14a8cf69ef | ||
|
|
c8a6e8005e | ||
|
|
e6bb2bfaae | ||
|
|
8ba2d1cf72 | ||
|
|
a168fc3719 | ||
|
|
fd1dc15576 | ||
|
|
0b82874a52 | ||
|
|
434242858f | ||
|
|
3dfa0525bd | ||
|
|
36d4baaa8e | ||
|
|
7880cba0f9 | ||
|
|
f8d64528b5 | ||
|
|
f6ee61f29e | ||
|
|
2bb1310094 | ||
|
|
f6a137cd43 | ||
|
|
a575fe4934 | ||
|
|
4cd8d8a4a5 | ||
|
|
7a802726fb | ||
|
|
251245d315 | ||
|
|
fbf5a84c4a | ||
|
|
55412b94d2 | ||
|
|
f61fd02ac7 | ||
|
|
dc96b473cd | ||
|
|
abe40c84b0 | ||
|
|
9976fc9723 | ||
|
|
f8092f924a | ||
|
|
075a877284 | ||
|
|
f748395bda | ||
|
|
12018360fd | ||
|
|
821b2fda85 | ||
|
|
4cb2b29187 | ||
|
|
bb59778313 | ||
|
|
ed0ded33b7 | ||
|
|
db19528c24 | ||
|
|
7d1fb0a238 | ||
|
|
11956d9e16 | ||
|
|
cef782c388 | ||
|
|
e87e371c14 | ||
|
|
877a455ae0 | ||
|
|
a47618e986 | ||
|
|
2bbd759c25 | ||
|
|
4dd19884e0 | ||
|
|
5ab0f499ce | ||
|
|
053723647b | ||
|
|
725a44abd8 | ||
|
|
625f2d2410 | ||
|
|
e32748daf2 | ||
|
|
8a2bd1cac3 | ||
|
|
9076f213e6 | ||
|
|
e77edc56fd | ||
|
|
de356304c7 | ||
|
|
8cc9e30f86 | ||
|
|
384323031b | ||
|
|
b40a7b24d4 | ||
|
|
67b2a433a8 | ||
|
|
07bdf02af4 | ||
|
|
35b470dbac | ||
|
|
42eb49b84c | ||
|
|
9d3d11755f | ||
|
|
0fbc546696 | ||
|
|
220422ba0d | ||
|
|
2c9bae8111 | ||
|
|
320f6ddc6e | ||
|
|
0da6d51150 | ||
|
|
65e3170cd9 | ||
|
|
ee3750121c | ||
|
|
9736a4ddd0 | ||
|
|
53e310ff77 | ||
|
|
17285720f1 | ||
|
|
aa690cb9ab | ||
|
|
6d3596087a | ||
|
|
0fe80e8e1f | ||
|
|
f853f20b0c | ||
|
|
39e6fa35e1 | ||
|
|
23b911297e | ||
|
|
6113d1e3eb | ||
|
|
55f3b93958 | ||
|
|
c522987b6f | ||
|
|
8fe64755ec | ||
|
|
cc35328f28 | ||
|
|
a824a7fb73 | ||
|
|
075d7e52df | ||
|
|
cadbae31e4 | ||
|
|
39b5a67040 | ||
|
|
30580b2c57 | ||
|
|
00ab830ad1 | ||
|
|
fcf3577f67 | ||
|
|
2cdfeb8dd0 | ||
|
|
ce300aa75f | ||
|
|
a52caaec75 | ||
|
|
3f3489b292 | ||
|
|
1e059c5649 | ||
|
|
d9e1b2df7f | ||
|
|
94e51a8041 | ||
|
|
81fe90f605 | ||
|
|
69f643447d | ||
|
|
d8355371e3 | ||
|
|
cbe9b9c455 | ||
|
|
dd33c0e582 | ||
|
|
009b0aa361 | ||
|
|
f438176544 | ||
|
|
be8e8a0521 | ||
|
|
1a8b31bdb4 | ||
|
|
8a47055273 | ||
|
|
feb6f7930e | ||
|
|
0271ea7dad | ||
|
|
505b73d20e | ||
|
|
dcad8a9f79 | ||
|
|
9ce58115ab | ||
|
|
1c9d139ff5 | ||
|
|
70794c8eb5 | ||
|
|
c90c4a2e78 | ||
|
|
6797e8af52 | ||
|
|
5109802dfb | ||
|
|
935297b9e8 | ||
|
|
dad209d1cd | ||
|
|
e1072cc8ca | ||
|
|
1a1ba80188 | ||
|
|
c878c91e11 | ||
|
|
c518521d74 | ||
|
|
ce9c9078e5 | ||
|
|
9fe525bca1 | ||
|
|
352865ddaa | ||
|
|
bc950ee40a | ||
|
|
5c23dfd633 | ||
|
|
4633a0450c | ||
|
|
842255766f | ||
|
|
827c32fafd | ||
|
|
f531cd23ee | ||
|
|
2408758360 | ||
|
|
4c792f6f17 | ||
|
|
802ab90d87 | ||
|
|
c268a0ab14 | ||
|
|
b12c7f21b3 | ||
|
|
b3948910ff | ||
|
|
9ee1261204 | ||
|
|
8e3aa0e9ce | ||
|
|
db62a01224 | ||
|
|
d900f2c47c | ||
|
|
82e1fe3f8b | ||
|
|
85eb82e69d | ||
|
|
c529f8099d | ||
|
|
aa3c58917b | ||
|
|
bc591a2399 | ||
|
|
11d996bb56 | ||
|
|
088a78455c | ||
|
|
869d9d487b | ||
|
|
db3978fd4e | ||
|
|
ae7e46fe5e | ||
|
|
4bd85e1804 | ||
|
|
01f4434f6b | ||
|
|
d8f808bf38 | ||
|
|
807dc7d220 | ||
|
|
b14d0c9f60 | ||
|
|
738a1a330c | ||
|
|
676b02c8de | ||
|
|
7136400a33 | ||
|
|
4651d9df68 | ||
|
|
00555a8e9e | ||
|
|
e8c4615ff6 | ||
|
|
763467058b | ||
|
|
c055ba2985 | ||
|
|
7666541905 | ||
|
|
f4b0b39beb | ||
|
|
cfc0925e75 | ||
|
|
4dcc368378 | ||
|
|
8207908d9e | ||
|
|
e08da096dd | ||
|
|
9a903a1ca3 | ||
|
|
1b22bf0e08 | ||
|
|
fc3187a781 | ||
|
|
7fbb4045e2 | ||
|
|
d0dc22794e | ||
|
|
0f235a80e0 | ||
|
|
2a28046382 | ||
|
|
169bbfd2db | ||
|
|
1f52bb35ba | ||
|
|
4a03b3d7d9 | ||
|
|
2544a7e4ea | ||
|
|
26e77ba2c3 | ||
|
|
28c11801f3 | ||
|
|
b3e2ab0f3b | ||
|
|
fb12ba8a2b | ||
|
|
7a5bc864fa | ||
|
|
2e85d4b55a | ||
|
|
80951a8e6e | ||
|
|
9acddede65 | ||
|
|
22165ec1a5 | ||
|
|
d7036aae48 | ||
|
|
fe3924b432 | ||
|
|
510cffb305 | ||
|
|
05c789ae50 | ||
|
|
5d95d61aef | ||
|
|
c61fa71a70 | ||
|
|
3145269f65 | ||
|
|
2d00a1e265 | ||
|
|
f7f178d6e3 | ||
|
|
18c591e0d0 | ||
|
|
749241f4e5 | ||
|
|
997c368604 | ||
|
|
ed28928c84 | ||
|
|
b55c916e77 | ||
|
|
4d537b2a9a | ||
|
|
21f7d6c9b9 | ||
|
|
c1b865d00e | ||
|
|
a32e740242 | ||
|
|
c4f09b5598 | ||
|
|
5a89aa3b32 | ||
|
|
149d57150c | ||
|
|
bbc241748b | ||
|
|
74ecea6307 | ||
|
|
2e829956f4 | ||
|
|
9e2c9cbba9 | ||
|
|
1ad740800b | ||
|
|
c00d4c1a7b | ||
|
|
e0985ebb1c | ||
|
|
d5706442de | ||
|
|
ad5a173f3f | ||
|
|
fbb1d9247f | ||
|
|
63591941b8 | ||
|
|
fdfeec54d7 | ||
|
|
b2404809fc | ||
|
|
f9f463e799 | ||
|
|
f1d3a553d1 | ||
|
|
e997b148e1 | ||
|
|
af906fac03 | ||
|
|
19945df0b3 | ||
|
|
bf83d552f8 | ||
|
|
f5d8e99fc7 | ||
|
|
975037c5a0 | ||
|
|
8b0b45e089 | ||
|
|
182f2ae26e | ||
|
|
26982787ee | ||
|
|
4a42e3ef1b | ||
|
|
4f4498666e | ||
|
|
8f9ba44c2c | ||
|
|
1f91d4fa7b | ||
|
|
cf97d090f3 | ||
|
|
9cd96a65d6 | ||
|
|
5fc75cb4cd | ||
|
|
27fe566412 | ||
|
|
a68c7bf019 | ||
|
|
11388849de | ||
|
|
aa8fb55b30 | ||
|
|
472bea2baa | ||
|
|
0a7a6c64ce | ||
|
|
fde8196874 | ||
|
|
ea8576d344 | ||
|
|
9adda25e00 | ||
|
|
2cc0bf22cb | ||
|
|
892ebc2e03 | ||
|
|
5c9ee03389 | ||
|
|
3702b0c694 | ||
|
|
451d662af4 | ||
|
|
7b8e0a2755 | ||
|
|
63ba4f4f91 | ||
|
|
5f92465d0f | ||
|
|
64c8c5a014 | ||
|
|
2d6322f799 | ||
|
|
8127ce18a3 | ||
|
|
7b5801920b | ||
|
|
c18c1e59df | ||
|
|
4959e664a9 | ||
|
|
fb5ba257ef | ||
|
|
0ed1c0aa6b | ||
|
|
a12fac780b | ||
|
|
0b16e13597 | ||
|
|
45a9e54631 | ||
|
|
b969307c5e | ||
|
|
20325e87be | ||
|
|
fc48d9047a | ||
|
|
16b4db5083 | ||
|
|
4c7487cd2e | ||
|
|
271033e79d | ||
|
|
d2ddcf2d38 | ||
|
|
8a2ab30302 | ||
|
|
3070b0019e | ||
|
|
68ef1fc9e0 | ||
|
|
c13d67dea5 | ||
|
|
b52cf070f5 | ||
|
|
e363cd9813 | ||
|
|
c10fc26cce | ||
|
|
3bb7123dd5 | ||
|
|
2f2a7d1f89 | ||
|
|
013d307bcd | ||
|
|
5e616dd502 | ||
|
|
f200bd2198 | ||
|
|
bd36ee4f67 | ||
|
|
e0e5cc076d | ||
|
|
95e3fb24f3 | ||
|
|
83d47aed2d | ||
|
|
bbd7cf306a | ||
|
|
f6b5c752f4 | ||
|
|
ff20acc367 | ||
|
|
46c2720fc7 | ||
|
|
cf0e3ec303 | ||
|
|
c6bb3d6ae2 | ||
|
|
b39ba92cfe | ||
|
|
deb1c190c9 | ||
|
|
5d0384f580 | ||
|
|
d3ce8203be | ||
|
|
6e80f0d93b | ||
|
|
c9a923decf | ||
|
|
1be5f2d647 | ||
|
|
a38bba80ee | ||
|
|
b15d84359b | ||
|
|
a3670b731e | ||
|
|
b89546de37 | ||
|
|
fad4314538 | ||
|
|
a331961ef3 | ||
|
|
e6f62dc95e | ||
|
|
bd6d863921 | ||
|
|
c11117b070 | ||
|
|
f387ccb9e4 | ||
|
|
fd49830c35 | ||
|
|
578ca6975f | ||
|
|
c0442edb8d | ||
|
|
1416197b62 | ||
|
|
e1dd53f146 | ||
|
|
73abd0f8b8 | ||
|
|
ab982e86c3 | ||
|
|
20a4cd49de | ||
|
|
387d712b67 | ||
|
|
3d9ec91b35 | ||
|
|
8892c8c883 | ||
|
|
c51c98d682 | ||
|
|
3f4ac84cfb | ||
|
|
c5a864e86b | ||
|
|
e945dceab9 | ||
|
|
f176832851 | ||
|
|
ab195ea520 | ||
|
|
e4b861e766 | ||
|
|
ead6d8d3a1 | ||
|
|
796e2ec825 | ||
|
|
a597c3f835 | ||
|
|
c14cb29334 | ||
|
|
a233d28efc | ||
|
|
c7bc2ca82d | ||
|
|
bd6323ccae | ||
|
|
4326bfa504 | ||
|
|
b7a4c0664b | ||
|
|
da7bcf89d4 | ||
|
|
3907cc679a | ||
|
|
8cc5aee528 | ||
|
|
d0b5c4de28 | ||
|
|
431150c262 | ||
|
|
ee994ea393 | ||
|
|
53cd259ffa | ||
|
|
c0ed44abf9 | ||
|
|
0807eec4cc | ||
|
|
aec7271f50 | ||
|
|
a723518346 | ||
|
|
5d6d8e68ed | ||
|
|
197a9330df | ||
|
|
8b807d7b50 | ||
|
|
03c0111017 | ||
|
|
e55752869b | ||
|
|
94830cffca | ||
|
|
8d4319ba5f | ||
|
|
06987c4ca9 | ||
|
|
70c0edcbe7 | ||
|
|
7a482fd22a | ||
|
|
022b8ec13a | ||
|
|
b02f169764 | ||
|
|
6c0254b5f4 | ||
|
|
0a60d7016d | ||
|
|
315a2a695f | ||
|
|
863c8de28e | ||
|
|
92213f9228 | ||
|
|
253c8118a2 | ||
|
|
290c980d5f | ||
|
|
6ead1f4bd9 | ||
|
|
6e4a5b64b7 | ||
|
|
6e3e60a44d | ||
|
|
b88fafe5ff | ||
|
|
17a56bbf48 | ||
|
|
d8e51c6b14 | ||
|
|
44554cb54b | ||
|
|
0bd86a8211 | ||
|
|
7fcb3d70bb | ||
|
|
563cd828ad | ||
|
|
3581e0beed | ||
|
|
da04de7b2d | ||
|
|
661ce29519 | ||
|
|
56bed3f297 | ||
|
|
79a8715c8b | ||
|
|
69062dca16 | ||
|
|
40d3085cc2 | ||
|
|
5e444de031 | ||
|
|
3e61b89499 | ||
|
|
76e9d749c9 | ||
|
|
3e47c352a3 | ||
|
|
6028cfc1a3 | ||
|
|
20f1a85e69 | ||
|
|
00d3d0f094 | ||
|
|
8756ae0fe6 | ||
|
|
bdb1fc2ed7 | ||
|
|
34d64fbcaf | ||
|
|
3eeb0628f5 | ||
|
|
3ed71fa21e | ||
|
|
731ecfda64 | ||
|
|
230eb76532 | ||
|
|
93f2b288b5 | ||
|
|
d9ee9ba238 | ||
|
|
e000fdfb50 | ||
|
|
4e97d2503b | ||
|
|
f21f793343 | ||
|
|
c470f2734d | ||
|
|
28ddf6cf07 | ||
|
|
e0302d1f09 | ||
|
|
a536f79f6d | ||
|
|
5c48fb9e66 | ||
|
|
994ba1edd9 | ||
|
|
ed90979417 | ||
|
|
4dc2172426 | ||
|
|
daa41f8664 | ||
|
|
4e93ffb924 | ||
|
|
f29363f56d | ||
|
|
23680ccb14 | ||
|
|
986e58aeec | ||
|
|
13da75c2b6 | ||
|
|
28b28cfef6 | ||
|
|
167bf70cd6 | ||
|
|
5236dcfe52 | ||
|
|
a437524c8f | ||
|
|
fcbd48648c | ||
|
|
3f29273f6e | ||
|
|
b78ffdad02 | ||
|
|
fd4cd3ed04 | ||
|
|
d8001fcaea | ||
|
|
9771c652c5 | ||
|
|
a0c7bbe213 | ||
|
|
6b43042828 | ||
|
|
73f0b1e8a3 | ||
|
|
fa1b61b3e3 | ||
|
|
c116e94cba | ||
|
|
7229652e31 | ||
|
|
03acf7a05c | ||
|
|
d83d8c18fc | ||
|
|
2080e56f87 | ||
|
|
90f0f27fca | ||
|
|
5837026e83 | ||
|
|
edc67e5da2 | ||
|
|
55b43f4612 | ||
|
|
32a32e4a72 | ||
|
|
0eae0da781 | ||
|
|
51b62ea467 | ||
|
|
462fa5999f | ||
|
|
82b730c5b8 | ||
|
|
492ea7264a | ||
|
|
a1c910e3aa | ||
|
|
bf5f58e0ce | ||
|
|
428bc9b419 | ||
|
|
a2742caa87 | ||
|
|
d2a5edda46 | ||
|
|
38f8956bd0 | ||
|
|
ec8deab454 | ||
|
|
cdc92fc552 | ||
|
|
53fb48fe7d | ||
|
|
73e4006447 | ||
|
|
c2ff269b5f | ||
|
|
c7310b64ad | ||
|
|
83daa702f9 | ||
|
|
cb1a06270e | ||
|
|
fd34f97120 | ||
|
|
2dc8f3b3e4 | ||
|
|
7e5b81ff4d | ||
|
|
f5b945c09b | ||
|
|
6014dd05a0 | ||
|
|
0c6aa381c5 | ||
|
|
0246f050e2 | ||
|
|
5f222c4df2 | ||
|
|
800f3f765f | ||
|
|
635f346b12 | ||
|
|
abe79dbf64 | ||
|
|
2b72622fe8 | ||
|
|
201c7a7e49 | ||
|
|
ef21283a61 | ||
|
|
4eb76cdc30 | ||
|
|
3b68f598b1 | ||
|
|
31db43dbb0 | ||
|
|
1efc4a03cc | ||
|
|
607c818879 | ||
|
|
6d5f044948 | ||
|
|
f24a8b3918 | ||
|
|
2e402098a2 | ||
|
|
3389c798f6 | ||
|
|
738ad474c1 | ||
|
|
86e2ac1497 | ||
|
|
b2a4f11e0b | ||
|
|
09d380ba8f | ||
|
|
12bfa72f31 | ||
|
|
b0990ac6ec | ||
|
|
6f1f07a1c4 | ||
|
|
f55fdae9eb | ||
|
|
b0225880de | ||
|
|
74be6af3e6 | ||
|
|
72f5fbd6ad | ||
|
|
ae3efa1151 | ||
|
|
348ed268c3 | ||
|
|
df19ccf998 | ||
|
|
db2e4f30a7 | ||
|
|
a1e1f5aab6 | ||
|
|
7ffe11b000 | ||
|
|
eb5ea17610 | ||
|
|
8657381dce | ||
|
|
55114082e3 | ||
|
|
64051e9cfa | ||
|
|
780d64a349 | ||
|
|
bcd1827d8a | ||
|
|
0097f5fc8d | ||
|
|
3766d2b97b | ||
|
|
f2b7bfc561 | ||
|
|
4d3484002d | ||
|
|
34f20f914a | ||
|
|
19444353b4 | ||
|
|
b4f0a13779 | ||
|
|
fa815af798 | ||
|
|
f1e2aa8c96 | ||
|
|
9fd24db257 | ||
|
|
73414f2ee1 | ||
|
|
7b37e5183c | ||
|
|
bb8438c770 | ||
|
|
3dd329a999 | ||
|
|
ab2672777e | ||
|
|
45aea4176f | ||
|
|
f8dfbbb0b9 | ||
|
|
48a348c4d1 | ||
|
|
870ff56629 | ||
|
|
b2ed8d8560 | ||
|
|
e8510ddc58 | ||
|
|
97f7575409 | ||
|
|
5220157b01 | ||
|
|
900f9ec9a9 | ||
|
|
52ec2c2538 | ||
|
|
31101221e0 | ||
|
|
ae37c2ab2a | ||
|
|
e8d014d80d | ||
|
|
b88f1dc79a | ||
|
|
dccc791c99 | ||
|
|
474d2402b7 | ||
|
|
7a9f1e9c6c | ||
|
|
98364e83b6 | ||
|
|
bf65f033dd | ||
|
|
2d9b430327 | ||
|
|
ec54443674 | ||
|
|
0fb84ccc49 | ||
|
|
0b4aad4931 | ||
|
|
ab470d4a4c | ||
|
|
88e1245d10 | ||
|
|
bc4dbd6194 | ||
|
|
b35df54c81 | ||
|
|
c53c7b1aaa | ||
|
|
0a0653358c | ||
|
|
e2ccaf2c91 | ||
|
|
75f5212661 | ||
|
|
1e548af987 | ||
|
|
2c2c828b47 | ||
|
|
19e49a7de7 | ||
|
|
18c9ad25be | ||
|
|
9e3f0304de | ||
|
|
55dbcb646b | ||
|
|
db0ab2e4a9 | ||
|
|
c254eebea2 | ||
|
|
45b0e7dc01 | ||
|
|
4c4c62c7e5 | ||
|
|
cb2038442c | ||
|
|
1d219eb8f2 | ||
|
|
7d33563010 | ||
|
|
e3d34c46c7 | ||
|
|
9129e82804 | ||
|
|
d4f4a64937 | ||
|
|
2b4a51ba24 | ||
|
|
6a8d755b27 | ||
|
|
754fea538a | ||
|
|
4d0b660c73 | ||
|
|
d0173fbdc5 | ||
|
|
62ae4aeac9 | ||
|
|
e98aa81794 | ||
|
|
a5dcac137f | ||
|
|
96215d32b7 | ||
|
|
4a20989028 | ||
|
|
f5fe6a36eb | ||
|
|
852bab6e0b | ||
|
|
52ec27f785 | ||
|
|
c6d617f190 | ||
|
|
1a6ef23ee7 | ||
|
|
759bbeac06 | ||
|
|
15c72fe7d3 | ||
|
|
f6973f9a70 | ||
|
|
f1d5afe72a | ||
|
|
e1f2cd21e7 | ||
|
|
ea5c60af7a | ||
|
|
558567d399 | ||
|
|
0c1899a0af | ||
|
|
28d8ad1e61 | ||
|
|
eb0a0662a3 | ||
|
|
3a21efbaae | ||
|
|
405363da59 | ||
|
|
e5bcfeaad5 | ||
|
|
8b8cfa7a1b | ||
|
|
af131cd1e5 | ||
|
|
a7bb90e7e6 | ||
|
|
25cf36a948 | ||
|
|
cc3ff284f7 | ||
|
|
0af6f271c6 | ||
|
|
441cddbde6 | ||
|
|
1ab09c65f0 | ||
|
|
b9892fc2d0 | ||
|
|
14bf3056de | ||
|
|
b3b9555daa | ||
|
|
db66ffc868 | ||
|
|
91decea302 | ||
|
|
065c19cbbc | ||
|
|
296ebd0341 | ||
|
|
5293e3b277 | ||
|
|
78f5b1e607 | ||
|
|
17af5622ec | ||
|
|
2afce3f1f4 | ||
|
|
dc29b4afa1 | ||
|
|
55fc0cb63b | ||
|
|
5e1eda9e97 | ||
|
|
d582af8cb2 | ||
|
|
0931341a7f | ||
|
|
92916d9820 | ||
|
|
12fbbbb5b3 | ||
|
|
0a603d022f | ||
|
|
2ada9fbee3 | ||
|
|
6e2132c65e | ||
|
|
71dbf0fab2 | ||
|
|
048e97e1ee | ||
|
|
f1c0f1d0a4 | ||
|
|
7242d03f56 | ||
|
|
58bbc33aa6 | ||
|
|
85e82d0bd5 | ||
|
|
d6c23bb5f3 | ||
|
|
8235bca664 | ||
|
|
cd2541a9d2 | ||
|
|
4f7036a405 | ||
|
|
227c9594db | ||
|
|
ea41a60057 | ||
|
|
eee47eae61 | ||
|
|
b6c1816833 | ||
|
|
4e359848d1 | ||
|
|
9cce56caf8 | ||
|
|
f5f0bae2ef | ||
|
|
4fc65aac84 | ||
|
|
bf32477f89 | ||
|
|
969eeff636 | ||
|
|
958607ba9b | ||
|
|
dcfcc1f271 | ||
|
|
bc3c6af95d | ||
|
|
701220246d | ||
|
|
e3b0cdaf69 | ||
|
|
c61cbfc581 | ||
|
|
58073484fe | ||
|
|
8ce1bb1b0b | ||
|
|
3fab42b8d1 | ||
|
|
5b802e9edd | ||
|
|
a9308dd992 | ||
|
|
937a3e7fd6 | ||
|
|
c9caa5687a | ||
|
|
d6950c680f | ||
|
|
4fe33db4f3 | ||
|
|
96d9604fe1 | ||
|
|
0c6eacb141 | ||
|
|
e83a97349f | ||
|
|
6c5f0aecb4 | ||
|
|
d9160f9126 | ||
|
|
1d93de8628 | ||
|
|
54de5b0300 | ||
|
|
b7145959a7 | ||
|
|
1d32a96b01 | ||
|
|
9cc02d6fcb | ||
|
|
5d18c04661 | ||
|
|
e88e17a4b0 | ||
|
|
a75d73b8ac | ||
|
|
bbac6b55d2 | ||
|
|
3eeed39f52 | ||
|
|
7356b7a104 | ||
|
|
54310b925d | ||
|
|
85b5c60d60 | ||
|
|
ab48ae6ca6 | ||
|
|
7a62eb0ebf | ||
|
|
c9423509a9 | ||
|
|
0c77d6d918 | ||
|
|
a0cc6afa7e | ||
|
|
8a08a0211f | ||
|
|
2c044d7262 | ||
|
|
9ff3160808 | ||
|
|
7671ed857d | ||
|
|
b9bbe7a432 | ||
|
|
76a18f5ed3 | ||
|
|
0ee2b02700 | ||
|
|
173984ffd4 | ||
|
|
f309ae0c67 | ||
|
|
26ef2539df | ||
|
|
bc7df4c841 | ||
|
|
26c95bab66 | ||
|
|
95b588f58e | ||
|
|
15e8f6ffb7 | ||
|
|
10cdd7640e | ||
|
|
f216287aee | ||
|
|
38871a98b4 | ||
|
|
9158bdfcf9 | ||
|
|
4dd694ab05 | ||
|
|
553e3ee758 | ||
|
|
a470bf127e | ||
|
|
52adf51d33 | ||
|
|
d688e34521 | ||
|
|
50b8744f24 | ||
|
|
3e6faf8364 | ||
|
|
720de651f3 | ||
|
|
3d382d0354 | ||
|
|
8fea5585e5 | ||
|
|
3f12a17246 | ||
|
|
087ff278aa | ||
|
|
301abcaa49 | ||
|
|
e33b8cc8e5 | ||
|
|
d1bc8a7202 | ||
|
|
77fa3bd7fc | ||
|
|
3162fcf154 | ||
|
|
c618bdfe91 | ||
|
|
dd5c9939a0 | ||
|
|
80082b0880 | ||
|
|
b7aa115bd2 | ||
|
|
0e9950638c | ||
|
|
a957acd893 | ||
|
|
a9244333fb | ||
|
|
63a19bc0a1 | ||
|
|
c25d648321 | ||
|
|
2f6711dd2d | ||
|
|
c450b41e8f | ||
|
|
9937650062 | ||
|
|
56ff88934f | ||
|
|
c1a577797a | ||
|
|
257227920d | ||
|
|
461633cd83 | ||
|
|
a0887e9285 | ||
|
|
23354d371f | ||
|
|
0e838d59d5 | ||
|
|
f305c9d96a | ||
|
|
226665403f | ||
|
|
88d0460e3c | ||
|
|
e09b4f878e | ||
|
|
a7ef3ce58a | ||
|
|
256f33b720 | ||
|
|
e17002c6da | ||
|
|
f2d96b895f | ||
|
|
dade589075 | ||
|
|
0d9cd25a71 | ||
|
|
cb73490107 | ||
|
|
535405521c | ||
|
|
8785c08861 | ||
|
|
0020a37029 | ||
|
|
33cbed592a | ||
|
|
28c0e15058 | ||
|
|
a316411f76 | ||
|
|
66ad519dbd | ||
|
|
2a8dc1d34a | ||
|
|
228b670b4f | ||
|
|
da9fb46b6a | ||
|
|
f4d120b11f | ||
|
|
bc954b75ce | ||
|
|
b787734913 | ||
|
|
fb5a6c20de | ||
|
|
3fda085cbb | ||
|
|
d87ea854bc | ||
|
|
f41cb0d81c | ||
|
|
8427b03a39 | ||
|
|
9cb7786182 | ||
|
|
6666dece5d | ||
|
|
efdd17fa9a | ||
|
|
7f7d5d9f4c | ||
|
|
9b542f7653 | ||
|
|
b27c3ff169 | ||
|
|
115221098a | ||
|
|
5a6a7e8d82 | ||
|
|
9adac5686b | ||
|
|
fad88c2718 | ||
|
|
e205577145 | ||
|
|
6368ab691c | ||
|
|
5b2358c97f | ||
|
|
072430cef5 | ||
|
|
5f7055e282 | ||
|
|
be982d95ea | ||
|
|
b9c8df7019 | ||
|
|
e89ae475f6 | ||
|
|
9476d7fdbb | ||
|
|
da16a799fa | ||
|
|
9e0feb0b64 | ||
|
|
93a600a0a8 | ||
|
|
06ee68f836 | ||
|
|
d6f1abad95 | ||
|
|
933c99110c | ||
|
|
8999bbf297 | ||
|
|
200fc56a4a |
20
.github/ISSUE_TEMPLATE/task.yaml
vendored
20
.github/ISSUE_TEMPLATE/task.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 📝 Task
|
||||
description: Create a task for the team to work on, used internally only. We will delete tasks created by non-team members.
|
||||
title: "[Task]: "
|
||||
labels: [Task]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Are you our team member?
|
||||
description: If you are not our team member, please go to discussions.
|
||||
options:
|
||||
- label: Yes, I am?
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: SubTasks
|
||||
placeholder: |
|
||||
- Sub Task 1
|
||||
- Sub Task 2
|
||||
validations:
|
||||
required: false
|
||||
40
.github/workflows/bridge.yml
vendored
40
.github/workflows/bridge.yml
vendored
@@ -1,14 +1,14 @@
|
||||
# This yaml shares the build bridge steps with ci and nightly.
|
||||
name: Build flutter-rust-bridge
|
||||
# 2023-04-19 15:48:00+00:00
|
||||
# 2023-11-23 18:00:00+00:00
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.10.6"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.75.3"
|
||||
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||
|
||||
jobs:
|
||||
generate_bridge:
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
@@ -23,21 +23,35 @@ jobs:
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install prerequisites
|
||||
run: |
|
||||
sudo apt install ca-certificates -y
|
||||
sudo apt update -y
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang cmake libclang-dev ninja-build llvm-dev libclang-10-dev llvm-10-dev pkg-config
|
||||
sudo apt-get install ca-certificates -y
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
gcc \
|
||||
git \
|
||||
g++ \
|
||||
libclang-10-dev \
|
||||
libclang-dev \
|
||||
libgtk-3-dev \
|
||||
llvm-10-dev \
|
||||
llvm-dev \
|
||||
nasm \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
wget
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.job.target }}
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
targets: ${{ matrix.job.target }}
|
||||
components: ''
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -74,5 +88,5 @@ jobs:
|
||||
path: |
|
||||
./src/bridge_generated.rs
|
||||
./src/bridge_generated.io.rs
|
||||
./flutter/lib/generated_bridge.dart
|
||||
./flutter/lib/generated_bridge.dart
|
||||
./flutter/lib/generated_bridge.freezed.dart
|
||||
|
||||
67
.github/workflows/ci.yml
vendored
67
.github/workflows/ci.yml
vendored
@@ -1,8 +1,12 @@
|
||||
name: CI
|
||||
|
||||
# env:
|
||||
env:
|
||||
# MIN_SUPPORTED_RUST_VERSION: "1.46.0"
|
||||
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2023.10.19
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "8eb57355a4ffb410a2e94c07b4dca2dffbee8e50"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -76,36 +80,63 @@ jobs:
|
||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
|
||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.job.target }} in
|
||||
x86_64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev;;
|
||||
x86_64-unknown-linux-gnu)
|
||||
sudo apt-get -y update
|
||||
sudo apt-get install -y \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
gcc \
|
||||
git \
|
||||
g++ \
|
||||
libasound2-dev \
|
||||
libgstreamer1.0-dev \
|
||||
libgstreamer-plugins-base1.0-dev \
|
||||
libgtk-3-dev \
|
||||
libpulse-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxdo-dev \
|
||||
libxfixes-dev \
|
||||
nasm \
|
||||
wget
|
||||
;;
|
||||
# arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
||||
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
||||
esac
|
||||
|
||||
- name: Restore from cache and install vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
- name: Setup vcpkg with Github Actions binary cache
|
||||
uses: lukka/run-vcpkg@v11
|
||||
with:
|
||||
setupOnly: true
|
||||
vcpkgGitCommitId: '501db0f17ef6df184fcdbfbe0f87cde2313b6ab1' #2023.04.15
|
||||
|
||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: |
|
||||
$VCPKG_ROOT/vcpkg install libvpx libyuv opus aom
|
||||
shell: bash
|
||||
$VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
||||
shell: bash
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.job.target }}
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
targets: ${{ matrix.job.target }}
|
||||
components: ''
|
||||
|
||||
- name: Show version information (Rust, cargo, GCC)
|
||||
shell: bash
|
||||
@@ -117,8 +148,8 @@ jobs:
|
||||
cargo -V
|
||||
rustc -V
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -183,7 +214,9 @@ jobs:
|
||||
;;
|
||||
esac;
|
||||
|
||||
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
#deprecated echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_ENV
|
||||
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
706
.github/workflows/flutter-build.yml
vendored
706
.github/workflows/flutter-build.yml
vendored
File diff suppressed because it is too large
Load Diff
1
.github/workflows/flutter-ci.yml
vendored
1
.github/workflows/flutter-ci.yml
vendored
@@ -19,4 +19,3 @@ jobs:
|
||||
uses: ./.github/workflows/flutter-build.yml
|
||||
with:
|
||||
upload-artifact: false
|
||||
|
||||
|
||||
2
.github/workflows/flutter-nightly.yml
vendored
2
.github/workflows/flutter-nightly.yml
vendored
@@ -12,4 +12,4 @@ jobs:
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
upload-tag: "nightly"
|
||||
upload-tag: "nightly"
|
||||
|
||||
22
.github/workflows/flutter-tag.yml
vendored
22
.github/workflows/flutter-tag.yml
vendored
@@ -15,4 +15,24 @@ jobs:
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
upload-tag: "1.2.2"
|
||||
upload-tag: ${{ env.GITHUB_REF_NAME }}
|
||||
|
||||
update-fdroid-version-file:
|
||||
name: Publish RustDesk version file for F-Droid updater
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate RustDesk version file
|
||||
run: |
|
||||
UPSTREAM_VERNAME="$GITHUB_REF_NAME"
|
||||
UPSTREAM_VERCODE="$(echo "$UPSTREAM_VERNAME" | tr -d '.')"
|
||||
echo "versionName=$UPSTREAM_VERNAME" > rustdesk-version.txt
|
||||
echo "versionCode=$UPSTREAM_VERCODE" >> rustdesk-version.txt
|
||||
shell: bash
|
||||
|
||||
- name: Publish RustDesk version file
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: "fdroid-version"
|
||||
files: |
|
||||
./rustdesk-version.txt
|
||||
|
||||
28
.github/workflows/history.yml
vendored
28
.github/workflows/history.yml
vendored
@@ -4,13 +4,11 @@ on: [workflow_dispatch]
|
||||
|
||||
env:
|
||||
LLVM_VERSION: "10.0"
|
||||
FLUTTER_VERSION: "3.10.6"
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
TAG_NAME: "tmp"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.75.3"
|
||||
# vcpkg version: 2022.05.10
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
|
||||
VERSION: "1.2.2"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
VERSION: "1.2.4"
|
||||
|
||||
jobs:
|
||||
build-for-history-windows:
|
||||
@@ -23,7 +21,7 @@ jobs:
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-2019, arch: x86_64, date: 2023-08-04, ref: 72c198a1e94cc1e0242fce88f92b3f3caedcd0c3 }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ matrix.job.ref }}
|
||||
|
||||
@@ -54,14 +52,18 @@ jobs:
|
||||
Push-Location flutter ; flutter pub get ; Pop-Location
|
||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
||||
|
||||
- name: Setup vcpkg with Github Actions binary cache
|
||||
uses: lukka/run-vcpkg@v11
|
||||
with:
|
||||
vcpkgDirectory: C:\vcpkg
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: |
|
||||
cd C:\
|
||||
git clone https://github.com/Kingtous/rustdesk_thirdpary_lib --depth=1
|
||||
|
||||
$VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
||||
shell: bash
|
||||
|
||||
- name: Build rustdesk
|
||||
env:
|
||||
VCPKG_ROOT: C:\rustdesk_thirdpary_lib\vcpkg
|
||||
run: python3 .\build.py --portable --hwcodec --flutter --feature IddDriver
|
||||
|
||||
- name: Build self-extracted executable
|
||||
@@ -72,7 +74,7 @@ jobs:
|
||||
popd
|
||||
mkdir -p ./SignOutput
|
||||
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ matrix.job.date }}-${{ matrix.job.target }}.exe
|
||||
|
||||
|
||||
- name: Publish Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
88
.github/workflows/vcpkg-deps-linux.yml
vendored
88
.github/workflows/vcpkg-deps-linux.yml
vendored
@@ -1,88 +0,0 @@
|
||||
name: Build vcpkg dependencies for linux clients
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-vcpkg-deps-linux:
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
job:
|
||||
- { arch: armv7, os: ubuntu-20.04 }
|
||||
- { arch: x86_64, os: ubuntu-20.04 }
|
||||
- { arch: aarch64, os: ubuntu-20.04 }
|
||||
steps:
|
||||
- name: Create vcpkg artifacts folder
|
||||
run: mkdir -p /opt/artifacts
|
||||
|
||||
- name: Cache Vcpkg
|
||||
id: cache-vcpkg
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /opt/artifacts
|
||||
key: vcpkg-${{ matrix.job.arch }}
|
||||
|
||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
||||
name: Run vcpkg install on ${{ matrix.job.arch }}
|
||||
id: vcpkg
|
||||
with:
|
||||
arch: ${{ matrix.job.arch }}
|
||||
distro: ubuntu18.04
|
||||
githubToken: ${{ github.token }}
|
||||
setup: |
|
||||
ls -l "/opt/artifacts"
|
||||
dockerRunArgs: |
|
||||
--volume "/opt/artifacts:/artifacts"
|
||||
shell: /bin/bash
|
||||
install: |
|
||||
apt update -y
|
||||
case "${{ matrix.job.arch }}" in
|
||||
x86_64)
|
||||
# CMake 3.15+
|
||||
apt install -y gpg wget ca-certificates
|
||||
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
|
||||
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
|
||||
apt update -y
|
||||
apt install -y curl zip unzip tar git cmake g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev
|
||||
cmake --version
|
||||
gcc -v
|
||||
;;
|
||||
aarch64|armv7)
|
||||
apt install -y curl zip unzip git
|
||||
esac
|
||||
run: |
|
||||
# disable git safe.directory
|
||||
git config --global --add safe.directory "*"
|
||||
case "${{ matrix.job.arch }}" in
|
||||
x86_64)
|
||||
export VCPKG_FORCE_SYSTEM_BINARIES=1
|
||||
pushd /artifacts
|
||||
git clone https://github.com/microsoft/vcpkg.git || true
|
||||
pushd vcpkg
|
||||
git reset --hard ${{ env.VCPKG_COMMIT_ID }}
|
||||
./bootstrap-vcpkg.sh
|
||||
./vcpkg install libvpx libyuv opus aom
|
||||
;;
|
||||
aarch64)
|
||||
pushd /artifacts
|
||||
rm -rf rustdesk_thirdparty_lib
|
||||
git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
|
||||
mkdir -p /artifacts/vcpkg/installed
|
||||
mv ./rustdesk_thirdparty_lib/vcpkg/installed/arm64-linux /artifacts/vcpkg/installed/arm64-linux
|
||||
;;
|
||||
armv7)
|
||||
pushd /artifacts
|
||||
rm -rf rustdesk_thirdparty_lib
|
||||
git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
|
||||
mkdir -p /artifacts/vcpkg/installed
|
||||
mv ./rustdesk_thirdparty_lib/vcpkg/installed/arm-linux /artifacts/vcpkg/installed/arm-linux
|
||||
;;
|
||||
esac
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: vcpkg-artifact-${{ matrix.job.arch }}
|
||||
path: |
|
||||
/opt/artifacts/vcpkg/installed
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -49,4 +49,6 @@ lib/generated_bridge.dart
|
||||
.ssh
|
||||
.devcontainer/.*
|
||||
# build cache in examples
|
||||
examples/**/target/
|
||||
examples/**/target/
|
||||
# ===
|
||||
vcpkg_installed
|
||||
3520
Cargo.lock
generated
3520
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
Cargo.toml
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
description = "A remote control software."
|
||||
default-run = "rustdesk"
|
||||
rust-version = "1.75"
|
||||
|
||||
[lib]
|
||||
name = "librustdesk"
|
||||
@@ -27,11 +28,19 @@ use_dasp = ["dasp"]
|
||||
flutter = ["flutter_rust_bridge"]
|
||||
default = ["use_dasp"]
|
||||
hwcodec = ["scrap/hwcodec"]
|
||||
gpucodec = ["scrap/gpucodec"]
|
||||
mediacodec = ["scrap/mediacodec"]
|
||||
linux_headless = ["pam" ]
|
||||
virtual_display_driver = ["virtual_display"]
|
||||
plugin_framework = []
|
||||
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
|
||||
unix-file-copy-paste = [
|
||||
"dep:x11-clipboard",
|
||||
"dep:x11rb",
|
||||
"dep:percent-encoding",
|
||||
"dep:once_cell",
|
||||
"clipboard/unix-file-copy-paste",
|
||||
]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -47,13 +56,12 @@ cfg-if = "1.0"
|
||||
lazy_static = "1.4"
|
||||
sha2 = "0.10"
|
||||
repng = "0.2"
|
||||
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
|
||||
parity-tokio-ipc = { git = "https://github.com/rustdesk-org/parity-tokio-ipc" }
|
||||
runas = "=1.0" # https://github.com/mitsuhiko/rust-runas/issues/13
|
||||
magnum-opus = { git = "https://github.com/rustdesk/magnum-opus" }
|
||||
magnum-opus = { git = "https://github.com/rustdesk-org/magnum-opus" }
|
||||
dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true }
|
||||
rubato = { version = "0.12", optional = true }
|
||||
samplerate = { version = "0.2", optional = true }
|
||||
async-trait = "0.1"
|
||||
uuid = { version = "1.3", features = ["v4"] }
|
||||
clap = "4.2"
|
||||
rpassword = "7.2"
|
||||
@@ -62,18 +70,19 @@ num_cpus = "1.15"
|
||||
bytes = { version = "1.4", features = ["serde"] }
|
||||
default-net = "0.14"
|
||||
wol-rs = "1.0"
|
||||
flutter_rust_bridge = { version = "1.75", features = ["uuid"], optional = true}
|
||||
flutter_rust_bridge = { version = "=1.80", features = ["uuid"], optional = true}
|
||||
errno = "0.3"
|
||||
rdev = { git = "https://github.com/fufesou/rdev" }
|
||||
url = { version = "2.3", features = ["serde"] }
|
||||
crossbeam-queue = "0.3"
|
||||
hex = "0.4"
|
||||
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "rustls-tls"], default-features=false }
|
||||
chrono = "0.4"
|
||||
cidr-utils = "0.5"
|
||||
libloading = "0.8"
|
||||
fon = "0.6"
|
||||
zip = "0.6"
|
||||
shutdown_hooks = "0.1"
|
||||
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
|
||||
cpal = "0.15"
|
||||
@@ -86,17 +95,18 @@ sys-locale = "0.3"
|
||||
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
|
||||
clipboard = { path = "libs/clipboard" }
|
||||
ctrlc = "3.2"
|
||||
arboard = "3.2"
|
||||
arboard = { version = "3.2", features = ["wayland-data-control"] }
|
||||
system_shutdown = "4.0"
|
||||
qrcode-generator = "4.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi"] }
|
||||
winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi", "pdh", "synchapi", "memoryapi", "shellapi"] }
|
||||
winreg = "0.11"
|
||||
windows-service = "0.6"
|
||||
virtual_display = { path = "libs/virtual_display", optional = true }
|
||||
impersonate_system = { git = "https://github.com/21pages/impersonate-system" }
|
||||
shared_memory = "0.12"
|
||||
shutdown_hooks = "0.1"
|
||||
tauri-winrt-notification = "0.1.2"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2"
|
||||
@@ -105,18 +115,27 @@ dispatch = "0.2"
|
||||
core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
include_dir = "0.7"
|
||||
dark-light = "1.0"
|
||||
fruitbasket = "0.10"
|
||||
objc_id = "0.1"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
|
||||
tray-icon = { git = "https://github.com/rustdesk-org/tray-icon" }
|
||||
tray-icon = { git = "https://github.com/tauri-apps/tray-icon" }
|
||||
tao = { git = "https://github.com/rustdesk-org/tao", branch = "dev" }
|
||||
image = "0.24"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
|
||||
keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
||||
wallpaper = { git = "https://github.com/21pages/wallpaper.rs" }
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||
# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support
|
||||
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "native-tls", "gzip"], default-features=false }
|
||||
|
||||
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
|
||||
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
psimple = { package = "libpulse-simple-binding", version = "2.27" }
|
||||
pulse = { package = "libpulse-binding", version = "2.27" }
|
||||
@@ -128,10 +147,15 @@ dbus = "0.9"
|
||||
dbus-crossroads = "0.5"
|
||||
pam = { git="https://github.com/fufesou/pam", optional = true }
|
||||
users = { version = "0.11" }
|
||||
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
|
||||
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
|
||||
percent-encoding = {version = "2.3", optional = true}
|
||||
once_cell = {version = "1.18", optional = true}
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
jni = "0.21"
|
||||
android-wakelock = { git = "https://github.com/21pages/android-wakelock" }
|
||||
|
||||
[workspace]
|
||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable"]
|
||||
@@ -144,12 +168,11 @@ FileDescription = "RustDesk"
|
||||
|
||||
[target.'cfg(target_os="windows")'.build-dependencies]
|
||||
winres = "0.1"
|
||||
winapi = { version = "0.3", features = [ "winnt" ] }
|
||||
winapi = { version = "0.3", features = [ "winnt", "pdh", "synchapi" ] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
flutter_rust_bridge_codegen = "1.75"
|
||||
os-version = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -169,3 +192,7 @@ panic = 'abort'
|
||||
strip = true
|
||||
#opt-level = 'z' # only have smaller size after strip
|
||||
rpath = true
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = '...' # Platform-specific.
|
||||
#strip = "debuginfo"
|
||||
|
||||
55
Dockerfile
55
Dockerfile
@@ -1,21 +1,54 @@
|
||||
FROM debian
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
WORKDIR /
|
||||
RUN apt update -y && apt install -y g++ gcc git curl nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake unzip zip sudo libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev cmake ninja-build && rm -rf /var/lib/apt/lists/*
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update -y && \
|
||||
apt install --yes --no-install-recommends \
|
||||
g++ \
|
||||
gcc \
|
||||
git \
|
||||
curl \
|
||||
nasm \
|
||||
yasm \
|
||||
libgtk-3-dev \
|
||||
clang \
|
||||
libxcb-randr0-dev \
|
||||
libxdo-dev \
|
||||
libxfixes-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libasound2-dev \
|
||||
libpulse-dev \
|
||||
make \
|
||||
cmake \
|
||||
unzip \
|
||||
zip \
|
||||
sudo \
|
||||
libgstreamer1.0-dev \
|
||||
libgstreamer-plugins-base1.0-dev \
|
||||
ca-certificates \
|
||||
ninja-build && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg
|
||||
RUN /vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
||||
RUN /vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
|
||||
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \
|
||||
/vcpkg/bootstrap-vcpkg.sh -disableMetrics && \
|
||||
/vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
|
||||
|
||||
RUN groupadd -r user && \
|
||||
useradd -r -g user user --home /home/user && \
|
||||
mkdir -p /home/user/rustdesk && \
|
||||
chown -R user: /home/user && \
|
||||
echo "user ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/user
|
||||
|
||||
RUN groupadd -r user && useradd -r -g user user --home /home/user && mkdir -p /home/user && chown user /home/user && echo "user ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/user
|
||||
WORKDIR /home/user
|
||||
RUN curl -LO https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
|
||||
USER user
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh
|
||||
RUN chmod +x rustup.sh
|
||||
RUN ./rustup.sh -y
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
|
||||
chmod +x rustup.sh && \
|
||||
./rustup.sh -y
|
||||
|
||||
USER root
|
||||
ENV HOME=/home/user
|
||||
COPY ./entrypoint /
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
COPY ./entrypoint.sh /
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
42
README.md
42
README.md
@@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>]<br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>]<br>
|
||||
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
|
||||
</p>
|
||||
|
||||
@@ -13,6 +13,8 @@ Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitt
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
[](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/server), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
@@ -47,7 +49,7 @@ Go through [DEVCONTAINER.md](docs/DEVCONTAINER.md) for more info.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Desktop versions use [Sciter](https://sciter.com/) or Flutter for GUI, this tutorial is for Sciter only.
|
||||
Desktop versions use Flutter or Sciter (deprecated) for GUI, this tutorial is for Sciter only, since it is easier and more friendly to start. Check out our [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) for building Flutter version.
|
||||
|
||||
Please download Sciter dynamic library yourself.
|
||||
|
||||
@@ -78,11 +80,12 @@ sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxc
|
||||
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
```
|
||||
|
||||
### openSUSE Tumbleweed
|
||||
### openSUSE Tumbleweed
|
||||
|
||||
```sh
|
||||
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
@@ -133,34 +136,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Change Wayland to X11 (Xorg)
|
||||
|
||||
RustDesk does not support Wayland. Check [this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) to configuring Xorg as the default GNOME session.
|
||||
|
||||
## Wayland support
|
||||
|
||||
Wayland does not seem to provide any API for sending keypresses to other windows. Therefore, the RustDesk uses an API from a lower level, namely the `/dev/uinput` device (Linux kernel level).
|
||||
|
||||
When Wayland is the controlled side, you have to start in the following way:
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Notice**: Wayland screen recording uses different interfaces. RustDesk currently only supports org.freedesktop.portal.ScreenCast.
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## How to build with Docker
|
||||
|
||||
Begin by cloning the repository and building the Docker container:
|
||||
@@ -196,12 +171,13 @@ Please ensure that you are running these commands from the root of the RustDesk
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: file copy and paste implementation for Windows, Linux, macOS.
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: obsolete Sciter UI (deprecated)
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for desktop and mobile
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
|
||||
|
||||
## Snapshots
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.2.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.4.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.2
|
||||
version: 1.2.4
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.2.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.4.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.2
|
||||
version: 1.2.4
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
60
build.py
60
build.py
@@ -16,7 +16,7 @@ osx = platform.platform().startswith(
|
||||
hbb_name = 'rustdesk' + ('.exe' if windows else '')
|
||||
exe_path = 'target/release/' + hbb_name
|
||||
if windows:
|
||||
flutter_build_dir = 'build/windows/runner/Release/'
|
||||
flutter_build_dir = 'build/windows/x64/runner/Release/'
|
||||
elif osx:
|
||||
flutter_build_dir = 'build/macos/Build/Products/Release/'
|
||||
else:
|
||||
@@ -24,18 +24,21 @@ else:
|
||||
flutter_build_dir_2 = f'flutter/{flutter_build_dir}'
|
||||
skip_cargo = False
|
||||
|
||||
|
||||
def get_arch() -> str:
|
||||
custom_arch = os.environ.get("ARCH")
|
||||
if custom_arch is None:
|
||||
return "amd64"
|
||||
return custom_arch
|
||||
|
||||
|
||||
def system2(cmd):
|
||||
err = os.system(cmd)
|
||||
if err != 0:
|
||||
print(f"Error occurred when executing: {cmd}. Exiting.")
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def get_version():
|
||||
with open("Cargo.toml", encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
@@ -46,17 +49,11 @@ def get_version():
|
||||
|
||||
def parse_rc_features(feature):
|
||||
available_features = {
|
||||
'IddDriver': {
|
||||
'platform': ['windows'],
|
||||
'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.3/RustDeskIddDriver_x64.zip',
|
||||
'checksum_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.3/checksum_md5',
|
||||
'exclude': ['README.md', 'certmgr.exe', 'install_cert_runas_admin.bat', 'RustDeskIddApp.exe'],
|
||||
},
|
||||
'PrivacyMode': {
|
||||
'platform': ['windows'],
|
||||
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1'
|
||||
'/TempTopMostWindow_x64_pic_en.zip',
|
||||
'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1/checksum_md5',
|
||||
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.3'
|
||||
'/TempTopMostWindow_x64.zip',
|
||||
'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.3/checksum_md5',
|
||||
'include': ['WindowInjection.dll'],
|
||||
}
|
||||
}
|
||||
@@ -109,7 +106,7 @@ def make_parser():
|
||||
nargs='+',
|
||||
default='',
|
||||
help='Integrate features, windows only.'
|
||||
'Available: IddDriver, PrivacyMode. Special value is "ALL" and empty "". Default is empty.')
|
||||
'Available: PrivacyMode. Special value is "ALL" and empty "". Default is empty.')
|
||||
parser.add_argument('--flutter', action='store_true',
|
||||
help='Build flutter package', default=False)
|
||||
parser.add_argument(
|
||||
@@ -118,11 +115,21 @@ def make_parser():
|
||||
help='Enable feature hwcodec' + (
|
||||
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--gpucodec',
|
||||
action='store_true',
|
||||
help='Enable feature gpucodec, only available on windows now.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--portable',
|
||||
action='store_true',
|
||||
help='Build windows portable'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--unix-file-copy-paste',
|
||||
action='store_true',
|
||||
help='Build with unix file copy paste feature'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--flatpak',
|
||||
action='store_true',
|
||||
@@ -172,8 +179,8 @@ def generate_build_script_for_docker():
|
||||
export VCPKG_ROOT=`pwd`/vcpkg
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
popd
|
||||
$VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
||||
# build rustdesk
|
||||
./build.py --flutter --hwcodec
|
||||
''')
|
||||
@@ -185,6 +192,7 @@ def download_extract_features(features, res_dir):
|
||||
import re
|
||||
|
||||
proxy = ''
|
||||
|
||||
def req(url):
|
||||
if not proxy:
|
||||
return url
|
||||
@@ -196,9 +204,9 @@ def download_extract_features(features, res_dir):
|
||||
|
||||
for (feat, feat_info) in features.items():
|
||||
includes = feat_info['include'] if 'include' in feat_info and feat_info['include'] else []
|
||||
includes = [ re.compile(p) for p in includes ]
|
||||
includes = [re.compile(p) for p in includes]
|
||||
excludes = feat_info['exclude'] if 'exclude' in feat_info and feat_info['exclude'] else []
|
||||
excludes = [ re.compile(p) for p in excludes ]
|
||||
excludes = [re.compile(p) for p in excludes]
|
||||
|
||||
print(f'{feat} download begin')
|
||||
download_filename = feat_info['zip_url'].split('/')[-1]
|
||||
@@ -261,10 +269,10 @@ def external_resources(flutter, args, res_dir):
|
||||
|
||||
def get_features(args):
|
||||
features = ['inline'] if not args.flutter else []
|
||||
if windows:
|
||||
features.append('virtual_display_driver')
|
||||
if args.hwcodec:
|
||||
features.append('hwcodec')
|
||||
if args.gpucodec:
|
||||
features.append('gpucodec')
|
||||
if args.flutter:
|
||||
features.append('flutter')
|
||||
features.append('flutter_texture_render')
|
||||
@@ -272,6 +280,8 @@ def get_features(args):
|
||||
features.append('flatpak')
|
||||
if args.appimage:
|
||||
features.append('appimage')
|
||||
if args.unix_file_copy_paste:
|
||||
features.append('unix-file-copy-paste')
|
||||
print("features:", features)
|
||||
return features
|
||||
|
||||
@@ -350,6 +360,7 @@ def build_flutter_deb(version, features):
|
||||
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
def build_deb_from_folder(version, binary_folder):
|
||||
os.chdir('flutter')
|
||||
system2('mkdir -p tmpdeb/usr/bin/')
|
||||
@@ -388,10 +399,12 @@ def build_deb_from_folder(version, binary_folder):
|
||||
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
def build_flutter_dmg(version, features):
|
||||
if not skip_cargo:
|
||||
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
||||
system2(f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
||||
system2(
|
||||
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
||||
# copy dylib
|
||||
system2(
|
||||
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
||||
@@ -481,6 +494,7 @@ def main():
|
||||
system2('mv target/release/rustdesk.exe target/release/RustDesk.exe')
|
||||
pa = os.environ.get('P')
|
||||
if pa:
|
||||
# https://certera.com/kb/tutorial-guide-for-safenet-authentication-client-for-code-signing/
|
||||
system2(
|
||||
f'signtool sign /a /v /p {pa} /debug /f .\\cert.pfx /t http://timestamp.digicert.com '
|
||||
'target\\release\\rustdesk.exe')
|
||||
@@ -545,13 +559,6 @@ def main():
|
||||
'cp libsciter.dylib target/release/bundle/osx/RustDesk.app/Contents/MacOS/')
|
||||
# https://github.com/sindresorhus/create-dmg
|
||||
system2('/bin/rm -rf *.dmg')
|
||||
plist = "target/release/bundle/osx/RustDesk.app/Contents/Info.plist"
|
||||
txt = open(plist).read()
|
||||
with open(plist, "wt") as fh:
|
||||
fh.write(txt.replace("</dict>", """
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>"""))
|
||||
pa = os.environ.get('P')
|
||||
if pa:
|
||||
system2('''
|
||||
@@ -564,7 +571,8 @@ def main():
|
||||
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/*
|
||||
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app
|
||||
'''.format(pa))
|
||||
system2('create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version)
|
||||
system2(
|
||||
'create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version)
|
||||
os.rename('RustDesk %s.dmg' %
|
||||
version, 'rustdesk-%s.dmg' % version)
|
||||
if pa:
|
||||
@@ -584,7 +592,7 @@ def main():
|
||||
else:
|
||||
print('Not signed')
|
||||
else:
|
||||
# buid deb package
|
||||
# build deb package
|
||||
system2(
|
||||
'mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb')
|
||||
system2('dpkg-deb -R rustdesk.deb tmpdeb')
|
||||
|
||||
56
build.rs
56
build.rs
@@ -41,7 +41,7 @@ fn build_manifest() {
|
||||
}
|
||||
}
|
||||
|
||||
fn install_oboe() {
|
||||
fn install_android_deps() {
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
if target_os != "android" {
|
||||
return;
|
||||
@@ -49,6 +49,8 @@ fn install_oboe() {
|
||||
let mut target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if target_arch == "x86_64" {
|
||||
target_arch = "x64".to_owned();
|
||||
} else if target_arch == "x86" {
|
||||
target_arch = "x86".to_owned();
|
||||
} else if target_arch == "aarch64" {
|
||||
target_arch = "arm64".to_owned();
|
||||
} else {
|
||||
@@ -66,62 +68,16 @@ fn install_oboe() {
|
||||
path.join("lib").to_str().unwrap()
|
||||
)
|
||||
);
|
||||
println!("cargo:rustc-link-lib=ndk_compat");
|
||||
println!("cargo:rustc-link-lib=oboe");
|
||||
println!("cargo:rustc-link-lib=oboe_wrapper");
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
println!("cargo:rustc-link-lib=OpenSLES");
|
||||
// I always got some strange link error with oboe, so as workaround, put oboe.cc into oboe src: src/common/AudioStreamBuilder.cpp
|
||||
// also to avoid libc++_shared not found issue, cp ndk's libc++_shared.so to jniLibs, e.g.
|
||||
// ./flutter_hbb/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
|
||||
// let include = path.join("include");
|
||||
//cc::Build::new().file("oboe.cc").include(include).compile("oboe_wrapper");
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
fn gen_flutter_rust_bridge() {
|
||||
if !std::env::var("RUN_FFIGEN").is_ok() {
|
||||
return;
|
||||
}
|
||||
use lib_flutter_rust_bridge_codegen::{
|
||||
config_parse, frb_codegen, get_symbols_if_no_duplicates, RawOpts,
|
||||
};
|
||||
let llvm_path = match std::env::var("LLVM_HOME") {
|
||||
Ok(path) => Some(vec![path]),
|
||||
Err(_) => None,
|
||||
};
|
||||
// Tell Cargo that if the given file changes, to rerun this build script.
|
||||
println!("cargo:rerun-if-changed=src/flutter_ffi.rs");
|
||||
// Options for frb_codegen
|
||||
let raw_opts = RawOpts {
|
||||
// Path of input Rust code
|
||||
rust_input: vec!["src/flutter_ffi.rs".to_string()],
|
||||
// Path of output generated Dart code
|
||||
dart_output: vec!["flutter/lib/generated_bridge.dart".to_string()],
|
||||
// Path of output generated C header
|
||||
c_output: Some(vec!["flutter/macos/Runner/bridge_generated.h".to_string()]),
|
||||
/// Path to the installed LLVM
|
||||
llvm_path,
|
||||
// for other options use defaults
|
||||
..Default::default()
|
||||
};
|
||||
// get opts from raw opts
|
||||
let configs = config_parse(raw_opts);
|
||||
// generation of rust api for ffi
|
||||
let all_symbols = get_symbols_if_no_duplicates(&configs).unwrap();
|
||||
for config in configs.iter() {
|
||||
frb_codegen(config, &all_symbols).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
hbb_common::gen_version();
|
||||
install_oboe();
|
||||
// there is problem with cfg(target_os) in build.rs, so use our workaround
|
||||
// let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
// if target_os == "android" || target_os == "ios" {
|
||||
#[cfg(feature = "flutter")]
|
||||
gen_flutter_rust_bridge();
|
||||
// return;
|
||||
// }
|
||||
install_android_deps();
|
||||
#[cfg(all(windows, feature = "inline"))]
|
||||
build_manifest();
|
||||
#[cfg(windows)]
|
||||
|
||||
101
docs/CODE_OF_CONDUCT-JP.md
Normal file
101
docs/CODE_OF_CONDUCT-JP.md
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
# コントリビューター規約 行動規範
|
||||
|
||||
## 私たちの誓い
|
||||
|
||||
私たちは、メンバー、貢献者、リーダーとして、年齢、体格、目に見える・見えない障害、
|
||||
民族性、性の特徴、性自認と表現、経験のレベル、教育、社会経済的地位、国籍、個人の外見、
|
||||
人種、宗教、性的自認と指向に関係なく、誰もがハラスメントのないコミュニティに参加できるようにすることを誓います。
|
||||
|
||||
私たちは、開かれた、歓迎された、多様で、包容力のある、健全な地域社会に貢献するように行動し、交流することを誓います。
|
||||
|
||||
## 私たちの基準
|
||||
|
||||
地域社会にとって好ましい環境にコントリビュートする行動の例には、以下のようなものがある:
|
||||
|
||||
* 他者への共感と優しさ
|
||||
* 異なる意見、視点、経験を尊重すること
|
||||
* 建設的なフィードバックを与え、潔く受け入れること
|
||||
* 私たちの過ちによって影響を受けた人々に責任を受け入れ、謝罪し、経験から学ぶこと
|
||||
* 私たち個人にとってだけでなく、地域社会全体にとって何が最善であるかに焦点を合わせること
|
||||
|
||||
許されない行為の例:
|
||||
|
||||
* 性的な言葉やイメージの使用、性的な注目や誘いかけ
|
||||
* 荒らし、侮辱的または軽蔑的なコメント、個人的または政治的な攻撃
|
||||
* 公的または私的な嫌がらせ
|
||||
* 明示的な許可なく、他人の住所や電子メールアドレスなどの個人情報を公開すること
|
||||
* 職業上不適切と見なされるその他の行為
|
||||
|
||||
## 執行責任
|
||||
|
||||
コミュニティリーダーは、許容される行動の基準を明確にし、実施する責任があり、
|
||||
不適切、脅迫的、攻撃的、または有害と判断される行動に対しては、適切かつ公正な是正措置をとります
|
||||
|
||||
コミュニティリーダーは、本行動規範に沿わないコメント、コミット、コード、ウィキ編集、
|
||||
課題、その他の貢献を削除、編集、拒否する権利と責任を有し、適切な場合にはモデレーション決定の理由を伝えます。
|
||||
|
||||
## スコープ
|
||||
|
||||
この行動規範は、すべてのコミュニティスペースで適用され、また個人が公的なスペースでコミュニティを公式に代表している場合にも適用されます。
|
||||
当コミュニティを代表する例としては、公式 E メールアドレスの使用、公式ソーシャルメディアアカウントによる投稿、
|
||||
オンラインまたはオフラインのイベントでの任命された代表としての行動などが挙げられます。
|
||||
|
||||
## 施行
|
||||
|
||||
虐待、ハラスメント、その他容認できない行為があった場合は、[info@rustdesk.com](mailto:info@rustdesk.com) の
|
||||
執行担当コミュニティリーダーに報告することができる。
|
||||
すべての苦情は、迅速かつ公正に検討・調査されます。
|
||||
|
||||
すべての地域社会の指導者は、いかなる事件の報告者のプライバシーと安全を尊重する義務がある。
|
||||
|
||||
## 執行ガイドライン
|
||||
|
||||
コミュニティリーダーは、本行動規範に違反すると判断した行為に対する結果を決定する際、
|
||||
以下の「コミュニティへの影響に関するガイドライン」に従います:
|
||||
|
||||
### 1. 修正
|
||||
|
||||
**コミュニティへの影響**: 不適切な言葉の使用、またはプロフェッショナルでない、あるいは地域社会で歓迎されないとみなされるその他の行動。
|
||||
|
||||
**結果**: コミュニティリーダーからの私的な書面による警告。違反の性質と、
|
||||
なぜその行為が不適切であったのかについての説明を明確にする。公的な謝罪が要求される場合もある。
|
||||
|
||||
### 2. 警告
|
||||
|
||||
**コミュニティへの影響**: 単一の出来事または一連の行動による違反。
|
||||
|
||||
**結果**: 行動を続けた場合の結果を伴う警告。一定期間、行動規範の実施者との勝手な交流を含め、
|
||||
関係者と交流しないこと。これには、ソーシャルメディアなどの外部チャンネルだけでなく、
|
||||
コミュニティスペースでの交流を避けることも含まれます。これらの条件に違反した場合、一時的または恒久的に追放される可能性があります。
|
||||
|
||||
### 3. 一時的な禁止
|
||||
|
||||
**コミュニティへの影響**: 継続的な不適切な行動を含む、コミュニティ基準に対する重大な違反。
|
||||
|
||||
**結果**: 一定期間、地域社会とのあらゆる交流や公的なコミュニケーションを一時的に禁止すること。
|
||||
この期間中は、行動規範を執行する人々との未承諾の交流を含め、関係者との公私にわたる交流は許されない。
|
||||
これらの条件に違反した場合、永久禁止となる可能性があります。
|
||||
|
||||
### 4. 永久禁止
|
||||
|
||||
**コミュニティへの影響**: 継続的な不適切な行動、個人に対する嫌がらせ、
|
||||
または個人クラスに対する攻撃や中傷など、地域社会の基準に対する違反のパターンを示すこと。
|
||||
|
||||
**結果**: コミュニティ内でのあらゆる公的交流の永久禁止。
|
||||
|
||||
## 帰属
|
||||
|
||||
この行動規範は、[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0] に掲載されている
|
||||
[コントリビューター規約][ホームページ]、バージョン 2.0 から引用したものです。
|
||||
|
||||
コミュニティインパクトガイドラインは、[Mozilla's code of conduct enforcement ladder][Mozilla CoC] に触発されました。
|
||||
|
||||
この行動規範に関するよくある質問については、[https://www.contributor-covenant.org/faq][FAQ] の FAQ をご覧ください。
|
||||
翻訳は [https://www.contributor-covenant.org/translations][翻訳] にあります。
|
||||
|
||||
[ホームページ]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[翻訳]: https://www.contributor-covenant.org/translations
|
||||
89
docs/CODE_OF_CONDUCT-TR.md
Normal file
89
docs/CODE_OF_CONDUCT-TR.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Katkıda Bulunanların Davranış Kuralları
|
||||
|
||||
## Taahhüdümüz
|
||||
|
||||
Biz üyeler, katkıda bulunanlar ve liderler olarak, yaş, beden büyüklüğü, görünür veya görünmez engellilik, etnik köken, cinsiyet özellikleri, cinsiyet kimliği ve ifadesi, deneyim seviyesi, eğitim, sosyo-ekonomik durum, milliyet, kişisel görünüm, ırk, din veya cinsel kimlik ve yönelim ayrımı gözetmeksizin herkes için topluluğumuzdaki katılımı taciz içermeyen bir deneyim haline getirmeyi taahhüt ederiz.
|
||||
|
||||
Açık, hoşgörülü, çeşitli, kapsayıcı ve sağlıklı bir topluluğa katkıda bulunacak şekillerde hareket etmeyi ve etkileşimde bulunmayı taahhüt ederiz.
|
||||
|
||||
## Standartlarımız
|
||||
|
||||
Topluluğumuz için olumlu bir ortam yaratmaya katkıda bulunan davranış örnekleri şunlardır:
|
||||
|
||||
* Diğer insanlara empati ve nezaket göstermek
|
||||
* Farklı görüşlere, bakış açılarına ve deneyimlere saygılı olmak
|
||||
* Yapıcı eleştiriyi vermek ve zarifçe kabul etmek
|
||||
* Hatalarımızdan etkilenenlere sorumluluk kabul etmek, özür dilemek ve deneyimden öğrenmek
|
||||
* Sadece bireyler olarak değil, aynı zamanda genel topluluk için en iyisi üzerine odaklanmak
|
||||
|
||||
Kabul edilemez davranış örnekleri şunları içerir:
|
||||
|
||||
* Cinselleştirilmiş dil veya imgelerin kullanımı ve cinsel ilgi veya herhangi bir türdeki yaklaşımlar
|
||||
* Trollük, aşağılayıcı veya hakaret içeren yorumlar ve kişisel veya siyasi saldırılar
|
||||
* Kamuoyu veya özel taciz
|
||||
* Başkalarının fiziksel veya e-posta adresi gibi özel bilgilerini, açık izinleri olmadan yayınlamak
|
||||
* Profesyonel bir ortamda makul bir şekilde uygunsuz kabul edilebilecek diğer davranışlar
|
||||
|
||||
## Uygulama Sorumlulukları
|
||||
|
||||
Topluluk liderleri, kabul edilebilir davranış standartlarımızı açıklığa kavuşturmak ve uygulamakla sorumludur ve uygunsuz, tehditkar, saldırgan veya zarar verici herhangi bir davranışa yanıt olarak uygun ve adil düzeltici önlemler alacaklardır.
|
||||
|
||||
Topluluk liderleri, bu Davranış Kurallarına uyumlu olmayan yorumları, taahhütlerini veya kodu, wiki düzenlemelerini, sorunları ve diğer katkıları kaldırma, düzenleme veya reddetme hakkına sahiptir. Denetim kararlarının nedenlerini uygun olduğunda ileteceklerdir.
|
||||
|
||||
## Kapsam
|
||||
|
||||
Bu Davranış Kuralları, tüm topluluk alanlarında geçerlidir ve aynı zamanda birey resmi olarak topluluğu halka açık alanlarda temsil ettiğinde de geçerlidir. Topluluğumuzu temsil etme örnekleri, resmi bir e-posta adresi kullanmak, resmi bir sosyal medya hesabı üzerinden gönderi yapmak veya çevrimiçi veya çevrimdışı bir etkinlikte atanmış bir temsilci olarak hareket etmeyi içerir.
|
||||
|
||||
## Uygulama
|
||||
|
||||
Taciz edici, rahatsız edici veya başka türlü kabul edilemez davranış örnekleri, [info@rustdesk.com](mailto:info@rustdesk.com) adresindeki uygulama sorumlularına bildirilebilir. Tüm şikayetler hızlı ve adil bir şekilde incelenecek ve araştırılacaktır.
|
||||
|
||||
Tüm topluluk liderleri, olayın raporlayıcısının gizliliğine ve güvenliğine saygı gösterme yükümlülüğündedir.
|
||||
|
||||
## Uygulama Kılavuzları
|
||||
|
||||
Topluluk liderleri, bu Davranış Kurallarını ihlal olarak değerlendirdikleri herhangi bir eylem için bu Topluluk Etkisi Kılavuzlarını izleyeceklerdir:
|
||||
|
||||
### 1. Düzeltme
|
||||
|
||||
**Topluluk Etkisi**: Topluluk içinde profesyonel veya hoşgörülü olmayan uygun olmayan dil veya diğer davranışların kullanımı.
|
||||
|
||||
**Sonuç**: Topluluk liderlerinden özel ve yazılı bir uyarı almak, ihlalin niteliği ve davranışın nedeninin açıklığa kavuşturulması. Bir kamu özrü istenebilir.
|
||||
|
||||
### 2. Uyarı
|
||||
|
||||
**Topluluk Etkisi**: Tek bir olay veya dizi aracılığıyla bir ihlal.
|
||||
|
||||
**Sonuç**: Devam eden davranış için sonuçları olan bir uyarı. Topluluk liderleri de dahil olmak üzere ihlalle ilgili kişilerle etkileşim, belirli bir süre boyunca önerilmez. Bu, topluluk alanlarında ve sosyal medya gibi harici kanallarda etkileşimleri içerir. Bu koşulları ihlal etmek geçici veya kalıcı bir yasağa yol açabilir.
|
||||
|
||||
### 3. Geçici Yasak
|
||||
|
||||
**Topluluk Etkisi**: Sürekli uygunsuz davranış da dahil olmak üzere topluluk standartlarının ciddi bir ihlali.
|
||||
|
||||
**Sonuç**: Belirli bir süre için toplulukla herhangi bir türdeki etkileşim veya halka açık iletişimden geçici bir yasak. Bu dönem boyunca, toplul
|
||||
|
||||
ukla veya uygulama kurallarını uygulayanlarla her türlü kamuoyu veya özel etkileşim izin verilmez. Bu koşulları ihlal etmek geçici veya kalıcı bir yasağa yol açabilir.
|
||||
|
||||
### 4. Kalıcı Yasak
|
||||
|
||||
**Topluluk Etkisi**: Topluluk standartlarının ihlalinde sürekli bir desen sergilemek, bireye sürekli olarak uygun olmayan davranışlarda bulunmak, bir bireye tacizde bulunmak veya birey sınıflarına karşı saldırganlık veya aşağılama yapmak.
|
||||
|
||||
**Sonuç**: Topluluk içinde her türlü halka açık etkileşimden kalıcı bir yasak.
|
||||
|
||||
## Atıf
|
||||
|
||||
Bu Davranış Kuralları, [Contributor Covenant][anasayfa], 2.0 sürümünden uyarlanmıştır ve
|
||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0] adresinde bulunmaktadır.
|
||||
|
||||
Topluluk Etkisi Kılavuzları,
|
||||
[Mozilla'nın davranış kuralları uygulama merdiveni][Mozilla DK] tarafından ilham alınarak oluşturulmuştur.
|
||||
|
||||
Bu davranış kuralları hakkında yaygın soruların cevapları için, SSS'ye göz atın:
|
||||
[https://www.contributor-covenant.org/faq][SSS]. Çeviriler,
|
||||
[https://www.contributor-covenant.org/translations][çeviriler] adresinde bulunabilir.
|
||||
|
||||
[anasayfa]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[Mozilla DK]: https://github.com/mozilla/diversity
|
||||
[SSS]: https://www.contributor-covenant.org/faq
|
||||
[çeviriler]: https://www.contributor-covenant.org/translations
|
||||
31
docs/CONTRIBUTING-ID.md
Normal file
31
docs/CONTRIBUTING-ID.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Berkontribusi dalam pengembangan RustDesk
|
||||
|
||||
RustDesk mengajak semua orang untuk ikut berkontribusi. Berikut ini adalah panduan jika kamu sedang mempertimbangkan untuk memberikan bantuan kepada kami:
|
||||
|
||||
## Kontirbusi
|
||||
|
||||
Untuk melakukan kontribusi pada RustDesk atau dependensinya, sebaiknya dilakukan dalam bentuk pull request di GitHub. Setiap permintaan pull request akan ditinjau oleh kontributor utama atau seseorang yang memiliki wewenang untuk menggabungkan perubahan kode, baik yang sudah dimasukkan ke dalam struktur utama ataupun memberikan umpan balik untuk perubahan yang akan diperlukan. Setiap kontribusi harus sesuai dengan format ini, juga termasuk yang berasal dari kontributor utama.
|
||||
|
||||
Apabila kamu ingin mengatasi sebuah masalah yang sudah ada di daftar issue, harap klaim terlebih dahulu dengan memberikan komentar pada GitHub issue yang ingin kamu kerjakan. Hal ini dilakukan untuk mencegah terjadinya duplikasi dari kontributor pada daftar issue yang sama.
|
||||
|
||||
## Pemeriksaan Pull Request
|
||||
|
||||
- Branch yang menjadi acuan adalah branch master dari repositori utama dan, jika diperlukan, lakukan rebase ke branch master yang terbaru sebelum kamu mengirim pull request. Apabila terdapat masalah kita melakukan proses merge ke branch master kemungkinan kamu akan diminta untuk melakukan rebase pada perubahan yang sudah dibuat.
|
||||
|
||||
- Sebaiknya buatlah commit seminimal mungkin, sambil memastikan bahwa setiap commit yang dibuat sudah benar (contohnya, setiap commit harus bisa di kompilasi dan berhasil melewati tahap test).
|
||||
|
||||
- Setiap commit harus disertai dengan tanda tangan Sertifikat Asal Pengembang (Developer Certificate of Origin) (<http://developercertificate.org>), yang mengindikasikan bahwa kamu (and your employer if applicable) bersedia untuk patuh terhadap persyaratan dari [lisensi projek](../LICENCE). Di git bash, ini adalah opsi parameter `-s` pada `git commit`
|
||||
|
||||
- Jika perubahan yang kamu buat tidak mendapat tinjauan atau kamu membutuhkan orang tertentu untuk meninjaunya, kamu bisa @-reply seorang reviewer meminta peninjauan dalam permintaan pull request atau komentar, atau kamu bisa meminta tinjauan melalui [email](mailto:info@rustdesk.com).
|
||||
|
||||
- Sertakan test yang relevan terhadap bug atau fitur baru yang sudah dikerjakan.
|
||||
|
||||
Untuk instruksi Git yang lebih lanjut, cek disini [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
|
||||
|
||||
## Tindakan
|
||||
|
||||
<https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT-ID.md>
|
||||
|
||||
## Komunikasi
|
||||
|
||||
Kontributor RustDesk sering berkunjung ke [Discord](https://discord.gg/nDceKgxnkV).
|
||||
41
docs/CONTRIBUTING-JP.md
Normal file
41
docs/CONTRIBUTING-JP.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# RustDesk へのコントリビュート
|
||||
|
||||
RustDesk は皆さんからのコントリビュートを歓迎します。ご協力いただける方のガイドラインは
|
||||
以下の通りです:
|
||||
|
||||
## コントリビューション
|
||||
|
||||
RustDesk またはその依存関係へのコントリビュートは、GitHub のプルリクエストの形で行ってください。
|
||||
それぞれのプルリクエストは、コアコントリビューター(パッチの適用を許可されている人)によってレビューされ、
|
||||
メインツリーに適用されるか、必要な変更についてのフィードバックが与えられます。
|
||||
コアコントリビューターからのものであっても、すべてのコントリビューターはこのフォーマットに従うべきです。
|
||||
|
||||
ある issue に取り組みたい場合は、GitHub の issue にコメントすることで、まずその対応を主張してください。
|
||||
これは、同じ issue に対するコントリビューターの重複作業を防ぐためです。
|
||||
|
||||
## プルリクエストのチェックリスト
|
||||
|
||||
- master ブランチからブランチし、必要であればプルリクエストを提出する前に現在の master ブランチにリベースしてください。
|
||||
master と正しくマージできない場合、変更をリベースするよう求められる可能性があります。
|
||||
|
||||
- コミットは、各コミットが独立して正しい(すなわち、各コミットがコンパイルされ、テストに合格する)ことを保証しながら、
|
||||
可能な限り小さくすべきです。
|
||||
|
||||
- コミットには、Developer Certificate of Origin (http://developercertificate.org) の sign-off を添えてください。
|
||||
これは、あなた(および該当する場合はあなたの雇用主)が [プロジェクトのライセンス](../LICENCE) の条項に拘束されることに
|
||||
同意していることを示すものです。git では、これは `git commit` の `-s` オプションを使います。
|
||||
|
||||
- もしあなたのパッチがレビューされなかったり、特定の人にレビューしてもらう必要がある場合、
|
||||
プルリクエストやコメントでレビューを依頼するレビュアーに@返信したり、[email](mailto:info@rustdesk.com) でレビューを依頼することができます。
|
||||
|
||||
- 修正したバグや新機能に関連するテストを追加する。
|
||||
|
||||
具体的なgitの手順については、[GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow)を参照してください。
|
||||
|
||||
## 行動規範
|
||||
|
||||
https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md
|
||||
|
||||
## コミュニケーション
|
||||
|
||||
RustDesk のコントリビューターは、[Discord](https://discord.gg/nDceKgxnkV) を良く使っています。
|
||||
31
docs/CONTRIBUTING-TR.md
Normal file
31
docs/CONTRIBUTING-TR.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# RustDesk'a Katkı Sağlamak
|
||||
|
||||
RustDesk, herkesten katkıyı memnuniyetle karşılar. Eğer bize yardımcı olmayı düşünüyorsanız, işte rehberlik eden kurallar:
|
||||
|
||||
## Katkılar
|
||||
|
||||
RustDesk veya bağımlılıklarına yapılan katkılar, GitHub pull istekleri şeklinde yapılmalıdır. Her bir pull isteği, çekirdek katkıcı tarafından gözden geçirilecek (yamaları kabul etme izni olan biri) ve ana ağaca kabul edilecek veya gerekli değişiklikler için geri bildirim verilecektir. Tüm katkılar bu formata uymalıdır, çekirdek katkıcılardan gelenler bile.
|
||||
|
||||
Eğer bir konu üzerinde çalışmak isterseniz, önce üzerinde çalışmak istediğinizi belirten bir yorum yaparak konuyu talep ediniz. Bu, katkı sağlayanların aynı konuda çift çalışmasını engellemek içindir.
|
||||
|
||||
## Pull İstek Kontrol Listesi
|
||||
|
||||
- Master dalından dallandırın ve gerekiyorsa pull isteğinizi göndermeden önce mevcut master dalına rebase yapın. Eğer master ile temiz bir şekilde birleşmezse, değişikliklerinizi rebase yapmanız istenebilir.
|
||||
|
||||
- Her bir commit mümkün olduğunca küçük olmalıdır, ancak her commit'in bağımsız olarak doğru olduğundan emin olun (örneğin, her commit derlenebilir ve testleri geçmelidir).
|
||||
|
||||
- Commit'ler, bir Geliştirici Sertifikası ile desteklenmelidir (http://developercertificate.org). Bu, [proje lisansının](../LICENCE) koşullarına uymayı kabul ettiğinizi gösteren bir onaydır. Git'te bunu `git commit` seçeneği olarak `-s` seçeneği ile yapabilirsiniz.
|
||||
|
||||
- Yamalarınız gözden geçirilmiyorsa veya belirli bir kişinin gözden geçirmesine ihtiyacınız varsa, çekme isteği veya yorum içinde bir gözden geçirmeyi istemek için bir inceleyiciyi @etiketleyebilir veya inceleme için [e-posta](mailto:info@rustdesk.com) ile talep edebilirsiniz.
|
||||
|
||||
- Düzelttiğiniz hatanın veya eklediğiniz yeni özelliğin ilgili testlerini ekleyin.
|
||||
|
||||
Daha spesifik git talimatları için, [GitHub iş akışı 101](https://github.com/servo/servo/wiki/GitHub-workflow)'e bakınız.
|
||||
|
||||
## Davranış
|
||||
|
||||
https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT-TR.md
|
||||
|
||||
## İletişim
|
||||
|
||||
RustDesk katkı sağlayıcıları, [Discord](https://discord.gg/nDceKgxnkV) kanalını sık sık ziyaret ederler.
|
||||
14
docs/DEVCONTAINER-JP.md
Normal file
14
docs/DEVCONTAINER-JP.md
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
docker コンテナで devcontainer を起動すると、デバッグモードの linux バイナリが作成されます。
|
||||
|
||||
現在 devcontainer では、Linux と android のビルドをデバッグモードとリリースモードの両方で提供しています。
|
||||
|
||||
以下は、特定のビルドを作成するためにプロジェクトのルートから実行するコマンドの表になります。
|
||||
|
||||
コマンド|ビルド タイプ|モード
|
||||
-|-|-|
|
||||
`.devcontainer/build.sh --debug linux`|Linux|debug
|
||||
`.devcontainer/build.sh --release linux`|Linux|release
|
||||
`.devcontainer/build.sh --debug android`|android-arm64|debug
|
||||
`.devcontainer/build.sh --release android`|android-arm64|release
|
||||
|
||||
12
docs/DEVCONTAINER-TR.md
Normal file
12
docs/DEVCONTAINER-TR.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Docker konteynerinde devcontainer'ın başlatılmasından sonra, hata ayıklama modunda bir Linux ikili dosyası oluşturulur.
|
||||
|
||||
Şu anda devcontainer, hata ayıklama ve sürüm modunda hem Linux hem de Android derlemeleri sunmaktadır.
|
||||
|
||||
Aşağıda, belirli derlemeler oluşturmak için projenin kökünden çalıştırılması gereken komutlar yer almaktadır.
|
||||
|
||||
Komut | Derleme Türü | Mod
|
||||
-|-|-
|
||||
`.devcontainer/build.sh --debug linux` | Linux | hata ayıklama
|
||||
`.devcontainer/build.sh --release linux` | Linux | sürüm
|
||||
`.devcontainer/build.sh --debug android` | Android-arm64 | hata ayıklama
|
||||
`.devcontainer/build.sh --release android` | Android-arm64 | sürüm
|
||||
@@ -118,10 +118,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### X11 (Xorg) إلى Wayland تغيير
|
||||
|
||||
افتراضية GNOME session ك Xorg إتبع [هذه](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) الخطوات لإعداد Wayland لا تدعم RustDesk
|
||||
|
||||
## Docker طريقة البناء باستخدام
|
||||
|
||||
ابدأ باستنساخ المستودع وبناء الكونتاينر:
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<a href="#file-structure">Struktura</a> •
|
||||
<a href="#snapshot">Ukázky</a><br>
|
||||
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
||||
<b>Potřebujeme Vaši pomoc s překláním textů tohoto ČTIMNE, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">uživatelského rozhraní aplikace RustDesk</a> a <a href="https://github.com/rustdesk/doc.rustdesk.com">dokumentace k ní</a> do vašeho jazyka</b>
|
||||
<b>Potřebujeme Vaši pomoc s překladem tohoto README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">uživatelského rozhraní aplikace RustDesk</a> a <a href="https://github.com/rustdesk/doc.rustdesk.com">dokumentace k ní</a> do vašeho jazyka</b>
|
||||
</p>
|
||||
|
||||
Dopisujte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Popovídejte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
@@ -44,7 +44,7 @@ Varianta pro mobilní platformy používá aplikační rámec (framework) Flutte
|
||||
|
||||
- Připravte si vývojové prostředí pro jazyky Rust a C++
|
||||
|
||||
- Nainstalujte [vcpkg](https://github.com/microsoft/vcpkg), a nastavte správně proměnnou prostsředí `VCPKG_ROOT`
|
||||
- Nainstalujte [vcpkg](https://github.com/microsoft/vcpkg), a správně nastavte proměnnou prostředí `VCPKG_ROOT`
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
||||
@@ -111,10 +111,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Změna z Wayland na X11 (Xorg)
|
||||
|
||||
RustDesk (zatím) nepodporuje zobrazovací server Wayland. Jak nastavit Xorg jako výchozí pro relace v prostředí GNOME naleznete [zde](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/).
|
||||
|
||||
## Jak sestavit prostřednictvím Docker kontejnerizace
|
||||
|
||||
Začněte tím, že si naklonujete tento repozitář a sestavíte docker kontejner:
|
||||
@@ -131,7 +127,7 @@ Poté pokaždé, když bude třeba aplikaci sestavit, spusťte následující p
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
Všimněte si, že prvotní sestavení může trvat déle (než se do mezipaměti uloží veškeré softwarové součásti, které jsou potřeba) – následná opakování už budou rychlejší. Dále, pokud potřebujete příkazu pro sestavení zadat nějaké argumenty, je možné je zapsat na konec příkazu na pozici `<OPTIONAL-ARGS>`. Například, pokud byste chtěli sestavit optimalizovaně pro vydání, spustili byste výše uvedený příkaz následovaný `--release`. Výsledný spustitelný soubor se objeví v cílové složce na vašem systému a bude ho možné spustit pomocí:
|
||||
Všimněte si, že prvotní sestavení může trvat déle (než se do mezipaměti uloží veškeré softwarové součásti, které jsou potřeba) – následná opakování už budou rychlejší. Pokud navíc potřebujete zadat různé argumenty příkazu pro sestavení, můžete tak učinit na konci příkazu v pozici `<OPTIONAL-ARGS>`. Například, pokud byste chtěli sestavit optimalizovanou verzi pro vydání, spustili byste výše uvedený příkaz následovaný `--release`. Výsledný spustitelný soubor se objeví v cílové složce na vašem systému a bude ho možné spustit pomocí:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
@@ -143,7 +139,7 @@ Nebo, pokud spouštíte variantu pro vydání:
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Zajistětě, abyste tyto příkazy spouštěli z kořene repozitáře s RustDesk, jinak aplikace nemusí být schopná nalézt potřebné prostředky (resources). Také si všimněte, že ostatní dílčí príkazy nástroje cargo, jako třeba `install` nebo `run` zatím nejsou prostřednictvím této metody podporovány, protože by vedly k instalaci či spuštění program uvnitř kontejneru namísto přímo v systému.
|
||||
Ujistěte se, že tyto příkazy spouštíte z kořenového adresáře RustDesk, jinak aplikace nemusí být schopná nalézt potřebné prostředky (resources). Také si všimněte, že ostatní dílčí príkazy nástroje cargo, jako třeba `install` nebo `run` zatím nejsou prostřednictvím této metody podporovány, protože by vedly k instalaci či spuštění program uvnitř kontejneru namísto přímo v systému.
|
||||
|
||||
## Struktura souborů
|
||||
|
||||
|
||||
@@ -108,33 +108,6 @@ mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Skift Wayland til X11 (Xorg)
|
||||
|
||||
RustDesk understøtter ikke Wayland. Tjek [dette](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) for at konfigurere Xorg som standard GNOME-session.
|
||||
|
||||
## Wayland-support
|
||||
|
||||
Wayland ser ikke ud til at levere nogen API til at sende tastetryk til andre vinduer. Derfor bruger rustdesk et API fra et lavere niveau, nemlig `/dev/uinput`-enheden (Linux-kerneniveau).
|
||||
|
||||
Når wayland er den kontrollerede side, skal du starte på følgende måde:
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Bemærk**: Wayland-skærmoptagelse bruger forskellige grænseflader. RustDesk understøtter i øjeblikket kun org.freedesktop.portal.ScreenCast.
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
## Sådan bygger du med Docker
|
||||
|
||||
```sh
|
||||
|
||||
@@ -133,34 +133,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Wayland zu X11 (Xorg) ändern
|
||||
|
||||
RustDesk unterstützt Wayland nicht. Siehe [hier](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/), um Xorg als Standard-GNOME-Sitzung zu nutzen.
|
||||
|
||||
## Wayland-Unterstützung
|
||||
|
||||
Wayland scheint keine API für das Senden von Tastatureingaben an andere Fenster zu bieten. Daher verwendet RustDesk eine API von einer niedrigeren Ebene, nämlich dem Gerät `/dev/uinput` (Linux-Kernelebene).
|
||||
|
||||
Wenn Wayland die kontrollierte Seite ist, müssen Sie wie folgt vorgehen:
|
||||
```bash
|
||||
# Dienst uinput starten
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Hinweis**: Die Wayland-Bildschirmaufnahme verwendet verschiedene Schnittstellen. RustDesk unterstützt derzeit nur org.freedesktop.portal.ScreenCast.
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Keine Unterstützung
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Unterstützung
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## Auf Docker kompilieren
|
||||
|
||||
Beginnen Sie damit, das Repository zu klonen und den Docker-Container zu bauen:
|
||||
|
||||
@@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Ŝanĝi Wayland por X11 (Xorg)
|
||||
|
||||
RustDesk ne subtenas Wayland. Kontrolu [tion](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) por agordi Xorg kiel defaŭlta sesio GNOME.
|
||||
|
||||
## Kiel kompili kun Docker
|
||||
|
||||
Komencu klonante la deponejon kaj kompilu la konteneron Docker:
|
||||
|
||||
@@ -113,34 +113,6 @@ mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Cambia Wayland a X11 (Xorg)
|
||||
|
||||
RustDesk no soporta Wayland. Lee [esto](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) para configurar Xorg en la sesión por defecto de GNOME.
|
||||
|
||||
## Soporte para Wayland
|
||||
|
||||
Wayland no parece proporcionar ninguna API para enviar pulsaciones de teclas a otras ventanas. Por lo tanto, rustdesk usa una API de nivel bajo, a saber, el dispositivo `/dev/uinput` (a nivel del kernel de Linux).
|
||||
|
||||
Cuando wayland esta del lado controlado, hay que iniciar de la siguiente manera:
|
||||
```bash
|
||||
# Empezar el servicio uinput
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Aviso**: La grabación de pantalla de Wayland utiliza diferentes interfaces. RustDesk actualmente sólo soporta org.freedesktop.portal.ScreenCast
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# No soportado
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Soportado
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## Como compilar con Docker
|
||||
|
||||
Empieza clonando el repositorio y compilando el contenedor de docker:
|
||||
|
||||
@@ -112,10 +112,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### تغییر Wayland به (X11 (Xorg
|
||||
|
||||
راستدسک از Wayland پشتیبانی نمی کند. برای جایگزنی Xorg به عنوان پیشفرض GNOM، [اینجا](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) را کلیک کنید.
|
||||
|
||||
## نحوه ساخت با داکر
|
||||
|
||||
این مخزن Git را دریافت کنید و کانتینر را به روش زیر بسازید
|
||||
|
||||
@@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Vaihda Wayland-ympäristö X11 (Xorg)-ympäristöön
|
||||
|
||||
RustDesk ei tue Waylandia. Tarkista [tämä](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) asettamalla Xorg oletus GNOME-istuntoon.
|
||||
|
||||
## Kuinka rakennetaan Dockerin kanssa
|
||||
|
||||
Aloita kloonaamalla tietovarasto ja rakentamalla docker-säiliö:
|
||||
|
||||
@@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
||||
Exécution du cargo
|
||||
```
|
||||
|
||||
### Changer Wayland en X11 (Xorg)
|
||||
|
||||
RustDesk ne supporte pas Wayland. Lisez [cela](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) pour configurer Xorg comme la session GNOME par défaut.
|
||||
|
||||
## Comment construire avec Docker
|
||||
|
||||
Commencez par cloner le dépôt et construire le conteneur Docker :
|
||||
|
||||
@@ -133,34 +133,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Αλλαγή του Wayland σε X11 (Xorg)
|
||||
|
||||
Το RustDesk δεν υποστηρίζει το πρωτόκολλο Wayland. Διαβάστε [εδώ](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) ώστε να ορίσετε το Xorg ως το προκαθορισμένο GNOME περιβάλλον.
|
||||
|
||||
## Υποστήριξη Wayland
|
||||
|
||||
Το Wayland προς το παρόν δεν διαθέτει κάποιο API το οποίο να στέλνει τα πατήματα πλήκτρων στα υπόλοιπα παράθυρα. Για τον λόγο αυτό, το Rustdesk χρησιμοποιεί ένα API από κατώτερο επίπεδο, όπως το `/dev/uinput` (Linux kernel level).
|
||||
|
||||
Σε περίπτωση που το Wayland είναι η ελεγχόμενη πλευρά, θα πρέπει να ξεκινήσετε με τον παρακάτω τρόπο:
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Σημείωση**: Η εγγραφή οθόνης του Wayland χρησιμοποιεί διαφορετικές διεπαφές. Το RustDesk προς το παρόν υποστηρίζει μόνο org.freedesktop.portal.ScreenCast.
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## Πως να κάνετε build στο Docker
|
||||
|
||||
Ξεκινήστε κλωνοποιώντας το αποθετήριο και κάνοντας build το docker container:
|
||||
@@ -189,7 +161,7 @@ target/debug/rustdesk
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Βεβαιωθείτε ότι εκτελείτε αυτές τις εντολές από την αρχική διαδρομή του αποθετηρίου του Rustdesk, διαφορετικά η εφαρμογή ενδέχεται να μην είναι σε θέση να βρεί τους απαιτούμενους πόρους. Σημειώστε επίσης ότι άλλες υποεντολές, όπως το `install` ή το `run` δεν υποστηρίζονται επί του παρόντος μέσω αυτής της μεθόδου καθώς θα εγκαταστήσουν ή θα εκτελέσουν το πρόγραμμα εντός του container αντί του κεντρικού υπολογιστή.
|
||||
Βεβαιωθείτε ότι εκτελείτε αυτές τις εντολές από την αρχική διαδρομή του αποθετηρίου του RustDesk, διαφορετικά η εφαρμογή ενδέχεται να μην είναι σε θέση να βρεί τους απαιτούμενους πόρους. Σημειώστε επίσης ότι άλλες υποεντολές, όπως το `install` ή το `run` δεν υποστηρίζονται επί του παρόντος μέσω αυτής της μεθόδου καθώς θα εγκαταστήσουν ή θα εκτελέσουν το πρόγραμμα εντός του container αντί του κεντρικού υπολογιστή.
|
||||
|
||||
## Δομή φακέλων
|
||||
|
||||
|
||||
@@ -116,10 +116,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Válts Wayland-ról X11-re (Xorg)
|
||||
|
||||
A RustDesk nem támogatja a Waylendet. [Itt](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) található egy tutorial amelynek segítségével beállíthatod a Xorg-ot mint alap GNOME session.
|
||||
|
||||
## Hogyan építs Dockerrel
|
||||
|
||||
Kezdjünk a repo clónozásával, majd pedig a Docker container megépítésével:
|
||||
|
||||
@@ -13,15 +13,27 @@ Mari mengobrol bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Merupakan perangkat lunak Remote Desktop yang baru, dibangun dengan Rust. kamu bisa langsung menggunakannya tanpa perlu konfigurasi tambahan. Serta ,emiliki kontrol penuh terhadap semua data, tanpa perlu merasa was-was tentang isu keamanan, dan yang lebih menarik adalah memiliki opsi untuk menggunakan server rendezvous/relay milik kami, [konfigurasi server sendiri](https://rustdesk.com/server), atau [tulis rendezvous/relay server anda sendiri](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
[](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
RustDesk mengajak semua orang untuk ikut berkontribusi. Lihat [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) untuk melihat panduan.
|
||||
Merupakan perangkat lunak Remote Desktop yang baru, dan dibangun dengan Rust. Bahkan kamu bisa langsung menggunakannya tanpa perlu melakukan konfigurasi tambahan. Serta memiliki kontrol penuh terhadap semua data, tanpa perlu merasa was-was tentang isu keamanan, dan yang lebih menarik adalah memiliki opsi untuk menggunakan server rendezvous/relay milik kami, [konfigurasi server sendiri](https://rustdesk.com/server), atau [tulis rendezvous/relay server anda sendiri](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
RustDesk mengajak semua orang untuk ikut berkontribusi. Lihat [`docs/CONTRIBUTING-ID.md`](CONTRIBUTING-ID.md) untuk melihat panduan.
|
||||
|
||||
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
[**UNDUH BINARY**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
|
||||
## Server Publik Gratis
|
||||
|
||||
Di bawah ini merupakan server gratis yang bisa kamu gunakan, seiring waktu kemungkinan akan terjadi perubahan spesifikasi pada setiap server. Jika lokasi kamu berada jauh dengan salah satu server yang tersedia, kemungkinan koneksi akan terasa lambat ketika melakukan proses remote.
|
||||
Di bawah ini merupakan server gratis yang bisa kamu gunakan, seiring dengan waktu mungkin akan terjadi perubahan spesifikasi pada setiap server yang ada. Jika lokasi kamu berada jauh dengan salah satu server yang tersedia, kemungkinan koneksi akan terasa lambat ketika melakukan proses remote.
|
||||
| Lokasi | Penyedia | Spesifikasi |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Jerman | [Hetzner](https://www.hetzner.com) | 2 vCPU / 4GB RAM |
|
||||
@@ -31,11 +43,11 @@ Di bawah ini merupakan server gratis yang bisa kamu gunakan, seiring waktu kemun
|
||||
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
|
||||
|
||||
Apabila kamu sudah menginstall VS Code dan Docker, kamu bisa mengklik badge yang ada diatas untuk memulainya. Dengan mengklik badge tersebut secara otomatis akan menginstal ekstensi pada VS Code, lakukan kloning (clone) source code kedalam container volume, dan aktifkan dev container untuk menggunakannya.
|
||||
Apabila PC kamu sudah terinstal VS Code dan Docker, kamu bisa mengklik badge yang ada diatas untuk memulainya. Dengan mengklik badge tersebut secara otomatis akan menginstal ekstensi pada VS Code, lakukan kloning (clone) source code kedalam container volume, dan aktifkan dev container untuk menggunakannya.
|
||||
|
||||
## Dependensi
|
||||
|
||||
Pada versi desktop, antarmuka pengguna (GUI) menggunakan [Sciter](https://sciter.com/) atau flutter, tutorial ini hanya berlaku untuk Sciter
|
||||
Pada versi desktop, antarmuka pengguna (GUI) menggunakan [Sciter](https://sciter.com/) atau flutter
|
||||
|
||||
Kamu bisa mengunduh Sciter dynamic library disini.
|
||||
|
||||
@@ -116,37 +128,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Mengubah Wayland ke X11 (Xorg)
|
||||
|
||||
RustDesk tidak mendukung Wayland. Cek [ini](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) untuk mengonfigurasi Xorg sebagai sesi standar di GNOME.
|
||||
|
||||
## Kompatibilitas dengan Wayland
|
||||
|
||||
Sepertinya Wayland tidak memiliki API untuk mengirimkan ketukan tombol ke jendela lain. Maka dari itu, RustDesk menggunakan API dari level yang lebih rendah, lebih tepatnya perangkat `/dev/uinput` (linux kernel level)
|
||||
|
||||
Saat Wayland menjadi sisi yang dikendalikan atau sisi yang sedang diremote, kamu harus memulai dengan cara ini
|
||||
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
|
||||
**Harap Diperhatikan**: Saat Perekaman layar menggunakan Wayland antarmuka (UI) yang ditampilkan akan berbeda. Untuk saat ini RustDesk hanya mendukung org.freedesktop.portal.ScreenCast.
|
||||
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## Cara Build dengan Docker
|
||||
|
||||
Mulailah dengan melakukan kloning (clone) repositori dan build dengan docker container:
|
||||
|
||||
@@ -109,11 +109,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Cambiare Wayland in X11 (Xorg)
|
||||
|
||||
RustDesk non supporta Wayland.
|
||||
Controlla [qui](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) per configurare Xorg come sessione predefinita di GNOME.
|
||||
|
||||
## Come compilare con Docker
|
||||
|
||||
Clona il repository e compila i container docker:
|
||||
|
||||
@@ -114,11 +114,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Wayland の場合、X11(Xorg)に変更します
|
||||
|
||||
RustDeskはWaylandをサポートしていません。
|
||||
[こちら](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) を確認して、XorgをデフォルトのGNOMEセッションとして構成します。
|
||||
|
||||
## Dockerでビルドする方法
|
||||
|
||||
リポジトリのクローンを作成し、Dockerコンテナを構築することから始めます。
|
||||
|
||||
@@ -112,10 +112,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Wayland 일 경우, X11(Xorg)로 변경
|
||||
|
||||
RustDesk는 Wayland를 지원하지 않습니다. [링크](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/)를 확인해서 Xorg 기본값의 GNOME 세션을 구성합니다.
|
||||
|
||||
## Docker에 빌드하는 방법
|
||||
|
||||
레포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
||||
|
||||
@@ -103,10 +103,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### വേലാൻഡ് X11 (Xorg) ആയി മാറ്റുക
|
||||
|
||||
RustDesk Wayland-നെ പിന്തുണയ്ക്കുന്നില്ല. സ്ഥിരസ്ഥിതി ഗ്നോം സെഷനായി Xorg കോൺഫിഗർ ചെയ്യുന്നതിന് [ഇത്](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) പരിശോധിക്കുക.
|
||||
|
||||
## ഡോക്കർ ഉപയോഗിച്ച് എങ്ങനെ നിർമ്മിക്കാം
|
||||
|
||||
റെപ്പോസിറ്റോറി ക്ലോണുചെയ്ത് ഡോക്കർ കണ്ടെയ്നർ നിർമ്മിക്കുന്നതിലൂടെ ആരംഭിക്കുക:
|
||||
|
||||
@@ -130,34 +130,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Wissel van Wayland naar X11 (Xorg)
|
||||
|
||||
RustDesk ondersteunt Wayland niet. Lees [hier](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) hoe je Xorg als standaardsessie kunt instellen voor GNOME.
|
||||
|
||||
## Wayland support
|
||||
|
||||
Wayland lijkt geen API te bieden voor het verzenden van toetsaanslagen naar andere vensters. Daarom gebruikt de rustdesk een API van een lager niveau, namelijk het `/dev/uinput` apparaat (Linux kernel niveau).
|
||||
|
||||
Als wayland de gecontroleerde kant is, moet je op de volgende manier beginnen:
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Let op**: Wayland schermopname gebruikt verschillende interfaces. RustDesk ondersteunt momenteel alleen org.freedesktop.portal.ScreenCast.
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## Bouwen met Docker
|
||||
|
||||
Begin met het klonen van de repository en het bouwen van de docker container:
|
||||
|
||||
@@ -128,34 +128,6 @@ mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Zmień Wayland na X11 (Xorg)
|
||||
|
||||
RustDesk nie obsługuje Waylanda. Sprawdź [tutaj](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/), jak skonfigurować Xorg jako domyślną sesję GNOME.
|
||||
|
||||
## Wspracie Wayland
|
||||
|
||||
Wygląda na to, że Wayland nie wspiera żadnego API do wysyłania naciśnięć klawiszy do innych okien. Dlatego rustdesk używa API z niższego poziomu, urządzenia o nazwie `/dev/uinput` (poziom jądra Linux).
|
||||
|
||||
Gdy po stronie kontrolowanej pracuje Wayland, musisz uruchomić program w następujący sposób:
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
**Uwaga**: Nagrywanie ekranu Wayland wykorzystuje różne interfejsy. RustDesk obecnie obsługuje tylko org.freedesktop.portal.ScreenCast.
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## Jak kompilować za pomocą Dockera
|
||||
|
||||
Rozpocznij od sklonowania repozytorium i stworzenia kontenera docker:
|
||||
|
||||
@@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Mude Wayland para X11 (Xorg)
|
||||
|
||||
RustDesk não suporta Wayland. Veja [esse link](https://docs.fedoraproject.org/pt_BR/quick-docs/configuring-xorg-as-default-gnome-session/) para configurar o Xorg como a sessão padrão do GNOME.
|
||||
|
||||
## Como compilar com Docker
|
||||
|
||||
Comece clonando o repositório e montando o container docker:
|
||||
|
||||
@@ -114,10 +114,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Смените Wayland на X11 (Xorg)
|
||||
|
||||
RustDesk не поддерживает Wayland. Смотрите [этот документ](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) для настройки Xorg в качестве сеанса GNOME по умолчанию.
|
||||
|
||||
## Как собрать с помощью Docker
|
||||
|
||||
Начните с клонирования репозитория и создания docker-контейнера:
|
||||
|
||||
195
docs/README-TR.md
Normal file
195
docs/README-TR.md
Normal file
@@ -0,0 +1,195 @@
|
||||
|
||||
<p align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Uzak masaüstü uygulamanız"><br>
|
||||
<a href="#free-public-servers">Sunucular</a> •
|
||||
<a href="#raw-steps-to-build">Derleme</a> •
|
||||
<a href="#how-to-build-with-docker">Docker ile Derleme</a> •
|
||||
<a href="#file-structure">Dosya Yapısı</a> •
|
||||
<a href="#snapshot">Ekran Görüntüleri</a><br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>]<br>
|
||||
<b>README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> ve <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Belge</a>'sini ana dilinize çevirmemiz için yardımınıza ihtiyacımız var</b>
|
||||
</p>
|
||||
|
||||
Bizimle sohbet edin: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Başka bir uzak masaüstü yazılımı daha, Rust dilinde yazılmış. Hemen kullanıma hazır, hiçbir yapılandırma gerektirmez. Verilerinizin tam kontrolünü elinizde tutarsınız ve güvenlikle ilgili endişeleriniz olmaz. Kendi buluş/iletme sunucumuzu kullanabilirsiniz, [kendi sunucunuzu kurabilirsiniz](https://rustdesk.com/server) veya [kendi buluş/iletme sunucunuzu yazabilirsiniz](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
RustDesk, herkesten katkıyı kabul eder. Başlamak için [CONTRIBUTING.md](docs/CONTRIBUTING-TR.md) belgesine göz atın.
|
||||
|
||||
[**SSS**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
[**BİNARİ İNDİR**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**NİGHTLY DERLEME**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="F-Droid'de Alın"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
|
||||
## Ücretsiz Genel Sunucular
|
||||
|
||||
Aşağıda ücretsiz olarak kullandığınız sunucular listelenmiştir, zaman içinde değişebilirler. Eğer bunlardan birine yakın değilseniz, ağınız yavaş olabilir.
|
||||
| Konum | Sağlayıcı | Özellikler |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Almanya | [Hetzner](https://www.hetzner.com) | 2 vCPU / 4 GB RAM |
|
||||
| Almanya | [Codext](https://codext.de) | 4 vCPU / 8 GB RAM |
|
||||
| Ukrayna (Kiev) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
|
||||
|
||||
## Geliştirici Konteyneri
|
||||
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
|
||||
|
||||
Eğer zaten VS Code ve Docker kurulu ise yukarıdaki rozete tıklayarak başlayabilirsiniz. Tıklamak, VS Code'un gerektiğinde Dev Konteyner eklentisini otomatik olarak yüklemesine, kaynak kodunu bir konteyner hacmine klonlamasına ve kullanım için bir geliştirici konteyneri başlatmasına neden olur.
|
||||
|
||||
Daha fazla bilgi için [DEVCONTAINER.md](docs/DEVCONTAINER-TR.md) belgesine bakabilirsiniz.
|
||||
|
||||
## Bağımlılıklar
|
||||
|
||||
Masaüstü sürümleri GUI için
|
||||
|
||||
[Sciter](https://sciter.com/) veya Flutter kullanır, bu kılavuz sadece Sciter içindir.
|
||||
|
||||
Lütfen Sciter dinamik kütüphanesini kendiniz indirin.
|
||||
|
||||
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
|
||||
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||
[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
|
||||
## Temel Derleme Adımları
|
||||
|
||||
- Rust geliştirme ortamınızı ve C++ derleme ortamınızı hazırlayın.
|
||||
|
||||
- [vcpkg](https://github.com/microsoft/vcpkg) yükleyin ve `VCPKG_ROOT` çevresel değişkenini doğru bir şekilde ayarlayın.
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Linux/macOS: vcpkg install libvpx libyuv opus aom
|
||||
|
||||
- `cargo run` komutunu çalıştırın.
|
||||
|
||||
## [Derleme](https://rustdesk.com/docs/en/dev/build/)
|
||||
|
||||
## Linux Üzerinde Derleme Nasıl Yapılır
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
|
||||
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
|
||||
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
```
|
||||
|
||||
### openSUSE Tumbleweed
|
||||
|
||||
```sh
|
||||
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
||||
```
|
||||
|
||||
### vcpkg'yi Yükleyin
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2023.04.15
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||
```
|
||||
|
||||
### libvpx'i Düzeltin (Fedora için)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Derleme
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
## Docker ile Derleme Nasıl Yapılır
|
||||
|
||||
Öncelikle deposunu klonlayın ve Docker konteynerini oluşturun:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Ardından, uygulamayı derlemek için her seferinde aşağıdaki komutu çalıştırın:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
İlk derleme, bağımlılıklar önbelleğe alınmadan önce daha uzun sürebilir, sonraki derlemeler daha hızlı olacaktır. Ayrıca, derleme komutuna isteğe bağlı argümanlar belirtmeniz gerekiyorsa, bunu
|
||||
|
||||
komutun sonunda `<İSTEĞE BAĞLI-ARGÜMANLAR>` pozisyonunda yapabilirsiniz. Örneğin, optimize edilmiş bir sürümü derlemek isterseniz, yukarıdaki komutu çalıştırdıktan sonra `--release` ekleyebilirsiniz. Oluşan yürütülebilir dosya sisteminizdeki hedef klasöründe bulunacak ve şu komutla çalıştırılabilir:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Veya, yayın yürütülebilir dosyası çalıştırılıyorsa:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Lütfen bu komutları RustDesk deposunun kökünden çalıştırdığınızdan emin olun, aksi takdirde uygulama gereken kaynakları bulamayabilir. Ayrıca, `install` veya `run` gibi diğer cargo altkomutları şu anda bu yöntem aracılığıyla desteklenmemektedir, çünkü bunlar programı konteyner içinde kurar veya çalıştırır ve ana makinede değil.
|
||||
|
||||
## Dosya Yapısı
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video kodlayıcı, yapılandırma, tcp/udp sarmalayıcı, protobuf, dosya transferi için fs işlevleri ve diğer bazı yardımcı işlevler
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ekran yakalama
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platforma özgü klavye/fare kontrolü
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: ses/pasta/klavye/video hizmetleri ve ağ bağlantıları
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: bir eş bağlantısı başlatır
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server) ile iletişim kurar, uzak doğrudan (TCP delik vurma) veya iletme bağlantısını bekler
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platforma özgü kod
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: mobil için Flutter kodu
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter web istemcisi için JavaScript
|
||||
|
||||
## Ekran Görüntüleri
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
```
|
||||
@@ -1,29 +1,31 @@
|
||||
<p align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Ваш віддалений робочий стіл"><br>
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Ваша віддалена стільниця"><br>
|
||||
<a href="#безкоштовні-загальнодоступні-сервери">Сервери</a> •
|
||||
<a href="#первинні-кроки-для-складання">Складання</a> •
|
||||
<a href="#кроки-для-збірки">Збирання</a> •
|
||||
<a href="#як-зібрати-за-допомогою-docker">Docker</a> •
|
||||
<a href="#структура-файлів">Структура</a> •
|
||||
<a href="#знімки">Знімки</a><br>
|
||||
[<a href="../README.md">English</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
||||
<b>Нам потрібна ваша допомога для перекладу цього README і <a href="https://github.com/rustdesk/rustdesk/tree/master/src/rustdesk/tree/master/src/lang">RustDesk UI</a> на вашу рідну мову</B>
|
||||
[<a href="../README.md">English</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>]<br>
|
||||
<b>Нам потрібна ваша допомога для перекладу цього README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">інтерфейсу</a> та <a href="https://github.com/rustdesk/doc.rustdesk.com">документації</a> RustDesk на вашу рідну мову</B>
|
||||
</p>
|
||||
|
||||
Спілкування з нами: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Ще одне програмне забезпечення для віддаленого робочого столу, написане на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
[](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
Ще один застосунок для віддаленого керування стільницею, написаний на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
RustDesk вітає внесок кожного. Дивіться [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) для допомоги на початку роботи.
|
||||
RustDesk вітає внесок кожного. Ознайомтеся з [CONTRIBUTING.md](docs/CONTRIBUTING.md), щоб отримати допомогу на початковому етапі.
|
||||
|
||||
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
[**ЧаПи**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
[**Як працює RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
|
||||
[**ЗАВАНТАЖЕННЯ ЗАСТОСУНКУ**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**ЗАВАНТАЖИТИ ЗАСТОСУНОК**](https://github.com/rustdesk/rustdesk/releases)
|
||||
[**НІЧНІ ЗБІРКИ**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
@@ -34,38 +36,40 @@ RustDesk вітає внесок кожного. Дивіться [`docs/CONTRIB
|
||||
Нижче наведені сервери, для безкоштовного використання, вони можуть змінюватися з часом. Якщо ви не перебуваєте поруч з одним із них, ваша мережа може працювати повільно.
|
||||
| Місцезнаходження | Постачальник | Технічні характеристики |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Німеччина | Hetzner | 2 VCPU / 4GB RAM |
|
||||
| Німеччина | [Hetzner](https://www.hetzner.com) | 2 vCPU / 4GB RAM |
|
||||
| Україна (Київ) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
## Dev Container
|
||||
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
|
||||
|
||||
Якщо у вас уже встановлено VS Code і Docker, ви можете натиснути значок вище, щоб почати. Клацання призведе до того, що VS Code автоматично встановить розширення Dev Containers, якщо це необхідно, клонує виcхідний код у том контейнера та розгорне контейнер dev для використання.
|
||||
Якщо у вас уже встановлено VS Code та Docker, ви можете натиснути значок вище, щоб розпочати. Клацання призведе до того, що VS Code автоматично встановить розширення Dev Containers, якщо це необхідно, клонує вихідний код у том контейнера та розгорне контейнер dev для використання.
|
||||
|
||||
Дивіться [DEVCONTAINER.md](docs/DEVCONTAINER.md) для додаткової інфо.
|
||||
Дивіться [DEVCONTAINER.md](docs/DEVCONTAINER.md) для додаткової інформації
|
||||
|
||||
## Залежності
|
||||
|
||||
Настільні версії використовують [sciter](https://sciter.com/) для графічного інтерфейсу, завантажте динамічну бібліотеку sciter самостійно.
|
||||
Стільничні версії використовують Flutter чи Sciter (застаріле) для графічного інтерфейсу. Ця інструкція лише для Sciter, оскільки він є більш простим та дружнім для початківців. Перегляньте [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) для збірки версії на Flutter.
|
||||
|
||||
Будь ласка, завантажте динамічну бібліотеку Sciter самостійно.
|
||||
|
||||
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
|
||||
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||
[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
|
||||
Мобільні версії використовують Flutter. У майбутньому ми перенесемо настільну версію зі Sciter на Flutter.
|
||||
## Кроки для збірки
|
||||
|
||||
## Первинні кроки для складання
|
||||
|
||||
- Підготуйте середовище розробки Rust і середовище збірки C++.
|
||||
- Підготуйте середовище розробки Rust і середовище збирання C++.
|
||||
|
||||
- Встановіть [vcpkg](https://github.com/microsoft/vcpkg), і правильно встановіть змінну `VCPKG_ROOT`.
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
||||
- Linux/macOS: vcpkg install libvpx libyuv opus aom
|
||||
|
||||
- Запустіть `cargo run`
|
||||
|
||||
## [Збирання](https://rustdesk.com/docs/en/dev/build/)
|
||||
|
||||
## Як зібрати на Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
@@ -76,11 +80,12 @@ sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxc
|
||||
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
```
|
||||
|
||||
### openSUSE Tumbleweed
|
||||
### openSUSE Tumbleweed
|
||||
|
||||
```sh
|
||||
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
@@ -99,7 +104,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2023.04.15
|
||||
cd ...
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||
@@ -118,7 +123,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Збірка
|
||||
### Збирання
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
@@ -131,10 +136,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Змініть Wayland на X11 (Xorg)
|
||||
|
||||
RustDesk не підтримує Wayland. Дивіться [цей документ](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) для налаштування Xorg як сеансу GNOME за замовчуванням.
|
||||
|
||||
## Як зібрати за допомогою Docker
|
||||
|
||||
Почніть з клонування сховища та створення docker-контейнера:
|
||||
@@ -145,7 +146,7 @@ cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Потім кожного разу, коли вам потрібно зібрати додаток, запускайте таку команду:
|
||||
Надалі щоразу, коли вам буде потрібно зібрати застосунок, запускайте таку команду:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
@@ -170,6 +171,7 @@ target/release/rustdesk
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: відеокодек, конфіг, обгортка tcp/udp, protobuf, функції fs для передавання файлів і деякі інші службові функції
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: захоплення екрана
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: специфічне для платформи керування клавіатурою/мишею
|
||||
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: реалізація копіювання та вставлення файлів для Windows, Linux, macOS.
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графічний інтерфейс користувача
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: сервіси аудіо/буфера обміну/вводу/відео та мережевих підключень
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: однорангове з'єднання
|
||||
|
||||
@@ -116,10 +116,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Chuyển từ Wayland sang X11 (Xorg)
|
||||
|
||||
RustDesk hiện không hỗ trợ Wayland. Hãy xem [đường linh ở đây](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) cách để cài đặt Xorg làm session mặc định của GNOME.
|
||||
|
||||
## Cách để build sử dụng Docker
|
||||
|
||||
Bắt đầu bằng cách sao chép repo này về máy tính và build cái Docker cointainer:
|
||||
|
||||
@@ -18,7 +18,7 @@ Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https:
|
||||
|
||||

|
||||
|
||||
RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING.md](docs/CONTRIBUTING.md).
|
||||
RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
@@ -134,39 +134,6 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### 把 Wayland 修改成 X11 (Xorg)
|
||||
|
||||
RustDesk 暂时不支持 Wayland,不过正在积极开发中。
|
||||
> [点我](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/)
|
||||
查看如何将 Xorg 设置成默认的 GNOME session.
|
||||
|
||||
## Wayland 支持
|
||||
|
||||
Wayland 似乎没有提供任何将按键发送到其他窗口的 API. 因此, RustDesk 使用较低级别的 API, 即 `/dev/uinput` devices (Linux kernal level).
|
||||
|
||||
当 Wayland 是受控方时,您必须以下列方式开始操作:
|
||||
|
||||
```bash
|
||||
# Start uinput service
|
||||
$ sudo rustdesk --service
|
||||
$ rustdesk
|
||||
```
|
||||
|
||||
**Notice**: Wayland 屏幕录制使用不同的接口. RustDesk 目前只支持 org.freedesktop.portal.ScreenCast.
|
||||
|
||||
```bash
|
||||
$ dbus-send --session --print-reply \
|
||||
--dest=org.freedesktop.portal.Desktop \
|
||||
/org/freedesktop/portal/desktop \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.portal.ScreenCast string:version
|
||||
# Not support
|
||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||
# Support
|
||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||
variant uint32 4
|
||||
```
|
||||
|
||||
## 使用 Docker 编译
|
||||
|
||||
克隆版本库并构建 Docker 容器:
|
||||
|
||||
9
docs/SECURITY-JP.md
Normal file
9
docs/SECURITY-JP.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# セキュリティポリシー
|
||||
|
||||
## 脆弱性の報告
|
||||
|
||||
私たちはプロジェクトのセキュリティを非常に重視しています。私たちは、すべてのユーザーが脆弱性を発見した場合、私たちに報告することを奨励しています。
|
||||
RustDesk プロジェクトにセキュリティの脆弱性を発見した場合は、info@rustdesk.com までメールで責任を持って報告してください。
|
||||
|
||||
現時点では、バグ報奨金制度はありません。私たちは大きな問題を解決しようとしている小さなチームです。コミュニティ全体のために安全なアプリケーションを作り続けることができるよう、
|
||||
責任を持って脆弱性を報告してください。
|
||||
9
docs/SECURITY-TR.md
Normal file
9
docs/SECURITY-TR.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Güvenlik Politikası
|
||||
|
||||
## Bir Güvenlik Açığı Bildirme
|
||||
|
||||
Projemiz için güvenliği çok önemsiyoruz. Kullanıcıların keşfettikleri herhangi bir güvenlik açığını bize bildirmelerini teşvik ediyoruz.
|
||||
Eğer RustDesk projesinde bir güvenlik açığı bulursanız, lütfen info@rustdesk.com adresine sorumlu bir şekilde bildirin.
|
||||
|
||||
Şu an için bir hata ödül programımız bulunmamaktadır. Büyük bir sorunu çözmeye çalışan küçük bir ekibiz. Herhangi bir güvenlik açığını sorumlu bir şekilde bildirmenizi rica ederiz,
|
||||
böylece tüm topluluk için güvenli bir uygulama oluşturmaya devam edebiliriz.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "com.rustdesk.RustDesk",
|
||||
"runtime": "org.freedesktop.Platform",
|
||||
"runtime-version": "21.08",
|
||||
"runtime-version": "23.08",
|
||||
"sdk": "org.freedesktop.Sdk",
|
||||
"command": "rustdesk",
|
||||
"icon": "share/icons/hicolor/scalable/apps/rustdesk.svg",
|
||||
@@ -12,7 +12,7 @@
|
||||
"name": "rustdesk",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"bsdtar -zxvf rustdesk-1.2.2.deb",
|
||||
"bsdtar -zxvf rustdesk-1.2.4.deb",
|
||||
"tar -xvf ./data.tar.xz",
|
||||
"cp -r ./usr/* /app/",
|
||||
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
|
||||
@@ -26,7 +26,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"path": "../rustdesk-1.2.2.deb"
|
||||
"path": "../rustdesk-1.2.4.deb"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
|
||||
@@ -13,4 +13,4 @@ A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
samples and guidance on mobile development, and a full API reference.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import com.google.protobuf.gradle.*
|
||||
plugins {
|
||||
id "com.google.protobuf" version "0.9.4"
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
@@ -31,10 +36,33 @@ apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.20.1'
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = 'com.google.protobuf:protoc:3.20.1'
|
||||
}
|
||||
|
||||
generateProtoTasks {
|
||||
all().configureEach { task ->
|
||||
task.builtins {
|
||||
java {
|
||||
option "lite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
||||
main.proto.srcDirs += '../../../libs/hbb_common/protos'
|
||||
main.proto.includes += "message.proto"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -46,7 +74,7 @@ android {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.carriez.flutter_hbb"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
@@ -65,6 +93,7 @@ android {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +104,7 @@ flutter {
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.media:media:1.6.0"
|
||||
implementation 'com.github.getActivity:XXPermissions:16.2'
|
||||
implementation 'com.github.getActivity:XXPermissions:18.5'
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
|
||||
}
|
||||
|
||||
|
||||
4
flutter/android/app/proguard-rules
Normal file
4
flutter/android/app/proguard-rules
Normal file
@@ -0,0 +1,4 @@
|
||||
# Keep class members from protobuf generated code.
|
||||
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
@@ -61,6 +61,14 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent for deep linking-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="rustdesk" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -81,4 +89,4 @@
|
||||
android:value="2" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -10,12 +10,27 @@ import android.accessibilityservice.AccessibilityService
|
||||
import android.accessibilityservice.GestureDescription
|
||||
import android.graphics.Path
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.EditText
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.graphics.Rect
|
||||
import android.accessibilityservice.AccessibilityServiceInfo
|
||||
import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR
|
||||
import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.*
|
||||
import java.lang.Character
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import hbb.MessageOuterClass.KeyEvent
|
||||
import hbb.MessageOuterClass.KeyboardMode
|
||||
import hbb.KeyEventConverter
|
||||
|
||||
const val LIFT_DOWN = 9
|
||||
const val LIFT_MOVE = 8
|
||||
@@ -26,6 +41,13 @@ const val WHEEL_BUTTON_UP = 34
|
||||
const val WHEEL_DOWN = 523331
|
||||
const val WHEEL_UP = 963
|
||||
|
||||
const val TOUCH_SCALE_START = 1
|
||||
const val TOUCH_SCALE = 2
|
||||
const val TOUCH_SCALE_END = 3
|
||||
const val TOUCH_PAN_START = 4
|
||||
const val TOUCH_PAN_UPDATE = 5
|
||||
const val TOUCH_PAN_END = 6
|
||||
|
||||
const val WHEEL_STEP = 120
|
||||
const val WHEEL_DURATION = 50L
|
||||
const val LONG_TAP_DELAY = 200L
|
||||
@@ -51,6 +73,8 @@ class InputService : AccessibilityService() {
|
||||
private var isWheelActionsPolling = false
|
||||
private var isWaitingLongPress = false
|
||||
|
||||
private var fakeEditTextForTextStateCalculation: EditText? = null
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun onMouseInput(mask: Int, _x: Int, _y: Int) {
|
||||
val x = max(0, _x)
|
||||
@@ -167,6 +191,30 @@ class InputService : AccessibilityService() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun onTouchInput(mask: Int, _x: Int, _y: Int) {
|
||||
when (mask) {
|
||||
TOUCH_PAN_UPDATE -> {
|
||||
mouseX -= _x * SCREEN_INFO.scale
|
||||
mouseY -= _y * SCREEN_INFO.scale
|
||||
mouseX = max(0, mouseX);
|
||||
mouseY = max(0, mouseY);
|
||||
continueGesture(mouseX, mouseY)
|
||||
}
|
||||
TOUCH_PAN_START -> {
|
||||
mouseX = max(0, _x) * SCREEN_INFO.scale
|
||||
mouseY = max(0, _y) * SCREEN_INFO.scale
|
||||
startGesture(mouseX, mouseY)
|
||||
}
|
||||
TOUCH_PAN_END -> {
|
||||
endGesture(mouseX, mouseY)
|
||||
mouseX = max(0, _x) * SCREEN_INFO.scale
|
||||
mouseY = max(0, _y) * SCREEN_INFO.scale
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
private fun consumeWheelActions() {
|
||||
if (isWheelActionsPolling) {
|
||||
@@ -221,9 +269,296 @@ class InputService : AccessibilityService() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun onKeyEvent(data: ByteArray) {
|
||||
val keyEvent = KeyEvent.parseFrom(data)
|
||||
val keyboardMode = keyEvent.getMode()
|
||||
|
||||
var textToCommit: String? = null
|
||||
|
||||
if (keyboardMode == KeyboardMode.Legacy) {
|
||||
if (keyEvent.hasChr() && keyEvent.getDown()) {
|
||||
val chr = keyEvent.getChr()
|
||||
if (chr != null) {
|
||||
textToCommit = String(Character.toChars(chr))
|
||||
}
|
||||
}
|
||||
} else if (keyboardMode == KeyboardMode.Translate) {
|
||||
if (keyEvent.hasSeq() && keyEvent.getDown()) {
|
||||
val seq = keyEvent.getSeq()
|
||||
if (seq != null) {
|
||||
textToCommit = seq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit")
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
getInputMethod()?.let { inputMethod ->
|
||||
inputMethod.getCurrentInputConnection()?.let { inputConnection ->
|
||||
if (textToCommit != null) {
|
||||
textToCommit?.let { text ->
|
||||
inputConnection.commitText(text, 1, null)
|
||||
}
|
||||
} else {
|
||||
KeyEventConverter.toAndroidKeyEvent(keyEvent).let { event ->
|
||||
inputConnection.sendKeyEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
handler.post {
|
||||
KeyEventConverter.toAndroidKeyEvent(keyEvent)?.let { event ->
|
||||
val possibleNodes = possibleAccessibiltyNodes()
|
||||
Log.d(logTag, "possibleNodes:$possibleNodes")
|
||||
for (item in possibleNodes) {
|
||||
val success = trySendKeyEvent(event, item, textToCommit)
|
||||
if (success) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertAccessibilityNode(list: LinkedList<AccessibilityNodeInfo>, node: AccessibilityNodeInfo) {
|
||||
if (node == null) {
|
||||
return
|
||||
}
|
||||
if (list.contains(node)) {
|
||||
return
|
||||
}
|
||||
list.add(node)
|
||||
}
|
||||
|
||||
private fun findChildNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
|
||||
if (node == null) {
|
||||
return null
|
||||
}
|
||||
if (node.isEditable() && node.isFocusable()) {
|
||||
return node
|
||||
}
|
||||
val childCount = node.getChildCount()
|
||||
for (i in 0 until childCount) {
|
||||
val child = node.getChild(i)
|
||||
if (child != null) {
|
||||
if (child.isEditable() && child.isFocusable()) {
|
||||
return child
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < 33) {
|
||||
child.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i in 0 until childCount) {
|
||||
val child = node.getChild(i)
|
||||
if (child != null) {
|
||||
val result = findChildNode(child)
|
||||
if (Build.VERSION.SDK_INT < 33) {
|
||||
if (child != result) {
|
||||
child.recycle()
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun possibleAccessibiltyNodes(): LinkedList<AccessibilityNodeInfo> {
|
||||
val linkedList = LinkedList<AccessibilityNodeInfo>()
|
||||
val latestList = LinkedList<AccessibilityNodeInfo>()
|
||||
|
||||
val focusInput = findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
|
||||
var focusAccessibilityInput = findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)
|
||||
|
||||
val rootInActiveWindow = getRootInActiveWindow()
|
||||
|
||||
Log.d(logTag, "focusInput:$focusInput focusAccessibilityInput:$focusAccessibilityInput rootInActiveWindow:$rootInActiveWindow")
|
||||
|
||||
if (focusInput != null) {
|
||||
if (focusInput.isFocusable() && focusInput.isEditable()) {
|
||||
insertAccessibilityNode(linkedList, focusInput)
|
||||
} else {
|
||||
insertAccessibilityNode(latestList, focusInput)
|
||||
}
|
||||
}
|
||||
|
||||
if (focusAccessibilityInput != null) {
|
||||
if (focusAccessibilityInput.isFocusable() && focusAccessibilityInput.isEditable()) {
|
||||
insertAccessibilityNode(linkedList, focusAccessibilityInput)
|
||||
} else {
|
||||
insertAccessibilityNode(latestList, focusAccessibilityInput)
|
||||
}
|
||||
}
|
||||
|
||||
val childFromFocusInput = findChildNode(focusInput)
|
||||
Log.d(logTag, "childFromFocusInput:$childFromFocusInput")
|
||||
|
||||
if (childFromFocusInput != null) {
|
||||
insertAccessibilityNode(linkedList, childFromFocusInput)
|
||||
}
|
||||
|
||||
val childFromFocusAccessibilityInput = findChildNode(focusAccessibilityInput)
|
||||
if (childFromFocusAccessibilityInput != null) {
|
||||
insertAccessibilityNode(linkedList, childFromFocusAccessibilityInput)
|
||||
}
|
||||
Log.d(logTag, "childFromFocusAccessibilityInput:$childFromFocusAccessibilityInput")
|
||||
|
||||
if (rootInActiveWindow != null) {
|
||||
insertAccessibilityNode(linkedList, rootInActiveWindow)
|
||||
}
|
||||
|
||||
for (item in latestList) {
|
||||
insertAccessibilityNode(linkedList, item)
|
||||
}
|
||||
|
||||
return linkedList
|
||||
}
|
||||
|
||||
private fun trySendKeyEvent(event: android.view.KeyEvent, node: AccessibilityNodeInfo, textToCommit: String?): Boolean {
|
||||
node.refresh()
|
||||
this.fakeEditTextForTextStateCalculation?.setSelection(0,0)
|
||||
this.fakeEditTextForTextStateCalculation?.setText(null)
|
||||
|
||||
val text = node.getText()
|
||||
var isShowingHint = false
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
isShowingHint = node.isShowingHintText()
|
||||
}
|
||||
|
||||
var textSelectionStart = node.textSelectionStart
|
||||
var textSelectionEnd = node.textSelectionEnd
|
||||
|
||||
if (text != null) {
|
||||
if (textSelectionStart > text.length) {
|
||||
textSelectionStart = text.length
|
||||
}
|
||||
if (textSelectionEnd > text.length) {
|
||||
textSelectionEnd = text.length
|
||||
}
|
||||
if (textSelectionStart > textSelectionEnd) {
|
||||
textSelectionStart = textSelectionEnd
|
||||
}
|
||||
}
|
||||
|
||||
var success = false
|
||||
|
||||
Log.d(logTag, "existing text:$text textToCommit:$textToCommit textSelectionStart:$textSelectionStart textSelectionEnd:$textSelectionEnd")
|
||||
|
||||
if (textToCommit != null) {
|
||||
if ((textSelectionStart == -1) || (textSelectionEnd == -1)) {
|
||||
val newText = textToCommit
|
||||
this.fakeEditTextForTextStateCalculation?.setText(newText)
|
||||
success = updateTextForAccessibilityNode(node)
|
||||
} else if (text != null) {
|
||||
this.fakeEditTextForTextStateCalculation?.setText(text)
|
||||
this.fakeEditTextForTextStateCalculation?.setSelection(
|
||||
textSelectionStart,
|
||||
textSelectionEnd
|
||||
)
|
||||
this.fakeEditTextForTextStateCalculation?.text?.insert(textSelectionStart, textToCommit)
|
||||
success = updateTextAndSelectionForAccessibiltyNode(node)
|
||||
}
|
||||
} else {
|
||||
if (isShowingHint) {
|
||||
this.fakeEditTextForTextStateCalculation?.setText(null)
|
||||
} else {
|
||||
this.fakeEditTextForTextStateCalculation?.setText(text)
|
||||
}
|
||||
if (textSelectionStart != -1 && textSelectionEnd != -1) {
|
||||
Log.d(logTag, "setting selection $textSelectionStart $textSelectionEnd")
|
||||
this.fakeEditTextForTextStateCalculation?.setSelection(
|
||||
textSelectionStart,
|
||||
textSelectionEnd
|
||||
)
|
||||
}
|
||||
|
||||
this.fakeEditTextForTextStateCalculation?.let {
|
||||
// This is essiential to make sure layout object is created. OnKeyDown may not work if layout is not created.
|
||||
val rect = Rect()
|
||||
node.getBoundsInScreen(rect)
|
||||
|
||||
it.layout(rect.left, rect.top, rect.right, rect.bottom)
|
||||
it.onPreDraw()
|
||||
if (event.action == android.view.KeyEvent.ACTION_DOWN) {
|
||||
val succ = it.onKeyDown(event.getKeyCode(), event)
|
||||
Log.d(logTag, "onKeyDown $succ")
|
||||
} else if (event.action == android.view.KeyEvent.ACTION_UP) {
|
||||
val success = it.onKeyUp(event.getKeyCode(), event)
|
||||
Log.d(logTag, "keyup $success")
|
||||
} else {}
|
||||
}
|
||||
|
||||
success = updateTextAndSelectionForAccessibiltyNode(node)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
fun updateTextForAccessibilityNode(node: AccessibilityNodeInfo): Boolean {
|
||||
var success = false
|
||||
this.fakeEditTextForTextStateCalculation?.text?.let {
|
||||
val arguments = Bundle()
|
||||
arguments.putCharSequence(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
|
||||
it.toString()
|
||||
)
|
||||
success = node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
fun updateTextAndSelectionForAccessibiltyNode(node: AccessibilityNodeInfo): Boolean {
|
||||
var success = updateTextForAccessibilityNode(node)
|
||||
|
||||
if (success) {
|
||||
val selectionStart = this.fakeEditTextForTextStateCalculation?.selectionStart
|
||||
val selectionEnd = this.fakeEditTextForTextStateCalculation?.selectionEnd
|
||||
|
||||
if (selectionStart != null && selectionEnd != null) {
|
||||
val arguments = Bundle()
|
||||
arguments.putInt(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT,
|
||||
selectionStart
|
||||
)
|
||||
arguments.putInt(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
|
||||
selectionEnd
|
||||
)
|
||||
success = node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments)
|
||||
Log.d(logTag, "Update selection to $selectionStart $selectionEnd success:$success")
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
|
||||
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
ctx = this
|
||||
val info = AccessibilityServiceInfo()
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
info.flags = FLAG_INPUT_METHOD_EDITOR or FLAG_RETRIEVE_INTERACTIVE_WINDOWS
|
||||
} else {
|
||||
info.flags = FLAG_RETRIEVE_INTERACTIVE_WINDOWS
|
||||
}
|
||||
setServiceInfo(info)
|
||||
fakeEditTextForTextStateCalculation = EditText(this)
|
||||
// Size here doesn't matter, we won't show this view.
|
||||
fakeEditTextForTextStateCalculation?.layoutParams = LayoutParams(100, 100)
|
||||
fakeEditTextForTextStateCalculation?.onPreDraw()
|
||||
val layout = fakeEditTextForTextStateCalculation?.getLayout()
|
||||
Log.d(logTag, "fakeEditTextForTextStateCalculation layout:$layout")
|
||||
Log.d(logTag, "onServiceConnected!")
|
||||
}
|
||||
|
||||
@@ -232,7 +567,5 @@ class InputService : AccessibilityService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
|
||||
|
||||
override fun onInterrupt() {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package hbb;
|
||||
import android.view.KeyEvent
|
||||
import android.view.KeyCharacterMap
|
||||
import hbb.MessageOuterClass.KeyboardMode
|
||||
import hbb.MessageOuterClass.ControlKey
|
||||
|
||||
object KeyEventConverter {
|
||||
fun toAndroidKeyEvent(keyEventProto: hbb.MessageOuterClass.KeyEvent): KeyEvent {
|
||||
var chrValue = 0
|
||||
var modifiers = 0
|
||||
|
||||
val keyboardMode = keyEventProto.getMode()
|
||||
|
||||
if (keyEventProto.hasChr()) {
|
||||
if (keyboardMode == KeyboardMode.Map || keyboardMode == KeyboardMode.Translate) {
|
||||
chrValue = keyEventProto.getChr()
|
||||
} else {
|
||||
chrValue = convertUnicodeToKeyCode(keyEventProto.getChr() as Int)
|
||||
}
|
||||
} else if (keyEventProto.hasControlKey()) {
|
||||
chrValue = convertControlKeyToKeyCode(keyEventProto.getControlKey())
|
||||
}
|
||||
|
||||
var modifiersList = keyEventProto.getModifiersList()
|
||||
|
||||
if (modifiersList != null) {
|
||||
for (modifier in keyEventProto.getModifiersList()) {
|
||||
val modifierValue = convertModifier(modifier)
|
||||
modifiers = modifiers or modifierValue
|
||||
}
|
||||
}
|
||||
|
||||
var action = 0
|
||||
if (keyEventProto.getDown()) {
|
||||
action = KeyEvent.ACTION_DOWN
|
||||
} else {
|
||||
action = KeyEvent.ACTION_UP
|
||||
}
|
||||
|
||||
return KeyEvent(0, 0, action, chrValue, 0, modifiers)
|
||||
}
|
||||
|
||||
private fun convertModifier(controlKey: hbb.MessageOuterClass.ControlKey): Int {
|
||||
// Add logic to map ControlKey values to Android KeyEvent key codes.
|
||||
// You'll need to provide the mapping for each key.
|
||||
return when (controlKey) {
|
||||
ControlKey.Alt -> KeyEvent.META_ALT_ON
|
||||
ControlKey.Control -> KeyEvent.META_CTRL_ON
|
||||
ControlKey.CapsLock -> KeyEvent.META_CAPS_LOCK_ON
|
||||
ControlKey.Meta -> KeyEvent.META_META_ON
|
||||
ControlKey.NumLock -> KeyEvent.META_NUM_LOCK_ON
|
||||
ControlKey.RShift -> KeyEvent.META_SHIFT_RIGHT_ON
|
||||
ControlKey.Shift -> KeyEvent.META_SHIFT_ON
|
||||
ControlKey.RAlt -> KeyEvent.META_ALT_RIGHT_ON
|
||||
ControlKey.RControl -> KeyEvent.META_CTRL_RIGHT_ON
|
||||
else -> 0 // Default to unknown.
|
||||
}
|
||||
}
|
||||
|
||||
private val tag = "KeyEventConverter"
|
||||
|
||||
private fun convertUnicodeToKeyCode(unicode: Int): Int {
|
||||
val charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD)
|
||||
android.util.Log.d(tag, "unicode: $unicode")
|
||||
val events = charMap.getEvents(charArrayOf(unicode.toChar()))
|
||||
if (events != null && events.size > 0) {
|
||||
android.util.Log.d(tag, "keycode ${events[0].keyCode}")
|
||||
return events[0].keyCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun convertControlKeyToKeyCode(controlKey: hbb.MessageOuterClass.ControlKey): Int {
|
||||
// Add logic to map ControlKey values to Android KeyEvent key codes.
|
||||
// You'll need to provide the mapping for each key.
|
||||
return when (controlKey) {
|
||||
ControlKey.Alt -> KeyEvent.KEYCODE_ALT_LEFT
|
||||
ControlKey.Backspace -> KeyEvent.KEYCODE_DEL
|
||||
ControlKey.Control -> KeyEvent.KEYCODE_CTRL_LEFT
|
||||
ControlKey.CapsLock -> KeyEvent.KEYCODE_CAPS_LOCK
|
||||
ControlKey.Meta -> KeyEvent.KEYCODE_META_LEFT
|
||||
ControlKey.NumLock -> KeyEvent.KEYCODE_NUM_LOCK
|
||||
ControlKey.RShift -> KeyEvent.KEYCODE_SHIFT_RIGHT
|
||||
ControlKey.Shift -> KeyEvent.KEYCODE_SHIFT_LEFT
|
||||
ControlKey.RAlt -> KeyEvent.KEYCODE_ALT_RIGHT
|
||||
ControlKey.RControl -> KeyEvent.KEYCODE_CTRL_RIGHT
|
||||
ControlKey.DownArrow -> KeyEvent.KEYCODE_DPAD_DOWN
|
||||
ControlKey.LeftArrow -> KeyEvent.KEYCODE_DPAD_LEFT
|
||||
ControlKey.RightArrow -> KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
ControlKey.UpArrow -> KeyEvent.KEYCODE_DPAD_UP
|
||||
ControlKey.End -> KeyEvent.KEYCODE_MOVE_END
|
||||
ControlKey.Home -> KeyEvent.KEYCODE_MOVE_HOME
|
||||
ControlKey.PageUp -> KeyEvent.KEYCODE_PAGE_UP
|
||||
ControlKey.PageDown -> KeyEvent.KEYCODE_PAGE_DOWN
|
||||
ControlKey.Insert -> KeyEvent.KEYCODE_INSERT
|
||||
ControlKey.Escape -> KeyEvent.KEYCODE_ESCAPE
|
||||
ControlKey.F1 -> KeyEvent.KEYCODE_F1
|
||||
ControlKey.F2 -> KeyEvent.KEYCODE_F2
|
||||
ControlKey.F3 -> KeyEvent.KEYCODE_F3
|
||||
ControlKey.F4 -> KeyEvent.KEYCODE_F4
|
||||
ControlKey.F5 -> KeyEvent.KEYCODE_F5
|
||||
ControlKey.F6 -> KeyEvent.KEYCODE_F6
|
||||
ControlKey.F7 -> KeyEvent.KEYCODE_F7
|
||||
ControlKey.F8 -> KeyEvent.KEYCODE_F8
|
||||
ControlKey.F9 -> KeyEvent.KEYCODE_F9
|
||||
ControlKey.F10 -> KeyEvent.KEYCODE_F10
|
||||
ControlKey.F11 -> KeyEvent.KEYCODE_F11
|
||||
ControlKey.F12 -> KeyEvent.KEYCODE_F12
|
||||
ControlKey.Space -> KeyEvent.KEYCODE_SPACE
|
||||
ControlKey.Tab -> KeyEvent.KEYCODE_TAB
|
||||
ControlKey.Return -> KeyEvent.KEYCODE_ENTER
|
||||
ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL
|
||||
ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR
|
||||
ControlKey.Pause -> KeyEvent.KEYCODE_BREAK
|
||||
else -> 0 // Default to unknown.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ import java.nio.ByteBuffer
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
const val DEFAULT_NOTIFY_TITLE = "RustDesk"
|
||||
const val DEFAULT_NOTIFY_TEXT = "Service is running"
|
||||
const val DEFAULT_NOTIFY_ID = 1
|
||||
@@ -71,20 +70,35 @@ class MainService : Service() {
|
||||
|
||||
@Keep
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun rustMouseInput(mask: Int, x: Int, y: Int) {
|
||||
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) {
|
||||
// turn on screen with LIFT_DOWN when screen off
|
||||
if (!powerManager.isInteractive && mask == LIFT_DOWN) {
|
||||
if (!powerManager.isInteractive && (kind == "touch" || mask == LIFT_DOWN)) {
|
||||
if (wakeLock.isHeld) {
|
||||
Log.d(logTag,"Turn on Screen, WakeLock release")
|
||||
Log.d(logTag, "Turn on Screen, WakeLock release")
|
||||
wakeLock.release()
|
||||
}
|
||||
Log.d(logTag,"Turn on Screen")
|
||||
wakeLock.acquire(5000)
|
||||
} else {
|
||||
InputService.ctx?.onMouseInput(mask,x,y)
|
||||
when (kind) {
|
||||
"touch" -> {
|
||||
InputService.ctx?.onTouchInput(mask, x, y)
|
||||
}
|
||||
"mouse" -> {
|
||||
InputService.ctx?.onMouseInput(mask, x, y)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun rustKeyEventInput(input: ByteArray) {
|
||||
InputService.ctx?.onKeyEvent(input)
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun rustGetByName(name: String): String {
|
||||
return when (name) {
|
||||
@@ -197,6 +211,7 @@ class MainService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(logTag,"MainService onCreate")
|
||||
init(this)
|
||||
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
||||
start()
|
||||
serviceLooper = looper
|
||||
@@ -301,7 +316,6 @@ class MainService : Service() {
|
||||
mediaProjection =
|
||||
mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
|
||||
checkMediaPermission()
|
||||
init(this)
|
||||
_isReady = true
|
||||
} ?: let {
|
||||
Log.d(logTag, "getParcelableExtra intent null, invoke requestMediaProjection")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeWindowsChanged"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:accessibilityFlags="flagDefault"
|
||||
android:notificationTimeout="50"
|
||||
android:description="@string/accessibility_service_description"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.kotlin_version = '1.9.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
1
flutter/assets/auth-gitlab.svg
Normal file
1
flutter/assets/auth-gitlab.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="236" preserveAspectRatio="xMidYMid"><path fill="#e24329" d="m128.075 236.075 47.104-144.97H80.97z"/><path fill="#fc6d26" d="M128.075 236.074 80.97 91.104H14.956z"/><path fill="#fca326" d="M14.956 91.104.642 135.16a9.752 9.752 0 0 0 3.542 10.903l123.891 90.012z"/><path fill="#e24329" d="M14.956 91.105H80.97L52.601 3.79c-1.46-4.493-7.816-4.492-9.275 0z"/><path fill="#fc6d26" d="m128.075 236.074 47.104-144.97h66.015z"/><path fill="#fca326" d="m241.194 91.104 14.314 44.056a9.752 9.752 0 0 1-3.543 10.903l-123.89 90.012z"/><path fill="#e24329" d="M241.194 91.105h-66.015l28.37-87.315c1.46-4.493 7.816-4.492 9.275 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 684 B |
1
flutter/assets/checkbox-outline.svg
Normal file
1
flutter/assets/checkbox-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696255389449" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1922" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M435.2 704c-9 0-17.8-3.8-23.8-10.6l-115.2-128c-11.8-13.2-10.8-33.4 2.4-45.2 13.2-11.8 33.4-10.8 45.2 2.4l90.6 100.6 245.2-291.8c11.4-13.6 31.6-15.2 45-4 13.6 11.4 15.2 31.6 4 45l-268.8 320c-6 7-14.6 11.2-24 11.4-0.2 0.2-0.4 0.2-0.6 0.2z" p-id="1923"></path><path d="M800 928H224c-70.6 0-128-57.4-128-128V224c0-70.6 57.4-128 128-128h576c70.6 0 128 57.4 128 128v576c0 70.6-57.4 128-128 128zM224 160c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h576c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64H224z" p-id="1924"></path></svg>
|
||||
|
After Width: | Height: | Size: 856 B |
Binary file not shown.
1
flutter/assets/chevron_up_chevron_down.svg
Normal file
1
flutter/assets/chevron_up_chevron_down.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696245886035" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4133" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 132.717714c-9.435429 0-18.852571 3.84-29.147429 12.434286L194.011429 379.574857c-7.277714 6.418286-11.556571 15.433143-11.556572 28.288 0 22.272 16.713143 39.003429 39.424 39.003429 8.996571 0 18.432-3.437714 28.288-11.154286L512 222.281143l261.851429 213.430857c9.874286 7.716571 19.291429 11.154286 28.708571 11.154286 22.308571 0 39.003429-16.731429 39.003429-39.003429 0-12.854857-4.278857-21.869714-11.556572-28.288L541.147429 144.713143c-10.294857-8.137143-19.291429-11.995429-29.147429-11.995429z m0 758.564572c9.856 0 18.852571-3.84 29.147429-11.995429L829.988571 644.425143c7.277714-6.418286 11.556571-15.433143 11.556572-28.288 0-22.272-16.713143-39.424-38.985143-39.424-9.435429 0-18.870857 3.858286-28.708571 11.574857L512 801.718857 250.148571 588.288c-9.874286-7.716571-19.291429-11.574857-28.288-11.574857-22.710857 0-39.424 17.152-39.424 39.424 0 12.854857 4.278857 21.869714 11.556572 28.288l288.859428 234.422857c10.294857 8.594286 19.712 12.434286 29.147429 12.434286z" p-id="4134"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
flutter/assets/file_transfer.svg
Normal file
1
flutter/assets/file_transfer.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694049173782" class="icon" viewBox="0 0 1024 1024" width="24" height="24" fill="#fff" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="992" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M891.64 184.73H620.41c-27.41 0-54.41-7.77-77.32-22.5L428.13 87.36C402.77 71 372.91 62 342.64 62H131.95C93.5 62 62 93.5 62 132.36v759.68C62 930.91 93.5 962 131.95 962h759.68c38.86 0 70.36-31.09 70.36-69.96V255.09c0.01-38.86-31.49-70.36-70.35-70.36zM480.5 753.77c0 16.77-13.5 30.68-30.68 30.68-16.77 0-30.68-13.91-30.68-30.68V523.04l-31.91 55.64c-8.59 14.32-27.41 19.64-42.14 11.04-14.32-8.59-19.64-27.41-11.05-41.73l89.18-154.64c6.96-12.27 21.27-18 34.77-14.32 13.09 3.27 22.5 15.55 22.5 29.45v345.29z m209.04-139.5l-89.18 154.64c-5.32 9.82-15.55 15.55-26.59 15.55-2.46 0-5.32-0.41-7.77-1.23-13.5-3.68-22.91-15.55-22.91-29.46V408.5c0-16.77 13.91-30.68 30.68-30.68 17.18 0 30.68 13.91 30.68 30.68v230.73l31.91-55.64c8.59-14.73 27.41-19.64 42.14-11.05 14.73 8.6 19.64 27.01 11.04 41.73z" p-id="993"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
flutter/assets/scam.png
Normal file
BIN
flutter/assets/scam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 627 KiB |
@@ -1,13 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build libyuv / opus / libvpx / oboe for Android
|
||||
set -e -o pipefail
|
||||
|
||||
ANDROID_ABI=$1
|
||||
|
||||
# Build RustDesk dependencies for Android using vcpkg.json
|
||||
# Required:
|
||||
# 1. set VCPKG_ROOT / ANDROID_NDK path environment variables
|
||||
# 2. vcpkg initialized
|
||||
# 3. ndk, version: 22 (if ndk < 22 you need to change LD as `export LD=$TOOLCHAIN/bin/$NDK_LLVM_TARGET-ld`)
|
||||
# 3. ndk, version: r25c or newer
|
||||
|
||||
if [ -z "$ANDROID_NDK" ]; then
|
||||
echo "Failed! Please set ANDROID_NDK"
|
||||
if [ -z "$ANDROID_NDK_HOME" ]; then
|
||||
echo "Failed! Please set ANDROID_NDK_HOME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -18,107 +22,66 @@ fi
|
||||
|
||||
API_LEVEL="21"
|
||||
|
||||
# Get directory of this script
|
||||
|
||||
SCRIPTDIR="$(readlink -f "$0")"
|
||||
SCRIPTDIR="$(dirname "$SCRIPTDIR")"
|
||||
|
||||
# Check if vcpkg.json is one level up - in root directory of RD
|
||||
|
||||
if [ ! -f "$SCRIPTDIR/../vcpkg.json" ]; then
|
||||
echo "Failed! Please check where vcpkg.json is!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NDK llvm toolchain
|
||||
|
||||
HOST_TAG="linux-x86_64" # current platform, set as `ls $ANDROID_NDK/toolchains/llvm/prebuilt/`
|
||||
TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG
|
||||
|
||||
function build {
|
||||
ANDROID_ABI=$1
|
||||
VCPKG_TARGET=$2
|
||||
NDK_LLVM_TARGET=$3
|
||||
LIBVPX_TARGET=$4
|
||||
|
||||
PREFIX=$VCPKG_ROOT/installed/$VCPKG_TARGET/
|
||||
case "$ANDROID_ABI" in
|
||||
arm64-v8a)
|
||||
ABI=aarch64-linux-android$API_LEVEL
|
||||
VCPKG_TARGET=arm64-android
|
||||
;;
|
||||
armeabi-v7a)
|
||||
ABI=armv7a-linux-androideabi$API_LEVEL
|
||||
VCPKG_TARGET=arm-neon-android
|
||||
;;
|
||||
x86_64)
|
||||
ABI=x86_64-linux-android$API_LEVEL
|
||||
VCPKG_TARGET=x64-android
|
||||
;;
|
||||
x86)
|
||||
ABI=i686-linux-android$API_LEVEL
|
||||
VCPKG_TARGET=x86-android
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: ANDROID_ABI must be one of: arm64-v8a, armeabi-v7a, x86_64, x86" >&2
|
||||
return 1
|
||||
esac
|
||||
|
||||
# 1
|
||||
echo "*** [$ANDROID_ABI][Start] Build opus / libyuv from vcpkg"
|
||||
export ANDROID_NDK_HOME=$ANDROID_NDK
|
||||
pushd $VCPKG_ROOT
|
||||
$VCPKG_ROOT/vcpkg install opus --triplet $VCPKG_TARGET
|
||||
$VCPKG_ROOT/vcpkg install libyuv --triplet $VCPKG_TARGET
|
||||
echo "*** [$ANDROID_ABI][Start] Build and install vcpkg dependencies"
|
||||
pushd "$SCRIPTDIR/.."
|
||||
$VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed"
|
||||
popd
|
||||
echo "*** [$ANDROID_ABI][Finished] Build opus / libyuv from vcpkg"
|
||||
echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies"
|
||||
|
||||
# 2
|
||||
echo "*** [$ANDROID_ABI][Start] Build libvpx"
|
||||
pushd build/libvpx
|
||||
export AR=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ar
|
||||
export AS=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-as
|
||||
export LD=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ld.gold # if ndk < 22, use aarch64-linux-android-ld
|
||||
export RANLIB=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ranlib
|
||||
export STRIP=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-strip
|
||||
if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then
|
||||
echo "*** [Start] Move arm-neon-android to arm-android"
|
||||
|
||||
if [ $NDK_LLVM_TARGET == "arm-linux-androideabi" ]
|
||||
then
|
||||
export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang
|
||||
export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang++
|
||||
else
|
||||
export CC=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang
|
||||
export CXX=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang++
|
||||
fi
|
||||
make clean
|
||||
./configure --target=$LIBVPX_TARGET \
|
||||
--enable-pic
|
||||
--disable-webm-io \
|
||||
--disable-unit-tests \
|
||||
--disable-examples \
|
||||
--disable-libyuv \
|
||||
--disable-postproc \
|
||||
--disable-tools \
|
||||
--disable-docs \
|
||||
--prefix=$PREFIX
|
||||
make -j5
|
||||
make install
|
||||
mv "$VCPKG_ROOT/installed/arm-neon-android" "$VCPKG_ROOT/installed/arm-android"
|
||||
|
||||
popd
|
||||
echo "*** [$ANDROID_ABI][Finished] Build libvpx"
|
||||
|
||||
# 3
|
||||
echo "*** [$ANDROID_ABI][Start] Build oboe"
|
||||
pushd build/oboe
|
||||
make clean
|
||||
cmake -DBUILD_SHARED_LIBS=true \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DANDROID_TOOLCHAIN=clang \
|
||||
-DANDROID_STL=c++_shared \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DANDROID_ABI=$ANDROID_ABI \
|
||||
-DANDROID_PLATFORM=android-$API_LEVEL
|
||||
make -j5
|
||||
make install
|
||||
mv $PREFIX/lib/$ANDROID_ABI/liboboe.a $PREFIX/lib/
|
||||
popd
|
||||
echo "*** [$ANDROID_ABI][Finished] Build oboe"
|
||||
|
||||
echo "*** [$ANDROID_ABI][All Finished]"
|
||||
echo "*** [Finished] Move arm-neon-android to arm-android"
|
||||
fi
|
||||
}
|
||||
|
||||
git clone -b v1.11.0 --depth=1 https://github.com/webmproject/libvpx.git build/libvpx
|
||||
git clone -b 1.6.1 --depth=1 https://github.com/google/oboe build/oboe
|
||||
patch -N -d build/oboe -p1 < ../src/oboe.patch
|
||||
|
||||
# VCPKG_TARGET ANDROID_ABI
|
||||
# arm64-android arm64-v8a
|
||||
# arm-android armeabi-v7a
|
||||
# x64-android x86_64
|
||||
# x86-android x86
|
||||
|
||||
# NDK_LLVM_TARGET
|
||||
# aarch64-linux-android
|
||||
# arm-linux-androideabi
|
||||
# x86_64-linux-android
|
||||
# i686-linux-android
|
||||
|
||||
# LIBVPX_TARGET :
|
||||
# arm64-android-gcc
|
||||
# armv7-android-gcc
|
||||
# x86_64-android-gcc
|
||||
# x86-android-gcc
|
||||
|
||||
# args: ANDROID_ABI VCPKG_TARGET NDK_LLVM_TARGET LIBVPX_TARGET
|
||||
build arm64-v8a arm64-android aarch64-linux-android arm64-android-gcc
|
||||
build armeabi-v7a arm-android arm-linux-androideabi armv7-android-gcc
|
||||
|
||||
# rm -rf build/libvpx
|
||||
# rm -rf build/oboe
|
||||
if [ ! -z "$ANDROID_ABI" ]; then
|
||||
build "$ANDROID_ABI"
|
||||
else
|
||||
echo "Usage: build-android-deps.sh <ANDROID-ABI>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
|
||||
# https://docs.flutter.dev/deployment/ios
|
||||
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
|
||||
# no obfuscate, because no easy to check errors
|
||||
flutter build ipa --release
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '11.0'
|
||||
# platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
platform :ios, '11.0'
|
||||
platform :ios, '12.0'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
|
||||
@@ -38,9 +38,6 @@ PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_keyboard_visibility (0.0.1):
|
||||
- Flutter
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
@@ -52,12 +49,12 @@ PODS:
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- SDWebImage (5.15.5):
|
||||
- SDWebImage/Core (= 5.15.5)
|
||||
- SDWebImage/Core (5.15.5)
|
||||
- sqflite (0.0.2):
|
||||
- SDWebImage (5.18.11):
|
||||
- SDWebImage/Core (= 5.18.11)
|
||||
- SDWebImage/Core (5.18.11)
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- FlutterMacOS
|
||||
- SwiftyGif (5.4.4)
|
||||
- uni_links (0.0.1):
|
||||
- Flutter
|
||||
@@ -65,7 +62,8 @@ PODS:
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- wakelock (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -75,19 +73,18 @@ DEPENDENCIES:
|
||||
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FMDB
|
||||
- MTBBarcodeScanner
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
@@ -106,41 +103,40 @@ EXTERNAL SOURCES:
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
qr_code_scanner:
|
||||
:path: ".symlinks/plugins/qr_code_scanner/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
uni_links:
|
||||
:path: ".symlinks/plugins/uni_links/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
||||
wakelock:
|
||||
:path: ".symlinks/plugins/wakelock/ios"
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
||||
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
|
||||
PODFILE CHECKSUM: c649b4e69a3086d323110011d04604e416ad0dcd
|
||||
PODFILE CHECKSUM: d4cb12ad5d3bdb3352770b1d3db237584e155156
|
||||
|
||||
COCOAPODS: 1.12.0
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@@ -208,6 +208,7 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
@@ -346,7 +347,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -393,8 +394,6 @@
|
||||
"-framework",
|
||||
"\"DKPhotoGallery\"",
|
||||
"-framework",
|
||||
"\"FMDB\"",
|
||||
"-framework",
|
||||
"\"Foundation\"",
|
||||
"-framework",
|
||||
"\"ImageIO\"",
|
||||
@@ -433,10 +432,11 @@
|
||||
"-framework",
|
||||
"\"video_player_avfoundation\"",
|
||||
"-framework",
|
||||
"\"wakelock\"",
|
||||
"\"wakelock_plus\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_STYLE = "non-global";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -491,7 +491,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -541,7 +541,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -590,8 +590,6 @@
|
||||
"-framework",
|
||||
"\"DKPhotoGallery\"",
|
||||
"-framework",
|
||||
"\"FMDB\"",
|
||||
"-framework",
|
||||
"\"Foundation\"",
|
||||
"-framework",
|
||||
"\"ImageIO\"",
|
||||
@@ -630,10 +628,11 @@
|
||||
"-framework",
|
||||
"\"video_player_avfoundation\"",
|
||||
"-framework",
|
||||
"\"wakelock\"",
|
||||
"\"wakelock_plus\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_STYLE = "non-global";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -679,8 +678,6 @@
|
||||
"-framework",
|
||||
"\"DKPhotoGallery\"",
|
||||
"-framework",
|
||||
"\"FMDB\"",
|
||||
"-framework",
|
||||
"\"Foundation\"",
|
||||
"-framework",
|
||||
"\"ImageIO\"",
|
||||
@@ -719,10 +716,11 @@
|
||||
"-framework",
|
||||
"\"video_player_avfoundation\"",
|
||||
"-framework",
|
||||
"\"wakelock\"",
|
||||
"\"wakelock_plus\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_STYLE = "non-global";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -13,9 +13,7 @@ import Flutter
|
||||
}
|
||||
|
||||
public func dummyMethodToEnforceBundling() {
|
||||
get_rgba();
|
||||
// free_rgba(nil);
|
||||
// get_by_name("", "");
|
||||
// set_by_name("", "");
|
||||
dummy_method_to_enforce_bundling();
|
||||
session_get_rgba(nil, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +1,122 @@
|
||||
{
|
||||
"images": [
|
||||
"images" : [
|
||||
{
|
||||
"filename": "Icon-App-20x20@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-20x20@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "20x20"
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@1x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "29x29"
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "40x40"
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-60x60@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "60x60"
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-60x60@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "60x60"
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-20x20@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "20x20"
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-20x20@2x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@2x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "40x40"
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@2x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-76x76@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "76x76"
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-76x76@2x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "76x76"
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-83.5x83.5@2x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "83.5x83.5"
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-1024x1024@1x.png",
|
||||
"idiom": "ios-marketing",
|
||||
"scale": "1x",
|
||||
"size": "1024x1024"
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "icons_launcher",
|
||||
"version": 1
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
@@ -14,13 +16,14 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="48" y="-2"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -24,6 +24,21 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.carriez.rustdesk</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>rustdesk</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
|
||||
#import "ffi.h"
|
||||
#import "bridge_generated.h"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
void* get_rgba();
|
||||
void free_rgba(void*);
|
||||
void set_by_name(const char*, const char*);
|
||||
const char* get_by_name(const char*, const char*);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
|
||||
@@ -10,9 +11,11 @@ class HttpType {
|
||||
static const kAuthReqTypeMobile = "mobile";
|
||||
static const kAuthReqTypeSMSCode = "sms_code";
|
||||
static const kAuthReqTypeEmailCode = "email_code";
|
||||
static const kAuthReqTypeTfaCode = "tfa_code";
|
||||
|
||||
static const kAuthResTypeToken = "access_token";
|
||||
static const kAuthResTypeEmailCheck = "email_check";
|
||||
static const kAuthResTypeTfaCheck = "tfa_check";
|
||||
}
|
||||
|
||||
enum UserStatus { kDisabled, kNormal, kUnverified }
|
||||
@@ -48,11 +51,18 @@ class UserPayload {
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toGroupCacheJson() {
|
||||
final Map<String, dynamic> map = {
|
||||
'name': name,
|
||||
};
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class PeerPayload {
|
||||
String id = '';
|
||||
String info = '';
|
||||
Map<String, dynamic> info = {};
|
||||
int? status;
|
||||
String user = '';
|
||||
String user_name = '';
|
||||
@@ -60,14 +70,45 @@ class PeerPayload {
|
||||
|
||||
PeerPayload.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'] ?? '',
|
||||
info = json['info'] ?? '',
|
||||
info = (json['info'] is Map<String, dynamic>) ? json['info'] : {},
|
||||
status = json['status'],
|
||||
user = json['user'] ?? '',
|
||||
user_name = json['user_name'] ?? '',
|
||||
note = json['note'] ?? '';
|
||||
|
||||
static Peer toPeer(PeerPayload p) {
|
||||
return Peer.fromJson({"id": p.id, "username": p.user_name});
|
||||
return Peer.fromJson({
|
||||
"id": p.id,
|
||||
'loginName': p.user_name,
|
||||
"username": p.info['username'] ?? '',
|
||||
"platform": _platform(p.info['os']),
|
||||
"hostname": p.info['device_name'],
|
||||
});
|
||||
}
|
||||
|
||||
static String? _platform(dynamic field) {
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
final fieldStr = field.toString();
|
||||
List<String> list = fieldStr.split(' / ');
|
||||
if (list.isEmpty) return null;
|
||||
final os = list[0];
|
||||
switch (os.toLowerCase()) {
|
||||
case 'windows':
|
||||
return kPeerPlatformWindows;
|
||||
case 'linux':
|
||||
return kPeerPlatformLinux;
|
||||
case 'macos':
|
||||
return kPeerPlatformMacOS;
|
||||
case 'android':
|
||||
return kPeerPlatformAndroid;
|
||||
default:
|
||||
if (fieldStr.toLowerCase().contains('linux')) {
|
||||
return kPeerPlatformLinux;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +120,8 @@ class LoginRequest {
|
||||
bool? autoLogin;
|
||||
String? type;
|
||||
String? verificationCode;
|
||||
String? tfaCode;
|
||||
String? secret;
|
||||
|
||||
LoginRequest(
|
||||
{this.username,
|
||||
@@ -87,7 +130,9 @@ class LoginRequest {
|
||||
this.uuid,
|
||||
this.autoLogin,
|
||||
this.type,
|
||||
this.verificationCode});
|
||||
this.verificationCode,
|
||||
this.tfaCode,
|
||||
this.secret});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
@@ -100,6 +145,8 @@ class LoginRequest {
|
||||
if (verificationCode != null) {
|
||||
data['verificationCode'] = verificationCode;
|
||||
}
|
||||
if (tfaCode != null) data['tfaCode'] = tfaCode;
|
||||
if (secret != null) data['secret'] = secret;
|
||||
|
||||
Map<String, dynamic> deviceInfo = {};
|
||||
try {
|
||||
@@ -115,13 +162,18 @@ class LoginRequest {
|
||||
class LoginResponse {
|
||||
String? access_token;
|
||||
String? type;
|
||||
String? tfa_type;
|
||||
String? secret;
|
||||
UserPayload? user;
|
||||
|
||||
LoginResponse({this.access_token, this.type, this.user});
|
||||
LoginResponse(
|
||||
{this.access_token, this.type, this.tfa_type, this.secret, this.user});
|
||||
|
||||
LoginResponse.fromJson(Map<String, dynamic> json) {
|
||||
access_token = json['access_token'];
|
||||
type = json['type'];
|
||||
tfa_type = json['tfa_type'];
|
||||
secret = json['secret'];
|
||||
user = json['user'] != null ? UserPayload.fromJson(json['user']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../consts.dart';
|
||||
@@ -10,7 +11,7 @@ class PrivacyModeState {
|
||||
static void init(String id) {
|
||||
final key = tag(id);
|
||||
if (!Get.isRegistered(tag: key)) {
|
||||
final RxBool state = false.obs;
|
||||
final RxString state = ''.obs;
|
||||
Get.put(state, tag: key);
|
||||
}
|
||||
}
|
||||
@@ -20,11 +21,11 @@ class PrivacyModeState {
|
||||
if (Get.isRegistered(tag: key)) {
|
||||
Get.delete(tag: key);
|
||||
} else {
|
||||
Get.find<RxBool>(tag: key).value = false;
|
||||
Get.find<RxString>(tag: key).value = '';
|
||||
}
|
||||
}
|
||||
|
||||
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||
static RxString find(String id) => Get.find<RxString>(tag: tag(id));
|
||||
}
|
||||
|
||||
class BlockInputState {
|
||||
@@ -318,6 +319,7 @@ initSharedStates(String id) {
|
||||
FingerprintState.init(id);
|
||||
PeerBoolOption.init(id, 'zoom-cursor', () => false);
|
||||
UnreadChatCountState.init(id);
|
||||
if (isMobile) ConnectionTypeState.init(id); // desktop in other places
|
||||
}
|
||||
|
||||
removeSharedStates(String id) {
|
||||
@@ -330,4 +332,5 @@ removeSharedStates(String id) {
|
||||
FingerprintState.delete(id);
|
||||
PeerBoolOption.delete(id, 'zoom-cursor');
|
||||
UnreadChatCountState.delete(id);
|
||||
if (isMobile) ConnectionTypeState.delete(id);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
@@ -7,6 +10,7 @@ import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flex_color_picker/flex_color_picker.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import 'dialog.dart';
|
||||
@@ -34,7 +38,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Obx(() {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
if (!gFFI.userModel.isLogin) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog, child: Text(translate("Login"))));
|
||||
@@ -48,11 +52,13 @@ class _AddressBookState extends State<AddressBook> {
|
||||
children: [
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
if (gFFI.abModel.retrying.value) LinearProgressIndicator(),
|
||||
_buildErrorBanner(
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.abModel.abLoading,
|
||||
err: gFFI.abModel.pullError,
|
||||
retry: null,
|
||||
close: () => gFFI.abModel.pullError.value = ''),
|
||||
_buildErrorBanner(
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.abModel.abLoading,
|
||||
err: gFFI.abModel.pushError,
|
||||
retry: () => gFFI.abModel.pushAb(isRetry: true),
|
||||
close: () => gFFI.abModel.pushError.value = ''),
|
||||
@@ -65,61 +71,6 @@ class _AddressBookState extends State<AddressBook> {
|
||||
}
|
||||
});
|
||||
|
||||
Widget _buildErrorBanner(
|
||||
{required RxString err,
|
||||
required Function? retry,
|
||||
required Function close}) {
|
||||
const double height = 25;
|
||||
return Obx(() => Offstage(
|
||||
offstage: !(!gFFI.abModel.abLoading.value && err.value.isNotEmpty),
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: height,
|
||||
color: Color.fromARGB(255, 253, 238, 235),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Icon(
|
||||
Icons.info,
|
||||
color: Color.fromARGB(255, 249, 81, 81),
|
||||
),
|
||||
).marginAll(4),
|
||||
Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Tooltip(
|
||||
message: translate(err.value),
|
||||
child: Text(
|
||||
translate(err.value),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)).marginSymmetric(vertical: 2),
|
||||
),
|
||||
if (retry != null)
|
||||
InkWell(
|
||||
onTap: () {
|
||||
retry.call();
|
||||
},
|
||||
child: Text(
|
||||
translate("Retry"),
|
||||
style: TextStyle(color: MyTheme.accent),
|
||||
)).marginSymmetric(horizontal: 5),
|
||||
FittedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
close.call();
|
||||
},
|
||||
child: Icon(Icons.close).marginSymmetric(horizontal: 5),
|
||||
),
|
||||
).marginAll(4)
|
||||
],
|
||||
),
|
||||
)).marginOnly(bottom: 14),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildAddressBookDesktop() {
|
||||
return Row(
|
||||
children: [
|
||||
@@ -208,20 +159,31 @@ class _AddressBookState extends State<AddressBook> {
|
||||
} else {
|
||||
tags = gFFI.abModel.tags;
|
||||
}
|
||||
return Wrap(
|
||||
children: tags
|
||||
.map((e) => AddressBookTag(
|
||||
name: e,
|
||||
tags: gFFI.abModel.selectedTags,
|
||||
onTap: () {
|
||||
if (gFFI.abModel.selectedTags.contains(e)) {
|
||||
gFFI.abModel.selectedTags.remove(e);
|
||||
} else {
|
||||
gFFI.abModel.selectedTags.add(e);
|
||||
}
|
||||
}))
|
||||
.toList(),
|
||||
);
|
||||
tagBuilder(String e) {
|
||||
return AddressBookTag(
|
||||
name: e,
|
||||
tags: gFFI.abModel.selectedTags,
|
||||
onTap: () {
|
||||
if (gFFI.abModel.selectedTags.contains(e)) {
|
||||
gFFI.abModel.selectedTags.remove(e);
|
||||
} else {
|
||||
gFFI.abModel.selectedTags.add(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final gridView = DynamicGridView.builder(
|
||||
shrinkWrap: isMobile,
|
||||
gridDelegate: SliverGridDelegateWithWrapping(),
|
||||
itemCount: tags.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final e = tags[index];
|
||||
return tagBuilder(e);
|
||||
});
|
||||
final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return isDesktop
|
||||
? gridView
|
||||
: LimitedBox(maxHeight: maxHeight, child: gridView);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,11 +191,10 @@ class _AddressBookState extends State<AddressBook> {
|
||||
return Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.abModel.peers.value,
|
||||
))),
|
||||
child: AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
initPeers: gFFI.abModel.peers,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -268,6 +229,22 @@ class _AddressBookState extends State<AddressBook> {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> filterMenuItem() {
|
||||
return MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Filter by intersection'),
|
||||
getter: () async {
|
||||
return filterAbTagByIntersection();
|
||||
},
|
||||
setter: (bool v) async {
|
||||
bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : '');
|
||||
gFFI.abModel.filterByIntersection.value = v;
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _showMenu(RelativeRect pos) {
|
||||
final items = [
|
||||
getEntry(translate("Add ID"), abAddId),
|
||||
@@ -275,6 +252,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags),
|
||||
sortMenuItem(),
|
||||
syncMenuItem(),
|
||||
filterMenuItem(),
|
||||
];
|
||||
|
||||
mod_menu.showMenu(
|
||||
@@ -513,7 +491,7 @@ class AddressBookTag extends StatelessWidget {
|
||||
child: Obx(() => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: tags.contains(name)
|
||||
? str2color2(name, 0xFF)
|
||||
? gFFI.abModel.getTagColor(name)
|
||||
: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0),
|
||||
@@ -528,7 +506,7 @@ class AddressBookTag extends StatelessWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: tags.contains(name)
|
||||
? Colors.white
|
||||
: str2color2(name)),
|
||||
: gFFI.abModel.getTagColor(name)),
|
||||
).marginOnly(right: radius / 2),
|
||||
Expanded(
|
||||
child: Text(name,
|
||||
@@ -568,6 +546,30 @@ class AddressBookTag extends StatelessWidget {
|
||||
Future.delayed(Duration.zero, () => Get.back());
|
||||
});
|
||||
}),
|
||||
getEntry(translate(translate('Change Color')), () async {
|
||||
final model = gFFI.abModel;
|
||||
Color oldColor = model.getTagColor(name);
|
||||
Color newColor = await showColorPickerDialog(
|
||||
context,
|
||||
oldColor,
|
||||
pickersEnabled: {
|
||||
ColorPickerType.accent: false,
|
||||
ColorPickerType.wheel: true,
|
||||
},
|
||||
pickerTypeLabels: {
|
||||
ColorPickerType.primary: translate("Primary Color"),
|
||||
ColorPickerType.wheel: translate("HSV Color"),
|
||||
},
|
||||
actionButtons: ColorPickerActionButtons(
|
||||
dialogOkButtonLabel: translate("OK"),
|
||||
dialogCancelButtonLabel: translate("Cancel")),
|
||||
showColorCode: true,
|
||||
);
|
||||
if (oldColor != newColor) {
|
||||
model.setTagColor(name, newColor);
|
||||
model.pushAb();
|
||||
}
|
||||
}),
|
||||
getEntry(translate("Delete"), () {
|
||||
gFFI.abModel.deleteTag(name);
|
||||
gFFI.abModel.pushAb();
|
||||
|
||||
205
flutter/lib/common/widgets/autocomplete.dart
Normal file
205
flutter/lib/common/widgets/autocomplete.dart
Normal file
@@ -0,0 +1,205 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import '../../../models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
|
||||
Future<List<Peer>> getAllPeers() async {
|
||||
Map<String, dynamic> recentPeers = jsonDecode(bind.mainLoadRecentPeersSync());
|
||||
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
|
||||
Map<String, dynamic> abPeers = jsonDecode(bind.mainLoadAbSync());
|
||||
Map<String, dynamic> groupPeers = jsonDecode(bind.mainLoadGroupSync());
|
||||
|
||||
Map<String, dynamic> combinedPeers = {};
|
||||
|
||||
void mergePeers(Map<String, dynamic> peers) {
|
||||
if (peers.containsKey("peers")) {
|
||||
dynamic peerData = peers["peers"];
|
||||
|
||||
if (peerData is String) {
|
||||
try {
|
||||
peerData = jsonDecode(peerData);
|
||||
} catch (e) {
|
||||
print("Error decoding peers: $e");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (peerData is List) {
|
||||
for (var peer in peerData) {
|
||||
if (peer is Map && peer.containsKey("id")) {
|
||||
String id = peer["id"];
|
||||
if (!combinedPeers.containsKey(id)) {
|
||||
combinedPeers[id] = peer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mergePeers(recentPeers);
|
||||
mergePeers(lanPeers);
|
||||
mergePeers(abPeers);
|
||||
mergePeers(groupPeers);
|
||||
|
||||
List<Peer> parsedPeers = [];
|
||||
|
||||
for (var peer in combinedPeers.values) {
|
||||
parsedPeers.add(Peer.fromJson(peer));
|
||||
}
|
||||
return parsedPeers;
|
||||
}
|
||||
|
||||
class AutocompletePeerTile extends StatefulWidget {
|
||||
final VoidCallback onSelect;
|
||||
final Peer peer;
|
||||
|
||||
const AutocompletePeerTile({
|
||||
Key? key,
|
||||
required this.onSelect,
|
||||
required this.peer,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
AutocompletePeerTileState createState() => AutocompletePeerTileState();
|
||||
}
|
||||
|
||||
class AutocompletePeerTileState extends State<AutocompletePeerTile> {
|
||||
List _frontN<T>(List list, int n) {
|
||||
if (list.length <= n) {
|
||||
return list;
|
||||
} else {
|
||||
return list.sublist(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double tileRadius = 5;
|
||||
final name =
|
||||
'${widget.peer.username}${widget.peer.username.isNotEmpty && widget.peer.hostname.isNotEmpty ? '@' : ''}${widget.peer.hostname}';
|
||||
final greyStyle = TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
|
||||
final child = GestureDetector(
|
||||
onTap: () => widget.onSelect(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5, right: 5),
|
||||
child: Container(
|
||||
height: 42,
|
||||
margin: EdgeInsets.only(bottom: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: str2color(
|
||||
'${widget.peer.id}${widget.peer.platform}', 0x7f),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(tileRadius),
|
||||
bottomLeft: Radius.circular(tileRadius),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
width: 42,
|
||||
height: null,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: getPlatformImage(widget.peer.platform,
|
||||
size: 30))),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(tileRadius),
|
||||
bottomRight: Radius.circular(tileRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(top: 2),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(top: 2),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin:
|
||||
EdgeInsets.only(top: 2),
|
||||
child: Row(children: [
|
||||
getOnline(
|
||||
8, widget.peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.peer.alias.isEmpty
|
||||
? formatID(
|
||||
widget.peer.id)
|
||||
: widget.peer.alias,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall,
|
||||
)),
|
||||
widget.peer.alias.isNotEmpty
|
||||
? Padding(
|
||||
padding:
|
||||
const EdgeInsets
|
||||
.only(
|
||||
left: 5,
|
||||
right: 5),
|
||||
child: Text(
|
||||
"(${widget.peer.id})",
|
||||
style: greyStyle,
|
||||
overflow:
|
||||
TextOverflow
|
||||
.ellipsis,
|
||||
))
|
||||
: Container(),
|
||||
])),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
)))),
|
||||
],
|
||||
)),
|
||||
)
|
||||
],
|
||||
))));
|
||||
final colors = _frontN(widget.peer.tags, 25)
|
||||
.map((e) => gFFI.abModel.getTagColor(e))
|
||||
.toList();
|
||||
return Tooltip(
|
||||
message: isMobile
|
||||
? ''
|
||||
: widget.peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${widget.peer.tags.join(', ')}'
|
||||
: '',
|
||||
child: Stack(children: [
|
||||
child,
|
||||
if (colors.isNotEmpty)
|
||||
Positioned(
|
||||
top: 5,
|
||||
right: 10,
|
||||
child: CustomPaint(
|
||||
painter: TagPainter(radius: 3, colors: colors),
|
||||
),
|
||||
)
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/model.dart';
|
||||
@@ -302,6 +306,53 @@ Future<String> changeDirectAccessPort(
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
Future<String> changeAutoDisconnectTimeout(String old) async {
|
||||
final controller = TextEditingController(text: old);
|
||||
await gFFI.dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Timeout in minutes")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
hintText: '10',
|
||||
isCollapsed: true,
|
||||
suffix: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.clear, size: 16),
|
||||
onPressed: () => controller.clear())),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
controller: controller,
|
||||
autofocus: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: () async {
|
||||
await bind.mainSetOption(
|
||||
key: 'auto-disconnect-timeout', value: controller.text);
|
||||
close();
|
||||
}),
|
||||
],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
class DialogTextField extends StatelessWidget {
|
||||
final String title;
|
||||
final String? hintText;
|
||||
@@ -312,6 +363,8 @@ class DialogTextField extends StatelessWidget {
|
||||
final Widget? suffixIcon;
|
||||
final TextEditingController controller;
|
||||
final FocusNode? focusNode;
|
||||
final TextInputType? keyboardType;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
|
||||
static const kUsernameTitle = 'Username';
|
||||
static const kUsernameIcon = Icon(Icons.account_circle_outlined);
|
||||
@@ -327,6 +380,8 @@ class DialogTextField extends StatelessWidget {
|
||||
this.prefixIcon,
|
||||
this.suffixIcon,
|
||||
this.hintText,
|
||||
this.keyboardType,
|
||||
this.inputFormatters,
|
||||
required this.title,
|
||||
required this.controller})
|
||||
: super(key: key);
|
||||
@@ -351,6 +406,8 @@ class DialogTextField extends StatelessWidget {
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
inputFormatters: inputFormatters,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -358,17 +415,260 @@ class DialogTextField extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ValidationField extends StatelessWidget {
|
||||
ValidationField({Key? key}) : super(key: key);
|
||||
|
||||
String? validate();
|
||||
bool get isReady;
|
||||
}
|
||||
|
||||
class Dialog2FaField extends ValidationField {
|
||||
Dialog2FaField({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
this.autoFocus = true,
|
||||
this.reRequestFocus = false,
|
||||
this.title,
|
||||
this.hintText,
|
||||
this.errorText,
|
||||
this.readyCallback,
|
||||
this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final TextEditingController controller;
|
||||
final bool autoFocus;
|
||||
final bool reRequestFocus;
|
||||
final String? title;
|
||||
final String? hintText;
|
||||
final String? errorText;
|
||||
final VoidCallback? readyCallback;
|
||||
final VoidCallback? onChanged;
|
||||
final errMsg = translate('2FA code must be 6 digits.');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DialogVerificationCodeField(
|
||||
title: title ?? translate('2FA code'),
|
||||
controller: controller,
|
||||
errorText: errorText,
|
||||
autoFocus: autoFocus,
|
||||
reRequestFocus: reRequestFocus,
|
||||
hintText: hintText,
|
||||
readyCallback: readyCallback,
|
||||
onChanged: _onChanged,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String get text => controller.text;
|
||||
bool get isAllDigits => text.codeUnits.every((e) => e >= 48 && e <= 57);
|
||||
|
||||
@override
|
||||
bool get isReady => text.length == 6 && isAllDigits;
|
||||
|
||||
@override
|
||||
String? validate() => isReady ? null : errMsg;
|
||||
|
||||
_onChanged(StateSetter setState, SimpleWrapper<String?> errText) {
|
||||
onChanged?.call();
|
||||
|
||||
if (text.length > 6) {
|
||||
setState(() => errText.value = errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAllDigits) {
|
||||
setState(() => errText.value = errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
readyCallback?.call();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errText.value != null) {
|
||||
setState(() => errText.value = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DialogEmailCodeField extends ValidationField {
|
||||
DialogEmailCodeField({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
this.autoFocus = true,
|
||||
this.reRequestFocus = false,
|
||||
this.hintText,
|
||||
this.errorText,
|
||||
this.readyCallback,
|
||||
this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final TextEditingController controller;
|
||||
final bool autoFocus;
|
||||
final bool reRequestFocus;
|
||||
final String? hintText;
|
||||
final String? errorText;
|
||||
final VoidCallback? readyCallback;
|
||||
final VoidCallback? onChanged;
|
||||
final errMsg = translate('Email verification code must be 6 characters.');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DialogVerificationCodeField(
|
||||
title: translate('Verification code'),
|
||||
controller: controller,
|
||||
errorText: errorText,
|
||||
autoFocus: autoFocus,
|
||||
reRequestFocus: reRequestFocus,
|
||||
hintText: hintText,
|
||||
readyCallback: readyCallback,
|
||||
helperText: translate('verification_tip'),
|
||||
onChanged: _onChanged,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
);
|
||||
}
|
||||
|
||||
String get text => controller.text;
|
||||
|
||||
@override
|
||||
bool get isReady => text.length == 6;
|
||||
|
||||
@override
|
||||
String? validate() => isReady ? null : errMsg;
|
||||
|
||||
_onChanged(StateSetter setState, SimpleWrapper<String?> errText) {
|
||||
onChanged?.call();
|
||||
|
||||
if (text.length > 6) {
|
||||
setState(() => errText.value = errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
readyCallback?.call();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errText.value != null) {
|
||||
setState(() => errText.value = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DialogVerificationCodeField extends StatefulWidget {
|
||||
DialogVerificationCodeField({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.title,
|
||||
this.autoFocus = true,
|
||||
this.reRequestFocus = false,
|
||||
this.helperText,
|
||||
this.hintText,
|
||||
this.errorText,
|
||||
this.textLength,
|
||||
this.readyCallback,
|
||||
this.onChanged,
|
||||
this.keyboardType,
|
||||
this.inputFormatters,
|
||||
}) : super(key: key);
|
||||
|
||||
final TextEditingController controller;
|
||||
final bool autoFocus;
|
||||
final bool reRequestFocus;
|
||||
final String title;
|
||||
final String? helperText;
|
||||
final String? hintText;
|
||||
final String? errorText;
|
||||
final int? textLength;
|
||||
final VoidCallback? readyCallback;
|
||||
final Function(StateSetter setState, SimpleWrapper<String?> errText)?
|
||||
onChanged;
|
||||
final TextInputType? keyboardType;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
|
||||
@override
|
||||
State<DialogVerificationCodeField> createState() =>
|
||||
_DialogVerificationCodeField();
|
||||
}
|
||||
|
||||
class _DialogVerificationCodeField extends State<DialogVerificationCodeField> {
|
||||
final _focusNode = FocusNode();
|
||||
Timer? _timer;
|
||||
Timer? _timerReRequestFocus;
|
||||
SimpleWrapper<String?> errorText = SimpleWrapper(null);
|
||||
String _preText = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.autoFocus) {
|
||||
_timer =
|
||||
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
||||
|
||||
if (widget.onChanged != null) {
|
||||
widget.controller.addListener(() {
|
||||
final text = widget.controller.text.trim();
|
||||
if (text == _preText) return;
|
||||
widget.onChanged!(setState, errorText);
|
||||
_preText = text;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// software secure keyboard will take the focus since flutter 3.13
|
||||
// request focus again when android account password obtain focus
|
||||
if (Platform.isAndroid && widget.reRequestFocus) {
|
||||
_focusNode.addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_timerReRequestFocus?.cancel();
|
||||
_timerReRequestFocus = Timer(
|
||||
Duration(milliseconds: 100), () => _focusNode.requestFocus());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_timerReRequestFocus?.cancel();
|
||||
_focusNode.unfocus();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DialogTextField(
|
||||
title: widget.title,
|
||||
controller: widget.controller,
|
||||
errorText: widget.errorText ?? errorText.value,
|
||||
focusNode: _focusNode,
|
||||
helperText: widget.helperText,
|
||||
keyboardType: widget.keyboardType,
|
||||
inputFormatters: widget.inputFormatters,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordWidget extends StatefulWidget {
|
||||
PasswordWidget({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
this.autoFocus = true,
|
||||
this.reRequestFocus = false,
|
||||
this.hintText,
|
||||
this.errorText,
|
||||
}) : super(key: key);
|
||||
|
||||
final TextEditingController controller;
|
||||
final bool autoFocus;
|
||||
final bool reRequestFocus;
|
||||
final String? hintText;
|
||||
final String? errorText;
|
||||
|
||||
@@ -380,6 +680,7 @@ class _PasswordWidgetState extends State<PasswordWidget> {
|
||||
bool _passwordVisible = false;
|
||||
final _focusNode = FocusNode();
|
||||
Timer? _timer;
|
||||
Timer? _timerReRequestFocus;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -388,11 +689,23 @@ class _PasswordWidgetState extends State<PasswordWidget> {
|
||||
_timer =
|
||||
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
||||
}
|
||||
// software secure keyboard will take the focus since flutter 3.13
|
||||
// request focus again when android account password obtain focus
|
||||
if (Platform.isAndroid && widget.reRequestFocus) {
|
||||
_focusNode.addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_timerReRequestFocus?.cancel();
|
||||
_timerReRequestFocus = Timer(
|
||||
Duration(milliseconds: 100), () => _focusNode.requestFocus());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_timerReRequestFocus?.cancel();
|
||||
_focusNode.unfocus();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
@@ -664,6 +977,13 @@ void showWaitUacDialog(
|
||||
(setState, close, context) => CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
|
||||
actions: [
|
||||
dialogButton(
|
||||
'OK',
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: close,
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@@ -812,6 +1132,8 @@ void showRequestElevationDialog(
|
||||
} else {
|
||||
bind.sessionElevateDirect(sessionId: sessionId);
|
||||
}
|
||||
close();
|
||||
showWaitUacDialog(sessionId, dialogManager, "wait-uac");
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
@@ -882,7 +1204,7 @@ void showElevationError(SessionID sessionId, String type, String title,
|
||||
dialogButton('Cancel', onPressed: () {
|
||||
close();
|
||||
}, isOutline: true),
|
||||
dialogButton('Retry', onPressed: submit),
|
||||
if (text != 'No permission') dialogButton('Retry', onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
@@ -916,7 +1238,7 @@ void showRestartRemoteDevice(PeerInfo pi, String id, SessionID sessionId,
|
||||
title: Row(children: [
|
||||
Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28),
|
||||
Flexible(
|
||||
child: Text(translate("Restart Remote Device"))
|
||||
child: Text(translate("Restart remote device"))
|
||||
.paddingOnly(left: 10)),
|
||||
]),
|
||||
content: Text(
|
||||
@@ -1188,11 +1510,24 @@ void showConfirmSwitchSidesDialog(
|
||||
}
|
||||
|
||||
customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
||||
double qualityInitValue = 50;
|
||||
double fpsInitValue = 30;
|
||||
double initQuality = kDefaultQuality;
|
||||
double initFps = kDefaultFps;
|
||||
bool qualitySet = false;
|
||||
bool fpsSet = false;
|
||||
|
||||
bool? direct;
|
||||
try {
|
||||
direct =
|
||||
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
||||
} catch (_) {}
|
||||
bool hideFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
|
||||
bool hideMoreQuality =
|
||||
(await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||
versionCmp(ffi.ffiModel.pi.version, '1.2.2') < 0;
|
||||
|
||||
setCustomValues({double? quality, double? fps}) async {
|
||||
debugPrint("setCustomValues quality:$quality, fps:$fps");
|
||||
if (quality != null) {
|
||||
qualitySet = true;
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
@@ -1205,12 +1540,12 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
||||
if (!qualitySet) {
|
||||
qualitySet = true;
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
sessionId: sessionId, value: qualityInitValue.toInt());
|
||||
sessionId: sessionId, value: initQuality.toInt());
|
||||
}
|
||||
if (!fpsSet) {
|
||||
if (!hideFps && !fpsSet) {
|
||||
fpsSet = true;
|
||||
await bind.sessionSetCustomFps(
|
||||
sessionId: sessionId, fps: fpsInitValue.toInt());
|
||||
sessionId: sessionId, fps: initFps.toInt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1221,134 +1556,30 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
||||
|
||||
// quality
|
||||
final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId);
|
||||
qualityInitValue =
|
||||
quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0;
|
||||
const qualityMinValue = 10.0;
|
||||
const qualityMoreThresholdValue = 100.0;
|
||||
const qualityMaxValue = 2000.0;
|
||||
if (qualityInitValue < qualityMinValue) {
|
||||
qualityInitValue = qualityMinValue;
|
||||
initQuality = quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: kDefaultQuality;
|
||||
if (initQuality < kMinQuality ||
|
||||
initQuality > (!hideMoreQuality ? kMaxMoreQuality : kMaxQuality)) {
|
||||
initQuality = kDefaultQuality;
|
||||
}
|
||||
if (qualityInitValue > qualityMaxValue) {
|
||||
qualityInitValue = qualityMaxValue;
|
||||
}
|
||||
final RxDouble qualitySliderValue = RxDouble(qualityInitValue);
|
||||
final moreQualityInitValue = qualityInitValue > qualityMoreThresholdValue;
|
||||
final RxBool moreQualityChecked = RxBool(moreQualityInitValue);
|
||||
final debouncerQuality = Debouncer<double>(
|
||||
Duration(milliseconds: 1000),
|
||||
onChanged: (double v) {
|
||||
setCustomValues(quality: v);
|
||||
},
|
||||
initialValue: qualityInitValue,
|
||||
);
|
||||
final qualitySlider = Obx(() => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Slider(
|
||||
value: qualitySliderValue.value,
|
||||
min: qualityMinValue,
|
||||
max: moreQualityChecked.value
|
||||
? qualityMaxValue
|
||||
: qualityMoreThresholdValue,
|
||||
divisions: 18,
|
||||
onChanged: (double value) {
|
||||
qualitySliderValue.value = value;
|
||||
debouncerQuality.value = value;
|
||||
},
|
||||
)),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'${qualitySliderValue.value.round()}%',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
translate('Bitrate'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: moreQualityChecked.value,
|
||||
onChanged: (bool? value) {
|
||||
moreQualityChecked.value = value!;
|
||||
if (!value &&
|
||||
qualitySliderValue.value >
|
||||
qualityMoreThresholdValue) {
|
||||
qualitySliderValue.value = qualityMoreThresholdValue;
|
||||
debouncerQuality.value = qualityMoreThresholdValue;
|
||||
}
|
||||
},
|
||||
).marginOnly(right: 5),
|
||||
Expanded(
|
||||
child: Text(translate('More')),
|
||||
)
|
||||
],
|
||||
)),
|
||||
],
|
||||
));
|
||||
// fps
|
||||
final fpsOption =
|
||||
await bind.sessionGetOption(sessionId: sessionId, arg: 'custom-fps');
|
||||
fpsInitValue = fpsOption == null ? 30 : double.tryParse(fpsOption) ?? 30;
|
||||
if (fpsInitValue < 5 || fpsInitValue > 120) {
|
||||
fpsInitValue = 30;
|
||||
initFps = fpsOption == null
|
||||
? kDefaultFps
|
||||
: double.tryParse(fpsOption) ?? kDefaultFps;
|
||||
if (initFps < kMinFps || initFps > kMaxFps) {
|
||||
initFps = kDefaultFps;
|
||||
}
|
||||
final RxDouble fpsSliderValue = RxDouble(fpsInitValue);
|
||||
final debouncerFps = Debouncer<double>(
|
||||
Duration(milliseconds: 1000),
|
||||
onChanged: (double v) {
|
||||
setCustomValues(fps: v);
|
||||
},
|
||||
initialValue: qualityInitValue,
|
||||
);
|
||||
bool? direct;
|
||||
try {
|
||||
direct =
|
||||
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
||||
} catch (_) {}
|
||||
final fpsSlider = Offstage(
|
||||
offstage: (await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||
version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Obx((() => Slider(
|
||||
value: fpsSliderValue.value,
|
||||
min: 5,
|
||||
max: 120,
|
||||
divisions: 23,
|
||||
onChanged: (double value) {
|
||||
fpsSliderValue.value = value;
|
||||
debouncerFps.value = value;
|
||||
},
|
||||
)))),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Obx(() => Text(
|
||||
'${fpsSliderValue.value.round()}',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
translate('FPS'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final content = Column(
|
||||
children: [qualitySlider, fpsSlider],
|
||||
);
|
||||
final content = customImageQualityWidget(
|
||||
initQuality: initQuality,
|
||||
initFps: initFps,
|
||||
setQuality: (v) => setCustomValues(quality: v),
|
||||
setFps: (v) => setCustomValues(fps: v),
|
||||
showFps: !hideFps,
|
||||
showMoreQuality: !hideMoreQuality);
|
||||
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
|
||||
}
|
||||
|
||||
@@ -1519,3 +1750,162 @@ void renameDialog(
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void change2fa({Function()? callback}) async {
|
||||
if (bind.mainHasValid2FaSync()) {
|
||||
await bind.mainSetOption(key: "2fa", value: "");
|
||||
callback?.call();
|
||||
return;
|
||||
}
|
||||
var new2fa = (await bind.mainGenerate2Fa());
|
||||
final secretRegex = RegExp(r'secret=([^&]+)');
|
||||
final secret = secretRegex.firstMatch(new2fa)?.group(1);
|
||||
String? errorText;
|
||||
final controller = TextEditingController();
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
onVerify() async {
|
||||
if (await bind.mainVerify2Fa(code: controller.text.trim())) {
|
||||
callback?.call();
|
||||
close();
|
||||
} else {
|
||||
errorText = translate('wrong-2fa-code');
|
||||
}
|
||||
}
|
||||
|
||||
final codeField = Dialog2FaField(
|
||||
controller: controller,
|
||||
errorText: errorText,
|
||||
onChanged: () => setState(() => errorText = null),
|
||||
title: translate('Verification code'),
|
||||
readyCallback: () {
|
||||
onVerify();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
|
||||
getOnSubmit() => codeField.isReady ? onVerify : null;
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("enable-2fa-title")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(translate("enable-2fa-desc"),
|
||||
style: TextStyle(fontSize: 12))
|
||||
.marginOnly(bottom: 12),
|
||||
SizedBox(
|
||||
width: 160,
|
||||
height: 160,
|
||||
child: QrImageView(
|
||||
backgroundColor: Colors.white,
|
||||
data: new2fa,
|
||||
version: QrVersions.auto,
|
||||
size: 160,
|
||||
gapless: false,
|
||||
)).marginOnly(bottom: 6),
|
||||
SelectableText(secret ?? '', style: TextStyle(fontSize: 12))
|
||||
.marginOnly(bottom: 12),
|
||||
Row(children: [Expanded(child: codeField)]),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: getOnSubmit()),
|
||||
],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void enter2FaDialog(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
||||
final controller = TextEditingController();
|
||||
final RxBool submitReady = false.obs;
|
||||
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close, context) {
|
||||
cancel() {
|
||||
close();
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
submit() {
|
||||
gFFI.send2FA(sessionId, controller.text.trim());
|
||||
close();
|
||||
dialogManager.showLoading(translate('Logging in...'),
|
||||
onCancel: closeConnection);
|
||||
}
|
||||
|
||||
late Dialog2FaField codeField;
|
||||
|
||||
codeField = Dialog2FaField(
|
||||
controller: controller,
|
||||
title: translate('Verification code'),
|
||||
onChanged: () => submitReady.value = codeField.isReady,
|
||||
);
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('enter-2fa-title')),
|
||||
content: codeField,
|
||||
actions: [
|
||||
dialogButton('Cancel',
|
||||
onPressed: cancel,
|
||||
isOutline: true,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color)),
|
||||
Obx(() => dialogButton(
|
||||
'OK',
|
||||
onPressed: submitReady.isTrue ? submit : null,
|
||||
)),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: cancel);
|
||||
});
|
||||
}
|
||||
|
||||
// This dialog should not be dismissed, otherwise it will be black screen, have not reproduced this.
|
||||
void showWindowsSessionsDialog(
|
||||
String type,
|
||||
String title,
|
||||
String text,
|
||||
OverlayDialogManager dialogManager,
|
||||
SessionID sessionId,
|
||||
String peerId,
|
||||
String sessions) {
|
||||
List<dynamic> sessionsList = [];
|
||||
try {
|
||||
sessionsList = json.decode(sessions);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
List<String> sids = [];
|
||||
List<String> names = [];
|
||||
for (var session in sessionsList) {
|
||||
sids.add(session['sid']);
|
||||
names.add(session['name']);
|
||||
}
|
||||
String selectedUserValue = sids.first;
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close, context) {
|
||||
submit() {
|
||||
bind.sessionSendSelectedSessionId(
|
||||
sessionId: sessionId, sid: selectedUserValue);
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, title, text),
|
||||
actions: [
|
||||
ComboBox(
|
||||
keys: sids,
|
||||
values: names,
|
||||
initialKey: selectedUserValue,
|
||||
onChanged: (value) {
|
||||
selectedUserValue = value;
|
||||
}),
|
||||
dialogButton('Connect', onPressed: submit, isOutline: false),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import './dialog.dart';
|
||||
|
||||
const kOpSvgList = [
|
||||
'github',
|
||||
'gitlab',
|
||||
'google',
|
||||
'apple',
|
||||
'okta',
|
||||
@@ -72,6 +73,11 @@ class ButtonOP extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final opLabel = {
|
||||
'github': 'GitHub',
|
||||
'gitlab': 'GitLab'
|
||||
}[op.toLowerCase()] ??
|
||||
toCapitalized(op);
|
||||
return Row(children: [
|
||||
Container(
|
||||
height: height,
|
||||
@@ -97,8 +103,7 @@ class ButtonOP extends StatelessWidget {
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${translate("Continue with")} ${op.toLowerCase() == "github" ? "GitHub" : toCapitalized(op)}')),
|
||||
child: Text('${translate("Continue with")} $opLabel')),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -352,6 +357,7 @@ class LoginWidgetUserPass extends StatelessWidget {
|
||||
PasswordWidget(
|
||||
controller: pass,
|
||||
autoFocus: false,
|
||||
reRequestFocus: true,
|
||||
errorText: passMsg,
|
||||
),
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
@@ -384,8 +390,7 @@ class LoginWidgetUserPass extends StatelessWidget {
|
||||
|
||||
const kAuthReqTypeOidc = 'oidc/';
|
||||
|
||||
/// common login dialog for desktop
|
||||
/// call this directly
|
||||
// call this directly
|
||||
Future<bool?> loginDialog() async {
|
||||
var username =
|
||||
TextEditingController(text: UserModel.getLocalUserInfo()?['name'] ?? '');
|
||||
@@ -421,6 +426,55 @@ Future<bool?> loginDialog() async {
|
||||
close(false);
|
||||
}
|
||||
|
||||
handleLoginResponse(LoginResponse resp, bool storeIfAccessToken,
|
||||
void Function([dynamic])? close) async {
|
||||
switch (resp.type) {
|
||||
case HttpType.kAuthResTypeToken:
|
||||
if (resp.access_token != null) {
|
||||
if (storeIfAccessToken) {
|
||||
await bind.mainSetLocalOption(
|
||||
key: 'access_token', value: resp.access_token!);
|
||||
await bind.mainSetLocalOption(
|
||||
key: 'user_info', value: jsonEncode(resp.user ?? {}));
|
||||
}
|
||||
if (close != null) {
|
||||
close(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HttpType.kAuthResTypeEmailCheck:
|
||||
bool? isEmailVerification;
|
||||
if (resp.tfa_type == null ||
|
||||
resp.tfa_type == HttpType.kAuthResTypeEmailCheck) {
|
||||
isEmailVerification = true;
|
||||
} else if (resp.tfa_type == HttpType.kAuthResTypeTfaCheck) {
|
||||
isEmailVerification = false;
|
||||
} else {
|
||||
passwordMsg = "Failed, bad tfa type from server";
|
||||
}
|
||||
if (isEmailVerification != null) {
|
||||
if (isMobile) {
|
||||
if (close != null) close(false);
|
||||
verificationCodeDialog(
|
||||
resp.user, resp.secret, isEmailVerification);
|
||||
} else {
|
||||
setState(() => isInProgress = false);
|
||||
final res = await verificationCodeDialog(
|
||||
resp.user, resp.secret, isEmailVerification);
|
||||
if (res == true) {
|
||||
if (close != null) close(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
passwordMsg = "Failed, bad response from server";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onLogin() async {
|
||||
// validate
|
||||
if (username.text.isEmpty) {
|
||||
@@ -441,35 +495,7 @@ Future<bool?> loginDialog() async {
|
||||
uuid: await bind.mainGetUuid(),
|
||||
autoLogin: true,
|
||||
type: HttpType.kAuthReqTypeAccount));
|
||||
|
||||
switch (resp.type) {
|
||||
case HttpType.kAuthResTypeToken:
|
||||
if (resp.access_token != null) {
|
||||
await bind.mainSetLocalOption(
|
||||
key: 'access_token', value: resp.access_token!);
|
||||
await bind.mainSetLocalOption(
|
||||
key: 'user_info', value: jsonEncode(resp.user ?? {}));
|
||||
close(true);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HttpType.kAuthResTypeEmailCheck:
|
||||
if (isMobile) {
|
||||
close(true);
|
||||
verificationCodeDialog(resp.user);
|
||||
} else {
|
||||
setState(() => isInProgress = false);
|
||||
final res = await verificationCodeDialog(resp.user);
|
||||
if (res == true) {
|
||||
close(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
passwordMsg = "Failed, bad response from server";
|
||||
break;
|
||||
}
|
||||
await handleLoginResponse(resp, true, close);
|
||||
} on RequestException catch (err) {
|
||||
passwordMsg = translate(err.cause);
|
||||
} catch (err) {
|
||||
@@ -500,15 +526,21 @@ Future<bool?> loginDialog() async {
|
||||
.map((e) => ConfigOP(op: e['name'], icon: e['icon']))
|
||||
.toList(),
|
||||
curOP: curOP,
|
||||
cbLogin: (Map<String, dynamic> authBody) {
|
||||
cbLogin: (Map<String, dynamic> authBody) async {
|
||||
LoginResponse? resp;
|
||||
try {
|
||||
// access_token is already stored in the rust side.
|
||||
gFFI.userModel.getLoginResponseFromAuthBody(authBody);
|
||||
resp =
|
||||
gFFI.userModel.getLoginResponseFromAuthBody(authBody);
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Failed to parse oidc login body: "$authBody"');
|
||||
}
|
||||
close(true);
|
||||
|
||||
if (resp != null) {
|
||||
handleLoginResponse(resp, false, null);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -567,6 +599,7 @@ Future<bool?> loginDialog() async {
|
||||
],
|
||||
),
|
||||
onCancel: onDialogCancel,
|
||||
onSubmit: onLogin,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -577,37 +610,23 @@ Future<bool?> loginDialog() async {
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<bool?> verificationCodeDialog(UserPayload? user) async {
|
||||
Future<bool?> verificationCodeDialog(
|
||||
UserPayload? user, String? secret, bool isEmailVerification) async {
|
||||
var autoLogin = true;
|
||||
var isInProgress = false;
|
||||
String? errorText;
|
||||
|
||||
final code = TextEditingController();
|
||||
final focusNode = FocusNode()..requestFocus();
|
||||
Timer(Duration(milliseconds: 100), () => focusNode..requestFocus());
|
||||
|
||||
final res = await gFFI.dialogManager.show<bool>((setState, close, context) {
|
||||
bool validate() {
|
||||
return code.text.length >= 6;
|
||||
}
|
||||
|
||||
code.addListener(() {
|
||||
if (errorText != null) {
|
||||
setState(() => errorText = null);
|
||||
}
|
||||
});
|
||||
|
||||
void onVerify() async {
|
||||
if (!validate()) {
|
||||
setState(
|
||||
() => errorText = translate('Too short, at least 6 characters.'));
|
||||
return;
|
||||
}
|
||||
setState(() => isInProgress = true);
|
||||
|
||||
try {
|
||||
final resp = await gFFI.userModel.login(LoginRequest(
|
||||
verificationCode: code.text,
|
||||
tfaCode: isEmailVerification ? null : code.text,
|
||||
secret: secret,
|
||||
username: user?.name,
|
||||
id: await bind.mainGetMyId(),
|
||||
uuid: await bind.mainGetUuid(),
|
||||
@@ -636,27 +655,37 @@ Future<bool?> verificationCodeDialog(UserPayload? user) async {
|
||||
setState(() => isInProgress = false);
|
||||
}
|
||||
|
||||
final codeField = isEmailVerification
|
||||
? DialogEmailCodeField(
|
||||
controller: code,
|
||||
errorText: errorText,
|
||||
readyCallback: onVerify,
|
||||
onChanged: () => errorText = null,
|
||||
)
|
||||
: Dialog2FaField(
|
||||
controller: code,
|
||||
errorText: errorText,
|
||||
readyCallback: onVerify,
|
||||
onChanged: () => errorText = null,
|
||||
);
|
||||
|
||||
getOnSubmit() => codeField.isReady ? onVerify : null;
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Verification code")),
|
||||
contentBoxConstraints: BoxConstraints(maxWidth: 300),
|
||||
content: Column(
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: user?.email == null,
|
||||
offstage: !isEmailVerification || user?.email == null,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Email", prefixIcon: Icon(Icons.email)),
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: user?.email),
|
||||
)),
|
||||
const SizedBox(height: 8),
|
||||
DialogTextField(
|
||||
title: '${translate("Verification code")}:',
|
||||
controller: code,
|
||||
errorText: errorText,
|
||||
focusNode: focusNode,
|
||||
helperText: translate('verification_tip'),
|
||||
),
|
||||
isEmailVerification ? const SizedBox(height: 8) : const Offstage(),
|
||||
codeField,
|
||||
/*
|
||||
CheckboxListTile(
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
@@ -677,10 +706,10 @@ Future<bool?> verificationCodeDialog(UserPayload? user) async {
|
||||
],
|
||||
),
|
||||
onCancel: close,
|
||||
onSubmit: onVerify,
|
||||
onSubmit: getOnSubmit(),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("Verify", onPressed: onVerify),
|
||||
dialogButton("Verify", onPressed: getOnSubmit()),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
|
||||
import 'package:flutter_hbb/common/widgets/login.dart';
|
||||
@@ -29,49 +31,28 @@ class _MyGroupState extends State<MyGroup> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
// use username to be same with ab
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
if (!gFFI.userModel.isLogin) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog, child: Text(translate("Login"))));
|
||||
}
|
||||
return buildBody(context);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (gFFI.groupModel.groupLoading.value) {
|
||||
} else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
if (gFFI.groupModel.groupLoadError.isNotEmpty) {
|
||||
return _buildShowError(gFFI.groupModel.groupLoadError.value);
|
||||
}
|
||||
if (isDesktop) {
|
||||
return _buildDesktop();
|
||||
} else {
|
||||
return _buildMobile();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.groupModel.groupLoading,
|
||||
err: gFFI.groupModel.groupLoadError,
|
||||
retry: null,
|
||||
close: () => gFFI.groupModel.groupLoadError.value = ''),
|
||||
Expanded(child: isDesktop ? _buildDesktop() : _buildMobile())
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildShowError(String error) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(translate(error)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
gFFI.groupModel.pull();
|
||||
},
|
||||
child: Text(translate("Retry")))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDesktop() {
|
||||
return Row(
|
||||
children: [
|
||||
@@ -100,10 +81,9 @@ class _MyGroupState extends State<MyGroup> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => MyGroupPeerView(
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.groupModel.peersShow.value))),
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -133,16 +113,16 @@ class _MyGroupState extends State<MyGroup> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => MyGroupPeerView(
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.groupModel.peersShow.value))),
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLeftHeader() {
|
||||
final fontSize = 14.0;
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -151,16 +131,16 @@ class _MyGroupState extends State<MyGroup> {
|
||||
onChanged: (value) {
|
||||
searchUserText.value = value;
|
||||
},
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
decoration: InputDecoration(
|
||||
filled: false,
|
||||
prefixIcon: Icon(
|
||||
Icons.search_rounded,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 10),
|
||||
).paddingOnly(top: 2),
|
||||
hintText: translate("Search"),
|
||||
hintStyle:
|
||||
TextStyle(fontSize: 14, color: Theme.of(context).hintColor),
|
||||
hintStyle: TextStyle(fontSize: fontSize),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
@@ -171,16 +151,22 @@ class _MyGroupState extends State<MyGroup> {
|
||||
|
||||
Widget _buildUserContacts() {
|
||||
return Obx(() {
|
||||
return Column(
|
||||
children: gFFI.groupModel.users
|
||||
.where((p0) {
|
||||
if (searchUserText.isNotEmpty) {
|
||||
return p0.name.contains(searchUserText.value);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((e) => _buildUserItem(e))
|
||||
.toList());
|
||||
final items = gFFI.groupModel.users.where((p0) {
|
||||
if (searchUserText.isNotEmpty) {
|
||||
return p0.name
|
||||
.toLowerCase()
|
||||
.contains(searchUserText.value.toLowerCase());
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
final listView = ListView.builder(
|
||||
shrinkWrap: isMobile,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) => _buildUserItem(items[index]));
|
||||
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return isDesktop
|
||||
? listView
|
||||
: LimitedBox(maxHeight: maxHeight, child: listView);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,6 +181,8 @@ class _MyGroupState extends State<MyGroup> {
|
||||
}, child: Obx(
|
||||
() {
|
||||
bool selected = selectedUser.value == username;
|
||||
final isMe = username == gFFI.userModel.userName.value;
|
||||
final colorMe = MyTheme.color(context).me!;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? MyTheme.color(context).highlight : null,
|
||||
@@ -206,9 +194,42 @@ class _MyGroupState extends State<MyGroup> {
|
||||
child: Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.person_rounded, color: Colors.grey, size: 16)
|
||||
.marginOnly(right: 4),
|
||||
Expanded(child: Text(username)),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: str2color(username, 0xAF),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: Text(
|
||||
username.characters.first.toUpperCase(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
).marginOnly(right: 4),
|
||||
if (isMe) Flexible(child: Text(username)),
|
||||
if (isMe)
|
||||
Flexible(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(left: 5),
|
||||
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: colorMe.withAlpha(20),
|
||||
borderRadius: BorderRadius.all(Radius.circular(2)),
|
||||
border: Border.all(color: colorMe.withAlpha(100))),
|
||||
child: Text(
|
||||
translate('Me'),
|
||||
style: TextStyle(
|
||||
color: colorMe.withAlpha(200), fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isMe) Expanded(child: Text(username)),
|
||||
],
|
||||
).paddingSymmetric(vertical: 4),
|
||||
),
|
||||
|
||||
@@ -26,29 +26,45 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Draggable(
|
||||
checkKeyboard: true,
|
||||
position: position,
|
||||
width: width,
|
||||
height: height,
|
||||
builder: (context, onPanUpdate) {
|
||||
final child = isIOS
|
||||
? ChatPage(chatModel: chatModel)
|
||||
: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: CustomAppBar(
|
||||
onPanUpdate: onPanUpdate,
|
||||
appBar: isDesktop
|
||||
? _buildDesktopAppBar(context)
|
||||
: _buildMobileAppBar(context),
|
||||
return isIOS
|
||||
? IOSDraggable(
|
||||
position: position,
|
||||
chatModel: chatModel,
|
||||
width: width,
|
||||
height: height,
|
||||
builder: (context) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildMobileAppBar(context),
|
||||
Expanded(
|
||||
child: ChatPage(chatModel: chatModel),
|
||||
),
|
||||
body: ChatPage(chatModel: chatModel),
|
||||
);
|
||||
return Container(
|
||||
decoration:
|
||||
BoxDecoration(border: Border.all(color: MyTheme.border)),
|
||||
child: child);
|
||||
});
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
: Draggable(
|
||||
checkKeyboard: true,
|
||||
position: position,
|
||||
width: width,
|
||||
height: height,
|
||||
chatModel: chatModel,
|
||||
builder: (context, onPanUpdate) {
|
||||
final child = Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: CustomAppBar(
|
||||
onPanUpdate: onPanUpdate,
|
||||
appBar: isDesktop
|
||||
? _buildDesktopAppBar(context)
|
||||
: _buildMobileAppBar(context),
|
||||
),
|
||||
body: ChatPage(chatModel: chatModel),
|
||||
);
|
||||
return Container(
|
||||
decoration:
|
||||
BoxDecoration(border: Border.all(color: MyTheme.border)),
|
||||
child: child);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildMobileAppBar(BuildContext context) {
|
||||
@@ -226,6 +242,7 @@ class Draggable extends StatefulWidget {
|
||||
this.position = Offset.zero,
|
||||
required this.width,
|
||||
required this.height,
|
||||
this.chatModel,
|
||||
required this.builder})
|
||||
: super(key: key);
|
||||
|
||||
@@ -234,6 +251,7 @@ class Draggable extends StatefulWidget {
|
||||
final Offset position;
|
||||
final double width;
|
||||
final double height;
|
||||
final ChatModel? chatModel;
|
||||
final Widget Function(BuildContext, GestureDragUpdateCallback) builder;
|
||||
|
||||
@override
|
||||
@@ -242,6 +260,7 @@ class Draggable extends StatefulWidget {
|
||||
|
||||
class _DraggableState extends State<Draggable> {
|
||||
late Offset _position;
|
||||
late ChatModel? _chatModel;
|
||||
bool _keyboardVisible = false;
|
||||
double _saveHeight = 0;
|
||||
double _lastBottomHeight = 0;
|
||||
@@ -250,6 +269,7 @@ class _DraggableState extends State<Draggable> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_position = widget.position;
|
||||
_chatModel = widget.chatModel;
|
||||
}
|
||||
|
||||
void onPanUpdate(DragUpdateDetails d) {
|
||||
@@ -276,6 +296,7 @@ class _DraggableState extends State<Draggable> {
|
||||
setState(() {
|
||||
_position = Offset(x, y);
|
||||
});
|
||||
_chatModel?.setChatWindowPosition(_position);
|
||||
}
|
||||
|
||||
checkScreenSize() {}
|
||||
@@ -331,6 +352,107 @@ class _DraggableState extends State<Draggable> {
|
||||
}
|
||||
}
|
||||
|
||||
class IOSDraggable extends StatefulWidget {
|
||||
const IOSDraggable(
|
||||
{Key? key,
|
||||
this.position = Offset.zero,
|
||||
this.chatModel,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.builder})
|
||||
: super(key: key);
|
||||
|
||||
final Offset position;
|
||||
final ChatModel? chatModel;
|
||||
final double width;
|
||||
final double height;
|
||||
final Widget Function(BuildContext) builder;
|
||||
|
||||
@override
|
||||
IOSDraggableState createState() => IOSDraggableState();
|
||||
}
|
||||
|
||||
class IOSDraggableState extends State<IOSDraggable> {
|
||||
late Offset _position;
|
||||
late ChatModel? _chatModel;
|
||||
late double _width;
|
||||
late double _height;
|
||||
bool _keyboardVisible = false;
|
||||
double _saveHeight = 0;
|
||||
double _lastBottomHeight = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_position = widget.position;
|
||||
_chatModel = widget.chatModel;
|
||||
_width = widget.width;
|
||||
_height = widget.height;
|
||||
}
|
||||
|
||||
checkKeyboard() {
|
||||
final bottomHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
final currentVisible = bottomHeight != 0;
|
||||
|
||||
// save
|
||||
if (!_keyboardVisible && currentVisible) {
|
||||
_saveHeight = _position.dy;
|
||||
}
|
||||
|
||||
// reset
|
||||
if (_lastBottomHeight > 0 && bottomHeight == 0) {
|
||||
setState(() {
|
||||
_position = Offset(_position.dx, _saveHeight);
|
||||
});
|
||||
}
|
||||
|
||||
// onKeyboardVisible
|
||||
if (_keyboardVisible && currentVisible) {
|
||||
final sumHeight = bottomHeight + _height;
|
||||
final contextHeight = MediaQuery.of(context).size.height;
|
||||
if (sumHeight + _position.dy > contextHeight) {
|
||||
final y = contextHeight - sumHeight;
|
||||
setState(() {
|
||||
_position = Offset(_position.dx, y);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_keyboardVisible = currentVisible;
|
||||
_lastBottomHeight = bottomHeight;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
checkKeyboard();
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: _position.dx,
|
||||
top: _position.dy,
|
||||
child: GestureDetector(
|
||||
onPanUpdate: (details) {
|
||||
setState(() {
|
||||
_position += details.delta;
|
||||
});
|
||||
_chatModel?.setChatWindowPosition(_position);
|
||||
},
|
||||
child: Material(
|
||||
child: Container(
|
||||
width: _width,
|
||||
height: _height,
|
||||
decoration:
|
||||
BoxDecoration(border: Border.all(color: MyTheme.border)),
|
||||
child: widget.builder(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QualityMonitor extends StatelessWidget {
|
||||
final QualityMonitorModel qualityMonitorModel;
|
||||
QualityMonitor(this.qualityMonitorModel);
|
||||
@@ -376,6 +498,7 @@ class QualityMonitor extends StatelessWidget {
|
||||
"${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
|
||||
_row(
|
||||
"Codec", qualityMonitorModel.data.codecFormat ?? '-'),
|
||||
_row("Chroma", qualityMonitorModel.data.chroma ?? '-'),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -21,7 +19,7 @@ import 'dart:math' as math;
|
||||
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
||||
Function(BuildContext);
|
||||
|
||||
enum PeerUiType { grid, list }
|
||||
enum PeerUiType { grid, tile, list }
|
||||
|
||||
final peerCardUiType = PeerUiType.grid.obs;
|
||||
|
||||
@@ -63,75 +61,29 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
Widget _buildMobile() {
|
||||
final peer = super.widget.peer;
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
final PeerTabModel peerTabModel = Provider.of(context);
|
||||
final child = Card(
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (peerTabModel.multiSelectionMode) {
|
||||
peerTabModel.select(peer);
|
||||
} else {
|
||||
if (!isWebDesktop) {
|
||||
connectInPeerTab(context, peer.id, widget.tab);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDoubleTap: isWebDesktop
|
||||
? () => connectInPeerTab(context, peer.id, widget.tab)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
onTap: () {
|
||||
if (peerTabModel.multiSelectionMode) {
|
||||
peerTabModel.select(peer);
|
||||
},
|
||||
child: Container(
|
||||
} else {
|
||||
if (!isWebDesktop) {
|
||||
connectInPeerTab(context, peer.id, widget.tab);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDoubleTap: isWebDesktop
|
||||
? () => connectInPeerTab(context, peer.id, widget.tab)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
peerTabModel.select(peer);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 12, top: 8, bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: getPlatformImage(peer.platform)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
getOnline(4, peer.online),
|
||||
Text(peer.alias.isEmpty
|
||||
? formatID(peer.id)
|
||||
: peer.alias)
|
||||
]),
|
||||
Text(name)
|
||||
],
|
||||
).paddingOnly(left: 8.0),
|
||||
),
|
||||
checkBoxOrActionMoreMobile(peer),
|
||||
],
|
||||
),
|
||||
)));
|
||||
final colors = _frontN(peer.tags, 25).map((e) => str2color2(e)).toList();
|
||||
return Tooltip(
|
||||
message: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
: '',
|
||||
child: Stack(children: [
|
||||
child,
|
||||
if (colors.isNotEmpty)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 10,
|
||||
child: CustomPaint(
|
||||
painter: TagPainter(radius: 3, colors: colors),
|
||||
),
|
||||
)
|
||||
]),
|
||||
);
|
||||
child: _buildPeerTile(context, peer, null)),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDesktop() {
|
||||
@@ -178,87 +130,96 @@ class _PeerCardState extends State<_PeerCard>
|
||||
}
|
||||
|
||||
Widget _buildPeerTile(
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?>? deco) {
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
final greyStyle = TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
|
||||
final child = Obx(
|
||||
() => Container(
|
||||
foregroundDecoration: deco.value,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(_tileRadius),
|
||||
bottomLeft: Radius.circular(_tileRadius),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
width: 42,
|
||||
child: getPlatformImage(peer.platform, size: 30).paddingAll(6),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(_tileRadius),
|
||||
bottomRight: Radius.circular(_tileRadius),
|
||||
final child = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: isMobile
|
||||
? BorderRadius.circular(_tileRadius)
|
||||
: BorderRadius.only(
|
||||
topLeft: Radius.circular(_tileRadius),
|
||||
bottomLeft: Radius.circular(_tileRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(children: [
|
||||
getOnline(8, peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
peer.alias.isEmpty
|
||||
? formatID(peer.id)
|
||||
: peer.alias,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)),
|
||||
]).marginOnly(bottom: 0, top: 2),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
).marginOnly(top: 2),
|
||||
),
|
||||
checkBoxOrActionMoreDesktop(peer, isTile: true),
|
||||
],
|
||||
).paddingOnly(left: 10.0, top: 3.0),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
width: isMobile ? 50 : 42,
|
||||
height: isMobile ? 50 : null,
|
||||
child: getPlatformImage(peer.platform, size: isMobile ? 38 : 30)
|
||||
.paddingAll(6),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(_tileRadius),
|
||||
bottomRight: Radius.circular(_tileRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(children: [
|
||||
getOnline(isMobile ? 4 : 8, peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)),
|
||||
]).marginOnly(top: isMobile ? 0 : 2),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: isMobile ? null : greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
).marginOnly(top: 2),
|
||||
),
|
||||
isMobile
|
||||
? checkBoxOrActionMoreMobile(peer)
|
||||
: checkBoxOrActionMoreDesktop(peer, isTile: true),
|
||||
],
|
||||
).paddingOnly(left: 10.0, top: 3.0),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
final colors = _frontN(peer.tags, 25).map((e) => str2color2(e)).toList();
|
||||
final colors =
|
||||
_frontN(peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||
return Tooltip(
|
||||
message: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
: '',
|
||||
message: isMobile
|
||||
? ''
|
||||
: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
: '',
|
||||
child: Stack(children: [
|
||||
child,
|
||||
deco == null
|
||||
? child
|
||||
: Obx(
|
||||
() => Container(
|
||||
foregroundDecoration: deco.value,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
if (colors.isNotEmpty)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 10,
|
||||
right: isMobile ? 20 : 10,
|
||||
child: CustomPaint(
|
||||
painter: TagPainter(radius: 3, colors: colors),
|
||||
),
|
||||
@@ -349,7 +310,8 @@ class _PeerCardState extends State<_PeerCard>
|
||||
),
|
||||
);
|
||||
|
||||
final colors = _frontN(peer.tags, 25).map((e) => str2color2(e)).toList();
|
||||
final colors =
|
||||
_frontN(peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||
return Tooltip(
|
||||
message: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
@@ -533,7 +495,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
return _connectCommonAction(
|
||||
context,
|
||||
id,
|
||||
translate('Transfer File'),
|
||||
translate('Transfer file'),
|
||||
isFileTransfer: true,
|
||||
);
|
||||
}
|
||||
@@ -543,7 +505,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
return _connectCommonAction(
|
||||
context,
|
||||
id,
|
||||
translate('TCP Tunneling'),
|
||||
translate('TCP tunneling'),
|
||||
isTcpTunneling: true,
|
||||
);
|
||||
}
|
||||
@@ -606,7 +568,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
MenuEntryBase<String> _createShortCutAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Create Desktop Shortcut'),
|
||||
translate('Create desktop shortcut'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
@@ -638,8 +600,9 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs);
|
||||
|
||||
_openInWindowsAction(String id) async => await _openNewConnInAction(
|
||||
id, 'Open in New Window', kOptionOpenInWindows);
|
||||
id, 'Open in new window', kOptionOpenInWindows);
|
||||
|
||||
// ignore: unused_element
|
||||
_openNewConnInOptAction(String id) async =>
|
||||
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs)
|
||||
? await _openInWindowsAction(id)
|
||||
@@ -765,17 +728,18 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
MenuEntryBase<String> _unrememberPasswordAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Unremember Password'),
|
||||
translate('Forget Password'),
|
||||
style: style,
|
||||
),
|
||||
proc: () async {
|
||||
bool result = gFFI.abModel.changePassword(id, '');
|
||||
await bind.mainForgetPassword(id: id);
|
||||
bool toast = false;
|
||||
if (result) {
|
||||
bool toast = tab == PeerTabIndex.ab;
|
||||
toast = tab == PeerTabIndex.ab;
|
||||
gFFI.abModel.pushAb(toastIfFail: toast, toastIfSucc: toast);
|
||||
}
|
||||
showToast(translate('Successful'));
|
||||
if (!toast) showToast(translate('Successful'));
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
@@ -855,7 +819,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
MenuEntryBase<String> _addToAb(Peer peer) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Add to Address Book'),
|
||||
translate('Add to address book'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
@@ -900,12 +864,12 @@ class RecentPeerCard extends BasePeerCard {
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
@@ -954,12 +918,12 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
];
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
@@ -1008,12 +972,12 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(_wolAction(peer.id));
|
||||
@@ -1058,12 +1022,12 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
];
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
@@ -1126,21 +1090,26 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
];
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// menuItems.add(MenuEntryDivider());
|
||||
// menuItems.add(_renameAction(peer.id));
|
||||
// if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
// menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// }
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/address_book.dart';
|
||||
@@ -5,14 +7,18 @@ import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/common/widgets/my_group.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
import 'package:flutter_hbb/common/widgets/animated_rotation_widget.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||
as mod_menu;
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:pull_down_button/pull_down_button.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
@@ -63,14 +69,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
({dynamic hint}) => gFFI.groupModel.pull(force: hint == null),
|
||||
),
|
||||
];
|
||||
RelativeRect? mobileTabContextMenuPos;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final uiType = bind.getLocalFlutterOption(k: 'peer-card-ui-type');
|
||||
if (uiType != '') {
|
||||
peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index
|
||||
? PeerUiType.list
|
||||
: PeerUiType.grid;
|
||||
peerCardUiType.value = int.parse(uiType) == 0
|
||||
? PeerUiType.grid
|
||||
: int.parse(uiType) == 1
|
||||
? PeerUiType.tile
|
||||
: PeerUiType.list;
|
||||
}
|
||||
hideAbTagsPanel.value =
|
||||
bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
|
||||
@@ -79,6 +88,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Future<void> handleTabSelection(int tabIndex) async {
|
||||
if (tabIndex < entries.length) {
|
||||
if (tabIndex != gFFI.peerTabModel.currentTab) {
|
||||
gFFI.peerTabModel.setCurrentTabCachedPeers([]);
|
||||
}
|
||||
gFFI.peerTabModel.setCurrentTab(tabIndex);
|
||||
entries[tabIndex].load(hint: false);
|
||||
}
|
||||
@@ -102,40 +114,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
child: selectionWrap(Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: _createSwitchBar(context)),
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(),
|
||||
_createMultiSelection(),
|
||||
Offstage(
|
||||
offstage: !isDesktop,
|
||||
child: _createPeerViewTypeSwitch(context)),
|
||||
Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab == 0,
|
||||
child: PeerSortDropdown(),
|
||||
),
|
||||
Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab != 3,
|
||||
child: _hoverAction(
|
||||
context: context,
|
||||
hoverableWhenfalse: hideAbTagsPanel,
|
||||
child: Tooltip(
|
||||
message: translate('Toggle Tags'),
|
||||
child: Icon(
|
||||
Icons.tag_rounded,
|
||||
size: 18,
|
||||
)),
|
||||
onTap: () async {
|
||||
await bind.mainSetLocalOption(
|
||||
key: "hideAbTagsPanel",
|
||||
value: hideAbTagsPanel.value ? "" : "Y");
|
||||
hideAbTagsPanel.value = !hideAbTagsPanel.value;
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
visibleContextMenuListener(_createSwitchBar(context))),
|
||||
if (isMobile)
|
||||
..._mobileRightActions(context)
|
||||
else
|
||||
..._desktopRightActions(context)
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
).paddingOnly(right: isDesktop ? 12 : 0),
|
||||
_createPeersView(),
|
||||
],
|
||||
);
|
||||
@@ -147,7 +136,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
return ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: model.indexs.map((t) {
|
||||
children: model.visibleIndexs.map((t) {
|
||||
final selected = model.currentTab == t;
|
||||
final color = selected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
@@ -163,11 +152,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
));
|
||||
return Obx(() => InkWell(
|
||||
child: Container(
|
||||
decoration:
|
||||
selected ? decoBorder : (hover.value ? deco : null),
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Tooltip(
|
||||
message:
|
||||
model.tabTooltip(t, gFFI.groupModel.groupName.value),
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: Icon(model.tabIcon(t), color: color),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
@@ -184,14 +175,15 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Widget _createPeersView() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
Widget child;
|
||||
if (model.indexs.isEmpty) {
|
||||
child = Center(
|
||||
child: Text(translate('Right click to select tabs')),
|
||||
);
|
||||
if (model.visibleIndexs.isEmpty) {
|
||||
child = visibleContextMenuListener(Row(
|
||||
children: [Expanded(child: InkWell())],
|
||||
));
|
||||
} else {
|
||||
if (model.indexs.contains(model.currentTab)) {
|
||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
||||
child = entries[model.currentTab].widget;
|
||||
} else {
|
||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||
Future.delayed(Duration.zero, () {
|
||||
model.setCurrentTab(model.indexs[0]);
|
||||
});
|
||||
@@ -202,17 +194,19 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
|
||||
}
|
||||
|
||||
Widget _createRefresh() {
|
||||
Widget _createRefresh(
|
||||
{required PeerTabIndex index, required RxBool loading}) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index,
|
||||
offstage: model.currentTab != index.index,
|
||||
child: RefreshWidget(
|
||||
onPressed: () {
|
||||
if (gFFI.peerTabModel.currentTab < entries.length) {
|
||||
entries[gFFI.peerTabModel.currentTab].load();
|
||||
}
|
||||
},
|
||||
spinning: gFFI.abModel.abLoading,
|
||||
spinning: loading,
|
||||
child: RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Tooltip(
|
||||
@@ -226,68 +220,151 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
}
|
||||
|
||||
Widget _createPeerViewTypeSwitch(BuildContext context) {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
final types = [PeerUiType.grid, PeerUiType.list];
|
||||
|
||||
return Obx(() => _hoverAction(
|
||||
context: context,
|
||||
onTap: () async {
|
||||
final type = types
|
||||
.elementAt(peerCardUiType.value == types.elementAt(0) ? 1 : 0);
|
||||
await bind.setLocalFlutterOption(
|
||||
k: 'peer-card-ui-type', v: type.index.toString());
|
||||
peerCardUiType.value = type;
|
||||
},
|
||||
child: Tooltip(
|
||||
message: peerCardUiType.value == PeerUiType.grid
|
||||
? translate('List View')
|
||||
: translate('Grid View'),
|
||||
child: Icon(
|
||||
peerCardUiType.value == PeerUiType.grid
|
||||
? Icons.view_list_rounded
|
||||
: Icons.grid_view_rounded,
|
||||
size: 18,
|
||||
color: textColor,
|
||||
))));
|
||||
return PeerViewDropdown();
|
||||
}
|
||||
|
||||
Widget _createMultiSelection() {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
if (model.currentTabCachedPeers.isEmpty) return Offstage();
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
model.setMultiSelectionMode(true);
|
||||
if (isMobile && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Select'),
|
||||
child: Icon(
|
||||
IconFont.checkbox,
|
||||
size: 18,
|
||||
color: textColor,
|
||||
child: SvgPicture.asset(
|
||||
"assets/checkbox-outline.svg",
|
||||
width: 18,
|
||||
height: 18,
|
||||
colorFilter: svgColor(textColor),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void mobileShowTabVisibilityMenu() {
|
||||
final model = gFFI.peerTabModel;
|
||||
final items = List<PopupMenuItem>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
items.add(PopupMenuItem(
|
||||
height: kMinInteractiveDimension * 0.8,
|
||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.isVisible[i],
|
||||
onChanged: (_) {
|
||||
model.setTabVisible(i, !model.isVisible[i]);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}),
|
||||
Expanded(child: Text(model.tabTooltip(i))),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
if (mobileTabContextMenuPos != null) {
|
||||
showMenu(
|
||||
context: context, position: mobileTabContextMenuPos!, items: items);
|
||||
}
|
||||
}
|
||||
|
||||
Widget visibleContextMenuListener(Widget child) {
|
||||
if (isMobile) {
|
||||
return GestureDetector(
|
||||
onLongPressDown: (e) {
|
||||
final x = e.globalPosition.dx;
|
||||
final y = e.globalPosition.dy;
|
||||
mobileTabContextMenuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||
},
|
||||
onLongPressUp: () {
|
||||
mobileShowTabVisibilityMenu();
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return Listener(
|
||||
onPointerDown: (e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||
return;
|
||||
}
|
||||
if (e.buttons == 2) {
|
||||
showRightMenu(
|
||||
(CancelFunc cancelFunc) {
|
||||
return visibleContextMenu(cancelFunc);
|
||||
},
|
||||
target: e.position,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: child);
|
||||
}
|
||||
}
|
||||
|
||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
menu.add(MenuEntrySwitch(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: model.tabTooltip(i),
|
||||
getter: () async {
|
||||
return model.isVisible[i];
|
||||
},
|
||||
setter: (show) async {
|
||||
model.setTabVisible(i, show);
|
||||
cancelFunc();
|
||||
}));
|
||||
}
|
||||
return mod_menu.PopupMenu(
|
||||
items: menu
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: MyTheme.accent,
|
||||
height: 20.0,
|
||||
dividerHeight: 12.0,
|
||||
)))
|
||||
.expand((i) => i)
|
||||
.toList());
|
||||
}
|
||||
|
||||
Widget createMultiSelectionBar() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
deleteSelection(),
|
||||
addSelectionToFav(),
|
||||
addSelectionToAb(),
|
||||
editSelectionTags(),
|
||||
Expanded(child: Container()),
|
||||
selectionCount(model.selectedPeers.length),
|
||||
selectAll(),
|
||||
closeSelection(),
|
||||
Offstage(
|
||||
offstage: model.selectedPeers.isEmpty,
|
||||
child: Row(
|
||||
children: [
|
||||
deleteSelection(),
|
||||
addSelectionToFav(),
|
||||
addSelectionToAb(),
|
||||
editSelectionTags(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
selectionCount(model.selectedPeers.length),
|
||||
selectAll(),
|
||||
closeSelection(),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget deleteSelection() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
if (model.currentTab == PeerTabIndex.group.index) {
|
||||
return Offstage();
|
||||
}
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
@@ -393,7 +470,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
});
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Add to Address Book'),
|
||||
message: translate('Add to address book'),
|
||||
child: Icon(model.icons[PeerTabIndex.ab.index])),
|
||||
).marginOnly(left: isMobile ? 11 : 6),
|
||||
);
|
||||
@@ -457,6 +534,128 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Tooltip(message: translate('Close'), child: Icon(Icons.clear)))
|
||||
.marginOnly(left: 6);
|
||||
}
|
||||
|
||||
Widget _toggleTags() {
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
hoverableWhenfalse: hideAbTagsPanel,
|
||||
child: Tooltip(
|
||||
message: translate('Toggle Tags'),
|
||||
child: Icon(
|
||||
Icons.tag_rounded,
|
||||
size: 18,
|
||||
)),
|
||||
onTap: () async {
|
||||
await bind.mainSetLocalOption(
|
||||
key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y");
|
||||
hideAbTagsPanel.value = !hideAbTagsPanel.value;
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> _desktopRightActions(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return [
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading),
|
||||
Offstage(
|
||||
offstage: model.currentTabCachedPeers.isEmpty,
|
||||
child: _createMultiSelection(),
|
||||
),
|
||||
_createPeerViewTypeSwitch(context),
|
||||
Offstage(
|
||||
offstage: model.currentTab == PeerTabIndex.recent.index,
|
||||
child: PeerSortDropdown(),
|
||||
),
|
||||
Offstage(
|
||||
offstage: model.currentTab != PeerTabIndex.ab.index,
|
||||
child: _toggleTags(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _mobileRightActions(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
||||
final leftActionsSize =
|
||||
(leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length;
|
||||
final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
|
||||
final searchWidth = 120;
|
||||
final otherActionWidth = 18 + 10;
|
||||
|
||||
dropDown(List<Widget> menus) {
|
||||
final padding = 6.0;
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return PullDownButton(
|
||||
buttonBuilder:
|
||||
(BuildContext context, Future<void> Function() showMenu) {
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
child: Tooltip(
|
||||
message: translate('More'),
|
||||
child: SvgPicture.asset(
|
||||
"assets/chevron_up_chevron_down.svg",
|
||||
width: 18,
|
||||
height: 18,
|
||||
colorFilter: svgColor(textColor),
|
||||
)),
|
||||
onTap: showMenu,
|
||||
);
|
||||
},
|
||||
routeTheme: PullDownMenuRouteTheme(
|
||||
width: menus.length * (otherActionWidth + padding * 2) * 1.0),
|
||||
itemBuilder: (context) => [
|
||||
PullDownMenuEntryImpl(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: menus
|
||||
.map((e) =>
|
||||
Material(child: e.paddingSymmetric(horizontal: padding)))
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Always show search, refresh
|
||||
List<Widget> actions = [
|
||||
const PeerSearchBar(),
|
||||
if (model.currentTab == PeerTabIndex.ab.index)
|
||||
_createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
if (model.currentTab == PeerTabIndex.group.index)
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading),
|
||||
];
|
||||
final List<Widget> dynamicActions = [
|
||||
if (model.currentTabCachedPeers.isNotEmpty) _createMultiSelection(),
|
||||
if (model.currentTab != PeerTabIndex.recent.index) PeerSortDropdown(),
|
||||
if (model.currentTab == PeerTabIndex.ab.index) _toggleTags()
|
||||
];
|
||||
final rightWidth = availableWidth -
|
||||
searchWidth -
|
||||
(actions.length == 2 ? otherActionWidth : 0);
|
||||
final availablePositions = rightWidth ~/ otherActionWidth;
|
||||
|
||||
if (availablePositions < dynamicActions.length &&
|
||||
dynamicActions.length > 1) {
|
||||
if (availablePositions < 2) {
|
||||
actions.addAll([
|
||||
dropDown(dynamicActions),
|
||||
]);
|
||||
} else {
|
||||
actions.addAll([
|
||||
...dynamicActions.sublist(0, availablePositions - 1),
|
||||
dropDown(dynamicActions.sublist(availablePositions - 1)),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
actions.addAll(dynamicActions);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
class PeerSearchBar extends StatefulWidget {
|
||||
@@ -570,6 +769,87 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
}
|
||||
}
|
||||
|
||||
class PeerViewDropdown extends StatefulWidget {
|
||||
const PeerViewDropdown({super.key});
|
||||
|
||||
@override
|
||||
State<PeerViewDropdown> createState() => _PeerViewDropdownState();
|
||||
}
|
||||
|
||||
class _PeerViewDropdownState extends State<PeerViewDropdown> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<PeerUiType> types = [
|
||||
PeerUiType.grid,
|
||||
PeerUiType.tile,
|
||||
PeerUiType.list
|
||||
];
|
||||
final style = TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal);
|
||||
List<PopupMenuEntry> items = List.empty(growable: true);
|
||||
items.add(PopupMenuItem(
|
||||
height: 36,
|
||||
enabled: false,
|
||||
child: Text(translate("Change view"), style: style)));
|
||||
for (var e in PeerUiType.values) {
|
||||
items.add(PopupMenuItem(
|
||||
height: 36,
|
||||
child: Obx(() => Center(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: getRadio<PeerUiType>(
|
||||
Text(
|
||||
translate(types.indexOf(e) == 0
|
||||
? 'Big tiles'
|
||||
: types.indexOf(e) == 1
|
||||
? 'Small tiles'
|
||||
: 'List'),
|
||||
style: style),
|
||||
e,
|
||||
peerCardUiType.value,
|
||||
dense: true, (PeerUiType? v) async {
|
||||
if (v != null) {
|
||||
peerCardUiType.value = v;
|
||||
setState(() {});
|
||||
await bind.setLocalFlutterOption(
|
||||
k: "peer-card-ui-type",
|
||||
v: peerCardUiType.value.index.toString(),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
))));
|
||||
}
|
||||
|
||||
var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0);
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
child: Tooltip(
|
||||
message: translate('Change view'),
|
||||
child: Icon(
|
||||
peerCardUiType.value == PeerUiType.grid
|
||||
? Icons.grid_view_rounded
|
||||
: peerCardUiType.value == PeerUiType.tile
|
||||
? Icons.view_list_rounded
|
||||
: Icons.view_agenda_rounded,
|
||||
size: 18,
|
||||
)),
|
||||
onTapDown: (details) {
|
||||
final x = details.globalPosition.dx;
|
||||
final y = details.globalPosition.dy;
|
||||
menuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||
},
|
||||
onTap: () => showMenu(
|
||||
context: context,
|
||||
position: menuPos,
|
||||
items: items,
|
||||
elevation: 8,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class PeerSortDropdown extends StatefulWidget {
|
||||
const PeerSortDropdown({super.key});
|
||||
|
||||
@@ -732,3 +1012,14 @@ Widget _hoverAction(
|
||||
child: Container(padding: padding, child: child))),
|
||||
);
|
||||
}
|
||||
|
||||
class PullDownMenuEntryImpl extends StatelessWidget
|
||||
implements PullDownMenuEntry {
|
||||
final Widget child;
|
||||
const PullDownMenuEntryImpl({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/peer_model.dart';
|
||||
@@ -35,6 +38,7 @@ class LoadEvent {
|
||||
static const String favorite = 'load_fav_peers';
|
||||
static const String lan = 'load_lan_peers';
|
||||
static const String addressBook = 'load_address_book_peers';
|
||||
static const String group = 'load_group_peers';
|
||||
}
|
||||
|
||||
/// for peer search text, global obs value
|
||||
@@ -78,7 +82,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
final _curPeers = <String>{};
|
||||
var _lastChangeTime = DateTime.now();
|
||||
var _lastQueryPeers = <String>{};
|
||||
var _lastQueryTime = DateTime.now().subtract(const Duration(hours: 1));
|
||||
var _lastQueryTime = DateTime.now().add(const Duration(seconds: 30));
|
||||
var _queryCount = 0;
|
||||
var _exit = false;
|
||||
|
||||
@@ -93,6 +97,8 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
return width;
|
||||
}();
|
||||
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
_PeersViewState() {
|
||||
_startCheckOnlines();
|
||||
}
|
||||
@@ -174,28 +180,65 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
return FutureBuilder<List<Peer>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final peers = snapshot.data!;
|
||||
var peers = snapshot.data!;
|
||||
if (peers.length > 1000) peers = peers.sublist(0, 1000);
|
||||
gFFI.peerTabModel.setCurrentTabCachedPeers(peers);
|
||||
final cards = <Widget>[];
|
||||
for (final peer in peers) {
|
||||
buildOnePeer(Peer peer) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
key: ValueKey(_cardId(peer.id)),
|
||||
onVisibilityChanged: onVisibilityChanged,
|
||||
child: widget.peerCardBuilder(peer),
|
||||
);
|
||||
cards.add(isDesktop
|
||||
final windowWidth = MediaQuery.of(context).size.width;
|
||||
// `Provider.of<PeerTabModel>(context)` will causes infinete loop.
|
||||
// Because `gFFI.peerTabModel.setCurrentTabCachedPeers(peers)` will trigger `notifyListeners()`.
|
||||
//
|
||||
// No need to listen the currentTab change event.
|
||||
// Because the currentTab change event will trigger the peers change event,
|
||||
// and the peers change event will trigger _buildPeersView().
|
||||
final currentTab = Provider.of<PeerTabModel>(context, listen: false).currentTab;
|
||||
final hideAbTagsPanel = bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
|
||||
return isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: 220,
|
||||
width: peerCardUiType.value != PeerUiType.list
|
||||
? 220
|
||||
: currentTab == PeerTabIndex.group.index || (currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
|
||||
? windowWidth - 390 :
|
||||
windowWidth - 227,
|
||||
height:
|
||||
peerCardUiType.value == PeerUiType.grid ? 140 : 42,
|
||||
peerCardUiType.value == PeerUiType.grid ? 140 : peerCardUiType.value != PeerUiType.list ? 42 : 45,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild));
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild);
|
||||
}
|
||||
final child =
|
||||
Wrap(spacing: space, runSpacing: space, children: cards);
|
||||
|
||||
final Widget child;
|
||||
if (isMobile) {
|
||||
child = DynamicGridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2, crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index]);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
child = DesktopScrollWrapper(
|
||||
scrollController: _scrollController,
|
||||
child: DynamicGridView.builder(
|
||||
controller: _scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2, crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (updateEvent == UpdateEvent.load) {
|
||||
_curPeers.clear();
|
||||
_curPeers.addAll(peers.map((e) => e.id));
|
||||
@@ -229,8 +272,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
if (_queryCount < _maxQueryCount) {
|
||||
if (now.difference(_lastQueryTime) >= _queryInterval) {
|
||||
if (_curPeers.isNotEmpty) {
|
||||
platformFFI.ffiBind
|
||||
.queryOnlines(ids: _curPeers.toList(growable: false));
|
||||
bind.queryOnlines(ids: _curPeers.toList(growable: false));
|
||||
_lastQueryTime = DateTime.now();
|
||||
_queryCount += 1;
|
||||
}
|
||||
@@ -244,7 +286,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
|
||||
_queryOnlines(bool isLoadEvent) {
|
||||
if (_curPeers.isNotEmpty) {
|
||||
platformFFI.ffiBind.queryOnlines(ids: _curPeers.toList(growable: false));
|
||||
bind.queryOnlines(ids: _curPeers.toList(growable: false));
|
||||
_lastQueryPeers = {..._curPeers};
|
||||
if (isLoadEvent) {
|
||||
_lastChangeTime = DateTime.now();
|
||||
@@ -312,7 +354,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
final String loadEvent;
|
||||
final PeerFilter? peerFilter;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
final List<Peer> initPeers;
|
||||
final RxList<Peer>? initPeers;
|
||||
|
||||
const BasePeersView({
|
||||
Key? key,
|
||||
@@ -326,7 +368,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PeersView(
|
||||
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
|
||||
peers: Peers(name: name, loadEvent: loadEvent, initPeers: initPeers),
|
||||
peerFilter: peerFilter,
|
||||
peerCardBuilder: peerCardBuilder);
|
||||
}
|
||||
@@ -343,7 +385,7 @@ class RecentPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -365,7 +407,7 @@ class FavoritePeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -387,7 +429,7 @@ class DiscoveredPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -403,7 +445,7 @@ class AddressBookPeersView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required List<Peer> initPeers})
|
||||
required RxList<Peer> initPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'address book peer',
|
||||
@@ -421,12 +463,21 @@ class AddressBookPeersView extends BasePeersView {
|
||||
if (selectedTags.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
for (final tag in selectedTags) {
|
||||
if (idents.contains(tag)) {
|
||||
return true;
|
||||
if (gFFI.abModel.filterByIntersection.value) {
|
||||
for (final tag in selectedTags) {
|
||||
if (!idents.contains(tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
for (final tag in selectedTags) {
|
||||
if (idents.contains(tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,11 +486,11 @@ class MyGroupPeerView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required List<Peer> initPeers})
|
||||
required RxList<Peer> initPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'my group peer',
|
||||
loadEvent: 'load_my_group_peers',
|
||||
name: 'group peer',
|
||||
loadEvent: LoadEvent.group,
|
||||
peerFilter: filter,
|
||||
peerCardBuilder: (Peer peer) => MyGroupPeerCard(
|
||||
peer: peer,
|
||||
@@ -450,12 +501,12 @@ class MyGroupPeerView extends BasePeersView {
|
||||
|
||||
static bool filter(Peer peer) {
|
||||
if (gFFI.groupModel.searchUserText.isNotEmpty) {
|
||||
if (!peer.username.contains(gFFI.groupModel.searchUserText)) {
|
||||
if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (gFFI.groupModel.selectedUser.isNotEmpty) {
|
||||
if (gFFI.groupModel.selectedUser.value != peer.username) {
|
||||
if (gFFI.groupModel.selectedUser.value != peer.loginName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/input_model.dart';
|
||||
|
||||
@@ -33,7 +34,8 @@ class RawKeyFocusScope extends StatelessWidget {
|
||||
canRequestFocus: true,
|
||||
focusNode: focusNode,
|
||||
onFocusChange: onFocusChange,
|
||||
onKey: inputModel.handleRawKeyEvent,
|
||||
onKey: (FocusNode data, RawKeyEvent e) =>
|
||||
inputModel.handleRawKeyEvent(e),
|
||||
child: child));
|
||||
}
|
||||
}
|
||||
@@ -92,6 +94,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
return;
|
||||
}
|
||||
if (handleTouch) {
|
||||
// Desktop or mobile "Touch mode"
|
||||
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||
inputModel.tapDown(MouseButtons.left);
|
||||
}
|
||||
@@ -111,7 +114,10 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||
return;
|
||||
}
|
||||
inputModel.tap(MouseButtons.left);
|
||||
if (!handleTouch) {
|
||||
// Mobile, "Mouse mode"
|
||||
inputModel.tap(MouseButtons.left);
|
||||
}
|
||||
}
|
||||
|
||||
onDoubleTapDown(TapDownDetails d) {
|
||||
@@ -263,9 +269,9 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (scale != 0) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode({
|
||||
'touch': {'scale': scale}
|
||||
}));
|
||||
msg: json.encode(
|
||||
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
||||
.toJson()));
|
||||
}
|
||||
} else {
|
||||
// mobile
|
||||
@@ -283,9 +289,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isDesktop) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode({
|
||||
'touch': {'scale': 0}
|
||||
}));
|
||||
msg: json.encode(
|
||||
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
||||
} else {
|
||||
// mobile
|
||||
_scale = 1;
|
||||
|
||||
239
flutter/lib/common/widgets/setting_widgets.dart
Normal file
239
flutter/lib/common/widgets/setting_widgets.dart
Normal file
@@ -0,0 +1,239 @@
|
||||
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
customImageQualityWidget(
|
||||
{required double initQuality,
|
||||
required double initFps,
|
||||
required Function(double) setQuality,
|
||||
required Function(double) setFps,
|
||||
required bool showFps,
|
||||
required bool showMoreQuality}) {
|
||||
if (initQuality < kMinQuality ||
|
||||
initQuality > (showMoreQuality ? kMaxMoreQuality : kMaxQuality)) {
|
||||
initQuality = kDefaultQuality;
|
||||
}
|
||||
if (initFps < kMinFps || initFps > kMaxFps) {
|
||||
initFps = kDefaultFps;
|
||||
}
|
||||
final qualityValue = initQuality.obs;
|
||||
final fpsValue = initFps.obs;
|
||||
|
||||
final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality);
|
||||
final debouncerQuality = Debouncer<double>(
|
||||
Duration(milliseconds: 1000),
|
||||
onChanged: (double v) {
|
||||
setQuality(v);
|
||||
},
|
||||
initialValue: qualityValue.value,
|
||||
);
|
||||
final debouncerFps = Debouncer<double>(
|
||||
Duration(milliseconds: 1000),
|
||||
onChanged: (double v) {
|
||||
setFps(v);
|
||||
},
|
||||
initialValue: fpsValue.value,
|
||||
);
|
||||
|
||||
onMoreChanged(bool? value) {
|
||||
if (value == null) return;
|
||||
moreQualityChecked.value = value;
|
||||
if (!value && qualityValue.value > 100) {
|
||||
qualityValue.value = 100;
|
||||
}
|
||||
debouncerQuality.value = qualityValue.value;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Obx(() => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Slider(
|
||||
value: qualityValue.value,
|
||||
min: kMinQuality,
|
||||
max: moreQualityChecked.value ? kMaxMoreQuality : kMaxQuality,
|
||||
divisions: moreQualityChecked.value
|
||||
? ((kMaxMoreQuality - kMinQuality) / 10).round()
|
||||
: ((kMaxQuality - kMinQuality) / 5).round(),
|
||||
onChanged: (double value) async {
|
||||
qualityValue.value = value;
|
||||
debouncerQuality.value = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'${qualityValue.value.round()}%',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
Expanded(
|
||||
flex: isMobile ? 2 : 1,
|
||||
child: Text(
|
||||
translate('Bitrate'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
// mobile doesn't have enough space
|
||||
if (showMoreQuality && !isMobile)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: moreQualityChecked.value,
|
||||
onChanged: onMoreChanged,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(translate('More')),
|
||||
)
|
||||
],
|
||||
))
|
||||
],
|
||||
)),
|
||||
if (showMoreQuality && isMobile)
|
||||
Obx(() => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Checkbox(
|
||||
value: moreQualityChecked.value,
|
||||
onChanged: onMoreChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(translate('More')),
|
||||
)
|
||||
],
|
||||
)),
|
||||
if (showFps)
|
||||
Obx(() => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Slider(
|
||||
value: fpsValue.value,
|
||||
min: kMinFps,
|
||||
max: kMaxFps,
|
||||
divisions: ((kMaxFps - kMinFps) / 5).round(),
|
||||
onChanged: (double value) async {
|
||||
fpsValue.value = value;
|
||||
debouncerFps.value = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'${fpsValue.value.round()}',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
translate('FPS'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))
|
||||
],
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
customImageQualitySetting() {
|
||||
final qualityKey = 'custom_image_quality';
|
||||
final fpsKey = 'custom-fps';
|
||||
|
||||
var initQuality =
|
||||
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
|
||||
kDefaultQuality);
|
||||
var initFps = (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ??
|
||||
kDefaultFps);
|
||||
|
||||
return customImageQualityWidget(
|
||||
initQuality: initQuality,
|
||||
initFps: initFps,
|
||||
setQuality: (v) {
|
||||
bind.mainSetUserDefaultOption(key: qualityKey, value: v.toString());
|
||||
},
|
||||
setFps: (v) {
|
||||
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
|
||||
},
|
||||
showFps: true,
|
||||
showMoreQuality: true);
|
||||
}
|
||||
|
||||
List<Widget> ServerConfigImportExportWidgets(
|
||||
List<TextEditingController> controllers,
|
||||
List<RxString> errMsgs,
|
||||
) {
|
||||
import() {
|
||||
Clipboard.getData(Clipboard.kTextPlain).then((value) {
|
||||
importConfig(controllers, errMsgs, value?.text);
|
||||
});
|
||||
}
|
||||
|
||||
export() {
|
||||
final text = ServerConfig(
|
||||
idServer: controllers[0].text.trim(),
|
||||
relayServer: controllers[1].text.trim(),
|
||||
apiServer: controllers[2].text.trim(),
|
||||
key: controllers[3].text.trim())
|
||||
.encode();
|
||||
debugPrint("ServerConfig export: $text");
|
||||
Clipboard.setData(ClipboardData(text: text));
|
||||
showToast(translate('Export server configuration successfully'));
|
||||
}
|
||||
|
||||
return [
|
||||
Tooltip(
|
||||
message: translate('Import server config'),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.paste, color: Colors.grey), onPressed: import),
|
||||
),
|
||||
Tooltip(
|
||||
message: translate('Export Server Config'),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.copy, color: Colors.grey), onPressed: export))
|
||||
];
|
||||
}
|
||||
|
||||
List<(String, String)> otherDefaultSettings() {
|
||||
List<(String, String)> v = [
|
||||
('View Mode', 'view_only'),
|
||||
if (isDesktop) ('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||
if (isDesktop) ('Collapse toolbar', 'collapse_toolbar'),
|
||||
('Show remote cursor', 'show_remote_cursor'),
|
||||
if (isDesktop) ('Zoom cursor', 'zoom-cursor'),
|
||||
('Show quality monitor', 'show_quality_monitor'),
|
||||
('Mute', 'disable_audio'),
|
||||
if (isDesktop) ('Enable file copy and paste', 'enable_file_transfer'),
|
||||
('Disable clipboard', 'disable_clipboard'),
|
||||
('Lock after session end', 'lock_after_session_end'),
|
||||
('Privacy mode', 'privacy_mode'),
|
||||
if (isMobile) ('Touch mode', 'touch-mode'),
|
||||
('True color (4:4:4)', 'i444'),
|
||||
('Reverse mouse wheel', 'reverse_mouse_wheel'),
|
||||
('swap-left-right-mouse', 'swap-left-right-mouse'),
|
||||
if (isDesktop && useTextureRender)
|
||||
(
|
||||
'Show displays as individual windows',
|
||||
kKeyShowDisplaysAsIndividualWindows
|
||||
),
|
||||
if (isDesktop && useTextureRender)
|
||||
(
|
||||
'Use all my displays for the remote session',
|
||||
kKeyUseAllMyDisplaysForTheRemoteSession
|
||||
)
|
||||
];
|
||||
|
||||
return v;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
bool isEditOsPassword = false;
|
||||
@@ -49,7 +50,8 @@ class TToggleMenu {
|
||||
handleOsPasswordEditIcon(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) {
|
||||
isEditOsPassword = true;
|
||||
showSetOSPassword(sessionId, false, dialogManager, null, () => isEditOsPassword = false);
|
||||
showSetOSPassword(
|
||||
sessionId, false, dialogManager, null, () => isEditOsPassword = false);
|
||||
}
|
||||
|
||||
handleOsPasswordAction(
|
||||
@@ -62,7 +64,8 @@ handleOsPasswordAction(
|
||||
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
|
||||
'';
|
||||
if (password.isEmpty) {
|
||||
showSetOSPassword(sessionId, true, dialogManager, password, () => isEditOsPassword = false);
|
||||
showSetOSPassword(sessionId, true, dialogManager, password,
|
||||
() => isEditOsPassword = false);
|
||||
} else {
|
||||
bind.sessionInputOsPassword(sessionId: sessionId, value: password);
|
||||
}
|
||||
@@ -76,7 +79,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
|
||||
List<TTextMenu> v = [];
|
||||
// elevation
|
||||
if (ffi.elevationModel.showRequestMenu) {
|
||||
if (perms['keyboard'] != false && ffi.elevationModel.showRequestMenu) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Text(translate('Request Elevation')),
|
||||
@@ -85,29 +88,34 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
);
|
||||
}
|
||||
// osAccount / osPassword
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Row(children: [
|
||||
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
|
||||
Offstage(
|
||||
offstage: isDesktop,
|
||||
child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
|
||||
)
|
||||
]),
|
||||
trailingIcon: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: InkWell(
|
||||
onTap: () => pi.is_headless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordEditIcon(sessionId, ffi.dialogManager),
|
||||
child: Icon(Icons.edit),
|
||||
if (perms['keyboard'] != false) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Row(children: [
|
||||
Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')),
|
||||
]),
|
||||
trailingIcon: Transform.scale(
|
||||
scale: isDesktop ? 0.8 : 1,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
if (isMobile && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
if (pi.isHeadless) {
|
||||
showSetOSAccount(sessionId, ffi.dialogManager);
|
||||
} else {
|
||||
handleOsPasswordEditIcon(sessionId, ffi.dialogManager);
|
||||
}
|
||||
},
|
||||
icon: Icon(Icons.edit, color: isMobile ? MyTheme.accent : null),
|
||||
),
|
||||
),
|
||||
onPressed: () => pi.isHeadless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordAction(sessionId, ffi.dialogManager),
|
||||
),
|
||||
onPressed: () => pi.is_headless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordAction(sessionId, ffi.dialogManager),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
// paste
|
||||
if (isMobile && perms['keyboard'] != false && perms['clipboard'] != false) {
|
||||
v.add(TTextMenu(
|
||||
@@ -130,7 +138,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
if (isDesktop) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Text(translate('Transfer File')),
|
||||
child: Text(translate('Transfer file')),
|
||||
onPressed: () => connect(context, id, isFileTransfer: true)),
|
||||
);
|
||||
}
|
||||
@@ -138,7 +146,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
if (isDesktop) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Text(translate('TCP Tunneling')),
|
||||
child: Text(translate('TCP tunneling')),
|
||||
onPressed: () => connect(context, id, isTcpTunneling: true)),
|
||||
);
|
||||
}
|
||||
@@ -173,7 +181,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
pi.platform == kPeerPlatformMacOS)) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Text(translate('Restart Remote Device')),
|
||||
child: Text(translate('Restart remote device')),
|
||||
onPressed: () =>
|
||||
showRestartRemoteDevice(pi, id, sessionId, ffi.dialogManager)),
|
||||
);
|
||||
@@ -188,6 +196,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
}
|
||||
// blockUserInput
|
||||
if (ffi.ffiModel.keyboard &&
|
||||
ffi.ffiModel.permissions['block_input'] != false &&
|
||||
pi.platform == kPeerPlatformWindows) // privacy-mode != true ??
|
||||
{
|
||||
v.add(TTextMenu(
|
||||
@@ -206,7 +215,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
ffiModel.keyboard &&
|
||||
pi.platform != kPeerPlatformAndroid &&
|
||||
pi.platform != kPeerPlatformMacOS &&
|
||||
version_cmp(pi.version, '1.2.0') >= 0) {
|
||||
versionCmp(pi.version, '1.2.0') >= 0 &&
|
||||
bind.peerGetDefaultSessionsCount(id: id) == 1) {
|
||||
v.add(TTextMenu(
|
||||
child: Text(translate('Switch Sides')),
|
||||
onPressed: () =>
|
||||
@@ -215,15 +225,13 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
// refresh
|
||||
if (pi.version.isNotEmpty) {
|
||||
v.add(TTextMenu(
|
||||
child: Text(translate('Refresh')),
|
||||
onPressed: () => bind.sessionRefresh(sessionId: sessionId)));
|
||||
child: Text(translate('Refresh')),
|
||||
onPressed: () => sessionRefreshVideo(sessionId, pi),
|
||||
));
|
||||
}
|
||||
// record
|
||||
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
||||
if (!isDesktop &&
|
||||
(ffi.recordingModel.start ||
|
||||
(perms["recording"] != false &&
|
||||
(codecFormat == "VP8" || codecFormat == "VP9")))) {
|
||||
(ffi.recordingModel.start || (perms["recording"] != false))) {
|
||||
v.add(TTextMenu(
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -375,7 +383,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
// show remote cursor
|
||||
if (pi.platform != kPeerPlatformAndroid &&
|
||||
!ffi.canvasModel.cursorEmbedded &&
|
||||
!pi.is_wayland) {
|
||||
!pi.isWayland) {
|
||||
final state = ShowRemoteCursorState.find(id);
|
||||
final enabled = !ffiModel.viewOnly;
|
||||
final option = 'show-remote-cursor';
|
||||
@@ -434,19 +442,23 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
child: Text(translate('Mute'))));
|
||||
}
|
||||
// file copy and paste
|
||||
if (Platform.isWindows &&
|
||||
pi.platform == kPeerPlatformWindows &&
|
||||
perms['file'] != false) {
|
||||
if (ffiModel.keyboard &&
|
||||
perms['file'] != false &&
|
||||
bind.mainHasFileClipboard() &&
|
||||
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard)) {
|
||||
final enabled = !ffiModel.viewOnly;
|
||||
final option = 'enable-file-transfer';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
},
|
||||
child: Text(translate('Allow file copy and paste'))));
|
||||
onChanged: enabled
|
||||
? (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
}
|
||||
: null,
|
||||
child: Text(translate('Enable file copy and paste'))));
|
||||
}
|
||||
// disable clipboard
|
||||
if (ffiModel.keyboard && perms['clipboard'] != false) {
|
||||
@@ -467,34 +479,158 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
}
|
||||
// lock after session end
|
||||
if (ffiModel.keyboard) {
|
||||
final enabled = !ffiModel.viewOnly;
|
||||
final option = 'lock-after-session-end';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
},
|
||||
onChanged: enabled
|
||||
? (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
}
|
||||
: null,
|
||||
child: Text(translate('Lock after session end'))));
|
||||
}
|
||||
// privacy mode
|
||||
if (ffiModel.keyboard && pi.features.privacyMode) {
|
||||
final option = 'privacy-mode';
|
||||
final rxValue = PrivacyModeState.find(id);
|
||||
|
||||
if (useTextureRender &&
|
||||
pi.isSupportMultiDisplay &&
|
||||
PrivacyModeState.find(id).isEmpty &&
|
||||
pi.displaysCount.value > 1 &&
|
||||
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
||||
final value =
|
||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
v.add(TToggleMenu(
|
||||
value: rxValue.value,
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
if (ffiModel.pi.currentDisplay != 0) {
|
||||
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
|
||||
'Please switch to Display 1 first', '', ffi.dialogManager);
|
||||
return;
|
||||
}
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
bind.sessionSetDisplaysAsIndividualWindows(
|
||||
sessionId: sessionId, value: value ? 'Y' : '');
|
||||
},
|
||||
child: Text(translate('Privacy mode'))));
|
||||
child: Text(translate('Show displays as individual windows'))));
|
||||
}
|
||||
|
||||
final screenList = await getScreenRectList();
|
||||
if (useTextureRender && pi.isSupportMultiDisplay && screenList.length > 1) {
|
||||
final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
|
||||
sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionSetUseAllMyDisplaysForTheRemoteSession(
|
||||
sessionId: sessionId, value: value ? 'Y' : '');
|
||||
},
|
||||
child: Text(translate('Use all my displays for the remote session'))));
|
||||
}
|
||||
|
||||
// 444
|
||||
final codec_format = ffi.qualityMonitorModel.data.codecFormat;
|
||||
if (versionCmp(pi.version, "1.2.4") >= 0 &&
|
||||
(codec_format == "AV1" || codec_format == "VP9")) {
|
||||
final option = 'i444';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
bind.sessionChangePreferCodec(sessionId: sessionId);
|
||||
},
|
||||
child: Text(translate('True color (4:4:4)'))));
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
v.addAll(toolbarKeyboardToggles(ffi));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
var togglePrivacyModeTime = DateTime.now().subtract(const Duration(hours: 1));
|
||||
|
||||
List<TToggleMenu> toolbarPrivacyMode(
|
||||
RxString privacyModeState, BuildContext context, String id, FFI ffi) {
|
||||
final ffiModel = ffi.ffiModel;
|
||||
final pi = ffiModel.pi;
|
||||
final sessionId = ffi.sessionId;
|
||||
|
||||
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
|
||||
final enabled = !ffi.ffiModel.viewOnly;
|
||||
return TToggleMenu(
|
||||
value: privacyModeState.isNotEmpty,
|
||||
onChanged: enabled
|
||||
? (value) {
|
||||
if (value == null) return;
|
||||
if (ffiModel.pi.currentDisplay != 0 &&
|
||||
ffiModel.pi.currentDisplay != kAllDisplayValue) {
|
||||
msgBox(
|
||||
sessionId,
|
||||
'custom-nook-nocancel-hasclose',
|
||||
'info',
|
||||
'Please switch to Display 1 first',
|
||||
'',
|
||||
ffi.dialogManager);
|
||||
return;
|
||||
}
|
||||
final option = 'privacy-mode';
|
||||
toggleFunc(sessionId, option);
|
||||
}
|
||||
: null,
|
||||
child: Text(translate('Privacy mode')));
|
||||
}
|
||||
|
||||
final privacyModeImpls =
|
||||
pi.platformAdditions[kPlatformAdditionsSupportedPrivacyModeImpl]
|
||||
as List<dynamic>?;
|
||||
if (privacyModeImpls == null) {
|
||||
return [
|
||||
getDefaultMenu((sid, opt) async {
|
||||
bind.sessionToggleOption(sessionId: sid, value: opt);
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
})
|
||||
];
|
||||
}
|
||||
if (privacyModeImpls.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (privacyModeImpls.length == 1) {
|
||||
final implKey = (privacyModeImpls[0] as List<dynamic>)[0] as String;
|
||||
return [
|
||||
getDefaultMenu((sid, opt) async {
|
||||
bind.sessionTogglePrivacyMode(
|
||||
sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty);
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
})
|
||||
];
|
||||
} else {
|
||||
return privacyModeImpls.map((e) {
|
||||
final implKey = (e as List<dynamic>)[0] as String;
|
||||
final implName = (e)[1] as String;
|
||||
return TToggleMenu(
|
||||
child: Text(translate(implName)),
|
||||
value: privacyModeState.value == implKey,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
bind.sessionTogglePrivacyMode(
|
||||
sessionId: sessionId, implKey: implKey, on: value);
|
||||
});
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
List<TToggleMenu> toolbarKeyboardToggles(FFI ffi) {
|
||||
final ffiModel = ffi.ffiModel;
|
||||
final pi = ffiModel.pi;
|
||||
final sessionId = ffi.sessionId;
|
||||
List<TToggleMenu> v = [];
|
||||
|
||||
// swap key
|
||||
if (ffiModel.keyboard &&
|
||||
((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) ||
|
||||
@@ -502,13 +638,53 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
final option = 'allow_swap_key';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
onChanged(bool? value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
}
|
||||
|
||||
final enabled = !ffi.ffiModel.viewOnly;
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
},
|
||||
onChanged: enabled ? onChanged : null,
|
||||
child: Text(translate('Swap control-command key'))));
|
||||
}
|
||||
|
||||
// reverse mouse wheel
|
||||
if (ffiModel.keyboard) {
|
||||
var optionValue =
|
||||
bind.sessionGetReverseMouseWheelSync(sessionId: sessionId) ?? '';
|
||||
if (optionValue == '') {
|
||||
optionValue = bind.mainGetUserDefaultOption(key: 'reverse_mouse_wheel');
|
||||
}
|
||||
onChanged(bool? value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionSetReverseMouseWheel(
|
||||
sessionId: sessionId, value: value ? 'Y' : 'N');
|
||||
}
|
||||
|
||||
final enabled = !ffi.ffiModel.viewOnly;
|
||||
v.add(TToggleMenu(
|
||||
value: optionValue == 'Y',
|
||||
onChanged: enabled ? onChanged : null,
|
||||
child: Text(translate('Reverse mouse wheel'))));
|
||||
}
|
||||
|
||||
// swap left right mouse
|
||||
if (ffiModel.keyboard) {
|
||||
final option = 'swap-left-right-mouse';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
onChanged(bool? value) {
|
||||
if (value == null) return;
|
||||
bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
}
|
||||
|
||||
final enabled = !ffi.ffiModel.viewOnly;
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
child: Text(translate('swap-left-right-mouse'))));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,35 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
const int kMaxVirtualDisplayCount = 4;
|
||||
const int kAllVirtualDisplay = -1;
|
||||
|
||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||
const int kInvalidWindowId = -1;
|
||||
const int kMainWindowId = 0;
|
||||
|
||||
const kAllDisplayValue = -1;
|
||||
|
||||
const kKeyLegacyMode = 'legacy';
|
||||
const kKeyMapMode = 'map';
|
||||
const kKeyTranslateMode = 'translate';
|
||||
|
||||
const String kPlatformAdditionsIsWayland = "is_wayland";
|
||||
const String kPlatformAdditionsHeadless = "headless";
|
||||
const String kPlatformAdditionsIsInstalled = "is_installed";
|
||||
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
|
||||
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
||||
const String kPlatformAdditionsSupportedPrivacyModeImpl = "supported_privacy_mode_impl";
|
||||
|
||||
const String kPeerPlatformWindows = "Windows";
|
||||
const String kPeerPlatformLinux = "Linux";
|
||||
const String kPeerPlatformMacOS = "Mac OS";
|
||||
const String kPeerPlatformAndroid = "Android";
|
||||
|
||||
const double kScrollbarThickness = 12.0;
|
||||
|
||||
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
|
||||
const String kAppTypeMain = "main";
|
||||
|
||||
@@ -24,6 +44,8 @@ const String kAppTypeDesktopPortForward = "port forward";
|
||||
|
||||
const String kWindowMainWindowOnTop = "main_window_on_top";
|
||||
const String kWindowGetWindowInfo = "get_window_info";
|
||||
const String kWindowGetScreenList = "get_screen_list";
|
||||
// This method is not used, maybe it can be removed.
|
||||
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
|
||||
const String kWindowActionRebuild = "rebuild";
|
||||
const String kWindowEventHide = "hide";
|
||||
@@ -34,11 +56,13 @@ const String kWindowEventNewRemoteDesktop = "new_remote_desktop";
|
||||
const String kWindowEventNewFileTransfer = "new_file_transfer";
|
||||
const String kWindowEventNewPortForward = "new_port_forward";
|
||||
const String kWindowEventActiveSession = "active_session";
|
||||
const String kWindowEventActiveDisplaySession = "active_display_session";
|
||||
const String kWindowEventGetRemoteList = "get_remote_list";
|
||||
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||
|
||||
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
||||
const String kWindowEventCloseForSeparateWindow = "close_for_separate_window";
|
||||
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
||||
const String kWindowEventOpenMonitorSession = "open_monitor_session";
|
||||
|
||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
||||
@@ -54,6 +78,15 @@ const String kTabLabelSettingPage = "Settings";
|
||||
const String kWindowPrefix = "wm_";
|
||||
const int kWindowMainId = 0;
|
||||
|
||||
const String kPointerEventKindTouch = "touch";
|
||||
const String kPointerEventKindMouse = "mouse";
|
||||
|
||||
const String kKeyShowDisplaysAsIndividualWindows =
|
||||
'displays_as_individual_windows';
|
||||
const String kKeyUseAllMyDisplaysForTheRemoteSession =
|
||||
'use_all_my_displays_for_the_remote_session';
|
||||
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
|
||||
|
||||
// the executable name of the portable version
|
||||
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
||||
|
||||
@@ -68,16 +101,29 @@ const int kDesktopDefaultDisplayHeight = 720;
|
||||
const int kMobileMaxDisplaySize = 1280;
|
||||
const int kDesktopMaxDisplaySize = 3840;
|
||||
|
||||
const double kDesktopFileTransferNameColWidth = 200;
|
||||
const double kDesktopFileTransferModifiedColWidth = 120;
|
||||
const double kDesktopFileTransferMinimumWidth = 100;
|
||||
const double kDesktopFileTransferMaximumWidth = 300;
|
||||
const double kDesktopFileTransferRowHeight = 30.0;
|
||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||
|
||||
const double kMinFps = 5;
|
||||
const double kDefaultFps = 30;
|
||||
const double kMaxFps = 120;
|
||||
|
||||
const double kMinQuality = 10;
|
||||
const double kDefaultQuality = 50;
|
||||
const double kMaxQuality = 100;
|
||||
const double kMaxMoreQuality = 2000;
|
||||
|
||||
double kNewWindowOffset = Platform.isWindows
|
||||
? 56.0
|
||||
: Platform.isLinux
|
||||
? 50.0
|
||||
: Platform.isMacOS
|
||||
? 30.0
|
||||
: 50.0;
|
||||
|
||||
EdgeInsets get kDragToResizeAreaPadding =>
|
||||
!kUseCompatibleUiMode && Platform.isLinux
|
||||
? stateGlobal.fullscreen || stateGlobal.isMaximized.value
|
||||
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.all(5.0)
|
||||
: EdgeInsets.zero;
|
||||
@@ -134,6 +180,12 @@ const kRemoteScrollStyleAuto = 'scrollauto';
|
||||
/// [kRemoteScrollStyleBar] Scroll image with scroll bar.
|
||||
const kRemoteScrollStyleBar = 'scrollbar';
|
||||
|
||||
/// [kScrollModeDefault] Mouse or touchpad, the default scroll mode.
|
||||
const kScrollModeDefault = 'default';
|
||||
|
||||
/// [kScrollModeReverse] Mouse or touchpad, the reverse scroll mode.
|
||||
const kScrollModeReverse = 'reverse';
|
||||
|
||||
/// [kRemoteImageQualityBest] Best image quality.
|
||||
const kRemoteImageQualityBest = 'best';
|
||||
|
||||
@@ -154,6 +206,12 @@ const kRemoteAudioDualWay = 'dual-way';
|
||||
|
||||
const kIgnoreDpi = true;
|
||||
|
||||
// ================================ mobile ================================
|
||||
|
||||
// Magic numbers, maybe need to avoid it or use a better way to get them.
|
||||
const kMobileDelaySoftKeyboard = Duration(milliseconds: 30);
|
||||
const kMobileDelaySoftKeyboardFocus = Duration(milliseconds: 30);
|
||||
|
||||
/// Android constants
|
||||
const kActionApplicationDetailsSettings =
|
||||
"android.settings.APPLICATION_DETAILS_SETTINGS";
|
||||
|
||||
@@ -7,15 +7,16 @@ import 'dart:io';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/formatter/id_formatter.dart';
|
||||
import '../../common/widgets/peer_tab_page.dart';
|
||||
import '../../common/widgets/autocomplete.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../widgets/button.dart';
|
||||
|
||||
@@ -33,18 +34,18 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
/// Controller for the id input bar.
|
||||
final _idController = IDTextEditingController();
|
||||
|
||||
/// Nested scroll controller
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
Timer? _updateTimer;
|
||||
|
||||
final RxBool _idInputFocused = false.obs;
|
||||
final FocusNode _idFocusNode = FocusNode();
|
||||
|
||||
var svcStopped = Get.find<RxBool>(tag: 'stop-service');
|
||||
var svcIsUsingPublicServer = true.obs;
|
||||
|
||||
bool isWindowMinimized = false;
|
||||
List<Peer> peers = [];
|
||||
|
||||
bool isPeersLoading = false;
|
||||
bool isPeersLoaded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -62,12 +63,6 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
_updateTimer = periodic_immediate(Duration(seconds: 1), () async {
|
||||
updateStatus();
|
||||
});
|
||||
_idFocusNode.addListener(() {
|
||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||
// select all to faciliate removing text, just following the behavior of address input of chrome
|
||||
_idController.selection = TextSelection(
|
||||
baseOffset: 0, extentOffset: _idController.value.text.length);
|
||||
});
|
||||
Get.put<IDTextEditingController>(_idController);
|
||||
windowManager.addListener(this);
|
||||
}
|
||||
@@ -80,6 +75,9 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
if (Get.isRegistered<IDTextEditingController>()) {
|
||||
Get.delete<IDTextEditingController>();
|
||||
}
|
||||
if (Get.isRegistered<TextEditingController>()) {
|
||||
Get.delete<TextEditingController>();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -106,7 +104,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
@override
|
||||
void onWindowLeaveFullScreen() {
|
||||
// Restore edge border to default edge size.
|
||||
stateGlobal.resizeEdgeSize.value = kWindowEdgeSize;
|
||||
stateGlobal.resizeEdgeSize.value =
|
||||
stateGlobal.isMaximized.isTrue ? kMaximizeEdgeSize : kWindowEdgeSize;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -120,30 +119,18 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DesktopScrollWrapper(
|
||||
scrollController: _scrollController,
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
Row(
|
||||
children: [
|
||||
Flexible(child: _buildRemoteIDTextField(context)),
|
||||
],
|
||||
).marginOnly(top: 22),
|
||||
SizedBox(height: 12),
|
||||
Divider().paddingOnly(right: 12),
|
||||
])),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: PeerTabPage().paddingOnly(right: 12.0),
|
||||
)
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(child: _buildRemoteIDTextField(context)),
|
||||
],
|
||||
).paddingOnly(left: 12.0),
|
||||
),
|
||||
),
|
||||
).marginOnly(top: 22),
|
||||
SizedBox(height: 12),
|
||||
Divider().paddingOnly(right: 12),
|
||||
Expanded(child: PeerTabPage()),
|
||||
],
|
||||
).paddingOnly(left: 12.0)),
|
||||
const Divider(height: 1),
|
||||
buildStatus()
|
||||
],
|
||||
@@ -157,8 +144,20 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
connect(context, id, isFileTransfer: isFileTransfer);
|
||||
}
|
||||
|
||||
Future<void> _fetchPeers() async {
|
||||
setState(() {
|
||||
isPeersLoading = true;
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
peers = await getAllPeers();
|
||||
setState(() {
|
||||
isPeersLoading = false;
|
||||
isPeersLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/// UI for the remote ID TextField.
|
||||
/// Search for a peer and connect to it if the id exists.
|
||||
/// Search for a peer.
|
||||
Widget _buildRemoteIDTextField(BuildContext context) {
|
||||
var w = Container(
|
||||
width: 320 + 20 * 2,
|
||||
@@ -172,51 +171,192 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoSizeText(
|
||||
translate('Control Remote Desktop'),
|
||||
maxLines: 1,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.merge(TextStyle(height: 1)),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
AutoSizeText(
|
||||
translate('Control Remote Desktop'),
|
||||
maxLines: 1,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.merge(TextStyle(height: 1)),
|
||||
).marginOnly(right: 4),
|
||||
Tooltip(
|
||||
waitDuration: Duration(milliseconds: 0),
|
||||
message: translate("id_input_tip"),
|
||||
child: Icon(
|
||||
Icons.help_outline_outlined,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color
|
||||
?.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
).marginOnly(bottom: 15),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => TextField(
|
||||
maxLength: 90,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
focusNode: _idFocusNode,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'WorkSans',
|
||||
fontSize: 22,
|
||||
height: 1.4,
|
||||
),
|
||||
maxLines: 1,
|
||||
cursorColor:
|
||||
Theme.of(context).textTheme.titleLarge?.color,
|
||||
decoration: InputDecoration(
|
||||
filled: false,
|
||||
counterText: '',
|
||||
hintText: _idInputFocused.value
|
||||
? null
|
||||
: translate('Enter Remote ID'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 15, vertical: 13)),
|
||||
controller: _idController,
|
||||
inputFormatters: [IDTextInputFormatter()],
|
||||
onSubmitted: (s) {
|
||||
onConnect();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Autocomplete<Peer>(
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text == '') {
|
||||
return const Iterable<Peer>.empty();
|
||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
||||
Peer emptyPeer = Peer(
|
||||
id: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
alias: '',
|
||||
platform: '',
|
||||
tags: [],
|
||||
hash: '',
|
||||
forceAlwaysRelay: false,
|
||||
rdpPort: '',
|
||||
rdpUsername: '',
|
||||
loginName: '',
|
||||
);
|
||||
return [emptyPeer];
|
||||
} else {
|
||||
String textWithoutSpaces =
|
||||
textEditingValue.text.replaceAll(" ", "");
|
||||
if (int.tryParse(textWithoutSpaces) != null) {
|
||||
textEditingValue = TextEditingValue(
|
||||
text: textWithoutSpaces,
|
||||
selection: textEditingValue.selection,
|
||||
);
|
||||
}
|
||||
String textToFind = textEditingValue.text.toLowerCase();
|
||||
|
||||
return peers
|
||||
.where((peer) =>
|
||||
peer.id.toLowerCase().contains(textToFind) ||
|
||||
peer.username
|
||||
.toLowerCase()
|
||||
.contains(textToFind) ||
|
||||
peer.hostname
|
||||
.toLowerCase()
|
||||
.contains(textToFind) ||
|
||||
peer.alias.toLowerCase().contains(textToFind))
|
||||
.toList();
|
||||
}
|
||||
},
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController fieldTextEditingController,
|
||||
FocusNode fieldFocusNode,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
fieldTextEditingController.text = _idController.text;
|
||||
Get.put<TextEditingController>(fieldTextEditingController);
|
||||
fieldFocusNode.addListener(() async {
|
||||
_idInputFocused.value = fieldFocusNode.hasFocus;
|
||||
if (fieldFocusNode.hasFocus && !isPeersLoading) {
|
||||
_fetchPeers();
|
||||
}
|
||||
});
|
||||
final textLength =
|
||||
fieldTextEditingController.value.text.length;
|
||||
// select all to facilitate removing text, just following the behavior of address input of chrome
|
||||
fieldTextEditingController.selection =
|
||||
TextSelection(baseOffset: 0, extentOffset: textLength);
|
||||
return Obx(() => TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
focusNode: fieldFocusNode,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'WorkSans',
|
||||
fontSize: 22,
|
||||
height: 1.4,
|
||||
),
|
||||
maxLines: 1,
|
||||
cursorColor:
|
||||
Theme.of(context).textTheme.titleLarge?.color,
|
||||
decoration: InputDecoration(
|
||||
filled: false,
|
||||
counterText: '',
|
||||
hintText: _idInputFocused.value
|
||||
? null
|
||||
: translate('Enter Remote ID'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 15, vertical: 13)),
|
||||
controller: fieldTextEditingController,
|
||||
inputFormatters: [IDTextInputFormatter()],
|
||||
onChanged: (v) {
|
||||
_idController.id = v;
|
||||
},
|
||||
));
|
||||
},
|
||||
onSelected: (option) {
|
||||
setState(() {
|
||||
_idController.id = option.id;
|
||||
FocusScope.of(context).unfocus();
|
||||
});
|
||||
},
|
||||
optionsViewBuilder: (BuildContext context,
|
||||
AutocompleteOnSelected<Peer> onSelected,
|
||||
Iterable<Peer> options) {
|
||||
double maxHeight = options.length * 50;
|
||||
if (options.length == 1) {
|
||||
maxHeight = 52;
|
||||
} else if (options.length == 3) {
|
||||
maxHeight = 146;
|
||||
} else if (options.length == 4) {
|
||||
maxHeight = 193;
|
||||
}
|
||||
maxHeight = maxHeight.clamp(0, 200);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 5,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxHeight,
|
||||
maxWidth: 319,
|
||||
),
|
||||
child: peers.isEmpty && isPeersLoading
|
||||
? Container(
|
||||
height: 80,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
))
|
||||
: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: ListView(
|
||||
children: options
|
||||
.map((peer) =>
|
||||
AutocompletePeerTile(
|
||||
onSelect: () =>
|
||||
onSelected(peer),
|
||||
peer: peer))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
))),
|
||||
);
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
@@ -227,7 +367,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
Button(
|
||||
isOutline: true,
|
||||
onTap: () => onConnect(isFileTransfer: true),
|
||||
text: "Transfer File",
|
||||
text: "Transfer file",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 17,
|
||||
@@ -280,7 +420,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
onTap: () async {
|
||||
await start_service(true);
|
||||
},
|
||||
child: Text(translate("Start Service"),
|
||||
child: Text(translate("Start service"),
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: em)))
|
||||
@@ -323,7 +463,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
}
|
||||
|
||||
void onUsePublicServerGuide() {
|
||||
const url = "https://rustdesk.com/blog/id-relay-set/";
|
||||
const url = "https://rustdesk.com/pricing.html";
|
||||
canLaunchUrlString(url).then((can) {
|
||||
if (can) {
|
||||
launchUrlString(url);
|
||||
|
||||
@@ -48,6 +48,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
var watchIsInputMonitoring = false;
|
||||
var watchIsCanRecordAudio = false;
|
||||
Timer? _updateTimer;
|
||||
bool isCardClosed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -186,12 +187,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
? Theme.of(context).scaffoldBackgroundColor
|
||||
: Theme.of(context).colorScheme.background,
|
||||
child: Tooltip(
|
||||
message: translate('Settings'),
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
)),
|
||||
message: translate('Settings'),
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
)),
|
||||
),
|
||||
),
|
||||
onHover: (value) => hover.value = value,
|
||||
@@ -255,27 +256,27 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
child: Obx(() => RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Tooltip(
|
||||
message: translate('Refresh Password'),
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
))
|
||||
)),
|
||||
message: translate('Refresh Password'),
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)))),
|
||||
onHover: (value) => refreshHover.value = value,
|
||||
).marginOnly(right: 8, top: 4),
|
||||
InkWell(
|
||||
child: Obx(
|
||||
() => Tooltip(
|
||||
message: translate('Change Password'),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
color:
|
||||
editHover.value ? textColor : Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)).marginOnly(right: 8, top: 4),
|
||||
message: translate('Change Password'),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
color: editHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)).marginOnly(right: 8, top: 4),
|
||||
),
|
||||
onTap: () => DesktopSettingPage.switch2page(1),
|
||||
onHover: (value) => editHover.value = value,
|
||||
@@ -321,25 +322,31 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
}
|
||||
|
||||
Future<Widget> buildHelpCards() async {
|
||||
if (updateUrl.isNotEmpty) {
|
||||
if (updateUrl.isNotEmpty && !isCardClosed) {
|
||||
return buildInstallCard(
|
||||
"Status",
|
||||
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
|
||||
"Click to download", () async {
|
||||
final Uri url = Uri.parse('https://rustdesk.com/download');
|
||||
await launchUrl(url);
|
||||
});
|
||||
}, closeButton: true);
|
||||
}
|
||||
if (systemError.isNotEmpty) {
|
||||
return buildInstallCard("", systemError, "", () {});
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
if (!bind.mainIsInstalled()) {
|
||||
return buildInstallCard(
|
||||
"", "install_tip", "Install", bind.mainGotoInstall);
|
||||
return buildInstallCard("", "install_tip", "Install", () async {
|
||||
await rustDeskWinManager.closeAllSubWindows();
|
||||
bind.mainGotoInstall();
|
||||
});
|
||||
} else if (bind.mainIsInstalledLowerVersion()) {
|
||||
return buildInstallCard("Status", "Your installation is lower version.",
|
||||
"Click to upgrade", bind.mainUpdateMe);
|
||||
return buildInstallCard(
|
||||
"Status", "Your installation is lower version.", "Click to upgrade",
|
||||
() async {
|
||||
await rustDeskWinManager.closeAllSubWindows();
|
||||
bind.mainUpdateMe();
|
||||
});
|
||||
}
|
||||
} else if (Platform.isMacOS) {
|
||||
if (!bind.mainIsCanScreenRecording(prompt: false)) {
|
||||
@@ -377,16 +384,42 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
// });
|
||||
// }
|
||||
} else if (Platform.isLinux) {
|
||||
final LinuxCards = <Widget>[];
|
||||
if (bind.isSelinuxEnforcing()) {
|
||||
// Check is SELinux enforcing, but show user a tip of is SELinux enabled for simple.
|
||||
final keyShowSelinuxHelpTip = "show-selinux-help-tip";
|
||||
if (bind.mainGetLocalOption(key: keyShowSelinuxHelpTip) != 'N') {
|
||||
LinuxCards.add(buildInstallCard(
|
||||
"Warning",
|
||||
"selinux_tip",
|
||||
"",
|
||||
() async {},
|
||||
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
|
||||
help: 'Help',
|
||||
link:
|
||||
'https://rustdesk.com/docs/en/client/linux/#permissions-issue',
|
||||
closeButton: true,
|
||||
closeOption: keyShowSelinuxHelpTip,
|
||||
));
|
||||
}
|
||||
}
|
||||
if (bind.mainCurrentIsWayland()) {
|
||||
return buildInstallCard(
|
||||
LinuxCards.add(buildInstallCard(
|
||||
"Warning", "wayland_experiment_tip", "", () async {},
|
||||
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
|
||||
help: 'Help',
|
||||
link: 'https://rustdesk.com/docs/en/manual/linux/#x11-required');
|
||||
link: 'https://rustdesk.com/docs/en/client/linux/#x11-required'));
|
||||
} else if (bind.mainIsLoginWayland()) {
|
||||
return buildInstallCard("Warning",
|
||||
LinuxCards.add(buildInstallCard("Warning",
|
||||
"Login screen using Wayland is not supported", "", () async {},
|
||||
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
|
||||
help: 'Help',
|
||||
link: 'https://rustdesk.com/docs/en/manual/linux/#login-screen');
|
||||
link: 'https://rustdesk.com/docs/en/client/linux/#login-screen'));
|
||||
}
|
||||
if (LinuxCards.isNotEmpty) {
|
||||
return Column(
|
||||
children: LinuxCards,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Container();
|
||||
@@ -394,92 +427,128 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
|
||||
Widget buildInstallCard(String title, String content, String btnText,
|
||||
GestureTapCallback onPressed,
|
||||
{String? help, String? link}) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Color.fromARGB(255, 226, 66, 188),
|
||||
Color.fromARGB(255, 244, 114, 124),
|
||||
],
|
||||
)),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: (title.isNotEmpty
|
||||
? <Widget>[
|
||||
Center(
|
||||
child: Text(
|
||||
translate(title),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15),
|
||||
).marginOnly(bottom: 6)),
|
||||
]
|
||||
: <Widget>[]) +
|
||||
<Widget>[
|
||||
Text(
|
||||
translate(content),
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 13),
|
||||
).marginOnly(bottom: 20)
|
||||
] +
|
||||
(btnText.isNotEmpty
|
||||
? <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FixedWidthButton(
|
||||
width: 150,
|
||||
padding: 8,
|
||||
isOutline: true,
|
||||
text: translate(btnText),
|
||||
textColor: Colors.white,
|
||||
borderColor: Colors.white,
|
||||
textSize: 20,
|
||||
radius: 10,
|
||||
onTap: onPressed,
|
||||
)
|
||||
])
|
||||
]
|
||||
: <Widget>[]) +
|
||||
(help != null
|
||||
? <Widget>[
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () async =>
|
||||
await launchUrl(Uri.parse(link!)),
|
||||
{double marginTop = 20.0,
|
||||
String? help,
|
||||
String? link,
|
||||
bool? closeButton,
|
||||
String? closeOption}) {
|
||||
void closeCard() async {
|
||||
if (closeOption != null) {
|
||||
await bind.mainSetLocalOption(key: closeOption, value: 'N');
|
||||
if (bind.mainGetLocalOption(key: closeOption) == 'N') {
|
||||
setState(() {
|
||||
isCardClosed = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
isCardClosed = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: marginTop),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Color.fromARGB(255, 226, 66, 188),
|
||||
Color.fromARGB(255, 244, 114, 124),
|
||||
],
|
||||
)),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: (title.isNotEmpty
|
||||
? <Widget>[
|
||||
Center(
|
||||
child: Text(
|
||||
translate(help),
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: Colors.white,
|
||||
fontSize: 12),
|
||||
)).marginOnly(top: 6)),
|
||||
]
|
||||
: <Widget>[]))),
|
||||
translate(title),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15),
|
||||
).marginOnly(bottom: 6)),
|
||||
]
|
||||
: <Widget>[]) +
|
||||
<Widget>[
|
||||
Text(
|
||||
translate(content),
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 13),
|
||||
).marginOnly(bottom: 20)
|
||||
] +
|
||||
(btnText.isNotEmpty
|
||||
? <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FixedWidthButton(
|
||||
width: 150,
|
||||
padding: 8,
|
||||
isOutline: true,
|
||||
text: translate(btnText),
|
||||
textColor: Colors.white,
|
||||
borderColor: Colors.white,
|
||||
textSize: 20,
|
||||
radius: 10,
|
||||
onTap: onPressed,
|
||||
)
|
||||
])
|
||||
]
|
||||
: <Widget>[]) +
|
||||
(help != null
|
||||
? <Widget>[
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () async =>
|
||||
await launchUrl(Uri.parse(link!)),
|
||||
child: Text(
|
||||
translate(help),
|
||||
style: TextStyle(
|
||||
decoration:
|
||||
TextDecoration.underline,
|
||||
color: Colors.white,
|
||||
fontSize: 12),
|
||||
)).marginOnly(top: 6)),
|
||||
]
|
||||
: <Widget>[]))),
|
||||
),
|
||||
if (closeButton != null && closeButton == true)
|
||||
Positioned(
|
||||
top: 18,
|
||||
right: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: closeCard,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
updateUrl = await bind.mainGetSoftwareUpdateUrl();
|
||||
if (updateUrl.isNotEmpty) setState(() {});
|
||||
});
|
||||
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
|
||||
await gFFI.serverModel.fetchID();
|
||||
final url = await bind.mainGetSoftwareUpdateUrl();
|
||||
if (updateUrl != url) {
|
||||
updateUrl = url;
|
||||
setState(() {});
|
||||
}
|
||||
final error = await bind.mainGetError();
|
||||
if (systemError != error) {
|
||||
systemError = error;
|
||||
@@ -530,6 +599,22 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
Get.put<RxBool>(svcStopped, tag: 'stop-service');
|
||||
rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged);
|
||||
|
||||
screenToMap(window_size.Screen screen) => {
|
||||
'frame': {
|
||||
'l': screen.frame.left,
|
||||
't': screen.frame.top,
|
||||
'r': screen.frame.right,
|
||||
'b': screen.frame.bottom,
|
||||
},
|
||||
'visibleFrame': {
|
||||
'l': screen.visibleFrame.left,
|
||||
't': screen.visibleFrame.top,
|
||||
'r': screen.visibleFrame.right,
|
||||
'b': screen.visibleFrame.bottom,
|
||||
},
|
||||
'scaleFactor': screen.scaleFactor,
|
||||
};
|
||||
|
||||
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
||||
debugPrint(
|
||||
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||
@@ -538,24 +623,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
} else if (call.method == kWindowGetWindowInfo) {
|
||||
final screen = (await window_size.getWindowInfo()).screen;
|
||||
if (screen == null) {
|
||||
return "";
|
||||
return '';
|
||||
} else {
|
||||
return jsonEncode({
|
||||
'frame': {
|
||||
'l': screen.frame.left,
|
||||
't': screen.frame.top,
|
||||
'r': screen.frame.right,
|
||||
'b': screen.frame.bottom,
|
||||
},
|
||||
'visibleFrame': {
|
||||
'l': screen.visibleFrame.left,
|
||||
't': screen.visibleFrame.top,
|
||||
'r': screen.visibleFrame.right,
|
||||
'b': screen.visibleFrame.bottom,
|
||||
},
|
||||
'scaleFactor': screen.scaleFactor,
|
||||
});
|
||||
return jsonEncode(screenToMap(screen));
|
||||
}
|
||||
} else if (call.method == kWindowGetScreenList) {
|
||||
return jsonEncode(
|
||||
(await window_size.getScreenList()).map(screenToMap).toList());
|
||||
} else if (call.method == kWindowActionRebuild) {
|
||||
reloadCurrentWindow();
|
||||
} else if (call.method == kWindowEventShow) {
|
||||
@@ -579,8 +653,18 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
debugPrint("Failed to parse window id '${call.arguments}': $e");
|
||||
}
|
||||
if (windowId != null) {
|
||||
await rustDeskWinManager.moveTabToNewWindow(windowId, args[1], args[2]);
|
||||
await rustDeskWinManager.moveTabToNewWindow(
|
||||
windowId, args[1], args[2]);
|
||||
}
|
||||
} else if (call.method == kWindowEventOpenMonitorSession) {
|
||||
final args = jsonDecode(call.arguments);
|
||||
final windowId = args['window_id'] as int;
|
||||
final peerId = args['peer_id'] as String;
|
||||
final display = args['display'] as int;
|
||||
final displayCount = args['display_count'] as int;
|
||||
final screenRect = parseParamScreenRect(args);
|
||||
await rustDeskWinManager.openMonitorSession(
|
||||
windowId, peerId, display, displayCount, screenRect);
|
||||
}
|
||||
});
|
||||
_uniLinksSubscription = listenUniLinks();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user