2018-12-11 14:19:28 -05:00
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright ( C ) 2018 Red Hat , Inc
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
*
* Author : Matthias Clasen < mclasen @ redhat . com >
*/
2023-06-04 10:12:28 +02:00
# include "cc-location-page.h"
2018-12-11 14:19:28 -05:00
# include "cc-util.h"
# include <gio/gdesktopappinfo.h>
# include <glib/gi18n.h>
# define LOCATION_ENABLED "enabled"
2021-08-13 12:01:16 -03:00
# define APP_PERMISSIONS_TABLE "location"
# define APP_PERMISSIONS_ID "location"
2018-12-11 14:19:28 -05:00
2023-06-04 10:12:28 +02:00
struct _CcLocationPage
2018-12-11 14:19:28 -05:00
{
2023-06-04 10:12:28 +02:00
AdwNavigationPage parent_instance ;
2018-12-11 14:19:28 -05:00
2023-12-14 17:48:20 -03:00
AdwPreferencesPage * location_services_page ;
2018-12-11 14:19:28 -05:00
GtkListBox * location_apps_list_box ;
2023-04-01 01:11:22 +02:00
AdwSwitchRow * location_row ;
2018-12-11 14:19:28 -05:00
GSettings * location_settings ;
2023-06-04 10:12:28 +02:00
GCancellable * cancellable ;
2018-12-11 14:19:28 -05:00
GDBusProxy * perm_store ;
GVariant * location_apps_perms ;
GVariant * location_apps_data ;
GHashTable * location_app_switches ;
GtkSizeGroup * location_icon_size_group ;
} ;
2023-06-04 10:12:28 +02:00
G_DEFINE_TYPE ( CcLocationPage , cc_location_page , ADW_TYPE_NAVIGATION_PAGE )
2018-12-11 14:19:28 -05:00
typedef struct
{
2023-06-04 10:12:28 +02:00
CcLocationPage * self ;
GtkWidget * widget ;
gchar * app_id ;
gboolean changing_state ;
gboolean pending_state ;
2018-12-11 14:19:28 -05:00
} LocationAppStateData ;
static void
location_app_state_data_free ( LocationAppStateData * data )
{
g_free ( data - > app_id ) ;
g_slice_free ( LocationAppStateData , data ) ;
}
static void
on_perm_store_set_done ( GObject * source_object ,
GAsyncResult * res ,
gpointer user_data )
{
g_autoptr ( GVariant ) results = NULL ;
g_autoptr ( GError ) error = NULL ;
LocationAppStateData * data ;
results = g_dbus_proxy_call_finish ( G_DBUS_PROXY ( source_object ) ,
res ,
& error ) ;
if ( results = = NULL )
{
if ( ! g_error_matches ( error , G_IO_ERROR , G_IO_ERROR_CANCELLED ) )
g_warning ( " Failed to store permissions: %s " , error - > message ) ;
return ;
}
data = ( LocationAppStateData * ) user_data ;
data - > changing_state = FALSE ;
gtk_switch_set_state ( GTK_SWITCH ( data - > widget ) , data - > pending_state ) ;
}
static gboolean
on_location_app_state_set ( GtkSwitch * widget ,
gboolean state ,
gpointer user_data )
{
LocationAppStateData * data = ( LocationAppStateData * ) user_data ;
2023-06-04 10:12:28 +02:00
CcLocationPage * self = data - > self ;
2018-12-11 14:19:28 -05:00
GVariant * params ;
GVariantIter iter ;
2019-11-21 21:06:36 +13:00
const gchar * key ;
2018-12-11 14:19:28 -05:00
gchar * * value ;
GVariantBuilder builder ;
2023-02-07 11:54:01 +01:00
gboolean active_location ;
2018-12-11 14:19:28 -05:00
if ( data - > changing_state )
return TRUE ;
2023-02-07 11:54:01 +01:00
active_location = g_settings_get_boolean ( self - > location_settings ,
LOCATION_ENABLED ) ;
2018-12-11 14:19:28 -05:00
data - > changing_state = TRUE ;
2023-02-07 11:54:01 +01:00
data - > pending_state = active_location & & state ;
2018-12-11 14:19:28 -05:00
g_variant_iter_init ( & iter , self - > location_apps_perms ) ;
g_variant_builder_init ( & builder , G_VARIANT_TYPE_ARRAY ) ;
2019-11-21 21:06:36 +13:00
while ( g_variant_iter_loop ( & iter , " {&s^a&s} " , & key , & value ) )
2018-12-11 14:19:28 -05:00
{
/* It's OK to drop the entry if it's not in expected format */
if ( g_strv_length ( value ) < 2 )
continue ;
if ( g_strcmp0 ( data - > app_id , key ) = = 0 )
2019-11-21 21:06:36 +13:00
value [ 0 ] = state ? " EXACT " : " NONE " ;
2018-12-11 14:19:28 -05:00
g_variant_builder_add ( & builder , " {s^as} " , key , value ) ;
}
params = g_variant_new ( " (sbsa{sas}v) " ,
APP_PERMISSIONS_TABLE ,
TRUE ,
APP_PERMISSIONS_ID ,
& builder ,
self - > location_apps_data ) ;
g_dbus_proxy_call ( self - > perm_store ,
" Set " ,
params ,
G_DBUS_CALL_FLAGS_NONE ,
- 1 ,
2023-06-04 10:12:28 +02:00
self - > cancellable ,
2018-12-11 14:19:28 -05:00
on_perm_store_set_done ,
data ) ;
return TRUE ;
}
2023-02-07 11:54:01 +01:00
static gboolean
update_app_switch_state ( GValue * value ,
GVariant * variant ,
gpointer data )
{
GtkSwitch * w = GTK_SWITCH ( data ) ;
gboolean active_location ;
gboolean active_app ;
gboolean state ;
active_location = g_variant_get_boolean ( variant ) ;
active_app = gtk_switch_get_active ( w ) ;
state = active_location & & active_app ;
g_value_set_boolean ( value , state ) ;
return TRUE ;
}
2018-12-11 14:19:28 -05:00
static void
2023-06-04 10:12:28 +02:00
add_location_app ( CcLocationPage * self ,
const gchar * app_id ,
gboolean enabled ,
gint64 last_used )
2018-12-11 14:19:28 -05:00
{
LocationAppStateData * data ;
GDesktopAppInfo * app_info ;
GDateTime * t ;
2022-01-14 22:52:56 -08:00
GtkWidget * row , * w ;
2018-12-11 14:19:28 -05:00
GIcon * icon ;
gchar * last_used_str ;
gchar * desktop_id ;
w = g_hash_table_lookup ( self - > location_app_switches , app_id ) ;
if ( w ! = NULL )
{
gtk_switch_set_active ( GTK_SWITCH ( w ) , enabled ) ;
return ;
}
desktop_id = g_strdup_printf ( " %s.desktop " , app_id ) ;
app_info = g_desktop_app_info_new ( desktop_id ) ;
g_free ( desktop_id ) ;
if ( app_info = = NULL )
return ;
2022-01-14 22:52:56 -08:00
row = adw_action_row_new ( ) ;
2021-10-20 17:08:28 -03:00
gtk_list_box_append ( self - > location_apps_list_box , row ) ;
2018-12-11 14:19:28 -05:00
icon = g_app_info_get_icon ( G_APP_INFO ( app_info ) ) ;
2021-10-20 17:08:28 -03:00
w = gtk_image_new_from_gicon ( icon ) ;
2023-11-30 11:17:12 +01:00
gtk_image_set_icon_size ( GTK_IMAGE ( w ) , GTK_ICON_SIZE_LARGE ) ;
2018-12-11 14:19:28 -05:00
gtk_widget_set_valign ( w , GTK_ALIGN_CENTER ) ;
gtk_size_group_add_widget ( self - > location_icon_size_group , w ) ;
2022-01-14 22:52:56 -08:00
adw_action_row_add_prefix ( ADW_ACTION_ROW ( row ) , w ) ;
2018-12-11 14:19:28 -05:00
2022-01-14 22:52:56 -08:00
adw_preferences_row_set_title ( ADW_PREFERENCES_ROW ( row ) ,
g_app_info_get_name ( G_APP_INFO ( app_info ) ) ) ;
2018-12-11 14:19:28 -05:00
t = g_date_time_new_from_unix_utc ( last_used ) ;
last_used_str = cc_util_get_smart_date ( t ) ;
w = gtk_label_new ( last_used_str ) ;
g_free ( last_used_str ) ;
2023-01-03 12:27:42 +01:00
gtk_widget_add_css_class ( w , " dim-label " ) ;
2018-12-11 14:19:28 -05:00
gtk_widget_set_margin_start ( w , 12 ) ;
gtk_widget_set_margin_end ( w , 12 ) ;
gtk_widget_set_valign ( w , GTK_ALIGN_CENTER ) ;
2022-01-14 22:52:56 -08:00
adw_action_row_add_suffix ( ADW_ACTION_ROW ( row ) , w ) ;
2018-12-11 14:19:28 -05:00
w = gtk_switch_new ( ) ;
gtk_switch_set_active ( GTK_SWITCH ( w ) , enabled ) ;
gtk_widget_set_valign ( w , GTK_ALIGN_CENTER ) ;
2022-01-14 22:52:56 -08:00
adw_action_row_add_suffix ( ADW_ACTION_ROW ( row ) , w ) ;
2023-02-07 11:54:01 +01:00
g_settings_bind_with_mapping ( self - > location_settings ,
LOCATION_ENABLED ,
w ,
" state " ,
G_SETTINGS_BIND_GET ,
update_app_switch_state ,
NULL ,
g_object_ref ( w ) ,
g_object_unref ) ;
2018-12-11 14:19:28 -05:00
g_hash_table_insert ( self - > location_app_switches ,
g_strdup ( app_id ) ,
g_object_ref ( w ) ) ;
data = g_slice_new ( LocationAppStateData ) ;
data - > self = self ;
data - > app_id = g_strdup ( app_id ) ;
data - > widget = w ;
data - > changing_state = FALSE ;
g_signal_connect_data ( w ,
" state-set " ,
G_CALLBACK ( on_location_app_state_set ) ,
data ,
( GClosureNotify ) location_app_state_data_free ,
0 ) ;
}
/* Steals permissions and permissions_data references */
static void
2023-06-04 10:12:28 +02:00
update_perm_store ( CcLocationPage * self ,
GVariant * permissions ,
GVariant * permissions_data )
2018-12-11 14:19:28 -05:00
{
GVariantIter iter ;
2019-11-21 21:06:36 +13:00
const gchar * key ;
2018-12-11 14:19:28 -05:00
gchar * * value ;
g_clear_pointer ( & self - > location_apps_perms , g_variant_unref ) ;
self - > location_apps_perms = permissions ;
g_clear_pointer ( & self - > location_apps_data , g_variant_unref ) ;
self - > location_apps_data = permissions_data ;
g_variant_iter_init ( & iter , permissions ) ;
2019-11-21 21:06:36 +13:00
while ( g_variant_iter_loop ( & iter , " {&s^a&s} " , & key , & value ) )
2018-12-11 14:19:28 -05:00
{
gboolean enabled ;
gint64 last_used ;
if ( g_strv_length ( value ) < 2 )
{
g_debug ( " Permissions for %s in incorrect format, ignoring.. " , key ) ;
continue ;
}
enabled = ( g_strcmp0 ( value [ 0 ] , " NONE " ) ! = 0 ) ;
last_used = g_ascii_strtoll ( value [ 1 ] , NULL , 10 ) ;
add_location_app ( self , key , enabled , last_used ) ;
}
}
static void
on_perm_store_signal ( GDBusProxy * proxy ,
gchar * sender_name ,
gchar * signal_name ,
GVariant * parameters ,
gpointer user_data )
{
GVariant * permissions , * permissions_data ;
if ( g_strcmp0 ( signal_name , " Changed " ) ! = 0 )
return ;
permissions = g_variant_get_child_value ( parameters , 4 ) ;
permissions_data = g_variant_get_child_value ( parameters , 3 ) ;
update_perm_store ( user_data , permissions , permissions_data ) ;
}
static void
on_perm_store_lookup_done ( GObject * source_object ,
GAsyncResult * res ,
gpointer user_data )
{
g_autoptr ( GError ) error = NULL ;
GVariant * ret , * permissions , * permissions_data ;
ret = g_dbus_proxy_call_finish ( G_DBUS_PROXY ( source_object ) ,
res ,
& error ) ;
if ( ret = = NULL )
{
if ( ! g_error_matches ( error , G_IO_ERROR , G_IO_ERROR_CANCELLED ) )
g_warning ( " Failed fetch permissions from flatpak permission store: %s " ,
error - > message ) ;
return ;
}
permissions = g_variant_get_child_value ( ret , 0 ) ;
permissions_data = g_variant_get_child_value ( ret , 1 ) ;
update_perm_store ( user_data , permissions , permissions_data ) ;
g_signal_connect_object ( source_object ,
" g-signal " ,
G_CALLBACK ( on_perm_store_signal ) ,
user_data ,
0 ) ;
}
static void
on_perm_store_ready ( GObject * source_object ,
GAsyncResult * res ,
gpointer user_data )
{
g_autoptr ( GError ) error = NULL ;
2023-06-04 10:12:28 +02:00
CcLocationPage * self ;
2018-12-11 14:19:28 -05:00
GDBusProxy * proxy ;
GVariant * params ;
proxy = g_dbus_proxy_new_for_bus_finish ( res , & error ) ;
if ( proxy = = NULL )
{
if ( ! g_error_matches ( error , G_IO_ERROR , G_IO_ERROR_CANCELLED ) )
g_warning ( " Failed to connect to flatpak permission store: %s " ,
error - > message ) ;
return ;
}
self = user_data ;
self - > perm_store = proxy ;
params = g_variant_new ( " (ss) " ,
APP_PERMISSIONS_TABLE ,
APP_PERMISSIONS_ID ) ;
g_dbus_proxy_call ( self - > perm_store ,
" Lookup " ,
params ,
G_DBUS_CALL_FLAGS_NONE ,
- 1 ,
2023-06-04 10:12:28 +02:00
self - > cancellable ,
2018-12-11 14:19:28 -05:00
on_perm_store_lookup_done ,
self ) ;
}
static void
2023-06-04 10:12:28 +02:00
cc_location_page_finalize ( GObject * object )
2018-12-11 14:19:28 -05:00
{
2023-06-04 10:12:28 +02:00
CcLocationPage * self = CC_LOCATION_PAGE ( object ) ;
g_cancellable_cancel ( self - > cancellable ) ;
g_clear_object ( & self - > cancellable ) ;
2018-12-11 14:19:28 -05:00
g_clear_object ( & self - > location_settings ) ;
g_clear_object ( & self - > perm_store ) ;
g_clear_object ( & self - > location_icon_size_group ) ;
g_clear_pointer ( & self - > location_apps_perms , g_variant_unref ) ;
g_clear_pointer ( & self - > location_apps_data , g_variant_unref ) ;
g_clear_pointer ( & self - > location_app_switches , g_hash_table_unref ) ;
2023-06-04 10:12:28 +02:00
G_OBJECT_CLASS ( cc_location_page_parent_class ) - > finalize ( object ) ;
2018-12-11 14:19:28 -05:00
}
static void
2023-06-04 10:12:28 +02:00
cc_location_page_class_init ( CcLocationPageClass * klass )
2018-12-11 14:19:28 -05:00
{
GtkWidgetClass * widget_class = GTK_WIDGET_CLASS ( klass ) ;
GObjectClass * object_class = G_OBJECT_CLASS ( klass ) ;
2023-06-04 10:12:28 +02:00
object_class - > finalize = cc_location_page_finalize ;
2018-12-11 14:19:28 -05:00
2023-06-04 10:12:28 +02:00
gtk_widget_class_set_template_from_resource ( widget_class , " /org/gnome/control-center/privacy/cc-location-page.ui " ) ;
2018-12-11 14:19:28 -05:00
2023-12-14 17:48:20 -03:00
gtk_widget_class_bind_template_child ( widget_class , CcLocationPage , location_services_page ) ;
2023-06-04 10:12:28 +02:00
gtk_widget_class_bind_template_child ( widget_class , CcLocationPage , location_apps_list_box ) ;
gtk_widget_class_bind_template_child ( widget_class , CcLocationPage , location_row ) ;
2018-12-11 14:19:28 -05:00
}
static void
2023-06-04 10:12:28 +02:00
cc_location_page_init ( CcLocationPage * self )
2018-12-11 14:19:28 -05:00
{
2023-12-14 17:48:20 -03:00
g_autofree gchar * privacy_policy_link = NULL ;
g_autofree gchar * page_description = NULL ;
2018-12-11 14:19:28 -05:00
gtk_widget_init_template ( GTK_WIDGET ( self ) ) ;
self - > location_icon_size_group = gtk_size_group_new ( GTK_SIZE_GROUP_BOTH ) ;
self - > location_settings = g_settings_new ( " org.gnome.system.location " ) ;
2023-06-04 10:12:28 +02:00
self - > cancellable = g_cancellable_new ( ) ;
2023-12-14 17:48:20 -03:00
/* Translators: This will be presented as the text of a link to the privacy policy */
privacy_policy_link = g_strdup_printf ( " <a href='https://location.services.mozilla.com/privacy'>%s</a> " , _ ( " Learn about what data is collected, and how it is used. " ) ) ;
/* Translators: %s is a link to the privacy policy with the label "Learn about what data is collected, and how it is used." */
page_description = g_strdup_printf ( _ ( " Location services use GPS, Wi-Fi and cellular connections to determine the approximate location of this device. \n \n %s " ) , privacy_policy_link ) ;
adw_preferences_page_set_description ( self - > location_services_page , page_description ) ;
2022-01-19 18:38:32 -03:00
g_settings_bind ( self - > location_settings ,
LOCATION_ENABLED ,
2023-01-21 17:27:23 +01:00
self - > location_row ,
2022-01-19 18:38:32 -03:00
" active " ,
G_SETTINGS_BIND_DEFAULT ) ;
2018-12-11 14:19:28 -05:00
self - > location_app_switches = g_hash_table_new_full ( g_str_hash ,
g_str_equal ,
g_free ,
g_object_unref ) ;
g_dbus_proxy_new_for_bus ( G_BUS_TYPE_SESSION ,
G_DBUS_PROXY_FLAGS_NONE ,
NULL ,
" org.freedesktop.impl.portal.PermissionStore " ,
" /org/freedesktop/impl/portal/PermissionStore " ,
" org.freedesktop.impl.portal.PermissionStore " ,
2023-06-04 10:12:28 +02:00
self - > cancellable ,
2018-12-11 14:19:28 -05:00
on_perm_store_ready ,
self ) ;
}