diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 6c22e89e..97ca1132 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -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`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bcc4781..8a2c0019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/README.md b/README.md index e6450c07..89c9fca6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.pt-BR.md b/README.pt-BR.md index 94e425b9..d3d3daf9 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -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 diff --git a/docs/GUIDE.md b/docs/GUIDE.md index f0d6b6cc..7da5ecfb 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -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 diff --git a/docs/GUIDE.pt-BR.md b/docs/GUIDE.pt-BR.md index 218cadbf..32ec010d 100644 --- a/docs/GUIDE.pt-BR.md +++ b/docs/GUIDE.pt-BR.md @@ -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 diff --git a/docs/TESTING.md b/docs/TESTING.md index 836c6eaf..a578ae2a 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -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). diff --git a/docs/ui-redesign.md b/docs/ui-redesign.md index 944995f8..a5897f38 100644 --- a/docs/ui-redesign.md +++ b/docs/ui-redesign.md @@ -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 | @@ -324,7 +324,6 @@ form the group "Nesta pasta ()", 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 ()" → 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` | diff --git a/src/ControlFS.App/Views/MainWindow.cs b/src/ControlFS.App/Views/MainWindow.cs index 20e9e79b..afd91cf2 100644 --- a/src/ControlFS.App/Views/MainWindow.cs +++ b/src/ControlFS.App/Views/MainWindow.cs @@ -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), diff --git a/src/ControlFS.Application/AppController.Browser.cs b/src/ControlFS.Application/AppController.Browser.cs index 0bcb4707..da717f74 100644 --- a/src/ControlFS.Application/AppController.Browser.cs +++ b/src/ControlFS.Application/AppController.Browser.cs @@ -650,9 +650,6 @@ private List 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), diff --git a/src/ControlFS.Application/AppController.Controllers.cs b/src/ControlFS.Application/AppController.Controllers.cs index cd3159b9..547d08e9 100644 --- a/src/ControlFS.Application/AppController.Controllers.cs +++ b/src/ControlFS.Application/AppController.Controllers.cs @@ -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) diff --git a/src/ControlFS.Core/Input/ControllerFamily.cs b/src/ControlFS.Core/Input/ControllerFamily.cs index a8c773c2..15998da4 100644 --- a/src/ControlFS.Core/Input/ControllerFamily.cs +++ b/src/ControlFS.Core/Input/ControllerFamily.cs @@ -40,6 +40,15 @@ public static ControllerFamily Detect(bool isGamepad, string? sdlGamepadType, us }; } + /// + /// 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 , que nunca usa o nome). + /// + public static bool IsXboxLike(ushort vendorId, string? name) => + vendorId == MicrosoftVendor + || (name is not null && (name.Contains("xbox", StringComparison.OrdinalIgnoreCase) || name.Contains("xinput", StringComparison.OrdinalIgnoreCase))); + /// Família usada nas legendas: a escolha manual vence; no automático, a do controle ativo. public static ControllerFamily Resolve(ButtonLabelStyle style, ControllerFamily? active) => style switch { diff --git a/tests/ControlFS.UnitTests/Application/ControllerMappingJourneyTests.cs b/tests/ControlFS.UnitTests/Application/ControllerMappingJourneyTests.cs index c211ba37..6f255868 100644 --- a/tests/ControlFS.UnitTests/Application/ControllerMappingJourneyTests.cs +++ b/tests/ControlFS.UnitTests/Application/ControllerMappingJourneyTests.cs @@ -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(() => { diff --git a/tests/ControlFS.UnitTests/Application/GitStatusJourneyTests.cs b/tests/ControlFS.UnitTests/Application/GitStatusJourneyTests.cs index 8b35eb34..a2b7e5c2 100644 --- a/tests/ControlFS.UnitTests/Application/GitStatusJourneyTests.cs +++ b/tests/ControlFS.UnitTests/Application/GitStatusJourneyTests.cs @@ -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)); diff --git a/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs b/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs index bb6f5726..91c88311 100644 --- a/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs +++ b/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs @@ -292,12 +292,10 @@ 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(); @@ -305,8 +303,6 @@ public void Settings_grids_per_section_navigate_in_two_dimensions_with_the_lists 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);