Skip to content

Listener class PR acceptable? #31

@zwimer

Description

@zwimer

Would a PR containing a listener class be acceptable? This would not have to remove old functionality, it could be purely additive.

Why?

  1. In my opinion, the most important reason, is allowing guaranteed cleanup. Consider a listener which uses subprocess.POpen. If a user were to force quit (for example a SIGQUIT on linux) the process, or a native component of a GUI library were to segfault, python may not clean up the child process it was listening to. Proving an API that promises to do so would be greatly appreciated. For example, a user could add a segfault handler to kill child subprocesses before os._exit(1). Right now I'm using a somewhat reimplemented version of darkdetect due to the lack of this functionality.
  2. Right now, the listeners are uninterruptible. For some listeners, like the Windows listener, this may make sense, but for others that poll processes, it is avoidable as these processes can be killed. In this case, NotImpelementedError()s are acceptable.
  3. Allowing subclassing will allow editing of functionality

Possible API + Example

class BaseListener:

  def __init__(self, callback):
      self.set_callback(callback)

  def set_callback(self, callback):
      self._callback = callback

  def listen(self):
     raise NotImpelementedError()

  def stop(self):
     raise NotImpelementedError()

In _linux_detect.py:

class Listener(BaseListener):
  def __init__(self, callback):
    super().__init__(callback)
    self._proc = None
  
  def listen(self):
    self._proc = subprocess.Popen(
        ('gsettings', 'monitor', 'org.gnome.desktop.interface', 'gtk-theme'),
        stdout=subprocess.PIPE,
        universal_newlines=True,
    ) 
    with self._proc:
        for line in p.stdout:
            self._callback('Dark' if '-dark' in line.strip().removeprefix("gtk-theme: '").removesuffix("'").lower() else 'Light')

  def stop(self):
    if self._proc is not None:
      self._proc.kill()
      self._proc.wait()
      self._proc = None

# For those who want to use the old API        
def listener(callback): 
  Listener(callback).listen()

This would allow users to ensure that cleanup has happened properly, interrupt a listener, or subclass to add / edit functionality. For listeners that subclass, really the important bit is having the subprocess saved into something that users can kill if the need should arise, but this class struct has other benefits as mentioned above.

Possible Reasons to Subclass

These are other possible benefits of allowing subclassing:

  1. A user wants to use an alternative listener for a specific OS. Perhaps their linux distro doesn't use gnome; but they still want the macOS and Windows listeners to work as is.
  2. A user wants to edit the windows listener so that interrupts are possible, they can override the listen function and perhaps poll once per second if they desire
  3. They want to easily swap out the callback function for some reason; they can use the base classes' set_callback, or have it set via a subclass method
  4. A user may want to subclass Listener and their own class as part of a mixin design pattern or some other pattern.

In the examples above, darkdetect still only provides an API that users can use, so unlike in #29 we don't need to do threading stuff. We simply provide a listener object for the user to use; and to support the existing API we simply just have the standard listener(callback) function invoke Listener(callback).listen().

If I were to make a PR to add such functionality for each supported OS, would it be considered or rejected?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions