Adding a sound to HKS's

I have noticed that it is quite easy to inadvertently move the HKS’s.
Maybe adding an (optional) sound would be nice. Or a vibration, maybe.

6 Likes

This shouldn’t be too hard to script, if someone can identify what change in state is notified in the terminal.

For example, I created an auditory notification here (for something unrelated): Script to Play a Battery Charge Notification or Warning

3 Likes

Ooh! That would be nice, @amarok: a DIY solution. Very much in the vein of what the L5 is all about.

4 Likes

Don’t forget to share it when you have something working. Maybe it could even be upstreamed. :wink:

1 Like

That is a excellent idea to hook it up to feedbackd, in addition to HKS, all physical buttons for that matter should have an option for feedback.

1 Like

I took a shot at making this work. I got something that works but it certainly isn’t how it should work.

I tend to use Python so here is my script:

from enum import Enum
import fcntl
import os
import struct


class RfKillEventType(Enum):
    RFKILL_TYPE_ALL = 0
    RFKILL_TYPE_WLAN = 1
    RFKILL_TYPE_BLUETOOTH = 2
    RFKILL_TYPE_UWB = 3
    RFKILL_TYPE_WIMAX = 4
    RFKILL_TYPE_WWAN = 5
    RFKILL_TYPE_GPS = 6
    RFKILL_TYPE_FM = 7
    RFKILL_TYPE_NFC = 8
    RFKILL_TYPE_CAMERA = 9
    RFKILL_TYPE_MIC = 10


class RfKillEventOp(Enum):
    RFKILL_OP_ADD = 0
    RFKILL_OP_DEL = 1
    RFKILL_OP_CHANGE = 2
    RFKILL_OP_CHANGE_ALL = 3


class RfKillEvent:
    def __init__(self, idx,  event_type, event_op, event_soft, event_hard):
        self.idx = idx
        self.event_type = RfKillEventType(event_type)
        self.event_op = RfKillEventOp(event_op)
        self.event_soft = bool(event_soft)
        self.event_hard = bool(event_hard)

    def __str__(self):
        return f'{self.__class__.__name__}({self.idx}, {self.event_type}, {self.event_op}, {self.event_soft}, {self.event_hard})'


class RfKillEventStruct(struct.Struct):
    def unpack(self, *args):
        unpacked = super().unpack(*args)
        return RfKillEvent(*unpacked)


def main():
    f = open('/dev/rfkill', 'rb')
    fcntl.fcntl(f.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
    data = f.read()
    fcntl.fcntl(f.fileno(), fcntl.F_SETFL, 0)
    event_struct = RfKillEventStruct('I4B')
    try:
        while True:
            data = f.read(event_struct.size)
            event = event_struct.unpack(data)
            print(event)
            # Top HKS is WWAN, middle is WLAN and BLUETOOTH, bottom is CAMERA and MIC
            #if event.event_type is RfKillEventType.RFKILL_TYPE_CAMERA:
            #    if event.event_op is RfKillEventOp.RFKILL_OP_CHANGE:
            #        if event.event_hard:
            #            # Camera HKS was turned OFF
            #        else:
            #            # Camera HKS was turned ON

    except KeyboardInterrupt:
        pass
    f.close()


if __name__=='__main__':
    main()

It outputs something like this if you toggle the Camera/Microphone switch off and on:

RfKillEvent(1, RfKillEventType.RFKILL_TYPE_CAMERA, RfKillEventOp.RFKILL_OP_CHANGE, False, True)
RfKillEvent(0, RfKillEventType.RFKILL_TYPE_MIC, RfKillEventOp.RFKILL_OP_CHANGE, False, True)
RfKillEvent(1, RfKillEventType.RFKILL_TYPE_CAMERA, RfKillEventOp.RFKILL_OP_CHANGE, False, False)
RfKillEvent(0, RfKillEventType.RFKILL_TYPE_MIC, RfKillEventOp.RFKILL_OP_CHANGE, False, False)

This uses the rfkill userspace interface at /dev/rfkill (See: https://www.kernel.org/doc/html/latest/driver-api/rfkill.html). I don’t know the details about how Linux device trees work, but the Librem 5 device tree (https://source.puri.sm/Librem5/linux/-/blob/pureos/byzantium/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi) specifies the hardware kill switches with rfkill. So, that led me to looking how to get rfkill events. The rest was just parsing the data with the help of the C headers: https://source.puri.sm/Librem5/linux/-/blob/pureos/byzantium/include/uapi/linux/rfkill.h. The devices are “hard blocked” when the switch is off. Hard blocked is the last part of each line of the output above.

The first read of the file provides the current status of rfkill, but I couldn’t figure out what struct defines that data so I just ignore it. If you could read it, you could make a cron job to read that file and store the status and react to changes that way. The latency could be pretty long for that method though.

Certainly, this is not how you would want to do this normally. There is probably a way to register a service for rfkill events, or you could try to get at the gpiochip events that I think rkfill is attached to. I just don’t know anything about that stuff. But anyway, this script is at least something to play with.

Update: I also discovered “rfkill event” which does pretty much what my script does. You could easily write a bash script around that to parse the events and do something. Playing with that also made me realize the initial output of /dev/rfkill is just a sequence of events, so that could be parsed as well in the Python script. Here is the output of “rfkill event” including the initial status and a toggle on and off of the mic switch:

$ rfkill event
2023-02-28 17:49:40,556021-07:00: idx 0 type 10 op 0 soft 0 hard 1
2023-02-28 17:49:40,557376-07:00: idx 1 type 9 op 0 soft 0 hard 1
2023-02-28 17:49:40,557401-07:00: idx 2 type 1 op 0 soft 0 hard 0
2023-02-28 17:49:40,557423-07:00: idx 3 type 5 op 0 soft 0 hard 1
2023-02-28 17:49:40,557442-07:00: idx 4 type 1 op 0 soft 0 hard 0
2023-02-28 17:49:40,557459-07:00: idx 5 type 2 op 0 soft 0 hard 0
2023-02-28 17:49:44,129663-07:00: idx 1 type 9 op 2 soft 0 hard 0
2023-02-28 17:49:44,130408-07:00: idx 0 type 10 op 2 soft 0 hard 0
2023-02-28 17:49:45,930859-07:00: idx 1 type 9 op 2 soft 0 hard 1
2023-02-28 17:49:45,947506-07:00: idx 0 type 10 op 2 soft 0 hard 1
6 Likes

Wow! I had not expected this much enthousiasm. I am not sure I can implement the code above, but I will look into it. Nice!

1 Like

udev could trigger a program/script each time a hks is toggled. You could look at /usr/lib/udev/rules.d/85-librem5-lockdown-support.rules for an example.

Running a short script works well, but the time the script executes the processing of other rules is blocked and udev kills any process needing longer than a few seconds.

For emitting a feedback (respecting the global settings for ‘quiet’, ‘silent’ and ‘normal’) via feedbackd there’s fbcli.

1 Like

Thanks for the pointer. I tried it out by creating a new udev rule. I can run scripts that way, but I can’t get fbcli to run in those scripts. I can get it to run just fine directly in the shell. I imagine it has something to do with the different environment between an interactive user session and being run in udev, but I don’t know how to go about fixing it. I did try just playing a tone using play (from the sox package) and that worked. So every time I flip a switch, it plays a tone. But it seems like feedbackd is the right way to do that, just need to figure out how to send it events from the udev environment.

Yeah, sorry, that was somehow to be expected.

fbcli talks to feedbackd through dbus. To find the dbus socket/address fbcli should use to connect to feedbackd it uses the environment it is running in. Here’s an example taken from an ssh session in which there is no x-forward active and no DISPLAY variable set:

purism@pureos:~$ fbcli -E message-new-instant -t 3
Failed to init libfeedback: D-Bus kann nicht automatisch ohne X11 $DISPLAY gestartet werden
purism@pureos:~$ export DISPLAY=:0
purism@pureos:~$ fbcli -E message-new-instant -t 3
Triggering feedback for event 'message-new-instant'
Press <RETURN> to end feedback right away.

So you need to make sure in your rule that udev provides the environment needed for fbcli to find the right dbus.

Putting this RUN+="/bin/sh -c 'set | /usr/bin/logger -t my_udev_environment'" in your rule writes the environment to the journal and you can look it up by using journalctl. You also could write it to a temporary file which always includes the risk of accidentality overwriting a file you still needed.

You’ll need your udev rule providing its own root/udev environment to first add the missing information to that environment.

You can take some inspiration from this script and this udev rule.

Beware when using xpub.sh (to be found on github): it is crafted to work in an X11 environment. Once the software stack will move on to wayland-only the script will not work anymore - the reason I didn’t want to use it in my modem reset script.

BTW: if you’re able to deal with C(++) (which I can’t say for myself, yet) you could start a completely different adventure if @guido.gunther would approve that idea: integrate that sound/rumble/feedback for hks toggle into the default feedbackd theme (if no other could be used) and write a small code snippet as a merge request to phosh to do the job: phosh already includes code to send feedback to feedbackd and it already watches the hks in some way to change the icons showing there state.

From a design standpoint I’d say the functionality belongs there and the configuration would be part of any future user interface to configure feedback themes.

If you’re not the person to do (or learn) that and you’d like to go with the system integration way of doing it (which I would start using also once available :wink: ) it might be worth looking through the phosh issues and if there’s not already a feature request for that you could open an issue.

4 Likes