Kalmat / PyWinCtl

Cross-Platform module to get info on and control windows on screen
Other
179 stars 19 forks source link

Error with `pymonctl.getAllMonitorsDict()` on Linux Mint and Arch Linux #86

Open recluzegeek opened 5 months ago

recluzegeek commented 5 months ago

Currently, I'm playing with this wonderful cross-platform library to track my application usage across systems and encountered the errors on the way described below:

DeprecationWarning: getAllScreens() is deprecated. Use getAllMonitorsDict() from PyMonCtl module instead monitors = pywinctl.getAllScreens()

Following the warning, refactored the code to use pymonctl.getAllMonitorsDict() and got this error,

  PLATFORM: linux

  MONITORS:
  Traceback (most recent call last):
  File "/src/active_window.py", line 19, in test_basic
    monitors = pymonctl.getAllMonitorsDict()
  File "/.venv/lib/python3.11/site-packages/pymonctl/_main.py", line 75, in getAllMonitorsDict
    return _getAllMonitorsDict()
           ^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11/site-packages/pymonctl/_pymonctl_linux.py", line 54, in _getAllMonitorsDict
    wx, wy, wr, wb = wa[0], wa[1], wa[2], wa[3]
                     ~~^^^
TypeError: 'NoneType' object is not subscriptable

From what I've read in the pymonctl README.md, properties may be None if can't be read/obtained. Also the key thing to note is that it doesn't work on Linux Mint with XLib installed

I'm using dual monitor and managing them using ARandR — Screen Layout Editor for Linux.

Kalmat commented 5 months ago

Hi! Thanks A LOT for your feedback!!!

Sorry I've been extremely busy, so I had no time to finish and fully test next version of all involved modules (PyWinBox, PyMonCtl and PyWinCtl)... I hope to be able to test everything soon, so I can eventually upload all these new versions which fix tones of issues and improves some other features.

In your case, this is how that part of the code looks now:

# Thanks to odknt (https://github.com/odknt) for his HELP!!!
if isinstance(wa, list) and len(wa) >= 4:
    wx, wy, wr, wb = wa[0], wa[1], wa[2], wa[3]
else:
    wx, wy, wr, wb = x, y, w, h

Linux and all possible combinations is particularly complex (well, it's sort of a nightmare, to be hones), so testing everything takes a lot. I hope you can wait!!!!

In the meantime, do not hesitate to arise any comment, issue or suggestion you may have. Thanks again!!!

EDIT: Did you fix the other issue regarding DWM? Is awesome an alternative you may consider? Just curious

recluzegeek commented 5 months ago

Thanks for the quick reply. I'll look into your provided code snippet and come back to you once I test it. Shifting to awesome from dwm doesn't seem feasible at the moment because dwm is deeply integrated into my daily workflow. I rely heavily on it, and my muscle memory is attuned to its shortcuts, minimizing my use of the mouse.

EDIT: I tested these lines of code in windows, linux mint and arch running dwm and all of them passed, except on arch empty list and dictionary were returned

import pywinctl

print(pywinctl.getAllTitles())
print(pywinctl.getAllAppsWindowsTitles())

# ------------------------
# RESULT ON ARCH

[]
{}

Process finished with exit code 0
Kalmat commented 5 months ago

HAHAHAHA! Sorry. SInce it says awesome is a fork of dwm I thought it would be more straightforward.

I guess you mean that you have tested Mint and Arch together with dwm, right? If so, since PyWinCtl requires EMWH, it is likely not properly working. In case it is not dwm, please let me know which desktop/window managers you are using in those cases.

Thanks!

recluzegeek commented 5 months ago

No worries, I too was confused in my early linux days. It happens and IMO its normal :) I've tested arch with dwm and used Linux Mint 21.3 x86_64 with Cinnamon 6.0.4 as desktop environment.

Kalmat commented 5 months ago

Ah, ok! That's why Mint works, whilst Arch doesn't... Arch with other desktop/window managers works if those managers are EWMH-compliant. I already have actual installs of Ubuntu/GNOME (Wayland is not working since it's not EMWH-compliant, Linux Mint/Cinnamon and Manjaro/KDE; but not Arch yet, sorry.

recluzegeek commented 5 months ago

Manjaro actually is derived from Arch, as Mint is derived of Ubuntu. If it works on Manjaro, then it also should be working in arch. Issue itself is not in the arch, rather in the desktop/windows manager. In this case, dwm doesn't seems to be EWMH-compliant.

I created this git issue for mint on this issue, AttributeError: module 'Xlib.ext.randr' has no attribute 'get_monitors', since Xlib should be performing well across linux distros. Surprisingly, this issue arose during my Mint testing and not on Arch :)

Arch with dwm is my main development machine, yet I think I've to work in mint for sometime till new release.

Kalmat commented 5 months ago

Yes, that's correct. EMWH-compliance depends on the Desktop/Windows Manager, not the OS. E.g. in "standard" Ubuntu, GNOME is EWMH-compliant, but Wayland is not (furthermore, there is no way in Wayland to get the list of windows nor the active one... I mean, that feature is not supported at all, even using any other protocol to communicate with it)

