-
-
Notifications
You must be signed in to change notification settings - Fork 685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Linux location provider #2990
Comments
Thanks for that research. You're already miles ahead of where my understanding of the problem was! |
After much documentation reading, and the help of one very useful example based on the Bluez DBus, I've got a working, and testable implementation of Geoclue in Python using just PyGObject: import gi
gi.require_version("Geoclue", "2.0")
from gi.repository import Gio, GLib, Geoclue
class GeoclueDBusClient:
def __init__(self):
self.client = Geoclue.ClientProxy.create_full_sync(
"org.freedesktop.GeoClue2",
Geoclue.AccuracyLevel.CITY,
Geoclue.ClientProxyCreateFlags.AUTO_DELETE,
None,
)
self.cancel_start = Gio.Cancellable()
self.client.connect("location-updated", self.on_location_updated)
self.client.call_start(self.cancel_start, self.on_call_start)
def on_call_start(self, client, task):
self.client.call_start_finish(task)
print("Started Geoclue connection")
def on_location_updated(self, client, old_location, new_location):
print("on_location_updated", old_location, new_location)
self.current_location = Geoclue.LocationProxy.new_sync(
client.props.g_connection,
Gio.DBusProxyFlags.NONE,
"org.freedesktop.GeoClue2",
new_location,
None,
)
print(self.current_location.get_properties("latitude", "longitude", "altitude"))
if __name__ == "__main__":
mainloop = GLib.MainLoop()
geoclue = GeoclueDBusClient()
breakpoint()
try:
mainloop.run()
except KeyboardInterrupt:
mainloop.quit() That script will print the location as it changes, and responds to modifications in
|
Some more information, and progress towards the end goal 🎉 Here is a new implementation that uses Geoclue.Simple. It turns out you can watch for changes on Geoclue.Simple, through the generic GObject notify API. Once you have a Also, I've collapsed this code block for readability, expand it to see a working implementation of the Toga helloworld app that will display the current location"""
My first application
"""
from enum import StrEnum, auto
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
from gi.repository import Geoclue, Gio, GLib
class _Geoclue:
class State(StrEnum):
INITIAL = auto()
STOPPED = auto()
ACTIVE = auto()
FAILED = auto()
DENIED = auto()
def __init__(self, location_listener, state_listener):
self.location_listener = location_listener
self.state_listener = state_listener
self.state = _Geoclue.State.INITIAL
self.simple = None
self.notify_location_listener = None
self.notify_active_listener = None
def start(self):
def new_finish(object, async_result):
try:
self.simple = Geoclue.Simple.new_finish(async_result)
except GLib.Error as e:
if e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED):
# TODO: In practice, I cannot get this to occur. Maybe it is KDE's portal
# implementation, but when I deny location permissions in the prompt,
# the failure is a generic DBusError.FAILED, not a permission denied
# failure.
self.state = _Geoclue.State.DENIED
else:
self.state = _Geoclue.State.FAILED
print("Failed to get location", e.message, e.code)
return
self.notify_location_listener = self.simple.connect(
"notify::location", self.notify_location
)
self.state = _Geoclue.State.ACTIVE
# Manually call to initialise the starting location
self.update_location()
Geoclue.Simple.new(
"com.example.helloworld", Geoclue.AccuracyLevel.CITY, None, new_finish
)
def stop(self):
if self.notify_location_listener is not None:
self.simple.disconnect(self.notify_location_listener)
self.state = _Geoclue.State.STOPPED
@property
def state(self):
return self._state
@state.setter
def state(self, state):
self._state = state
if self.state_listener:
self.state_listener(self._state)
def notify_location(self, *args):
self.update_location()
def update_location(self):
self.current_location = self.simple.get_location()
self.location_listener(self.current_location)
class HelloWorld(toga.App):
def startup(self):
"""Construct and show the Toga application.
Usually, you would add your application to a main content box.
We then create a main window (with a name matching the app), and
show the main window.
"""
main_box = toga.Box()
self.label = toga.Label("Waiting...")
self.state_label = toga.Label("Waiting...")
main_box.add(self.label, self.state_label)
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()
self.geoclue = _Geoclue(self.on_location_updated, self.on_state_updated)
self.geoclue.start()
def on_location_updated(self, location):
self.label.text = (
f"lat: {location.props.latitude}; lng: {location.props.longitude}"
)
def on_state_updated(self, state):
self.state_label.text = f"State: {str(state)}"
def main():
return HelloWorld() Obviously there is a bit of work that will be needed to get that into a place that works correctly with toga's When running the application directly on the host, libgeoclue (whatever equivalents on each platform) will need to be in the I have not quite sorted out how permissions should work. When running on the host directly (not Flatpak), there's no permission management, as far as I can tell, you can always just get the location (this is a criticism of Geoclue, if you look around the web for it). Flatpak does isolate things, though, and KDE gives me a prompt for location permission. However, if I deny permission, the error is a generic Some of this permissions stuff is really Linux usability issues. For example, the only way I've found right now to undo the location request response is using Flatseal, despite the fact that KDE has its own Flatpak permissions pane in the system settings. I had a brief look at how Flatseal manages this, I presume it itself requires permissions that would defeat trying to respect the Flatpak sandbox anyway. In the absolute worst case scenario, there is a bad user experience if someone rejects the location permission (they'd need to somehow find out to use Flatseal to undo that rejection). In the easiest case, there are no permissions to consider at all. In that sense, the permission probing is going to be kind of ugly, and with no great way to give feedback to the toga user so they can build a notice into their app regarding permissions. And Toga will need to deduce whether permissions are even relevant based on the environment. Anyway, made a lot of progress today, and feeling very good about being able to get at least the most basic implementation of Toga's |
I've tested on GNOME, and there is no difference in the deny/allow behaviour with respect to the error message emitted. It's a generic failure there as well. I've forced the only other error case I know how to trigger, which is to force the use of MLS, which in turn means Geoclue can't find a location. Unfortunately, Quick update: Yep, seems like it's just something to deal with, at least based on this GNOME maps discussion: https://mail.gnome.org/archives/commits-list/2017-March/msg03936.html. The solution there was to track "starting" as a distinct state when getting a handle on Geoclue. That seems like a good enough start, Toga could eventually configure a timeout around that. |
Alright, a very rough and untested pass at this is available in this branch: https://github.com/sarayourfriend/toga/tree/add/gtk-geoclue-geolocation It is completely untested aside from being based on my initial exploration in a helloworld app, so the next step for me is to actually wire it up to a helloworld app configured to use toga-gtk from my local environment. Overall, I think the code is turning out okay, and GObject has been super fun to learn about 🙂. Feeling optimistic about finding a good endpoint. |
What is the problem or limitation you are having?
Location services are not implemented for Linux.
Describe the solution you'd like
Using libgeoclue, it should be possible to implement a location provider for Linux, or at least for GTK. Qt looks like it might have its own approach to this.
Describe alternatives you've considered
Not implementing location services for Linux.
Additional context
I've done a bit of research on this today, but it's my first foray into Gtk GObject, Gio, all that stuff.
One up-front problem that exists is that Mozilla recently shut down its location service (MLS), so wifi location services won't work for the most common case (a typical consumer laptop). However, it will work fine if a hard-coded location is set on the OS (I managed to get this working locally), and presumably would work if using naive IP-based geolocation or if a real GPS module is available.
Work is underway to address MLS's shutdown, which is causing issues across GNOME's suite of apps: https://gitlab.freedesktop.org/geoclue/geoclue/-/issues/186
Beyond that, I'm having a difficult time sorting out how to watch for location changes. I'm pretty sure this comes down to my lack of experience working with DBus. I've tried to mimic the approach used by redshift but keep getting stuck. Edit: After reading a bit about dbus-python I have a strong suspicion the issue I ran into was due to me not connecting to the GLibMainLoop. There's a bit of discussion about how to integrate with DBus in #907. My main take away from that issue is to avoid a new dependency, if possible, so I'll keep trying to get things going with the regular python-gobject bindings.
Getting a one-off location is very easy using the "Simple" interface to Geoclue:
(I've set my location manually to the example from
man 5 geoclue
, the Statue of Liberty's torch)Just wanted to share what I've uncovered so far. In the worst case scenario, it would be possible to poll the location on an interval using the
Simple
API. Presumably that isn't to be preferred over getting a handle to a DBus signal for location update events. To complicate things, I have no way to test DBus location updates (MLS doesn't work, so I can't just physically move, and it doesn't seem to notify when I change/etc/geolocation
). Again, I'm sure this is just my lack of experience. My hope is someone else might know how to move this forward and documenting this so far will "get the ball rolling" a bit 🙂The text was updated successfully, but these errors were encountered: