OzymandiasTheGreat / python-libinput

[LOOKING FOR MAINTAINER] Object-oriented wrapper for libinput using ctypes.
MIT License
7 stars 11 forks source link

Interface comments #4

Open whot opened 6 years ago

whot commented 6 years ago

I had a read through the docs in regards to the API and I'll summarise the various comments here, easier than filing a million separate issues :)

The main issue is that, right now, the Python API is a C API in the Python language. That's not the same as a real Python API and there's lot for improvement. Implementation-wise what I did in libevdev-python was have an internal class that matches the C API (like you already have) but have the public API be a hand-written one that wraps the C wrapper.

Anyway, here's a list of comments regarding the current API:

OzymandiasTheGreat commented 6 years ago

First, thank you! This is probably the best feedback I've ever gotten. :smiley: All good points, some I have considered myself and decided to be lazy (like returning device events directly). DeviceConfig is a base class for Device, so it's methods are available through Device instance, that's why I left the config_ prefix, to clearly differentiate between configuration methods and the rest. Could you elaborate on subclassing config? I don't think I quite understand what you mean and what purpose it would serve. As for the rest, will do :+1:

whot commented 6 years ago

tbh, I don't think I really know what the best option is for config. First, having it exposed as device.config would be useful, that can be a class that handles the actual config requests.

The goal would be to have a pythonic API instead of a C wrapper, so we'd get e.g. AttributeError when calling a non-existent configuration item. The libinput config items are (almost) all a triplet of get/set/get_default. Obviously get/set would be property candidates, not sure how to handle get_default yet.

Leaving that aside, the config property can have sub-properties for each class, so that you can do

  device.config.tap.enabled = True

And that can either work, or throw an error where tapping isn't supported. I honestly don't know 100% how this should look like in detail, but I think the above would be better than having tap_set_enabled.

[edit: best approach would likely be to write a test program using libinput and writing it against the API you'd expect libinput to have (even if it doesn't right now). That gives you the best insighed on how it should look like]

OzymandiasTheGreat commented 6 years ago

This does look much nicer, I'll think 'bout how to best implement it. As for get_default, I guess I'll just make it a read-only property.

OzymandiasTheGreat commented 6 years ago

@whot I could use some advice. I'm halfway done rewriting the pythonic api, but I'm not sure if raising an exception when accessing a property on the wrong type of event is the best way to go. I do think it's better than just returning None, less error prone. Thoughts?

whot commented 6 years ago

I'm assuming you're talking about cases where a developer ties to e.g. get dx/dy from a keyboard event?

None should indicate 'no value', whereas an exception is more "you've done something wrong" or "something is out of the ordinary". So I do think that accessing properties that don't exist should be an exception. None can be mistaken for 0 or False in a condition.

OzymandiasTheGreat commented 6 years ago

I made a new release, I think I implemented all of your suggestions. That actually made the code neater. As for the event codes, I opted to remove them completely, because I haven't considered not all event codes may be defined in input.h.

whot commented 6 years ago

Ok, this looks a lot better now and a lot more Pythonic, thanks heaps for the work. I had a look through the API documentation on RTD again (not the code, just the docs):

Special case:

OzymandiasTheGreat commented 6 years ago

Sorry, life got a bit hectic lately. Got less time to work on this but I'm still going.

I'm not sure if it's a python thing, but keeping constants/enumerations in separate module is definitely my thing. Helps me keep track of what's where. I could expose them through main libinput namespace, I'm just not sure if keeping code in a separate module and importing names into the main namespace is pythonic.

grab is a leftover. When I was trying to figure out libinput I looked closely at libinput-debug-events source. While I can't think of a use case off the top of my head, I don't see a reason to remove it either. Edit: looking at the current implementation, it's a bit broken. Might as well remove it.

About the segfault, in my rush to get this out I have not even considered reporting it. That would make a lot more sense and I'll keep it mind. Also thanks for fixing it!

If I understand correctly, you're suggesting removing timeout handling from current implementation and instead returning plain generator as the events property. That may be a good idea.

OK, EventType.NONE will go. While the idea of pythonic is rather elusive to me, this is definitely it. Pythonic. :smiley:

Agreed. AttributeError is more appropriate.

I'm not looking at the code right now, but iirc using plain absolute would be confusing in some places and I want to keep this consistent. As for the coords vs position I don't really see a reason to use one over the other.

Agreed. Returning a tuple of values and a boolean is very good idea!

I don't see how led_update(leds) would work as a getter since libinput_device_led_update(leds) returns void.

I thought about that and decided that all is_*/has_* functions should be methods for consistence. I could make the is_available methods available properties if you think it's worth it.

device.capability property, will do. This is a great idea.

I got lazy here again. While it definitely makes sense for tablet pad devices, pointer and keyboard has only one function each. I do agree it would look much nicer. Will do.

whot commented 6 years ago

I don't see a reason to remove it either.

Take that as a lesson I learned the hard way: Unless you have a specific use-case in mind (and that use-case is one you want to enable and support), don't expose anything in your library you don't immediately need. You will have to spend time maintaining a bit of code that may never be used, and, worse, you may actually implement it wrong (or incompatible) with a real use case that comes later, making your life doubly hard.

As for the coords vs position I don't really see a reason to use one over the other.

Pick one and use it for both touch and absolute pointer coordinates, just for consistency. I'd go with position because it's less ambiguous.

OzymandiasTheGreat commented 6 years ago

So I implemented most of that. As for coords vs position, the documentation uses the word coordinates, so I think coords is less confusing. And I still don't see how led_update can be a getter/setter.

whot commented 6 years ago

yeah, forget about the led_update comment, this is a write-only thing right now so can't be done with getters/setters.