Compare commits
17 Commits
resize.2
...
seamless_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ae688764c | ||
|
|
73e722784d | ||
|
|
e1cd75792c | ||
|
|
ac4c8b4a3f | ||
|
|
fae3f9eeab | ||
|
|
f5aeecbc62 | ||
|
|
e40532a376 | ||
|
|
d860ad48e6 | ||
|
|
ec047b501e | ||
|
|
4c2e10fd74 | ||
|
|
6b1da2fcff | ||
|
|
2608b1dc62 | ||
|
|
31fa115655 | ||
|
|
82446b6b0d | ||
|
|
24c8039244 | ||
|
|
1a9429f3c7 | ||
|
|
0b4e484da0 |
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -21,5 +21,9 @@ assignees: ''
|
|||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
On errors, please provide the output of the console (and `adb logcat` if relevant).
|
On errors, please provide the output of the console (and `adb logcat` if relevant).
|
||||||
Format them between code blocks (delimited by ```).
|
|
||||||
|
```
|
||||||
|
Please paste terminal output in a code block.
|
||||||
|
```
|
||||||
|
|
||||||
Please do not post screenshots of your terminal, just post the content as text instead.
|
Please do not post screenshots of your terminal, just post the content as text instead.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
_Only the original [README](README.md) is guaranteed to be up-to-date._
|
||||||
|
|
||||||
# scrcpy (v1.11)
|
# scrcpy (v1.11)
|
||||||
|
|
||||||
This document will be updated frequently along with the original Readme file
|
This document will be updated frequently along with the original Readme file
|
||||||
|
|||||||
@@ -395,6 +395,10 @@ The list of display ids can be retrieved by:
|
|||||||
adb shell dumpsys display # search "mDisplayId=" in the output
|
adb shell dumpsys display # search "mDisplayId=" in the output
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The secondary display may only be controlled if the device runs at least Android
|
||||||
|
10 (otherwise it is mirrored in read-only).
|
||||||
|
|
||||||
|
|
||||||
#### Stay awake
|
#### Stay awake
|
||||||
|
|
||||||
To prevent the device to sleep after some delay:
|
To prevent the device to sleep after some delay:
|
||||||
@@ -478,6 +482,9 @@ both directions:
|
|||||||
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||||
breaks non-ASCII characters).
|
breaks non-ASCII characters).
|
||||||
|
|
||||||
|
Moreover, any time the Android clipboard changes, it is automatically
|
||||||
|
synchronized to the computer clipboard.
|
||||||
|
|
||||||
#### Text injection preference
|
#### Text injection preference
|
||||||
|
|
||||||
There are two kinds of [events][textevents] generated when typing text:
|
There are two kinds of [events][textevents] generated when typing text:
|
||||||
|
|||||||
531
README.pt-br.md
Normal file
531
README.pt-br.md
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
_Only the original [README](README.md) is guaranteed to be up-to-date._
|
||||||
|
|
||||||
|
# scrcpy (v1.12.1)
|
||||||
|
|
||||||
|
Esta aplicação fornece visualização e controle de dispositivos Android conectados via
|
||||||
|
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso root.
|
||||||
|
Funciona em _GNU/Linux_, _Windows_ e _macOS_.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Foco em:
|
||||||
|
|
||||||
|
- **leveza** (Nativo, mostra apenas a tela do dispositivo)
|
||||||
|
- **performance** (30~60fps)
|
||||||
|
- **qualidade** (1920×1080 ou acima)
|
||||||
|
- **baixa latência** ([35~70ms][lowlatency])
|
||||||
|
- **baixo tempo de inicialização** (~1 segundo para mostrar a primeira imagem)
|
||||||
|
- **não intrusivo** (nada é deixado instalado no dispositivo)
|
||||||
|
|
||||||
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
O Dispositivo Android requer pelo menos a API 21 (Android 5.0).
|
||||||
|
|
||||||
|
|
||||||
|
Tenha certeza de ter [ativado a depuração USB][enable-adb] no(s) seu(s) dispositivo(s).
|
||||||
|
|
||||||
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
|
|
||||||
|
|
||||||
|
Em alguns dispositivos, você também precisará ativar [uma opção adicional][control] para controlá-lo usando o teclado e mouse.
|
||||||
|
|
||||||
|
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
|
|
||||||
|
## Obtendo o app
|
||||||
|
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
No Debian (_em testes_ e _sid_ por enquanto):
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
O pacote [Snap] está disponível: [`scrcpy`][snap-link].
|
||||||
|
|
||||||
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
|
|
||||||
|
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
|
||||||
|
|
||||||
|
Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
|
Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
|
|
||||||
|
Você também pode [compilar a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil).
|
||||||
|
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências
|
||||||
|
(incluindo `adb`) está disponível:
|
||||||
|
|
||||||
|
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
|
||||||
|
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
|
||||||
|
|
||||||
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
|
||||||
|
|
||||||
|
Também disponível em [Chocolatey]:
|
||||||
|
|
||||||
|
[Chocolatey]: https://chocolatey.org/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
choco install scrcpy
|
||||||
|
choco install adb # se você ainda não o tem
|
||||||
|
```
|
||||||
|
|
||||||
|
E no [Scoop]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop install scrcpy
|
||||||
|
scoop install adb # se você ainda não o tem
|
||||||
|
```
|
||||||
|
|
||||||
|
[Scoop]: https://scoop.sh
|
||||||
|
|
||||||
|
Você também pode [compilar a aplicação manualmente][BUILD].
|
||||||
|
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
A aplicação está disponível em [Homebrew]. Apenas a instale:
|
||||||
|
|
||||||
|
[Homebrew]: https://brew.sh/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew cask install android-platform-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
Você também pode [compilar a aplicação manualmente][BUILD].
|
||||||
|
|
||||||
|
|
||||||
|
## Executar
|
||||||
|
|
||||||
|
Plugue um dispositivo Android e execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Também aceita argumentos de linha de comando, listados por:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Funcionalidades
|
||||||
|
|
||||||
|
### Configuração de captura
|
||||||
|
|
||||||
|
#### Redução de tamanho
|
||||||
|
|
||||||
|
Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para
|
||||||
|
aumentar performance.
|
||||||
|
|
||||||
|
Para limitar ambos(largura e altura) para algum valor (ex: 1024):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-size 1024
|
||||||
|
scrcpy -m 1024 # versão reduzida
|
||||||
|
```
|
||||||
|
|
||||||
|
A outra dimensão é calculada para que a proporção do dispositivo seja preservada.
|
||||||
|
Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576.
|
||||||
|
|
||||||
|
|
||||||
|
#### Mudanças no bit-rate
|
||||||
|
|
||||||
|
O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M
|
||||||
|
scrcpy -b 2M # versão reduzida
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limitar frame rates
|
||||||
|
|
||||||
|
Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cortar
|
||||||
|
|
||||||
|
A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela.
|
||||||
|
|
||||||
|
Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0)
|
||||||
|
```
|
||||||
|
|
||||||
|
Se `--max-size` também for especificado, redimensionar é aplicado após os cortes.
|
||||||
|
|
||||||
|
|
||||||
|
### Gravando
|
||||||
|
|
||||||
|
É possível gravar a tela enquanto ocorre o espelhamento:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --record file.mp4
|
||||||
|
scrcpy -r file.mkv
|
||||||
|
```
|
||||||
|
|
||||||
|
Para desativar o espelhamento durante a gravação:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-display --record file.mp4
|
||||||
|
scrcpy -Nr file.mkv
|
||||||
|
# interrompe a gravação com Ctrl+C
|
||||||
|
# Ctrl+C não encerrar propriamente no Windows, então desconecte o dispositivo
|
||||||
|
```
|
||||||
|
|
||||||
|
"Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance).
|
||||||
|
Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo.
|
||||||
|
|
||||||
|
[Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
### Conexão
|
||||||
|
|
||||||
|
#### Wireless/Sem fio
|
||||||
|
|
||||||
|
_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP:
|
||||||
|
|
||||||
|
1. Conecte o dispositivo a mesma rede Wi-Fi do seu computador.
|
||||||
|
2. Pegue o endereço de IP do seu dispositivo (Em Configurações → Sobre o Telefone → Status).
|
||||||
|
3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`.
|
||||||
|
4. Desplugue seu dispositivo.
|
||||||
|
5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `DEVICE_IP`)_.
|
||||||
|
6. Execute `scrcpy` como de costume.
|
||||||
|
|
||||||
|
Pode ser útil diminuir o bit-rate e a resolução:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
|
scrcpy -b2M -m800 # versão reduzida
|
||||||
|
```
|
||||||
|
|
||||||
|
[conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||||
|
|
||||||
|
|
||||||
|
#### N-dispositivos
|
||||||
|
|
||||||
|
Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 0123456789abcdef
|
||||||
|
scrcpy -s 0123456789abcdef # versão reduzida
|
||||||
|
```
|
||||||
|
|
||||||
|
Se o dispositivo está conectado via TCP/IP:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 192.168.0.1:5555
|
||||||
|
scrcpy -s 192.168.0.1:5555 # versão reduzida
|
||||||
|
```
|
||||||
|
|
||||||
|
Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos.
|
||||||
|
|
||||||
|
#### Conexão via SSH
|
||||||
|
|
||||||
|
Para conectar-se à um dispositivo remoto, é possível se conectar um cliente local `adb` à um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo _adb_):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb kill-server # encerra o servidor local na 5037
|
||||||
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
|
# mantém isso aberto
|
||||||
|
```
|
||||||
|
|
||||||
|
De outro terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Igual para conexões sem fio, pode ser útil reduzir a qualidade:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy -b2M -m800 --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configurações de Janela
|
||||||
|
|
||||||
|
#### Título
|
||||||
|
|
||||||
|
Por padrão, o título da janela é o modelo do dispositivo. Isto pode ser mudado:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-title 'Meu dispositivo'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Posição e tamanho
|
||||||
|
|
||||||
|
A posição e tamanho iniciais da janela podem ser especificados:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sem bordas
|
||||||
|
|
||||||
|
Para desativar decorações da janela:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-borderless
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sempre visível
|
||||||
|
|
||||||
|
Para manter a janela do scrcpy sempre visível:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --always-on-top
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tela cheia
|
||||||
|
|
||||||
|
A aplicação pode ser iniciada diretamente em tela cheia:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --fullscreen
|
||||||
|
scrcpy -f # versão reduzida
|
||||||
|
```
|
||||||
|
|
||||||
|
Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`.
|
||||||
|
|
||||||
|
|
||||||
|
### Outras opções de espelhamento
|
||||||
|
|
||||||
|
#### Apenas leitura
|
||||||
|
|
||||||
|
Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, eventos de mouse, arrastar e soltar arquivos):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-control
|
||||||
|
scrcpy -n
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Desligar a tela
|
||||||
|
|
||||||
|
É possível desligar a tela do dispositivo durante o início do espelhamento com uma opção de linha de comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --turn-screen-off
|
||||||
|
scrcpy -S
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou apertando `Ctrl`+`o` durante qualquer momento.
|
||||||
|
|
||||||
|
Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`).
|
||||||
|
|
||||||
|
#### Frames expirados de renderização
|
||||||
|
|
||||||
|
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado disponível e descarta o anterior.
|
||||||
|
|
||||||
|
Para forçar a renderização de todos os frames ( com o custo de aumento de latência), use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --render-expired-frames
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mostrar toques
|
||||||
|
|
||||||
|
Para apresentações, pode ser útil mostrar toques físicos(dispositivo físico).
|
||||||
|
|
||||||
|
Android fornece esta funcionalidade nas _Opções do Desenvolvedor_.
|
||||||
|
|
||||||
|
_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e desativar no encerramento:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --show-touches
|
||||||
|
scrcpy -t
|
||||||
|
```
|
||||||
|
|
||||||
|
Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo).
|
||||||
|
|
||||||
|
|
||||||
|
### Controle de entrada
|
||||||
|
|
||||||
|
#### Rotacionar a tela do dispositivo
|
||||||
|
|
||||||
|
Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem.
|
||||||
|
|
||||||
|
Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado.
|
||||||
|
|
||||||
|
#### Copiar-Colar
|
||||||
|
|
||||||
|
É possível sincronizar áreas de transferência entre computador e o dispositivo,
|
||||||
|
para ambas direções:
|
||||||
|
|
||||||
|
- `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador;
|
||||||
|
- `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo;
|
||||||
|
- `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas
|
||||||
|
quebra caracteres não-ASCII).
|
||||||
|
|
||||||
|
#### Preferências de injeção de texto
|
||||||
|
|
||||||
|
Existe dois tipos de [eventos][textevents] gerados ao digitar um texto:
|
||||||
|
- _eventos de teclas_, sinalizando que a tecla foi pressionada ou solta;
|
||||||
|
- _eventos de texto_, sinalizando que o texto foi inserido.
|
||||||
|
|
||||||
|
Por padrão, letras são injetadas usando eventos de teclas, assim teclados comportam-se
|
||||||
|
como esperado em jogos (normalmente para tecladas WASD)
|
||||||
|
|
||||||
|
Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema,
|
||||||
|
pode evitá-lo usando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --prefer-text
|
||||||
|
```
|
||||||
|
|
||||||
|
(mas isto vai quebrar o comportamento do teclado em jogos)
|
||||||
|
|
||||||
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
|
### Transferência de arquivo
|
||||||
|
|
||||||
|
#### Instalar APK
|
||||||
|
|
||||||
|
Para instalar um APK, arraste e solte o arquivo APK(com extensão `.apk`) na janela _scrcpy_.
|
||||||
|
|
||||||
|
Não existe feedback visual, um log é imprimido no console.
|
||||||
|
|
||||||
|
|
||||||
|
#### Enviar arquivo para o dispositivo
|
||||||
|
|
||||||
|
Para enviar um arquivo para o diretório `/sdcard/` no dispositivo, arraste e solte um arquivo não APK para a janela do
|
||||||
|
_scrcpy_.
|
||||||
|
|
||||||
|
Não existe feedback visual, um log é imprimido no console.
|
||||||
|
|
||||||
|
O diretório alvo pode ser mudado ao iniciar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --push-target /sdcard/foo/bar/
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Encaminhamento de áudio
|
||||||
|
|
||||||
|
Áudio não é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux).
|
||||||
|
|
||||||
|
Também veja [issue #14].
|
||||||
|
|
||||||
|
[USBaudio]: https://github.com/rom1v/usbaudio
|
||||||
|
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||||
|
|
||||||
|
|
||||||
|
## Atalhos
|
||||||
|
|
||||||
|
| Ação | Atalho | Atalho (macOS)
|
||||||
|
| ------------------------------------------------------------- |:------------------------------- |:-----------------------------
|
||||||
|
| Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f`
|
||||||
|
| Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g`
|
||||||
|
| Redimensionar janela para tirar as bordas pretas | `Ctrl`+`x` \| _Clique-duplo¹_ | `Cmd`+`x` \| _Clique-duplo¹_
|
||||||
|
| Clicar em `HOME` | `Ctrl`+`h` \| _Clique-central_ | `Ctrl`+`h` \| _Clique-central_
|
||||||
|
| Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_
|
||||||
|
| Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
||||||
|
| Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
||||||
|
| Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_
|
||||||
|
| Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_
|
||||||
|
| Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||||
|
| Ligar | _Clique-direito²_ | _Clique-direito²_
|
||||||
|
| Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o`
|
||||||
|
| Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r`
|
||||||
|
| Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n`
|
||||||
|
| Esconder painel de notificação | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||||
|
| Copiar área de transferência do dispositivo para o computador | `Ctrl`+`c` | `Cmd`+`c`
|
||||||
|
| Colar área de transferência do computador para o dispositivo | `Ctrl`+`v` | `Cmd`+`v`
|
||||||
|
| Copiar área de transferência do computador para dispositivo | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
||||||
|
| Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i`
|
||||||
|
|
||||||
|
_¹Clique-duplo em bordas pretas para removê-las._
|
||||||
|
_²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._
|
||||||
|
|
||||||
|
|
||||||
|
## Caminhos personalizados
|
||||||
|
|
||||||
|
Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`:
|
||||||
|
|
||||||
|
ADB=/caminho/para/adb scrcpy
|
||||||
|
|
||||||
|
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
|
||||||
|
`SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
|
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||||
|
|
||||||
|
|
||||||
|
## Por quê _scrcpy_?
|
||||||
|
|
||||||
|
Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet].
|
||||||
|
|
||||||
|
[`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een.
|
||||||
|
|
||||||
|
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
||||||
|
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||||
|
|
||||||
|
|
||||||
|
## Como compilar?
|
||||||
|
|
||||||
|
Veja [BUILD].
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
|
||||||
|
|
||||||
|
## Problemas comuns
|
||||||
|
|
||||||
|
Veja [FAQ](FAQ.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Desenvolvedores
|
||||||
|
|
||||||
|
Leia a [developers page].
|
||||||
|
|
||||||
|
[developers page]: DEVELOP.md
|
||||||
|
|
||||||
|
|
||||||
|
## Licença
|
||||||
|
|
||||||
|
Copyright (C) 2018 Genymobile
|
||||||
|
Copyright (C) 2018-2020 Romain Vimont
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
## Artigos
|
||||||
|
|
||||||
|
- [Introducing scrcpy][article-intro]
|
||||||
|
- [Scrcpy now works wirelessly][article-tcpip]
|
||||||
|
|
||||||
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
@@ -7,33 +7,6 @@
|
|||||||
#include "util/lock.h"
|
#include "util/lock.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
|
|
||||||
// coordinates (as provided in SDL mouse events)
|
|
||||||
//
|
|
||||||
// See my question:
|
|
||||||
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
|
|
||||||
static void
|
|
||||||
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
|
|
||||||
SDL_Rect viewport;
|
|
||||||
float scale_x, scale_y;
|
|
||||||
SDL_RenderGetViewport(renderer, &viewport);
|
|
||||||
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
|
|
||||||
*x = (int) (*x / scale_x) - viewport.x;
|
|
||||||
*y = (int) (*y / scale_y) - viewport.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct point
|
|
||||||
get_mouse_point(struct screen *screen) {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
SDL_GetMouseState(&x, &y);
|
|
||||||
convert_to_renderer_coordinates(screen->renderer, &x, &y);
|
|
||||||
return (struct point) {
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static const int ACTION_DOWN = 1;
|
static const int ACTION_DOWN = 1;
|
||||||
static const int ACTION_UP = 1 << 1;
|
static const int ACTION_UP = 1 << 1;
|
||||||
|
|
||||||
@@ -487,11 +460,17 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
|||||||
|
|
||||||
to->inject_touch_event.pointer_id = from->fingerId;
|
to->inject_touch_event.pointer_id = from->fingerId;
|
||||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
|
||||||
|
int ww;
|
||||||
|
int wh;
|
||||||
|
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
|
||||||
|
|
||||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||||
float x = from->x * screen->content_size.width;
|
int32_t x = from->x * ww;
|
||||||
float y = from->y * screen->content_size.height;
|
int32_t y = from->y * wh;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point =
|
||||||
screen_convert_to_frame_coords(screen, x, y);
|
screen_convert_to_frame_coords(screen, x, y);
|
||||||
|
|
||||||
to->inject_touch_event.pressure = from->pressure;
|
to->inject_touch_event.pressure = from->pressure;
|
||||||
to->inject_touch_event.buttons = 0;
|
to->inject_touch_event.buttons = 0;
|
||||||
return true;
|
return true;
|
||||||
@@ -508,13 +487,6 @@ input_manager_process_touch(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
is_outside_device_screen(struct input_manager *im, int x, int y)
|
|
||||||
{
|
|
||||||
return x < 0 || x >= im->screen->content_size.width ||
|
|
||||||
y < 0 || y >= im->screen->content_size.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
||||||
struct control_msg *to) {
|
struct control_msg *to) {
|
||||||
@@ -552,10 +524,15 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// double-click on black borders resize to fit the device screen
|
// double-click on black borders resize to fit the device screen
|
||||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||||
bool outside =
|
int32_t x = event->x;
|
||||||
is_outside_device_screen(im, event->x, event->y);
|
int32_t y = event->y;
|
||||||
|
screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||||
|
SDL_Rect *r = &im->screen->rect;
|
||||||
|
bool outside = x < r->x || x >= r->x + r->w
|
||||||
|
|| y < r->y || y >= r->y + r->h;
|
||||||
if (outside) {
|
if (outside) {
|
||||||
screen_resize_to_fit(im->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
return;
|
return;
|
||||||
@@ -579,9 +556,15 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
static bool
|
static bool
|
||||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||||
struct control_msg *to) {
|
struct control_msg *to) {
|
||||||
|
|
||||||
|
// mouse_x and mouse_y are expressed in pixels relative to the window
|
||||||
|
int mouse_x;
|
||||||
|
int mouse_y;
|
||||||
|
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
struct position position = {
|
struct position position = {
|
||||||
.screen_size = screen->frame_size,
|
.screen_size = screen->frame_size,
|
||||||
.point = get_mouse_point(screen),
|
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
|
||||||
};
|
};
|
||||||
|
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||||
|
|||||||
@@ -133,10 +133,10 @@ static int
|
|||||||
event_watcher(void *data, SDL_Event *event) {
|
event_watcher(void *data, SDL_Event *event) {
|
||||||
(void) data;
|
(void) data;
|
||||||
if (event->type == SDL_WINDOWEVENT
|
if (event->type == SDL_WINDOWEVENT
|
||||||
&& event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
// In practice, it seems to always be called from the same thread in
|
// In practice, it seems to always be called from the same thread in
|
||||||
// that specific case. Anyway, it's just a workaround.
|
// that specific case. Anyway, it's just a workaround.
|
||||||
screen_render(&screen);
|
screen_render(&screen, true);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
207
app/src/screen.c
207
app/src/screen.c
@@ -68,6 +68,16 @@ get_preferred_display_bounds(struct size *bounds) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_optimal_size(struct size current_size, struct size content_size) {
|
||||||
|
// The size is optimal if we can recompute one dimension of the current
|
||||||
|
// size from the other
|
||||||
|
return current_size.height == current_size.width * content_size.height
|
||||||
|
/ content_size.width
|
||||||
|
|| current_size.width == current_size.height * content_size.width
|
||||||
|
/ content_size.height;
|
||||||
|
}
|
||||||
|
|
||||||
// return the optimal size of the window, with the following constraints:
|
// return the optimal size of the window, with the following constraints:
|
||||||
// - it attempts to keep at least one dimension of the current_size (i.e. it
|
// - it attempts to keep at least one dimension of the current_size (i.e. it
|
||||||
// crops the black borders)
|
// crops the black borders)
|
||||||
@@ -80,40 +90,36 @@ get_optimal_size(struct size current_size, struct size content_size) {
|
|||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct size display_size;
|
struct size window_size;
|
||||||
// 32 bits because we need to multiply two 16 bits values
|
|
||||||
uint32_t w;
|
|
||||||
uint32_t h;
|
|
||||||
|
|
||||||
|
struct size display_size;
|
||||||
if (!get_preferred_display_bounds(&display_size)) {
|
if (!get_preferred_display_bounds(&display_size)) {
|
||||||
// could not get display bounds, do not constraint the size
|
// could not get display bounds, do not constraint the size
|
||||||
w = current_size.width;
|
window_size.width = current_size.width;
|
||||||
h = current_size.height;
|
window_size.height = current_size.height;
|
||||||
} else {
|
} else {
|
||||||
w = MIN(current_size.width, display_size.width);
|
window_size.width = MIN(current_size.width, display_size.width);
|
||||||
h = MIN(current_size.height, display_size.height);
|
window_size.height = MIN(current_size.height, display_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (h == w * content_size.height / content_size.width
|
if (is_optimal_size(window_size, content_size)) {
|
||||||
|| w == h * content_size.width / content_size.height) {
|
return window_size;
|
||||||
// The size is already optimal, if we ignore rounding errors due to
|
|
||||||
// integer window dimensions
|
|
||||||
return (struct size) {w, h};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keep_width = content_size.width * h > content_size.height * w;
|
bool keep_width = content_size.width * window_size.height
|
||||||
|
> content_size.height * window_size.width;
|
||||||
if (keep_width) {
|
if (keep_width) {
|
||||||
// remove black borders on top and bottom
|
// remove black borders on top and bottom
|
||||||
h = content_size.height * w / content_size.width;
|
window_size.height = content_size.height * window_size.width
|
||||||
|
/ content_size.width;
|
||||||
} else {
|
} else {
|
||||||
// remove black borders on left and right (or none at all if it already
|
// remove black borders on left and right (or none at all if it already
|
||||||
// fits)
|
// fits)
|
||||||
w = content_size.width * h / content_size.height;
|
window_size.width = content_size.width * window_size.height
|
||||||
|
/ content_size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// w and h must fit into 16 bits
|
return window_size;
|
||||||
assert(w < 0x10000 && h < 0x10000);
|
|
||||||
return (struct size) {w, h};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// same as get_optimal_size(), but read the current size from the window
|
// same as get_optimal_size(), but read the current size from the window
|
||||||
@@ -150,6 +156,43 @@ get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
|||||||
return window_size;
|
return window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
screen_update_content_rect(struct screen *screen) {
|
||||||
|
int dw;
|
||||||
|
int dh;
|
||||||
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
|
struct size content_size = screen->content_size;
|
||||||
|
// The drawable size is the window size * the HiDPI scale
|
||||||
|
struct size drawable_size = {dw, dh};
|
||||||
|
|
||||||
|
SDL_Rect *rect = &screen->rect;
|
||||||
|
|
||||||
|
if (is_optimal_size(drawable_size, content_size)) {
|
||||||
|
rect->x = 0;
|
||||||
|
rect->y = 0;
|
||||||
|
rect->w = drawable_size.width;
|
||||||
|
rect->h = drawable_size.height;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool keep_width = content_size.width * drawable_size.height
|
||||||
|
> content_size.height * drawable_size.width;
|
||||||
|
if (keep_width) {
|
||||||
|
rect->x = 0;
|
||||||
|
rect->w = drawable_size.width;
|
||||||
|
rect->h = drawable_size.width * content_size.height
|
||||||
|
/ content_size.width;
|
||||||
|
rect->y = (drawable_size.height - rect->h) / 2;
|
||||||
|
} else {
|
||||||
|
rect->y = 0;
|
||||||
|
rect->h = drawable_size.height;
|
||||||
|
rect->w = drawable_size.height * content_size.width
|
||||||
|
/ content_size.height;
|
||||||
|
rect->x = (drawable_size.width - rect->w) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_init(struct screen *screen) {
|
screen_init(struct screen *screen) {
|
||||||
*screen = (struct screen) SCREEN_INITIALIZER;
|
*screen = (struct screen) SCREEN_INITIALIZER;
|
||||||
@@ -174,7 +217,7 @@ create_texture(struct screen *screen) {
|
|||||||
// Enable trilinear filtering for downscaling
|
// Enable trilinear filtering for downscaling
|
||||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||||
GL_LINEAR_MIPMAP_LINEAR);
|
GL_LINEAR_MIPMAP_LINEAR);
|
||||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -.5f);
|
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||||
|
|
||||||
SDL_GL_UnbindTexture(texture);
|
SDL_GL_UnbindTexture(texture);
|
||||||
}
|
}
|
||||||
@@ -239,13 +282,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||||
|
|
||||||
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
|
|
||||||
content_size.height)) {
|
|
||||||
LOGE("Could not set renderer logical size: %s", SDL_GetError());
|
|
||||||
screen_destroy(screen);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// starts with "opengl"
|
// starts with "opengl"
|
||||||
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||||
if (screen->use_opengl) {
|
if (screen->use_opengl) {
|
||||||
@@ -269,7 +305,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
LOGI("Trilinear filtering disabled");
|
LOGI("Trilinear filtering disabled");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGW("Trilinear filtering disabled (not an OpenGL renderer)");
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface *icon = read_xpm(icon_xpm);
|
SDL_Surface *icon = read_xpm(icon_xpm);
|
||||||
@@ -289,6 +325,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||||
|
// HiDPI issues with some SDL renderers when several displays having
|
||||||
|
// different HiDPI scaling are connected
|
||||||
|
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
|
||||||
|
|
||||||
|
screen_update_content_rect(screen);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,18 +402,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
struct size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(screen->frame_size, rotation);
|
get_rotated_size(screen->frame_size, rotation);
|
||||||
|
|
||||||
if (SDL_RenderSetLogicalSize(screen->renderer,
|
|
||||||
new_content_size.width,
|
|
||||||
new_content_size.height)) {
|
|
||||||
LOGE("Could not set renderer logical size: %s", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
|
|
||||||
screen->rotation = rotation;
|
screen->rotation = rotation;
|
||||||
LOGI("Display rotation set to %u", rotation);
|
LOGI("Display rotation set to %u", rotation);
|
||||||
|
|
||||||
screen_render(screen);
|
screen_render(screen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
@@ -378,21 +415,17 @@ static bool
|
|||||||
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||||
if (screen->frame_size.width != new_frame_size.width
|
if (screen->frame_size.width != new_frame_size.width
|
||||||
|| screen->frame_size.height != new_frame_size.height) {
|
|| screen->frame_size.height != new_frame_size.height) {
|
||||||
struct size new_content_size =
|
|
||||||
get_rotated_size(new_frame_size, screen->rotation);
|
|
||||||
if (SDL_RenderSetLogicalSize(screen->renderer,
|
|
||||||
new_content_size.width,
|
|
||||||
new_content_size.height)) {
|
|
||||||
LOGE("Could not set renderer logical size: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// frame dimension changed, destroy texture
|
// frame dimension changed, destroy texture
|
||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyTexture(screen->texture);
|
||||||
|
|
||||||
set_content_size(screen, new_content_size);
|
|
||||||
screen->frame_size = new_frame_size;
|
screen->frame_size = new_frame_size;
|
||||||
|
|
||||||
|
struct size new_content_size =
|
||||||
|
get_rotated_size(new_frame_size, screen->rotation);
|
||||||
|
set_content_size(screen, new_content_size);
|
||||||
|
|
||||||
|
screen_update_content_rect(screen);
|
||||||
|
|
||||||
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
||||||
screen->frame_size.width, screen->frame_size.height);
|
screen->frame_size.width, screen->frame_size.height);
|
||||||
screen->texture = create_texture(screen);
|
screen->texture = create_texture(screen);
|
||||||
@@ -433,15 +466,19 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
|
|||||||
update_texture(screen, frame);
|
update_texture(screen, frame);
|
||||||
mutex_unlock(vb->mutex);
|
mutex_unlock(vb->mutex);
|
||||||
|
|
||||||
screen_render(screen);
|
screen_render(screen, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_render(struct screen *screen) {
|
screen_render(struct screen *screen, bool update_content_rect) {
|
||||||
|
if (update_content_rect) {
|
||||||
|
screen_update_content_rect(screen);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_RenderClear(screen->renderer);
|
SDL_RenderClear(screen->renderer);
|
||||||
if (screen->rotation == 0) {
|
if (screen->rotation == 0) {
|
||||||
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
|
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
||||||
} else {
|
} else {
|
||||||
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
||||||
// counterclockwise (to be consistent with --lock-video-orientation)
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
||||||
@@ -451,12 +488,14 @@ screen_render(struct screen *screen) {
|
|||||||
SDL_Rect *dstrect = NULL;
|
SDL_Rect *dstrect = NULL;
|
||||||
SDL_Rect rect;
|
SDL_Rect rect;
|
||||||
if (screen->rotation & 1) {
|
if (screen->rotation & 1) {
|
||||||
struct size size = screen->content_size;
|
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
|
||||||
rect.x = (size.width - size.height) / 2;
|
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
|
||||||
rect.y = (size.height - size.width) / 2;
|
rect.w = screen->rect.h;
|
||||||
rect.w = size.height;
|
rect.h = screen->rect.w;
|
||||||
rect.h = size.width;
|
|
||||||
dstrect = ▭
|
dstrect = ▭
|
||||||
|
} else {
|
||||||
|
assert(screen->rotation == 2);
|
||||||
|
dstrect = &screen->rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
||||||
@@ -479,20 +518,15 @@ screen_switch_fullscreen(struct screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||||
screen_render(screen);
|
screen_render(screen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_resize_to_fit(struct screen *screen) {
|
screen_resize_to_fit(struct screen *screen) {
|
||||||
if (screen->fullscreen) {
|
if (screen->fullscreen || screen->maximized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screen->maximized) {
|
|
||||||
SDL_RestoreWindow(screen->window);
|
|
||||||
screen->maximized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_window_size(screen, screen->content_size);
|
get_optimal_window_size(screen, screen->content_size);
|
||||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||||
@@ -517,41 +551,27 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
|||||||
content_size.height);
|
content_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
is_fullscreen(const struct screen *screen) {
|
|
||||||
uint32_t flags = SDL_GetWindowFlags(screen->window);
|
|
||||||
return !!(flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
update_fullscreen_state(struct screen *screen) {
|
|
||||||
// There is no SDL event to detect fullscreen changes, so store the
|
|
||||||
// state in a field and compare on every "size changed" event
|
|
||||||
bool fullscreen = is_fullscreen(screen);
|
|
||||||
if (fullscreen != screen->fullscreen) {
|
|
||||||
// Fullscreen state have changed
|
|
||||||
screen->fullscreen = fullscreen;
|
|
||||||
if (!fullscreen && !screen->maximized) {
|
|
||||||
apply_pending_resize(screen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_handle_window_event(struct screen *screen,
|
screen_handle_window_event(struct screen *screen,
|
||||||
const SDL_WindowEvent *event) {
|
const SDL_WindowEvent *event) {
|
||||||
switch (event->event) {
|
switch (event->event) {
|
||||||
case SDL_WINDOWEVENT_EXPOSED:
|
case SDL_WINDOWEVENT_EXPOSED:
|
||||||
screen_render(screen);
|
screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
update_fullscreen_state(screen);
|
screen_render(screen, true);
|
||||||
screen_render(screen);
|
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||||
screen->maximized = true;
|
screen->maximized = true;
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_RESTORED:
|
case SDL_WINDOWEVENT_RESTORED:
|
||||||
|
if (screen->fullscreen) {
|
||||||
|
// On Windows, in maximized+fullscreen, disabling fullscreen
|
||||||
|
// mode unexpectedly triggers the "restored" then "maximized"
|
||||||
|
// events, leaving the window in a weird state (maximized
|
||||||
|
// according to the events, but not maximized visually).
|
||||||
|
break;
|
||||||
|
}
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
apply_pending_resize(screen);
|
apply_pending_resize(screen);
|
||||||
break;
|
break;
|
||||||
@@ -565,6 +585,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
|
|||||||
|
|
||||||
int32_t w = screen->content_size.width;
|
int32_t w = screen->content_size.width;
|
||||||
int32_t h = screen->content_size.height;
|
int32_t h = screen->content_size.height;
|
||||||
|
|
||||||
|
screen_hidpi_scale_coords(screen, &x, &y);
|
||||||
|
|
||||||
|
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
|
||||||
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
||||||
|
|
||||||
|
// rotate
|
||||||
struct point result;
|
struct point result;
|
||||||
switch (rotation) {
|
switch (rotation) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -587,3 +614,15 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
|
||||||
|
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
||||||
|
int ww, wh, dw, dh;
|
||||||
|
SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||||
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
|
// scale for HiDPI (64 bits for intermediate multiplications)
|
||||||
|
*x = (int64_t) *x * dw / ww;
|
||||||
|
*y = (int64_t) *y * dh / wh;
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ struct screen {
|
|||||||
|
|
||||||
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
||||||
unsigned rotation;
|
unsigned rotation;
|
||||||
|
// rectangle of the content (excluding black borders)
|
||||||
|
struct SDL_Rect rect;
|
||||||
bool has_frame;
|
bool has_frame;
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool maximized;
|
bool maximized;
|
||||||
@@ -56,6 +58,12 @@ struct screen {
|
|||||||
.height = 0, \
|
.height = 0, \
|
||||||
}, \
|
}, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
|
.rect = { \
|
||||||
|
.x = 0, \
|
||||||
|
.y = 0, \
|
||||||
|
.w = 0, \
|
||||||
|
.h = 0, \
|
||||||
|
}, \
|
||||||
.has_frame = false, \
|
.has_frame = false, \
|
||||||
.fullscreen = false, \
|
.fullscreen = false, \
|
||||||
.maximized = false, \
|
.maximized = false, \
|
||||||
@@ -89,8 +97,11 @@ bool
|
|||||||
screen_update_frame(struct screen *screen, struct video_buffer *vb);
|
screen_update_frame(struct screen *screen, struct video_buffer *vb);
|
||||||
|
|
||||||
// render the texture to the renderer
|
// render the texture to the renderer
|
||||||
|
//
|
||||||
|
// Set the update_content_rect flag if the window or content size may have
|
||||||
|
// changed, so that the content rectangle is recomputed
|
||||||
void
|
void
|
||||||
screen_render(struct screen *screen);
|
screen_render(struct screen *screen, bool update_content_rect);
|
||||||
|
|
||||||
// switch the fullscreen mode
|
// switch the fullscreen mode
|
||||||
void
|
void
|
||||||
@@ -117,4 +128,11 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
|||||||
struct point
|
struct point
|
||||||
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
|
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
|
||||||
|
|
||||||
|
// Convert coordinates from window to drawable.
|
||||||
|
// Events are expressed in window coordinates, but content is expressed in
|
||||||
|
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
|
||||||
|
// otherwise.
|
||||||
|
void
|
||||||
|
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2008, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@hide}
|
||||||
|
*/
|
||||||
|
oneway interface IOnPrimaryClipChangedListener {
|
||||||
|
void dispatchPrimaryClipChanged();
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
|
|||||||
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||||
import com.genymobile.scrcpy.wrappers.WindowManager;
|
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||||
|
|
||||||
|
import android.content.IOnPrimaryClipChangedListener;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.view.IRotationWatcher;
|
import android.view.IRotationWatcher;
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
|
|
||||||
@@ -22,10 +22,15 @@ public final class Device {
|
|||||||
void onRotationChanged(int rotation);
|
void onRotationChanged(int rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ClipboardListener {
|
||||||
|
void onClipboardTextChanged(String text);
|
||||||
|
}
|
||||||
|
|
||||||
private final ServiceManager serviceManager = new ServiceManager();
|
private final ServiceManager serviceManager = new ServiceManager();
|
||||||
|
|
||||||
private ScreenInfo screenInfo;
|
private ScreenInfo screenInfo;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
|
private ClipboardListener clipboardListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical display identifier
|
* Logical display identifier
|
||||||
@@ -52,9 +57,9 @@ public final class Device {
|
|||||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
|
||||||
layerStack = displayInfo.getLayerStack();
|
layerStack = displayInfo.getLayerStack();
|
||||||
|
|
||||||
registerRotationWatcher(new IRotationWatcher.Stub() {
|
serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void onRotationChanged(int rotation) throws RemoteException {
|
public void onRotationChanged(int rotation) {
|
||||||
synchronized (Device.this) {
|
synchronized (Device.this) {
|
||||||
screenInfo = screenInfo.withDeviceRotation(rotation);
|
screenInfo = screenInfo.withDeviceRotation(rotation);
|
||||||
|
|
||||||
@@ -66,6 +71,23 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
}, displayId);
|
}, displayId);
|
||||||
|
|
||||||
|
if (options.getControl()) {
|
||||||
|
// If control is enabled, synchronize Android clipboard to the computer automatically
|
||||||
|
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||||
|
@Override
|
||||||
|
public void dispatchPrimaryClipChanged() {
|
||||||
|
synchronized (Device.this) {
|
||||||
|
if (clipboardListener != null) {
|
||||||
|
String text = getClipboardText();
|
||||||
|
if (text != null) {
|
||||||
|
clipboardListener.onClipboardTextChanged(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
|
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
|
||||||
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
|
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
|
||||||
}
|
}
|
||||||
@@ -134,14 +156,14 @@ public final class Device {
|
|||||||
return serviceManager.getPowerManager().isScreenOn();
|
return serviceManager.getPowerManager().isScreenOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
|
|
||||||
serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setRotationListener(RotationListener rotationListener) {
|
public synchronized void setRotationListener(RotationListener rotationListener) {
|
||||||
this.rotationListener = rotationListener;
|
this.rotationListener = rotationListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
|
||||||
|
this.clipboardListener = clipboardListener;
|
||||||
|
}
|
||||||
|
|
||||||
public void expandNotificationPanel() {
|
public void expandNotificationPanel() {
|
||||||
serviceManager.getStatusBarManager().expandNotificationsPanel();
|
serviceManager.getStatusBarManager().expandNotificationsPanel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,18 @@ public final class Server {
|
|||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
|
||||||
|
|
||||||
if (options.getControl()) {
|
if (options.getControl()) {
|
||||||
Controller controller = new Controller(device, connection);
|
final Controller controller = new Controller(device, connection);
|
||||||
|
|
||||||
// asynchronous
|
// asynchronous
|
||||||
startController(controller);
|
startController(controller);
|
||||||
startDeviceMessageSender(controller.getSender());
|
startDeviceMessageSender(controller.getSender());
|
||||||
|
|
||||||
|
device.setClipboardListener(new Device.ClipboardListener() {
|
||||||
|
@Override
|
||||||
|
public void onClipboardTextChanged(String text) {
|
||||||
|
controller.getSender().pushClipboardText(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.genymobile.scrcpy.wrappers;
|
|||||||
import com.genymobile.scrcpy.Ln;
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
|
import android.content.IOnPrimaryClipChangedListener;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ public class ClipboardManager {
|
|||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private Method getPrimaryClipMethod;
|
private Method getPrimaryClipMethod;
|
||||||
private Method setPrimaryClipMethod;
|
private Method setPrimaryClipMethod;
|
||||||
|
private Method addPrimaryClipChangedListener;
|
||||||
|
|
||||||
public ClipboardManager(IInterface manager) {
|
public ClipboardManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@@ -81,4 +83,37 @@ public class ClipboardManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener)
|
||||||
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
|
||||||
|
} else {
|
||||||
|
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
|
||||||
|
if (addPrimaryClipChangedListener == null) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
|
||||||
|
} else {
|
||||||
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addPrimaryClipChangedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
|
||||||
|
try {
|
||||||
|
Method method = getAddPrimaryClipChangedListener();
|
||||||
|
addPrimaryClipChangedListener(method, manager, listener);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user