Skip to content

chore: land the fork identity and project modernization on master - #5

Merged
WigoWigo10 merged 14 commits into
masterfrom
refactor/modernize-project-layout
Aug 1, 2026
Merged

chore: land the fork identity and project modernization on master#5
WigoWigo10 merged 14 commits into
masterfrom
refactor/modernize-project-layout

Conversation

@WigoWigo10

Copy link
Copy Markdown
Owner

PRs #3 and #4 were merged into their intermediate branches rather than into
master, which is the expected behaviour for a stacked series merged top-down.
The result is that master currently carries only the four fixes from #2 and
has neither pyproject.toml nor the src/ layout.

This PR brings the tip of the stack straight to master. The four commits from
#2 are common ancestors, so the diff is exactly the 14 commits that never
landed, with no duplication.

What lands here

Fork identity (was #3)

  • The README still carried the upstream "this project is currently
    unmaintained" banner, and the module docstring — which setup.py publishes as
    the PyPI description — still told readers to pip install keyboard.
  • Packaging metadata moved to pyproject.toml, 1.0.0 changelog entry, fork
    copyright alongside the original, CI, and the interactive scripts moved out
    of the repository root into tests/manual/.
  • Scaffolding comments and dead code removed from the Windows backend.

Modernization (was #4)

  • src/ layout, so the test run exercises the installed distribution.
  • Python 3.9+; all the Python 2 compatibility shims removed.
  • Ruff (lint + format), pre-commit, EditorConfig, Dependabot.
  • CI runs lint, the suite on Windows and Linux across 3.9/3.11/3.13, and a
    build + twine check.

Verification

  • Wheel built and installed into a clean venv: twine check passes and the
    fork's APIs work from the installed package.
  • The formatter commit was checked by comparing the AST of every touched file
    before and after: 17 byte-identical, 8 differing only in docstring
    whitespace, 0 structural changes.
  • 161 tests pass. The suite went from 157 to 161 because test_keyboard.py
    defined four tests twice under the same name, so the first of each pair was
    silently discarded and never ran.

Known issues, not addressed here

  • _darwinmouse.py calls four names that do not exist in the module, so mouse
    listening on macOS raises NameError. Inherited from upstream; fixing it
    needs a macOS machine to verify against.
  • test_record is timing-dependent and flaky. The margin was widened and the
    comment says it is a mitigation, not a fix.
  • CI has never actually run. All three jobs were reproduced locally, but this
    is the first time they execute on GitHub Actions.

🤖 Generated with Claude Code

WigoWigo10 and others added 14 commits July 31, 2026 22:26
setup.py publishes `directkeys.__doc__` as the PyPI long_description, and the
Makefile regenerates README.md from that same docstring — so the docstring was
the single source still advertising the upstream project. It told readers to
`pip install keyboard`, pointed every link at boppreh/keyboard, and the README
carried the upstream "this project is currently unmaintained" banner, which is
the opposite of this fork's premise.

- Retitle to directkeys and state up front that this is a maintained fork.
- Point install/clone/API links at this repository.
- Add a "New in this fork" section and a migration note for `import keyboard`.
- Drop the "Python 2 and 3" claim, which contradicts the 3.8+ classifiers.
- Rewrite the generated [source] links to this repo and the directkeys package.

References to boppreh/mouse and to upstream issues boppreh#20/boppreh#21/boppreh#22 are left alone:
they point at a sibling project and at real upstream discussions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ackend

The backend still carried editing instructions left over from when the AltGr
support was pasted in ("COLE ESTAS DUAS FUNCOES DEPOIS DE...", "O resto do
arquivo continua aqui...", "Esta funcao voce ja deve ter no final do
arquivo"), which read as documentation to anyone else opening the file.

Alongside that:

- Remove `_remove_alt_gr_mapping`, which was never called.
- Remove `shift_is_pressed` and `shift_vks`: the global was still being
  assigned on every shift event but nothing read it, since `get_modifiers`
  queries GetKeyState directly. Same for `ignore_next_right_alt`, only ever
  reset and never read.
- Guard the `to_name` mutation in `get_name` with `tables_lock`, matching every
  other access to the tables. This also serialises the shared ctypes buffers
  used by `get_event_names`.
- Fold the duplicated modifier vk lists in `force_reset_keyboard` and
  `get_stuck_keys` into a single `_modifier_vk_names` mapping.
- Restore the two upstream comments explaining the `fake_alt` / VK_PACKET
  guard, and move the `__main__` block back to the end of the file.
- Translate the new comments and docstrings to English, matching the rest of
  the codebase and the published API docs.

No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`set_alt_gr_abstraction`, `get_alt_gr_abstraction_state` and the fallback
helpers are part of the published API: docstring2markdown renders them into
README.md and setup.py ships the module docstring as the PyPI description, so
they were the only Portuguese text in the user-facing documentation.

Also correct the feature list: `KeyboardEvent.flags` is masked down to the
LLKHF_EXTENDED bit on Windows, so describing it as the raw hook flags
overstated what the attribute actually carries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
setup.py imported the package at build time to read the version and the
docstring, which meant a build could fail before install_requires had a chance
to provide pyobjc on macOS. Move all metadata to pyproject.toml and reduce
setup.py to a shim so legacy invocations keep working.

- Read the version statically via `[tool.setuptools.dynamic]`, keeping
  `directkeys.version` as the single source of truth without importing it.
- Take long_description from README.md, which the Makefile already generates
  from the module docstring.
- Declare requires-python >=3.8 to match the classifiers, add 3.13, and use the
  more specific "POSIX :: Linux" operating system classifier.
- Point the Makefile at `python -m build`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CHANGES.md still started at 0.13.5, so the release that renamed the package
and added the AltGr, stuck key and flags features had no entry at all.

LICENSE.txt keeps the original BoppreH copyright, as the MIT terms require,
with the fork's copyright added alongside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
run_test.py and run_test_2a_crash.py sat at the repository root, where they
read like the project's test entry point despite being interactive scripts
that block on a human pressing keys on a physical keyboard.

Neither keeps a `test_` prefix, so pytest cannot collect them: as test
functions their `read_event()` calls would hang the suite waiting for input.

The helper script is now resolved relative to __file__ rather than the current
working directory, and a module docstring states what the scripts need to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repository had no CI at all, which is an awkward gap for a fork whose
pitch is that it is the maintained one. Adds a workflow that runs the suite on
Windows and Linux across the Python versions declared in requires-python, plus
a job that builds the distribution and runs `twine check` on it.

tests/manual is excluded: those scripts need a physical keyboard and a human.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With the package at the repository root, `import directkeys` from the checkout
directory picks up the source tree rather than the installed distribution.
That hides packaging mistakes: a module missing from the wheel still imports
fine locally and only fails for users. Moving to src/ makes the test run
exercise the installed package.

Also folds .coveragerc into pyproject.toml and configures pytest there, so
`testpaths` and the tests/manual exclusion no longer have to be repeated on
every invocation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Python 3.8 reached end of life in October 2024 and no longer receives security
fixes, so requires-python moves to 3.9 and the CI matrix follows (3.9, 3.11,
3.13 on Windows and Linux). This also removes the need to pin an older Ubuntu
runner, since 3.8 is the version ubuntu-latest stopped shipping.

With Python 2 long out of scope, the compatibility layer was dead weight that
made every module harder to read:

- Remove the `basestring` / `unichr` / `long` fallbacks and the
  `from __future__` imports, using `str`, `chr` and `int` directly.
- Remove the `time.monotonic` fallback, present on every supported version.
- Replace the Python2 `Queue` / `threading._Event` branch with a plain import.
- Turn the `_is_str` / `_is_number` / `_is_list` lambdas into functions and drop
  the redundant `(object)` base classes and UTF-8 coding cookies.
- Restore two upstream comments explaining `_State` and the `_Event.wait`
  override, lost when the compatibility block was edited.

Also introduces `__version__` as the canonical attribute, keeping `version` as
an alias so code reading `keyboard.version` keeps working. The setuptools
dynamic version now points at `__version__`, which is a literal and can be read
statically; the alias is not, and would have forced an import at build time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds Ruff (lint + import sorting) configured in pyproject.toml, targeting
py39. Rules that the codebase deliberately breaks are ignored explicitly, each
with the reason, rather than left to fail: long lines in the ctypes tables,
imports that must follow the platform dispatch, and the Cyrillic/Greek key
names in _canonical_names.py that RUF001 reads as ambiguous characters.

The autofixable findings are applied. Two are worth calling out:

- tests/test_keyboard.py defined four tests twice under the same name, so the
  first definition of each was silently discarded and never ran. They are not
  duplicates, they are distinct cases: a `write` without delay, a hotkey
  removal without modifiers, a two-modifier release, and a multistep blocking
  state assertion. Renamed so both run; the suite goes from 157 to 161 tests
  and all four recovered cases pass.
- `index = 0` in the suppression path assigned to a local that nothing read,
  right before `set_index(0)` did the real work.

A group of upstream findings is ignored rather than fixed, because each would
change runtime behaviour and none is covered by a test: repeated dictionary
keys (F601), mutable argument defaults (B006), bare except (E722) and
`raise` without `from` (B904).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mechanical reformat of every Python file, in its own commit so the substantive
changes elsewhere stay reviewable.

Two settings needed care:

- `docstring-code-format` is left off. The __init__ docstring is published
  verbatim as the PyPI description and regenerated into README.md, so the
  formatter must not rewrite the example code inside it.
- Markdown is excluded. Ruff reformats fenced Python blocks in .md files, and
  since README.md is generated from that same docstring, the formatter and the
  generator would each keep undoing the other.

Verified by comparing the AST of every touched file before and after: 17 are
byte-identical, and the remaining 8 differ only in whitespace inside string
literals, where Ruff normalises `""" Text. """` to `"""Text."""`. No
structural change in any file. README.md is regenerated so it stays in sync
with the docstring after that normalisation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rounds out the tooling so the checks that CI runs can also run locally before
a commit, and so contributors get consistent whitespace regardless of editor.

- .pre-commit-config.yaml runs Ruff (check + format) plus the standard
  whitespace and syntax hooks. README.md is excluded from the whitespace hooks
  because it is generated from the module docstring.
- The `mixed-line-ending` hook is deliberately left out: `.gitattributes`
  already normalises endings at the git level, and forcing LF in the working
  tree would fight the CRLF checkout on Windows in a loop.
- .editorconfig mirrors the same rules for editors.
- Dependabot keeps the Actions and pip pins current, monthly.
- A `lint` job runs Ruff in CI.

.gitattributes drops the Visual Studio and msysgit boilerplate inherited from
the original template, keeps the `text=auto` normalisation, and marks README.md
as generated so it stops inflating pull request diffs.

Verified by running `pre-commit run --all-files` twice: the second run is clean,
so the hooks are idempotent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the modernisation in CHANGES.md, including the Python 3.9 requirement,
the src/ layout and the tooling, alongside the fixes made earlier on the fork.

The module docstring gains a Development section covering the editable install,
pre-commit and the two Ruff checks CI runs, and states the supported Python
version where the removed "Python 2 and 3" line used to be. README.md is
regenerated from it.

Also widens the margin in test_record. The test races a background thread that
has to register both the recording hook and the suppressing hotkey before the
events are fed in, and upstream already noted the 0.01s sleep had lost that
race once; it lost it again here. There is no public barrier to synchronise on,
so this is a larger margin rather than a real fix, and the comment says so.
Ten consecutive full runs pass afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@WigoWigo10
WigoWigo10 merged commit d10deaf into master Aug 1, 2026
16 checks passed
@WigoWigo10
WigoWigo10 deleted the refactor/modernize-project-layout branch August 1, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant