Trying to understand GLib.IOChannel.read_chars() under python

I am trying to watch and read “/dev/rfkill” to get the status of the switch. I have tried:

from gi.repository import GLib

def io_event(channel, condition):
    status, iodata = channel.read_chars()
    print(f"IOChannel.read_chars(): {status} {iodata}")
    return True

c = GLib.IOChannel.new_file("/dev/rfkill", "r")
# c.set_flags(GLib.IOFlags.NONBLOCK)
wid = GLib.io_add_watch(c, GLib.IO_IN, io_event)

loop = GLib.MainLoop()

But this blocks indefinitely. Ok lets try with GLib.IOFlags.NONBLOCK and see what we get, nothing. For comparison,, 8) in the same callback works fine.

How is GLib.IOChannel.read_chars() supposed to work under python?

Try File Monitor.

from gi.repository import Gio

def on_changed_(file_monitor,file_,other_file,event_type):
    if event_type == Gio.FileMonitorEvent.CHANGES_DONE_HINT:

rate_limit = 800
myfile = Gio.File.new_for_path("/dev/rfkill")
myfile_monitor = myfile.monitor_file(Gio.FileMonitorFlags.NONE)

I appreciate the comment but it isn’t an answer to the question. I can think of at least 3 other ways to monitor /watching /dev/rfkill. The question is how GLib.IOChannel.read_chars() is supposed to work. Am I doing it wrong, is it broken in python (or in general)?

The IOChannel watch is working perfectly, it is the reading that is causing problems.

And out of curiosity I tried your Gio approach, it does not work. I don’t think Gio.FileMonitor is intended to be used with kernel character devices.

And why is the python API completely different from the C api, very :confused:

g_io_channel_read_chars (GIOChannel *channel,
                         /* ↑ the IOChannel instance, like `self` */
                         gchar *buf,
                         /* ↑ a byte† array to put data in
                                            (that is, _returned_) */
                         gsize count,
                         /* ↑ how big is buf, really buf+count are
                              two parts of one “object”           */
                         gsize *bytes_read,
                         /* ↑ somewhere to put (again _return_)
                              the number of bytes, ≤ count,
                              actually read                       */
                         GError **error);
                         /* ↑ We might error (raise an exception),
                              this is where error information is put
                              (returned)                          */

/* † - Yes, simplifying I know */

Now understandably PyGObject hides GError completely, you’ve got exceptions for that

Since python allows returning multiple values bytes_read is quite simply handled: Just add it to the return tuple along with the status

The tricky part is buf+count, PyGObject has noticed byte array + length is basically just bytes - In theory a perfectly reasonable thing to do. Of course what’s failed here is that now you can’t set the buffer size, presumably PyGObject has picked one for you. So your not randomly hung, it’s just GIOChannel is trying to read some unreasonable number of bytes which /dev/rfkill won’t provide.

Congrats, you found a bug!

Though I’m guessing you probably want read_to_end here anyway

I just tried read_to_end, read_line and pretty much all the new api provides. All block indefinitely and when I set GLib.IOFlags.NONBLOCK always get 0 bytes back.

I went back to using for now because unfortunately reading from a IOChannel under python is too broken.


Apparently reading from an IOChannel in pygobject can only be done with the read() method. It works because it is a static binding around read_chars(), using read_chars() directly is broken (presumably all of the reading api is). However read() has it’s own set of problems :disappointed:.

For future reference, just use and ignore any of the read methods. Working example.

from gi.repository import GLib
import os

def io_event(channel, condition):
    fd = channel.unix_get_fd()
    data =, 8)
    print(f"Just use folks {data}")
    return True

c = GLib.IOChannel.new_file("/dev/rfkill", "r")
wid = GLib.io_add_watch(c, GLib.IO_IN, io_event)

loop = GLib.MainLoop()

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.