Repository: koreader/koreader
Pull request #13795: reMarkable: Make sleep/waking up w/o launcher work and properly exit when KO_DONT_GRAB_INPUT is set
Author: login: NoaHimesaka1873
Labels: name: reMarkable
Created: 2025-05-15T14:05:12Z | Merged: 2025-05-16T07:49:47Z

For waking up w/o launcher, we now handle event code 143, properly fixing issue #8891.
For proper exit when KO_DONT_GRAB_INPUT is set, we use a workaround that relaunches xochitl.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/koreader/koreader/13795)
<!-- Reviewable:end -->



Diff:
diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua
index 4cd88ed293c6..a55c32ed6d58 100644
--- a/frontend/device/generic/powerd.lua
+++ b/frontend/device/generic/powerd.lua
@@ -353,4 +353,8 @@ function BasePowerD:getBatterySymbol(is_charged, is_charging, capacity)
     end
 end
 
+function BasePowerD:hasHallSensor()
+    return false
+end
+
 return BasePowerD
diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua
index 33a713010d7a..c0404eb96136 100644
--- a/frontend/device/remarkable/device.lua
+++ b/frontend/device/remarkable/device.lua
@@ -58,6 +58,7 @@ local Remarkable = Generic:extend{
     -- Despite the SoC supporting it, it's finicky in practice (#6772)
     canHWInvert = no,
     home_dir = "/home/root",
+    input_hall = nil,
 }
 
 local Remarkable1 = Remarkable:extend{
@@ -122,6 +123,7 @@ local RemarkablePaperPro = Remarkable:extend{
     input_wacom = "/dev/input/event2",
     input_ts = "/dev/input/event3",
     input_buttons = "/dev/input/event0",
+    input_hall = "/dev/input/event1",
     battery_path = "/sys/class/power_supply/max1726x_battery/capacity",
     status_path = "/sys/class/power_supply/max1726x_battery/status",
     canSuspend = no, -- Suspend and Standby should be handled by xochitl with KO_DONT_GRAB_INPUT=1 set, otherwise bad things will happen
@@ -186,17 +188,28 @@ function Remarkable:init()
         device = self,
         capacity_file = self.battery_path,
         status_file = self.status_path,
+        hall_file = isRmPaperPro and "/sys/class/input/input1/inhibited" or nil,
     }
 
     local event_map = dofile("frontend/device/remarkable/event_map.lua")
     -- If we are launched while Oxide is running, remove Power from the event map
     if oxide_running then
         event_map[116] = nil
+        event_map[143] = nil
     end
 
     self.input = require("device/input"):new{
         device = self,
         event_map = dofile("frontend/device/remarkable/event_map.lua"),
+        event_map_adapter = {
+            SleepCover = function(ev)
+                if ev.value == 1 then
+                    return "Suspend"
+                else
+                    return "Resume"
+                end
+            end,
+        },
         wacom_protocol = true,
     }
 
@@ -225,6 +238,19 @@ function Remarkable:init()
     self.input:open(self.input_ts) -- Touchscreen
     self.input:open(self.input_buttons) -- Buttons
 
+    if self.input_hall ~= nil then
+        self.input:open(self.input_hall) -- Hall sensor
+        local hallSensorMangling = function(this, ev)
+            if ev.type == C.EV_SW then
+                if ev.code == 0 then
+                    ev.type = C.EV_KEY
+                    ev.code = 20001
+                end
+            end
+        end
+        self.input:registerEventAdjustHook(hallSensorMangling)
+    end
+
     local scalex = screen_width / self.mt_width
     local scaley = screen_height / self.mt_height
 
@@ -267,6 +293,18 @@ function Remarkable:init()
         PluginShare.pause_auto_suspend = true
     end
 
+    if isRmPaperPro then
+        -- disable autosuspend of xochitl
+        os.execute("cp -a ~/.config/remarkable/xochitl.conf ~/.config/remarkable/xochitl.conf.bak")
+        os.execute("sed -ri 's/IdleSuspendDelay=[0-9]+/IdleSuspendDelay=0/' ~/.config/remarkable/xochitl.conf")
+    end
+
+    if self.powerd:hasHallSensor() then
+        if G_reader_settings:has("remarkable_hall_effect_sensor_enabled") then
+            self.powerd:onToggleHallSensor(G_reader_settings:readSetting("remarkable_hall_effect_sensor_enabled"))
+        end
+    end
+
     Generic.init(self)
 end
 
@@ -303,6 +341,16 @@ function Remarkable:initNetworkManager(NetworkMgr)
     NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
 end
 
+function Remarkable:exit()
+    if isRmPaperPro then
+        os.execute("mv -f ~/.config/remarkable/xochitl.conf.bak ~/.config/remarkable/xochitl.conf")
+        if os.getenv("KO_DONT_GRAB_INPUT") == "1" then
+            os.execute("~/xovi/start")
+        end
+    end
+    Generic.exit(self)
+end
+
 function Remarkable:setDateTime(year, month, day, hour, min, sec)
     if hour == nil or min == nil then return true end
     local command
@@ -319,18 +367,33 @@ function Remarkable:saveSettings()
 end
 
 function Remarkable:resume()
+    if isRmPaperPro then
+        os.execute("csl wifi -p on")
+    else
+        os.execute("./enable-wifi.sh")
+    end
 end
 
 function Remarkable:suspend()
-    os.execute("./disable-wifi.sh")
+    if isRmPaperPro then
+        os.execute("csl wifi -p off")
+    else
+        os.execute("./disable-wifi.sh")
+    end
     os.execute("systemctl suspend")
 end
 
 function Remarkable:powerOff()
+    if isRmPaperPro then
+        os.execute("mv -f ~/.config/remarkable/xochitl.conf.bak ~/.config/remarkable/xochitl.conf")
+    end
     os.execute("systemctl poweroff")
 end
 
 function Remarkable:reboot()
+    if isRmPaperPro then
+        os.execute("mv -f ~/.config/remarkable/xochitl.conf.bak ~/.config/remarkable/xochitl.conf")
+    end
     os.execute("systemctl reboot")
 end
 
diff --git a/frontend/device/remarkable/event_map.lua b/frontend/device/remarkable/event_map.lua
index 2566f4e5f000..a44a287c15c3 100644
--- a/frontend/device/remarkable/event_map.lua
+++ b/frontend/device/remarkable/event_map.lua
@@ -1,7 +1,9 @@
 return {
+    [20001] = "SleepCover",
     [102] = "Home",
     [105] = "LPgBack",
     [106] = "RPgFwd",
     [116] = "Power",
+    [143] = "Resume",
 }
--- TODO libremarkable has 143 as "wakeup" - don't know what that corresponds to?
+-- 116 is issued when the device gets to sleep, 143 when it wakes up.
diff --git a/frontend/device/remarkable/powerd.lua b/frontend/device/remarkable/powerd.lua
index 36beb14f2ed5..c815d1dfeb45 100644
--- a/frontend/device/remarkable/powerd.lua
+++ b/frontend/device/remarkable/powerd.lua
@@ -74,6 +74,28 @@ function Remarkable_PowerD:isChargingHW()
     return self:read_str_file(self.status_file) == "Charging"
 end
 
+function Remarkable_PowerD:hasHallSensor()
+    return self.hall_file ~= nil
+end
+
+function Remarkable_PowerD:isHallSensorEnabled()
+    local int = self:read_int_file(self.hall_file)
+    return int == 0
+end
+
+function Remarkable_PowerD:onToggleHallSensor(toggle)
+    if toggle == nil then
+        -- Flip it
+        toggle = self:isHallSensorEnabled() and 1 or 0
+    else
+        -- Honor the requested state
+        toggle = toggle and 1 or 0
+    end
+    ffiUtil.writeToSysfs(toggle, self.hall_file)
+
+    G_reader_settings:saveSetting("remarkable_hall_effect_sensor_enabled", toggle == 0 and true or false)
+end
+
 function Remarkable_PowerD:beforeSuspend()
     -- Inhibit user input and emit the Suspend event.
     self.device:_beforeSuspend()
diff --git a/frontend/ui/elements/common_settings_menu_table.lua b/frontend/ui/elements/common_settings_menu_table.lua
index 7b4f0b7e2d7c..87b349b8043f 100644
--- a/frontend/ui/elements/common_settings_menu_table.lua
+++ b/frontend/ui/elements/common_settings_menu_table.lua
@@ -239,9 +239,9 @@ if Device:isKobo() then
     }
 end
 
