mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-27 19:54:45 +00:00
docs: what to do when neither keycode route reports a dead button (#853)
The page and config.xml send a reader two ways to find a dead remote button: the InputReader:D logcat filter while the button is pressed, and the getevent Linux scancode into settings_key_scancodes. On a Google TV 14 PEAQ "AI PONT DE" (#853) both came back empty for the same button, so it read as never delivered: InputReader logged no key line at all, and the MSC_SCAN getevent printed (0x000c009f) is an evdev code, not the scanCode the framework reports, so both spellings of it (159, 786591) matched nothing. A temporary Log.d at the top of the helper's onKeyEvent named the delivered values (keyCode=83 scanCode=240) and settings_key_keycodes = [83] then opened the TV's Settings. So the note gains the route that works, the reason the other two can fail, and the fact that a getevent code matching nothing is not proof the press never arrived. No separate device note: the finding is about finding the number, not about the set, and it belongs where a reader looks for a keycode. The same two caveats go into the settings_key_scancodes and settings_key_keycodes comments a customiser reads before the page. PROMPT.md carries the same half-truth ("read the scancode with getevent -lt") and is left for the maintainer.
This commit is contained in:
1 parent
c2e4600601
commit
355fb43c65
3 files changed
+175
-2
No files matched your search
@@ -447,6 +447,21 @@ tools/tv.py 'dumpsys accessibility'
|
||||
`tools/tv.py 'getevent -lt'` in `settings_key_scancodes` and install with `HOME_KEY_SERVICE=1`
|
||||
(an RC-36 remote sends its gear on scancode 240 and its notifications button on 362 that way; see
|
||||
[the FPD CG50-C3 note](devices/fpd-cg50-c3.md)).
|
||||
Not every set answers those two routes, though ([#853](https://github.com/Gathin23/cleanyourtv/issues/853),
|
||||
a PEAQ "AI PONT DE", `DVB99PQ` / Changhong `changi`, Google TV 14, `UKNV.260504.001`): a HID
|
||||
consumer-control usage prints an evdev `MSC_SCAN` that is not necessarily the `KeyEvent.getScanCode()`
|
||||
the framework reports, so a value read with `getevent -lt` can match nothing in
|
||||
`settings_key_scancodes` (that set's gear printed `MSC_SCAN 000c009f`, and neither 159 nor 786591, the
|
||||
two spellings of it, did anything), and `logcat -v time -s InputReader:D`, the filter `config.xml`
|
||||
suggests, printed no key line at all there, for any button, only `Reconfiguring input devices`. What
|
||||
Android really delivers comes from a temporary `Log.d` at the top of the helper's `onKeyEvent` printing
|
||||
`getKeyCode()`, `getScanCode()` and `getDeviceId()`, rebuilt with `HOME_KEY_SERVICE=1` and read with
|
||||
`tools/tv.py "logcat -s TvHomeKeys:D"` while pressing the real button: that set reported
|
||||
`action=0 keyCode=83 scanCode=240 deviceId=9`, and `settings_key_keycodes = [83]` opened the TV's
|
||||
Settings at once. So a `getevent` code that matches nothing is not proof the press never arrived, and
|
||||
consuming the key in the helper also keeps it from the vendor receiver
|
||||
`com.homwee.factory/.FactoryKeyReciver`, which `dumpsys window policy` routes `KEYCODE_NOTIFICATION`
|
||||
to once the stock launcher is gone.
|
||||
- **All vendor buttons die at once** and `logcat` says `GLOBAL_BUTTON ... process is bad`: the
|
||||
global-key receiver crashes on the disabled launcherx and Android stops starting it. Map every such
|
||||
keycode in the helpers, also for disabled apps. See [the MOSR4 note](devices/xiaomi-mitv-mosr4.md).
|
||||
|
||||
@@ -112,13 +112,18 @@
|
||||
<!-- Remote buttons that open Settings, by Linux scancode (HOME_KEY_SERVICE=1 build-and-install.sh). Remotes
|
||||
that send KEYCODE_SETTINGS need no entry. Some send a code Android does not map: the Xiaomi/Fengmi
|
||||
projector remote's gear button is scancode 384 (KEY_TAPE). Find yours with
|
||||
`tools/tv.py 'getevent -lt'` while pressing the button. Leave empty to omit. -->
|
||||
`tools/tv.py 'getevent -lt'` while pressing the button - but a HID consumer-control usage prints an
|
||||
evdev MSC_SCAN there, which is not always the scanCode the framework reports, so a number that takes
|
||||
nothing is no proof the press never arrived. Leave empty to omit. -->
|
||||
<integer-array name="settings_key_scancodes">
|
||||
</integer-array>
|
||||
<!-- The same by Android keycode, for buttons whose scancode is shared. TCL Google TV remotes send their
|
||||
Settings button as keycode 83 (KEYCODE_NOTIFICATION, which the stock Google TV home turned into its quick
|
||||
settings panel) on scancode 240 (KEY_UNKNOWN), the scancode several TCL buttons share. Find the keycode
|
||||
with `tools/tv.py 'logcat -v time -s InputReader:D'` while pressing the button. Leave empty to omit. -->
|
||||
with `tools/tv.py 'logcat -v time -s InputReader:D'` while pressing the button - some firmwares print no
|
||||
key line there at any verbosity, and a `getevent` MSC_SCAN is a different number, so the reliable route is
|
||||
a temporary `Log.d` in the helper's `onKeyEvent`: `getKeyCode()`, `getScanCode()`, `getDeviceId()`, read
|
||||
after a HOME_KEY_SERVICE=1 rebuild. Leave empty to omit. -->
|
||||
<integer-array name="settings_key_keycodes">
|
||||
</integer-array>
|
||||
<!-- What those Settings buttons open: false, the TV's Settings; true, a small glass panel at the top right
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
"""Finding a dead remote button's keycode when the documented routes come back empty (#853).
|
||||
|
||||
The page and `config.xml` used to send a reader two ways: `logcat -v time -s InputReader:D` while
|
||||
pressing the button, and, for a press that arrives as `KEY_UNKNOWN`, the Linux scancode from
|
||||
`getevent -lt` into `settings_key_scancodes`. On a Google TV 14 PEAQ "AI PONT DE" both failed for the
|
||||
same button - `InputReader` logged no key line at all, and the `MSC_SCAN` `getevent` printed
|
||||
(`0x000c009f`) is not the scanCode the framework reports - so the button read as "never delivered".
|
||||
A temporary `Log.d` at the top of the helper's `onKeyEvent` is what named the delivered values
|
||||
(`keyCode=83 scanCode=240`), and the keycode then worked.
|
||||
|
||||
Documentation only, so these tests pin what a reader has to be able to act on: that the logcat route
|
||||
can come back empty, that a `getevent` number is not the framework's scanCode, the recipe that does
|
||||
work, the evidence that it does, and that the same two caveats reach the `config.xml` comments a
|
||||
customiser reads before the page. The command names and the `Log.d` calls are asserted verbatim
|
||||
because a misspelled one is a reader reporting "no output" again.
|
||||
|
||||
Run from the repository root: python3 -m unittest discover -s tests -v
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TROUBLE = ROOT / "docs" / "TROUBLESHOOTING.md"
|
||||
CONFIG_XML = ROOT / "launcher" / "app" / "src" / "main" / "res" / "values" / "config.xml"
|
||||
START = "- **Which keycode** a dead button sends"
|
||||
LINK = re.compile(r"\]\(([^)\s]+)\)")
|
||||
|
||||
|
||||
def bullet(text, start):
|
||||
"""The markdown bullet that begins with `start`, plus the continuation lines under it."""
|
||||
lines = text.split("\n")
|
||||
first = next(i for i, line in enumerate(lines) if line.startswith(start))
|
||||
out = [lines[first]]
|
||||
for line in lines[first + 1:]:
|
||||
if line.startswith("- **") or line.startswith("## "):
|
||||
break
|
||||
out.append(line)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def comment_before(xml_text, array_name):
|
||||
"""The `<!-- ... -->` immediately above `<integer-array name="array_name">`.
|
||||
|
||||
Searched in the text before the tag, not with one greedy pattern: several comments above it end
|
||||
with `-->` too, and the launcher config opens with a file-header comment.
|
||||
"""
|
||||
marker = '<integer-array name="%s">' % array_name
|
||||
if marker not in xml_text:
|
||||
return ""
|
||||
comments = re.findall(r"<!--(.*?)-->", xml_text.split(marker, 1)[0], re.S)
|
||||
return comments[-1] if comments else ""
|
||||
|
||||
|
||||
class KeycodeDiscoveryNoteTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.trouble_text = TROUBLE.read_text(encoding="utf-8")
|
||||
cls.config_text = CONFIG_XML.read_text(encoding="utf-8")
|
||||
cls.note = bullet(cls.trouble_text, START)
|
||||
cls.keycodes_comment = comment_before(cls.config_text, "settings_key_keycodes")
|
||||
cls.scancodes_comment = comment_before(cls.config_text, "settings_key_scancodes")
|
||||
|
||||
def test_bullet_exists_once(self):
|
||||
self.assertEqual(self.trouble_text.count(START), 1,
|
||||
"the note must live in one bullet so the two routes cannot drift apart")
|
||||
|
||||
def test_bullet_says_the_logcat_route_can_be_empty(self):
|
||||
# The whole point of the report: following the page on this firmware printed nothing at all,
|
||||
# which reads as "the button is dead" instead of "this filter sees nothing here".
|
||||
self.assertIn("InputReader:D", self.note)
|
||||
self.assertIn("no key line", self.note)
|
||||
|
||||
def test_bullet_separates_getevent_from_the_framework_scancode(self):
|
||||
self.assertIn("MSC_SCAN", self.note)
|
||||
self.assertIn("evdev", self.note)
|
||||
self.assertIn("getScanCode()", self.note)
|
||||
self.assertIn("settings_key_scancodes", self.note)
|
||||
|
||||
def test_bullet_gives_the_logd_recipe_verbatim(self):
|
||||
self.assertIn("Log.d", self.note)
|
||||
self.assertIn("onKeyEvent", self.note)
|
||||
for call in ("getKeyCode()", "getScanCode()", "getDeviceId()"):
|
||||
self.assertIn(call, self.note)
|
||||
self.assertIn("TvHomeKeys", self.note)
|
||||
self.assertIn("logcat -s TvHomeKeys:D", self.note)
|
||||
self.assertIn("HOME_KEY_SERVICE=1", self.note)
|
||||
|
||||
def test_bullet_keeps_the_evidence(self):
|
||||
# A reader needs the worked example to recognise their own dump, and the two spellings that
|
||||
# were tried and did nothing, so the getevent route is not retried as if it were untested.
|
||||
self.assertIn("MSC_SCAN 000c009f", self.note)
|
||||
self.assertIn("159", self.note)
|
||||
self.assertIn("786591", self.note)
|
||||
self.assertIn("keyCode=83 scanCode=240", self.note)
|
||||
self.assertIn("settings_key_keycodes = [83]", self.note)
|
||||
|
||||
def test_bullet_names_the_device_and_build(self):
|
||||
self.assertIn("DVB99PQ", self.note)
|
||||
self.assertIn("changi", self.note)
|
||||
self.assertIn("UKNV.260504.001", self.note)
|
||||
|
||||
def test_bullet_says_a_mismatch_is_not_proof(self):
|
||||
# Without this sentence the old advice still reads as authoritative: no match, dead button.
|
||||
self.assertIn("not proof the press never arrived", self.note)
|
||||
|
||||
def test_bullet_keeps_the_vendor_receiver_fact(self):
|
||||
self.assertIn("com.homwee.factory/.FactoryKeyReciver", self.note)
|
||||
self.assertIn("dumpsys window policy", self.note)
|
||||
|
||||
def test_bullet_links_the_issue(self):
|
||||
self.assertIn("[#853](https://github.com/Gathin23/cleanyourtv/issues/853)", self.note)
|
||||
|
||||
def test_config_xml_keycodes_comment_points_at_the_logd_route(self):
|
||||
self.assertIn("InputReader:D", self.keycodes_comment)
|
||||
self.assertIn("Log.d", self.keycodes_comment)
|
||||
self.assertIn("onKeyEvent", self.keycodes_comment)
|
||||
|
||||
def test_config_xml_scancodes_comment_warns_about_msc_scan(self):
|
||||
self.assertIn("MSC_SCAN", self.scancodes_comment)
|
||||
self.assertIn("scanCode", self.scancodes_comment)
|
||||
|
||||
def test_comment_before_finds_the_comment_it_was_asked_for(self):
|
||||
# The two comments are adjacent and nearly identical in shape; a helper that returned the
|
||||
# first one it saw would make both comment tests pass on the wrong text.
|
||||
self.assertIn("keycode 83", self.keycodes_comment)
|
||||
self.assertNotIn("KEY_TAPE", self.keycodes_comment)
|
||||
self.assertIn("KEY_TAPE", self.scancodes_comment)
|
||||
self.assertNotIn("keycode 83", self.scancodes_comment)
|
||||
self.assertEqual(comment_before(self.config_text, "no_such_array"), "")
|
||||
|
||||
def test_bullet_extraction_stops_at_the_next_bullet(self):
|
||||
sample = "\n".join([START + " one", " two", "- **Next** three"])
|
||||
self.assertEqual(bullet(sample, START), START + " one\n two")
|
||||
|
||||
def test_bullet_links_resolve(self):
|
||||
broken = [target for target in LINK.findall(self.note)
|
||||
if not target.startswith(("http://", "https://", "#"))
|
||||
and not (TROUBLE.parent / target.split("#", 1)[0]).exists()]
|
||||
self.assertEqual(broken, [], "the bullet must not link a page that does not exist")
|
||||
|
||||
def test_config_xml_is_still_well_formed(self):
|
||||
# The change is a comment, but a comment edit that breaks the XML breaks the launcher build.
|
||||
root = ET.fromstring(self.config_text)
|
||||
self.assertEqual(root.tag, "resources")
|
||||
for name in ("settings_key_scancodes", "settings_key_keycodes"):
|
||||
self.assertIsNotNone(root.find("./integer-array[@name='%s']" % name))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user