After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 759009 - pygtkcompat Gdk.Windows.get_origin
pygtkcompat Gdk.Windows.get_origin
Status: RESOLVED FIXED
Product: pygobject
Classification: Bindings
Component: general
3.19.x
Other Windows
: Normal normal
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
Depends on:
Blocks:
 
 
Reported: 2015-12-04 06:22 UTC by Rod Pullmann
Modified: 2017-03-22 08:23 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
pygtkcompat: Allow multiple calls to enable(), enable_gtk() as long as the version matches (4.08 KB, patch)
2017-03-21 13:49 UTC, Christoph Reiter (lazka)
committed Details | Review

Description Rod Pullmann 2015-12-04 06:22:45 UTC
The cover function in pygtkcompat/pygtkcompat.py for Gdk.Window.get_origin has this:

  return orig_get_origin(self)[1:]

It should be this:

  return orig_get_origin(self)[-2:]

Somewhere along the line, this function is being called twice, nested.  Each nested invocation strips off an element from the front of the returned tuple, such that by the time it returns to the outermost caller, the tuple has one element instead of two.  The fix guarantees that the final two elements of the original tuple are always returned regardless of how many nested function invocations occur.
Comment 1 Christoph Reiter (lazka) 2016-03-01 14:33:45 UTC
I can't reproduce. Gdk.Window.get_origin() is tested here: https://git.gnome.org/browse/pygobject/tree/tests/compat_test_pygtk.py#n161

Can you provide an example where it returns the wrong info?
Comment 2 Rod Pullmann 2016-03-08 14:50:40 UTC
I have some code that calls Gdk.get_origin() on a Gdk.Window object.  Pretty simple.

Somewhere in the bowels of the PFM that is pygtkcompat, the get_origin() method is calling itself recursively, not twice, but three levels deep, actually.  Each time, the invocation strips off an element from the return value, when in fact the purpose of this stub is to return just the x,y coordinates -- a 2-tuple.  My patch always returns the final two elements -- the intended pair -- rather than the existing code which returns "all but the first", which shrinks the return value each time.

I don't know why your test case doesn't end up reproducing this recursion, this inability doesn't mean it's not a bug.  Perhaps it's some Windows API version-specific difference, but it's definitely a problem.

The fix I proposed works fine, and is easily explainable conceptually.  Why not just implement it?  It would be nice to not have to apply that
patch to every PyGI installation I do, which is necessary now.  Thanks.
Comment 3 Christoph Reiter (lazka) 2016-03-08 15:08:03 UTC
> I have some code that calls Gdk.get_origin() on a Gdk.Window object.  Pretty simple.

Can you provide an example?

> Somewhere in the bowels of the PFM that is pygtkcompat, the get_origin() method is calling itself recursivel

It shouldn't. How do you know it is recursing and can you provide a stack trace?

---

Please understand that I'd prefer to understand the problem first before trying to fix it.
Comment 4 Rod Pullmann 2016-03-08 15:23:37 UTC
Here's the recursive stack trace, from a pdb breakpoint at the offending line:

(Pdb) bt
  c:\python27\lib\bdb.py(400)run()
-> exec cmd in globals, locals
  <string>(1)<module>()
  o:\taipan\sabrecon\sabrecon.py(3134)<module>()
-> main() #@@@ DEBUG: pdb_main(main)
  o:\taipan\sabrecon\sabrecon.py(3128)main()
-> console.run()
  o:\taipan\sabrecon\sabrecon.py(1760)run()
-> Gtk.main()
  o:\taipan\sabrecon\sabrecon.py(2027)on_gui_console_input_key_press_event()
-> self.relocate_mouse(-8, 16)
  o:\taipan\sabrecon\sabrecon.py(1931)relocate_mouse()
-> ox, oy = Gdk.Window.get_origin(self.window.get_window())
  c:\python27\lib\site-packages\pygtkcompat\pygtkcompat.py(192)get_origin()
-> return orig_get_origin(self)[1:]  #@@@ needs bug fix: always extract final two coordinates
  c:\python27\lib\site-packages\pygtkcompat\pygtkcompat.py(192)get_origin()