-if Device:isKindle() and PowerD:hasHallSensor() then
+if PowerD:hasHallSensor() then
     common_settings.cover_events = {
-        text = _("Disable Kindle cover events"),
+        text = _("Disable cover events"),
         help_text = _([[Toggle the Hall effect sensor.
 This is used to detect if the cover is closed, which will automatically sleep and wake the device. If there is no cover present the sensor may cause spurious wakeups when located next to a magnetic source.]]),
         keep_menu_open = true,


Reviews:
user: login: Frenzie
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-15T14:07:39Z
user: login: NoaHimesaka1873
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T14:08:52Z
user: login: Frenzie
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T19:23:55Z
user: login: yparitcher
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T19:37:43Z
user: login: yparitcher
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T19:42:37Z
user: login: Frenzie
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T07:48:51Z

Review comments:
diff_hunk: @@ -3,5 +3,6 @@ return {
     [105] = "LPgBack",
     [106] = "RPgFwd",
     [116] = "Power",
+    [143] = "Resume",
 }
--- TODO libremarkable has 143 as "wakeup" - don't know what that corresponds to?
+-- 116 is issued when the device gets to sleep, 143 when it's waked up.
path: frontend/device/remarkable/event_map.lua
user: login: Frenzie
body: `when it's woken up`, but let's just shorten it to `when it wakes up`?
created_at: 2025-05-15T14:07:36Z
author_association: MEMBER
diff_hunk: @@ -3,5 +3,6 @@ return {
     [105] = "LPgBack",
     [106] = "RPgFwd",
     [116] = "Power",
+    [143] = "Resume",
 }
--- TODO libremarkable has 143 as "wakeup" - don't know what that corresponds to?
+-- 116 is issued when the device gets to sleep, 143 when it's waked up.
path: frontend/device/remarkable/event_map.lua
user: login: NoaHimesaka1873
body: Will do, also just don't merge it yet, I have discovered there's a wakelock that should be set.
created_at: 2025-05-15T14:08:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -250,6 +250,17 @@ This is used to detect if the cover is closed, which will automatically sleep an
     }
 end
 
