Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Before publishing a version, add a `## [VERSION]` section to **both** files: the release workflow uses the section matching the tag and fails if either is missing.

## [Unreleased]
### Fixes
- **Xbox controllers no longer ask to be configured** (#284): when an Xbox controller also showed up as a "joystick without a profile" (a second instance, a receiver or a generic driver), the app said "Controle sem perfil… hold any button" and opened the mapping wizard even though the controller already worked. Xbox controllers (Microsoft vendor, or "Xbox"/"XInput" in the name) now never trigger the notice or the wizard; real generic joysticks can still be configured.
### Changes
- **Git status left Configurações** (#284): the option, the "Status do Git: ligado/desligado" notice, the branch in the header and the "Git: modificado" marks are gone from the interface.
### What's new
- **Checks and updates by itself on open**: ControlFS now looks for a new version **every time it opens** (before, at most once a day) and, in the installed build, if a verified version exists (signed manifest, SHA-256 and size) and you haven't started using the app and nothing is running, it **downloads, installs and reopens already updated**, without asking. If you already started using it, the usual notice stays (Install and restart / Later). Turn it off in Menu → Configurações → Atualizações → **Atualizar sozinho ao abrir**. The portable build only notifies. It never runs in CI or with `--no-auto-update`.

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CHANGELOG.en-US.md`. Antes de publicar uma versão, adicione uma seção `## [VERSÃO]` **nos dois arquivos**. O workflow de release usa a seção da tag e falha se faltar alguma.

## [Unreleased]
### Correções
- **Controle Xbox não pede mais para ser configurado** (#284): quando um controle Xbox aparecia também como "joystick sem perfil" (segunda instância, receptor ou driver genérico), o app avisava "Controle sem perfil… segure qualquer botão" e abria o assistente de configuração, mesmo com o controle já funcionando. Agora controles Xbox (fabricante Microsoft, ou "Xbox"/"XInput" no nome) nunca disparam o aviso nem o assistente; joysticks genéricos de verdade continuam podendo ser configurados.
### Mudanças
- **Status do Git saiu das Configurações** (#284): a opção, o aviso "Status do Git: ligado/desligado", o ramo no cabeçalho e as marcas "Git: modificado" deixaram de existir na interface.
### Novidades
- **Verifica e se atualiza sozinho ao abrir**: o ControlFS agora procura versão nova **a cada vez que abre** (antes, no máximo uma vez por dia) e, na versão instalada, se há uma versão verificada (manifesto assinado, SHA-256 e tamanho) e você ainda não começou a usar o app e nada está em andamento, **baixa, instala e reabre já atualizado**, sem perguntar. Se você já começou a usar, fica o aviso de sempre (Instalar e reiniciar / Depois). Desligue em Menu → Configurações → Atualizações → **Atualizar sozinho ao abrir**. A versão portátil só avisa. Nunca roda em CI nem com `--no-auto-update`.

Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ Buttons follow **physical position**, so a Nintendo layout doesn't flip confirm
- **Tabs** (restored on launch, reopen closed tab, duplicate) and **two panes** side by side (L3 switches; copy/move/extract to the other pane)
- **Network locations** (mapped drives and network shortcuts), drive types at a glance (local, USB, optical, network), refreshed when a USB stick is plugged in or removed
- **Search** by name in the current folder (or the main folders from Home), with or without subfolders and with filters: results stream in, can be cancelled; no indexing, links never followed
- **Git status badges** on repository folders (optional, off by default)

**Views**
- **List or grid** (R3 or Ctrl+G), with 2D controller navigation, and a **details panel** with the item's info and preview
Expand Down
1 change: 0 additions & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ Os botões seguem a **posição física**, então um controle Nintendo não inve
- **Abas** (restauradas ao abrir, reabrir aba fechada, duplicar) e **dois painéis** lado a lado (L3 troca; copiar/mover/extrair para o outro painel)
- **Locais de rede** (unidades mapeadas e atalhos de rede), tipos de unidade à primeira vista (local, USB, óptica, rede), atualizados ao conectar ou remover um pendrive
- **Busca** por nome na pasta atual (ou nas pastas principais, a partir do Início), com ou sem subpastas e com filtros: os resultados aparecem enquanto são encontrados e dá para cancelar; sem índice, links nunca seguidos
- **Selos de status do Git** nas pastas de repositório (opcional, desligado por padrão)

**Exibição**
- **Lista ou grade** (R3 ou Ctrl+G), com navegação 2D pelo controle, e **painel de detalhes** com as informações e a prévia do item
Expand Down
1 change: 0 additions & 1 deletion docs/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ Menu → Configurações → **View** (or **R3**, pressing the right stick, or *

**Disk usage:** North on a folder or drive (or on empty space for the current folder) → **Analisar uso do disco** (analyze disk usage) reads the whole tree once in the background, showing the running total; **East** cancels at once. The result lists the subfolders and then the largest files, each from biggest to smallest, with size and share of the folder (e.g. "Jogos — 12 GB (45%)"). South on a folder drills into it without reading the disk again, East goes back up a level, South on a file opens its folder with the file focused, and **Abrir esta pasta** opens the level you're on. Same rules as folder size: junctions and links are never followed and unreadable folders are listed, so the totals match Explorer's "Size".

**Git status:** Menu → Configurações → **Status do Git** (off by default). In a folder inside a Git repository, the line above the list shows the branch and how many items changed ("GIT · ramo main · 3 itens com mudanças"), and items show "Git: modificado" (modified), "novo (não rastreado)" (untracked), "adicionado", "renomeado", "conflito" or, for folders, "com mudanças" (something inside changed). It is read-only (no Git operations), doesn't need Git installed and never runs anything configured in the repository; the status arrives after the list, so browsing never waits for it.

## Batch rename

Expand Down
1 change: 0 additions & 1 deletion docs/GUIDE.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ Menu → Configurações → **Exibição** (ou **R3**, apertando o analógico d

**Uso do disco:** Norte numa pasta ou unidade (ou num espaço vazio, para a pasta atual) → **Analisar uso do disco** lê a árvore inteira uma vez, em segundo plano, mostrando o total parcial; **Leste** cancela na hora. O resultado lista as subpastas e depois os maiores arquivos, cada grupo do maior para o menor, com o tamanho e a fatia da pasta (ex.: "Jogos — 12 GB (45%)"). Sul numa pasta desce nela sem ler o disco de novo, Leste sobe um nível, Sul num arquivo abre a pasta dele com o foco no arquivo, e **Abrir esta pasta** abre o nível atual. Mesmas regras do tamanho da pasta: junções e links nunca são seguidos e pastas sem acesso são listadas, então os totais batem com o "Tamanho" do Explorador.

**Status do Git:** Menu → Configurações → **Status do Git** (desligado por padrão). Numa pasta dentro de um repositório Git, a linha acima da lista mostra o ramo e quantos itens mudaram ("GIT · ramo main · 3 itens com mudanças"), e os itens mostram "Git: modificado", "novo (não rastreado)", "adicionado", "renomeado", "conflito" ou, em pastas, "com mudanças" (algo dentro mudou). É somente leitura (nenhuma operação do Git), não precisa do Git instalado e nunca executa nada configurado no repositório; o status chega depois da lista, então navegar nunca espera por ele.

## Renomear em lote

Expand Down
5 changes: 0 additions & 5 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -769,11 +769,6 @@ Automático: `DiskImageIntegrationTests` monta e desmonta uma ISO 9660 mínima g
- [ ] ISO montada pelo Explorador ("Montar"): o ControlFS oferece "Desmontar imagem…" nela.
- [ ] Com um arquivo da imagem aberto em outro programa, desmontar: a confirmação avisa; o resultado (desmonta ou erro explicado) é legível.

## Status do Git (#75) — não validado em hardware
- [ ] Repositório grande real (ex.: um clone do ControlFS): ligar em Configurações; a lista aparece na hora e as marcas chegam depois, sem travar a navegação.
- [ ] Marcas "Git: …" legíveis na lista e na grade a 3 m e em 1280×720; o Narrador lê o estado do Git do item.
- [ ] Pasta de repositório num pendrive exFAT ou criado por outro usuário: sem marcas e sem erro.

## Tema claro e cor de destaque (#37) — não validado em hardware
Automático: `ThemeContrastTests` (contraste de todo tema × destaque), `AppearanceJourneyTests`; capturas `7-light-folder`, `7b-light-menu`, `7c-light-keyboard`, `7d-light-accent-magenta`, `7e-dark-accent-amber`.
- [ ] Windows no modo claro com Tema: automático → o app abre claro; trocar o modo de apps do Windows com o app aberto muda o tema na hora (sem reiniciar).
Expand Down
3 changes: 1 addition & 2 deletions docs/ui-redesign.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ added line.
| Where | Pickers | Stays one press |
|---|---|---|
| Compactar | Formato, Compressão | Nome (keyboard), Compactar |
| Configurações | Exibição, Densidade da lista, Tema, Cor de destaque, Ordenar por, Confirmar com, Legendas, Fluidez | Ordem, Painel de detalhes, Itens ocultos, Status do Git, Tela cheia, Busca em subpastas, Recentes, Restaurar abas, Sugestões do teclado, Segundo plano, Mira por giroscópio (on/off or symmetric two-way toggles); Controle ativo, Teste de controles, Controles sem perfil, Atualizações (they open their own screen) |
| Configurações | Exibição, Densidade da lista, Tema, Cor de destaque, Ordenar por, Confirmar com, Legendas, Fluidez | Ordem, Painel de detalhes, Itens ocultos, Tela cheia, Busca em subpastas, Recentes, Restaurar abas, Sugestões do teclado, Segundo plano, Mira por giroscópio (on/off or symmetric two-way toggles); Controle ativo, Teste de controles, Controles sem perfil, Atualizações (they open their own screen) |
| Filtros da busca | Tamanho, Modificado | Tipos (checkboxes) |
| Renomear em lote | Modo, Converter para | Dígitos (1–6, a stepper), text fields |

Expand Down Expand Up @@ -324,7 +324,6 @@ form the group "Nesta pasta (<nome>)", whose heading shows even under the grid (
| Search: filters, Outras ações (Mostrar na pasta, Nova busca, Subpastas, Pastas puladas, Cancelar busca, Propriedades) | Y Filtros | `SearchFilterJourneyTests`, `SearchJourneyTests` |
| Properties with folder size on demand (cancel keeps partial, junctions not followed) | Y → Propriedades; List (C2) and grid (#177): details panel shows the real data of the focused item | `FolderSizeJourneyTests`, `FolderSizeIntegrationTests` |
| Disk usage analysis (#72): Y Ações on a folder/drive (or current folder) → Analisar uso do disco; ranked folders then files, drill down/up, open a file's folder, cancel with Back | `DiskUsageJourneyTests`, `FolderSizeIntegrationTests::Disk_usage_totals_match_…` |
| Git status (#75): Configurações → Status do Git (off by default); badge line "GIT · ramo …", row state "Git: modificado/novo…", read after the list | `GitStatusJourneyTests` |
| Open terminal here (#76): Y Ações → "Nesta pasta (<nome>)" → Abrir terminal aqui…; notice starts on Cancelar; optional Windows on-screen keyboard | `TerminalJourneyTests` (2) |
| Two panes (#56): left = active tab, right = own `PaneState`; active pane outlined in cyan with "ATIVO" title, the other dimmed with its path; L3/Tab or click switches without touching marks; tabs stay on the left; single pane on handhelds/narrow windows (setting kept); details panel hidden | Menu → "Dois painéis"; L3/Tab | `DualPaneJourneyTests` (2); Screens `2h-dual-pane`, `2i-dual-pane-right`; Manual "Dois painéis (#56)" |
| Copiar/Mover para o outro painel (quick tiles), Extrair para o outro painel (archives), with source/destination summary; unavailable with a reason when both panes show the same folder (#56) | Y Ações (two panes) | `DualPaneJourneyTests` |
Expand Down
1 change: 0 additions & 1 deletion src/ControlFS.App/Views/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ internal MainWindow(string? dataDirectory)
{
PdfRenderer = new Infrastructure.Media.Pdf.WindowsPdfRenderer(),
Terminal = new TerminalLauncher(),
Git = new Infrastructure.Git.GitStatusReader(),
MediaPlayer = new Infrastructure.Media.Playback.WindowsMediaPlayerFactory(),
DiskImages = new Infrastructure.Windows.DiskImages.VirtualDiskService(),
PlaybackPositions = new JsonPlaybackPositionStore(data),
Expand Down
3 changes: 0 additions & 3 deletions src/ControlFS.Application/AppController.Browser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,9 +650,6 @@ private List<MenuItem> SettingsItems()
if (inBrowser) Refresh(pane);
}, Detail: "Arquivos e pastas marcados como ocultos no Windows. A escolha fica salva e vale para todas as pastas.",
Icon: ActionIcon.Hidden, Section: view, KeepOpen: true, Placement: tile, ShortLabel: "Ocultos", Value: hidden),
new($"Status do Git: {OnOff(Settings.ShowGitStatus)}", ToggleGitStatus, Git is null ? "Indisponível nesta compilação." : null,
Detail: "Em pastas de repositórios Git: o ramo no topo e \"Git: modificado/novo\" nos itens. Somente leitura; não precisa do Git instalado.",
Icon: ActionIcon.Info, Section: view, KeepOpen: true),
new($"Tela cheia: {OnOff(Settings.FullScreen)}", ToggleFullScreen,
Detail: "Ocupa a tela inteira, sem a barra do Windows; fica salva para a próxima vez. Também F11 ou o botão ao lado de minimizar.",
Icon: Settings.FullScreen ? ActionIcon.ExitFullScreen : ActionIcon.FullScreen, Section: view, KeepOpen: true),
Expand Down
3 changes: 3 additions & 0 deletions src/ControlFS.Application/AppController.Controllers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public bool OnRawInput(InputDeviceInfo device, RawInputEvent input)
return true;
}
if (ProfileFor(device) is not null) return false;
// Controle Xbox que aparece como joystick cru (segunda instância, receptor, driver genérico): nunca pede configuração.
// O controle de verdade já comanda o app como gamepad; o assistente é só para quem não é reconhecido.
if (ControllerFamilies.IsXboxLike(device.VendorId, device.Name)) return true;

// Sem perfil: segurar um botão do próprio joystick abre o assistente (ele ainda não navega).
if (input.Kind == RawInputKind.Button && input.Value >= 0.5)
Expand Down
9 changes: 9 additions & 0 deletions src/ControlFS.Core/Input/ControllerFamily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ public static ControllerFamily Detect(bool isGamepad, string? sdlGamepadType, us
};
}

/// <summary>
/// O dispositivo é um controle Xbox (fabricante Microsoft, ou "Xbox"/"XInput" no nome)? Só para não incomodar: um Xbox que
/// aparece também como joystick cru (segunda instância do mesmo controle, receptor, driver genérico) nunca deve pedir
/// "configure seu controle". Não decide legendas (isso segue <see cref="Detect"/>, que nunca usa o nome).
/// </summary>
public static bool IsXboxLike(ushort vendorId, string? name) =>
vendorId == MicrosoftVendor
|| (name is not null && (name.Contains("xbox", StringComparison.OrdinalIgnoreCase) || name.Contains("xinput", StringComparison.OrdinalIgnoreCase)));

/// <summary>Família usada nas legendas: a escolha manual vence; no automático, a do controle ativo.</summary>
public static ControllerFamily Resolve(ButtonLabelStyle style, ControllerFamily? active) => style switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ private void MapRequiredAndSkipOptionals(AppController app)
while (app.MappingWizard!.Wizard.Phase != MappingPhase.Review) Button(app, 1);
}

[Fact]
public void An_xbox_controller_seen_as_a_raw_joystick_never_asks_to_be_configured() => UiContext.Run(async () =>
{
var xbox = new InputDeviceInfo("sdl:9", "Controller (XBOX 360 For Windows)", "030000005e0400008e02000000007200", 0x045E, 0x028E,
IsGamepad: false, IsVirtual: false, TypeName: "joystick sem perfil", Path: null);
var app = NewApp(new JsonControllerProfileStore(_tmp.MakeDir("data")));
Assert.True(app.OnRawInput(xbox, RawInputEvent.Button(0, true))); // consumido, em silêncio
Wait(app, 5); // bem mais que o tempo de "segurar para configurar"
Assert.Null(app.MappingWizard);
Assert.DoesNotContain("sem perfil", app.StatusMessage ?? string.Empty, StringComparison.Ordinal);

Assert.True(ControlFS.Core.Input.ControllerFamilies.IsXboxLike(0x045E, "qualquer"));
Assert.True(ControlFS.Core.Input.ControllerFamilies.IsXboxLike(0x2563, "Xbox Wireless Receiver"));
Assert.False(ControlFS.Core.Input.ControllerFamilies.IsXboxLike(0x0079, "Generic USB Joystick")); // o genérico segue pedindo
await Task.Yield();
});

[Fact]
public void Generic_joystick_is_mapped_by_itself_saved_and_used_after_restart() => UiContext.Run(() =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ public void Badges_show_modified_untracked_and_changed_folders_only_after_turnin
await d.Idle();
Assert.Null(app.GitSummary); // desligado por padrão: nada é lido

d.Press(InputAction.OpenAppMenu);
await d.ChooseMenu("Status do Git");
app.ToggleGitStatus(); // sem entrada nas Configurações (removida): o leitor segue coberto pelo motor
await UiContext.WaitUntil(() => app.GitSummary is not null, "status do Git");
Assert.StartsWith("GIT · ramo ", app.GitSummary, StringComparison.Ordinal);
string? State(string name) => app.GitState(app.Browser.List.Items.Single(i => i.Name == name));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,21 +292,17 @@ public void Settings_grids_per_section_navigate_in_two_dimensions_with_the_lists
d.Press(InputAction.NavigateDown);
Assert.StartsWith("Ordem:", Focused(), StringComparison.Ordinal);
d.Press(InputAction.NavigateDown); // última linha da grade: a lista do mesmo grupo
Assert.StartsWith("Status do Git:", Focused(), StringComparison.Ordinal);
Assert.StartsWith("Tela cheia:", Focused(), StringComparison.Ordinal); // #230: linha da lista do grupo
d.Press(InputAction.NavigateUp); // volta à coluna de onde saiu
Assert.StartsWith("Ordem:", Focused(), StringComparison.Ordinal);
d.Press(InputAction.NavigateDown);
d.Press(InputAction.NavigateDown);
Assert.StartsWith("Tela cheia:", Focused(), StringComparison.Ordinal); // #230: outra linha da lista do grupo
d.Press(InputAction.NavigateDown); // da lista para a grade do grupo seguinte: primeiro bloco
Assert.StartsWith("Busca em subpastas:", Focused(), StringComparison.Ordinal);
app.TakeAnnouncement();
d.Press(InputAction.NavigateRight); // Recentes: rótulo por extenso (com o valor) e a posição no bloco
Assert.Matches(@"^Recentes: (ligado|desligado), .*bloco 2 de 4$", app.TakeAnnouncement());
d.Press(InputAction.NavigateUp); // primeira linha: o item de lista acima
Assert.StartsWith("Tela cheia:", Focused(), StringComparison.Ordinal);
d.Press(InputAction.NavigateUp);
Assert.StartsWith("Status do Git:", Focused(), StringComparison.Ordinal);
d.Press(InputAction.NavigateUp); // grade de cima, última linha, coluna 1
Assert.StartsWith("Ordenar por:", Focused(), StringComparison.Ordinal);
d.Press(InputAction.NavigateRight);
Expand Down
Loading