-> return orig_get_origin(self)[1:]  #@@@ needs bug fix: always extract final two coordinates
> c:\python27\lib\site-packages\pygtkcompat\pygtkcompat.py(192)get_origin()
-> return orig_get_origin(self)[1:]  #@@@ needs bug fix: always extract final two coordinates

As you might expect, this is manifested in real application code, not a simple unit test.

I refer to this as PFM because whatever attribute lookup is occurring to cause this to be called recursively is untraceable at the level of pdb or any other debugger.

-------- Also...

I would be glad to try any unit test code that you provide, *that actually runs standalone*.

I'm unable to hack the code from the link you cited, to execute the unit test:
 1. Missing a 'helpers' module -- commented out
 2. No main().
 3. Using unitttest in the customary way by adding a main() with: TestGTKCompat().main() fails.
 4. Ditto instead adding a main with: TestGTKCompat().test_gdk_window()

Too much extra hacking required, I give up...
Comment 5 Christoph Reiter (lazka) 2016-03-08 15:35:47 UTC
Thanks. Could it be that you call pygtkcompat.enable_gtk() multiple times?
Comment 6 Rod Pullmann 2016-03-08 16:04:36 UTC
Yes, that probably does occur.  There are a number of modules used in this application, all of which follow a canned common recipe for including the Gtk/Gdk infrastructure, something like:

    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    from gi.repository import Gdk
    from gi.repository import GObject
    from gi import pygtkcompat
    pygtkcompat.enable()
    pygtkcompat.enable_gtk(version='3.0')
    import pango

Since each module may or may not be loaded with others that use/need the same infrastructure, each module includes this recipe individually.

Are you saying that the enable_gtk() invocation needs to be treated as if it were an application singleton?  If so, that is the first I've heard of that.  The application actually executes fine even if I ignore that requirement.

That said, wouldn't the fix to get_origin() in pygtkcompat I've suggested be a better implementation in light of others who might be doing the same thing?
Comment 7 Christoph Reiter (lazka) 2016-03-08 16:19:56 UTC
> Are you saying that the enable_gtk() invocation needs to be treated as if it were an application singleton?

Yeah, the current code doesn't support it. We should fix this :)

---

As a workaround you can monkey patch the functions in your toplevel module so it doesn't do anything if called again:

from gi import pygtkcompat
pygtkcompat.enable()
pygtkcompat.enable_gtk(version='3.0')

pygtkcompat.enable = lambda *args, **kwargs: None
pygtkcompat.enable_gtk = lambda *args, **kwargs: None
Comment 8 Rod Pullmann 2016-03-08 16:38:11 UTC
Blecch.  Yeah, this should definitely be fixed such that it's transparent to the unwitting pygtkcompat user if it gets invoked multiple times.  That said, I'm very grateful to have a compatibility layer like this -- it would've been a huge schedule hit to port our existing PyGTK code base and utilities over to native PyGObject.
Comment 9 Rod Pullmann 2016-03-08 18:53:28 UTC
As a naive hack, I've added:

 if hasattr(sys.modules['pygtkcompat'].pygtkcompat, 'is_initialized'): 
    return
 sys.modules['pygtkcompat'].pygtkcompat.is_initialized = True

to the start of pygtkcompat.enable_gtk().  enable() doesn't need monkey-patching -- it's idempotent.

Seems to work fine, and indeed solves the recursion problem.  It also obviates the need to monkey-patch each invocation.

However, I'm still thinking you'll want to make the get_origin() fix, in case others fall into the same pitfall I did -- it's always more robust to take the fixed number of elements you know you need than to assume you know how many elements you don't need and drop those...
Comment 10 Christoph Reiter (lazka) 2017-03-21 13:49:57 UTC
Created attachment 348413 [details] [review]
pygtkcompat: Allow multiple calls to enable(), enable_gtk() as long as the version matches

enable_gtk() isn't idempotent and was breaking the API when called multiple times.
This ignores the call in case the version passed is the same as for previous calls.
Comment 11 Simon Feltman 2017-03-22 07:25:09 UTC
Review of attachment 348413 [details] [review]:

LGTM, thanks.
Comment 12 Christoph Reiter (lazka) 2017-03-22 08:23:33 UTC
Thanks