+if Device:isRemarkable() and PowerD:hasHallSensor() then
+    common_settings.cover_events = {
+        text = _("Disable reMarkable cover events"),
path: frontend/ui/elements/common_settings_menu_table.lua
user: login: Frenzie
body: @yparitcher Do you recall if there's a particular reason you went for `Disable Kindle cover events`?

In any case, at a quick glance it'd make an awful lot more sense to change this string to `Disable cover events` and to unify the two.
created_at: 2025-05-15T19:23:54Z
author_association: MEMBER
diff_hunk: @@ -250,6 +250,17 @@ This is used to detect if the cover is closed, which will automatically sleep an
     }
 end
 
+if Device:isRemarkable() and PowerD:hasHallSensor() then
+    common_settings.cover_events = {
+        text = _("Disable reMarkable cover events"),
path: frontend/ui/elements/common_settings_menu_table.lua
user: login: yparitcher
body: Mostly because that was he only supported platform at the time.
but yes, definitely merge them to one menu entry
created_at: 2025-05-15T19:37:43Z
author_association: MEMBER
diff_hunk: @@ -250,6 +250,17 @@ This is used to detect if the cover is closed, which will automatically sleep an
     }
 end
 
+if Device:isRemarkable() and PowerD:hasHallSensor() then
+    common_settings.cover_events = {
+        text = _("Disable reMarkable cover events"),
path: frontend/ui/elements/common_settings_menu_table.lua
user: login: yparitcher
body: If so `Powerd:hasHallSensor()` should be copied to `generic/powerd.lua` with a default of false:
```lua
function BasePowerD:hasHallSensor() return false end
```
and simplify this check.
created_at: 2025-05-15T19:42:37Z
author_association: MEMBER
diff_hunk: @@ -353,4 +353,6 @@ function BasePowerD:getBatterySymbol(is_charged, is_charging, capacity)
     end
 end
 
+function BasePowerD:hasHallSensor() return false end
path: frontend/device/generic/powerd.lua
user: login: Frenzie
body: 
```suggestion
function BasePowerD:hasHallSensor()
    return false
end
```
created_at: 2025-05-16T07:48:51Z
author_association: MEMBER

PR comments:
user: login: NoaHimesaka1873
created_at: 2025-05-15T14:14:33Z
body: Forgot to check case lock situation, so making it draft for now.
author_association: CONTRIBUTOR
user: login: NoaHimesaka1873
created_at: 2025-05-15T15:53:35Z
body: csl wakelock doesn't work as intended, I should use another workaround I found.
author_association: CONTRIBUTOR
user: login: NoaHimesaka1873
created_at: 2025-05-15T16:20:28Z
body: > csl wakelock doesn't work as intended, I should use another workaround I found.

This workaround doesn't work since it seems like the config don't autoreload. Since there's no other way it should be addressed in the future since you could just go and turn off autosuspend at xochitl settings.
author_association: CONTRIBUTOR
user: login: NoaHimesaka1873
created_at: 2025-05-15T18:10:54Z
body: Also ready for review
author_association: CONTRIBUTOR