I hope to be able to start all tests soon, which include Mint/Cinnamon amongst many other combinations. I will keep you posted on any progress. Hopefully I will have a complete new version ready to be uploaded so all these issues are fixed.

Please, bear in mind that non-EWMH window managers (like dwm) will still not work with the new version, since it will inevitably continue to rely on EWMH to handle windows.

Back to the initial issue, and just if you are curious, this is a snippet of the new version, which includes a workaround specifically for Cinnamon:

def _getMonitorsData(handle: Optional[int] = None) -> (
                        List[Tuple[Xlib.display.Display, Struct, XWindow, randr.GetScreenResourcesCurrent,
                        randr.MonitorInfo, str, int, randr.GetOutputInfo, int, randr.GetCrtcInfo]]):
    monitors: List[Tuple[Xlib.display.Display, Struct, XWindow, randr.GetScreenResourcesCurrent,
                         randr.MonitorInfo, str, int, randr.GetOutputInfo, int, randr.GetCrtcInfo]] = []
    stopSearching = False
    for rootData in getRoots():
        display, screen, root = rootData
        try:
            mons = randr.get_monitors(root).monitors
        except:
            # In Cinnamon randr extension has no get_monitors() method (?!?!?!?)
            mons = _RgetAllMonitors()
            stopSearching = True
        for monitor in mons:
            if isinstance(monitor.name, int):
                monitor.name = display.get_atom_name(monitor.name)
            res = randr.get_screen_resources_current(root)
            output = monitor.crtcs[0]
            outputInfo = randr.get_output_info(display, output, res.config_timestamp)
            if outputInfo.crtc:
                if handle:
                    if handle == output:
                        crtcInfo = randr.get_crtc_info(display, outputInfo.crtc, res.config_timestamp)
                        return [(display, screen, root, res, monitor, monitor.name, output, outputInfo, outputInfo.crtc, crtcInfo)]
                else:
                    crtcInfo = randr.get_crtc_info(display, outputInfo.crtc, res.config_timestamp)
                    monitors.append((display, screen, root, res, monitor, monitor.name, output, outputInfo, outputInfo.crtc, crtcInfo))
        if stopSearching:
            break
    return monitors

def _RgetAllMonitors():
    # Check if this works in actual Cinnamon
    monitors: List[_Monitor] = []
    outputDict = {}
    for outputData in _XgetAllOutputs():
        display, screen, root, output, outputInfo = outputData
        outputDict[outputInfo.name] = {"outputData": outputData}

    namesData = _RgetMonitorsInfo()
    for item in namesData:
        monName, primary, x, y, w, h = item
        if monName in outputDict.keys():
            display, screen, root, output, outputInfo = outputDict[monName]["outputData"]
            wm, hm = outputInfo.mm_width, outputInfo.mm_height
            crtcs = [output]
            monitors.append(_Monitor(monName, primary, x, y, w, h, wm, hm, crtcs))
    return monitors
recluzegeek commented 5 months ago

Hi there again. This time I'm not creating a new issue, 😄 just for clarification and enhancements. Does pywinctl provides any interface or method to return the currently running app name along with its windows title.

Like the new_window = pywinctl.getActiveWindow() returns the currently focused windows title,

Error with pymonctl.getAllMonitorsDict() on Linux Mint and Arch Linux · Issue #86 · Kalmat/PyWinCtl — Mozilla Firefox

I want a method to return Mozilla Firefox or firefox.exe (container in which its running) or something like that. Maybe the method exists, I'm unaware of it.

Also the docs didn't provided any help either, there are just bunch of methods name, we should be documenting them properly, what they returns or what they do along with some code examples.

Kalmat commented 5 months ago

Hi again! I will try to clarify and summarize.

PYWINCTL: PyWinCtl returns the active window and the name of the app it belongs to, by using:

    new_window = pywinctl.getActiveWindow()
    if new_window is not None:
        app_name = new_window.getAppName()
        print(app_name)
    else:
        print("NOT FOUND")

But ONLY IF the desktop/window manager is EWMH-compliant (e.g. GNOME, Cinnamon, KDE, awesome, ...). PyWinCtl will not work with non-EWMH environments (e.g. DWM, Wayland, ...)

PYMONCTL

In short, you can use PyWinCtl to get the active app name, but not together with DWM. If you also need PyMonCtl, I can provide you with a wheel that fixes the Cinnamon issue so you can work with it whilst I finish all tests and upload the new version.

I hope this helps to clarify.

recluzegeek commented 5 months ago

Thanks for the clarification. I'm now working on mint in virtual box and was asking a general question about getting app name. It was my bad for not understanding the method naming convention. Again thanks for the clarification. 😶‍🌫️

Kalmat commented 5 months ago

No worries at all! Happy to hear this has been helpful. Thank you so much for your feedback. As you suggested, I have improved the module documentation (again, it will be available in next version).

Just let me know if you need anything else, you find any other issue or you have any suggestion!