Commit fb7f295e authored by Stepan Salenikovich's avatar Stepan Salenikovich

gnome: add autostart functionality

Autostart is enabled by creating a symlink to gnome-ring.desktop
in $XDG_CONFIG_HOME/autostart (this is supported by all XDG
compliant desktop environments.
This is enabled by default and will be done the first time the
client is launched and the setting is checked on every launch.
This patch also adds a GSettings schema for the client to keep
track of the client settings (for now just the autostart enabled).

To support non-installed builds, the gschema is compiled to the
directory of the binary by default. The client first checks for
the .desktop and the compiled gschema in the local directory of
the binary.

A patch is required in the packaging branch to support the new
functionality.

Refs #74056

Change-Id: Ife9c8149e3225ab765dc1d2472ca0fd44ad35691
parent 774ab61a
......@@ -25,8 +25,9 @@ INCLUDE_DIRECTORIES("${PROJECT_BINARY_DIR}")
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# the GResources.cmake module isn't automatically detected
# include custom cmake modules
INCLUDE(${CMAKE_SOURCE_DIR}/cmake/GResources.cmake)
INCLUDE(${CMAKE_SOURCE_DIR}/cmake/GSettings.cmake)
# make sure we're using Qt5
SET(ENABLE_QT5 true)
......@@ -207,6 +208,8 @@ SET( SRC_FILES
src/ringnotify.cpp
src/video/videowindow.h
src/video/videowindow.cpp
src/utils/files.h
src/utils/files.cpp
)
# compile glib resource files to c code
......@@ -224,7 +227,10 @@ GLIB_COMPILE_RESOURCES( GLIB_RESOURCES_RING
# FIND_PACKAGE ( Gettext REQUIRED )
# ADD_SUBDIRECTORY( po )
ADD_EXECUTABLE(gnome-ring ${GLIB_RESOURCES_RING} ${SRC_FILES})
# install and compile glib gsettings schema
add_schema("cx.ring.RingGnome.gschema.xml" GSCHEMA_RING)
ADD_EXECUTABLE(gnome-ring ${GLIB_RESOURCES_RING} ${GSCHEMA_RING} ${SRC_FILES})
IF(NOT ${ENABLE_STATIC} MATCHES false)
TARGET_LINK_LIBRARIES(gnome-ring
......@@ -274,10 +280,23 @@ INSTALL(TARGETS gnome-ring
RUNTIME DESTINATION bin
)
# install .desktop in XDG desktop dir so that it is recognized by the system
INSTALL(FILES ${PROJECT_BINARY_DIR}/gnome-ring.desktop
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications
)
# install .desktop in the gnome-ring data dir, so that it can be copied to the
# autostart dir by the client
INSTALL(FILES ${PROJECT_BINARY_DIR}/gnome-ring.desktop
DESTINATION
${CMAKE_INSTALL_PREFIX}/share/gnome-ring/
PERMISSIONS
WORLD_READ
OWNER_WRITE
OWNER_READ
GROUP_READ
)
INSTALL(FILES pixmaps/ring.svg
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps
)
......@@ -302,7 +321,6 @@ INSTALL(
WORLD_EXECUTE
)
# add a target to generate API documentation with Doxygen
FIND_PACKAGE(Doxygen)
IF(DOXYGEN_FOUND)
......
# CMake macros adapted from those written for Marlin, released under GPLv3:
# https://github.com/ammonkey/marlin/blob/master/cmake/GSettings.cmake
#
# Copyright (C) 2015 Savoir-Faire Linux Inc.
# Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
#
# 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 3 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Additional permission under GNU GPL version 3 section 7:
#
# If you modify this program, or any covered work, by linking or
# combining it with the OpenSSL project's OpenSSL library (or a
# modified version of that library), containing parts covered by the
# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
# grants you additional permission to convey the resulting work.
# Corresponding Source for a non-source form of such a combination
# shall include the source code for the parts of OpenSSL used as well
# as that of the covered work.
#
option (GSETTINGS_LOCALCOMPILE "Compile GSettings schemas locally during build to the location of the binary (no need to run 'make install')" ON)
option (GSETTINGS_PREFIXINSTALL "Install GSettings Schemas relative to the location specified by the install prefix (instead of relative to where GLib is installed)" ON)
option (GSETTINGS_COMPILE "Compile GSettings Schemas after installation" ${GSETTINGS_LOCALCOMPILE})
if (GSETTINGS_LOCALCOMPILE)
message(STATUS "GSettings schemas will be compiled to the build directory during the build.")
endif ()
if (GSETTINGS_PREFIXINSTALL)
message (STATUS "GSettings schemas will be installed relative to the cmake install prefix.")
else ()
message (STATUS "GSettings schemas will be installed relative to the GLib install location.")
endif ()
if (GSETTINGS_COMPILE)
message (STATUS "GSettings shemas will be compiled after install.")
endif ()
macro (add_schema SCHEMA_NAME OUTPUT)
set (PKG_CONFIG_EXECUTABLE pkg-config)
if (GSETTINGS_PREFIXINSTALL)
set (GSETTINGS_DIR "${CMAKE_INSTALL_PREFIX}/share/glib-2.0/schemas/")
else (GSETTINGS_PREFIXINSTALL)
execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} glib-2.0 --variable prefix OUTPUT_VARIABLE _glib_prefix OUTPUT_STRIP_TRAILING_WHITESPACE)
set (GSETTINGS_DIR "${_glib_prefix}/share/glib-2.0/schemas/")
endif (GSETTINGS_PREFIXINSTALL)
# Validate the schema
execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas OUTPUT_VARIABLE _glib_comple_schemas OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process (COMMAND ${_glib_comple_schemas} --dry-run --schema-file=${CMAKE_CURRENT_SOURCE_DIR}/data/${SCHEMA_NAME} ERROR_VARIABLE _schemas_invalid OUTPUT_STRIP_TRAILING_WHITESPACE)
if (_schemas_invalid)
message (SEND_ERROR "Schema validation error: ${_schemas_invalid}")
endif (_schemas_invalid)
if (GSETTINGS_LOCALCOMPILE)
# compile locally during build to not force the user to 'make install'
# when running from the build dir
add_custom_command(
OUTPUT "${PROJECT_BINARY_DIR}/gschemas.compiled"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/data"
COMMAND
"${_glib_comple_schemas}"
ARGS
"${PROJECT_SOURCE_DIR}/data"
"--targetdir=${PROJECT_BINARY_DIR}"
DEPENDS
"${PROJECT_SOURCE_DIR}/data/${SCHEMA_NAME}"
VERBATIM
)
set(${OUTPUT} "${PROJECT_BINARY_DIR}/gschemas.compiled")
endif (GSETTINGS_LOCALCOMPILE)
# Actually install and recompile schemas
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/data/${SCHEMA_NAME} DESTINATION ${GSETTINGS_DIR} OPTIONAL)
if (GSETTINGS_COMPILE)
install (CODE "message (STATUS \"Compiling GSettings schemas\")")
install (CODE "execute_process (COMMAND ${_glib_comple_schemas} ${GSETTINGS_DIR})")
endif ()
endmacro()
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/cx/ring/RingGnome/" id="cx.ring.RingGnome">
<key name="start-on-login" type="b">
<default>true</default>
<summary>Start Ring on login.</summary>
<description>Start Ring on login. Only supported on XDG compliant desktop environments.</description>
</key>
</schema>
</schemalist>
......@@ -7,3 +7,6 @@
#define USE_LIBNOTIFY @USE_LIBNOTIFY@
#define RING_CLIENT_APP_ID "cx.ring.RingGnome"
#define RING_CLIENT_INSTALL "@CMAKE_INSTALL_PREFIX@"
#define RING_DATA_DIR "/share/gnome-ring"
......@@ -32,6 +32,7 @@
#include <gtk/gtk.h>
#include <categorizedhistorymodel.h>
#include "utils/files.h"
struct _GeneralSettingsView
{
......@@ -47,6 +48,11 @@ typedef struct _GeneralSettingsViewPrivate GeneralSettingsViewPrivate;
struct _GeneralSettingsViewPrivate
{
GSettings *settings;
/* Rint settings */
GtkWidget *checkbutton_autostart;
/* history settings */
GtkWidget *adjustment_history_duration;
GtkWidget *button_clear_history;
......@@ -59,6 +65,10 @@ G_DEFINE_TYPE_WITH_PRIVATE(GeneralSettingsView, general_settings_view, GTK_TYPE_
static void
general_settings_view_dispose(GObject *object)
{
GeneralSettingsViewPrivate *priv = GENERAL_SETTINGS_VIEW_GET_PRIVATE(object);
g_clear_object(&priv->settings);
G_OBJECT_CLASS(general_settings_view_parent_class)->dispose(object);
}
......@@ -118,6 +128,13 @@ general_settings_view_init(GeneralSettingsView *self)
GeneralSettingsViewPrivate *priv = GENERAL_SETTINGS_VIEW_GET_PRIVATE(self);
priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
/* bind auto startup option to gsettings */
g_settings_bind(priv->settings, "start-on-login",
priv->checkbutton_autostart, "active",
G_SETTINGS_BIND_DEFAULT);
/* history limit */
gtk_adjustment_set_value(GTK_ADJUSTMENT(priv->adjustment_history_duration),
CategorizedHistoryModel::instance()->historyLimit());
......@@ -136,6 +153,7 @@ general_settings_view_class_init(GeneralSettingsViewClass *klass)
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
"/cx/ring/RingGnome/generalsettingsview.ui");
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_autostart);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, adjustment_history_duration);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, button_clear_history);
}
......
......@@ -52,10 +52,11 @@
#include "delegates/pixbufdelegate.h"
#include "ringnotify.h"
#include "config.h"
#include "utils/files.h"
struct _RingClientClass
{
GtkApplicationClass parent_class;
GtkApplicationClass parent_class;
};
struct _RingClient
......@@ -69,6 +70,9 @@ struct _RingClientPrivate {
/* args */
int argc;
char **argv;
GSettings *settings;
/* main window */
GtkWidget *win;
/* for libRingclient */
......@@ -171,12 +175,22 @@ activate_action(GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpoint
uam << a;
}
static void
autostart_toggled(GSettings *settings, G_GNUC_UNUSED gchar *key, G_GNUC_UNUSED gpointer user_data)
{
autostart_symlink(g_settings_get_boolean(settings, "start-on-login"));
}
static void
ring_client_startup(GApplication *app)
{
RingClient *client = RING_CLIENT(app);
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
/* make sure that the system corresponds to the autostart setting */
autostart_symlink(g_settings_get_boolean(priv->settings, "start-on-login"));
g_signal_connect(priv->settings, "changed::start-on-login", G_CALLBACK(autostart_toggled), NULL);
/* init clutter */
int clutter_error;
if ((clutter_error = gtk_clutter_init(&priv->argc, &priv->argv)) != CLUTTER_INIT_SUCCESS) {
......@@ -346,6 +360,8 @@ ring_client_shutdown(GApplication *app)
/* free the copied cmd line args */
g_strfreev(priv->argv);
g_clear_object(&priv->settings);
ring_notify_uninit();
/* Chain up to the parent class */
......@@ -357,10 +373,10 @@ ring_client_init(RingClient *self)
{
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(self);
/* init widget */
priv->win = NULL;
priv->qtapp = NULL;
priv->cancellable = g_cancellable_new();
priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
#if GLIB_CHECK_VERSION(2,40,0)
/* add custom cmd line options */
......
......@@ -32,9 +32,11 @@
#define RING_CLIENT_H_
#include <gtk/gtk.h>
#include "config.h"
G_BEGIN_DECLS
#define RING_GSETTINGS_SCHEMA RING_CLIENT_APP_ID
#define RING_CLIENT_TYPE (ring_client_get_type())
#define RING_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), RING_CLIENT_TYPE, RingClient))
......
#include "files.h"
#include <memory>
#include "config.h"
#include <unistd.h> // to create symlinks
#include <errno.h>
#include <glib/gstdio.h> // for glib file utils
#include <string.h>
void
autostart_symlink(gboolean autostart)
{
/* autostart is enabled by creating a symlink to gnome-ring.desktop in
* $XDG_CONFIG_HOME/autostart (by default ~/.config/autostart)
* and removing it to disable autostart
*/
GError *error = NULL;
gchar *autostart_path = g_strconcat(g_get_user_config_dir(), "/autostart/gnome-ring.desktop", NULL);
if (autostart) {
g_debug("enabling autostart");
gchar *desktop_path = NULL;
/* find where the .desktop file is by checking the following dirs in order
* - /usr/<data dir>
* - /usr/local/<data dir>
* - install data dir (check this last, as it might not be where the
* bin is actually isntalled on the system)
*
* note: no point in checking the local bin dir, even if the .desktop is
* there, if the binary is not installed on the system, the .desktop will
* not find it
*/
int num_paths = 3;
gchar *desktop_paths[num_paths];
desktop_paths[0] = g_strconcat("/usr", RING_DATA_DIR, "/gnome-ring.desktop", NULL);
desktop_paths[1] = g_strconcat("/usr/local", RING_DATA_DIR, "/gnome-ring.desktop", NULL);
desktop_paths[2] = g_strconcat(RING_CLIENT_INSTALL, RING_DATA_DIR, "/gnome-ring.desktop", NULL);
for (int i = 0; i < num_paths && !desktop_path; ++i) {
g_debug("checking %s", desktop_paths[i]);
if (g_file_test(desktop_paths[i], G_FILE_TEST_IS_REGULAR))
desktop_path = desktop_paths[i];
}
if (!desktop_path) {
/* could not find where the .desktop is... default to the install one */
desktop_path = desktop_paths[1];
g_warning("cannot locate '%s', will try to create a symlink to it anyways", desktop_path);
}
/* we want autostart_path to be a symlink to the current desktop_path
* if it is not, delete it and create one */
gboolean symlink_to_desktop = FALSE;
if (g_file_test(autostart_path, G_FILE_TEST_IS_SYMLINK)) {
/* is symlink, check if its to the right file */
gchar *current_link = g_file_read_link(autostart_path, &error);
if (error) {
g_warning("could not read contents of symlink '%s': %s",autostart_path, error->message);
g_clear_error(&error);
}
/* compare even if error occurs, as function handles nullptr */
if (g_strcmp0(current_link, desktop_path) == 0) {
g_debug("'%s' is already a symlink to '%s'", autostart_path, desktop_path);
symlink_to_desktop = TRUE;
} else {
g_debug("'%s' exists but does not point to '%s', instead: '%s'", autostart_path, desktop_path, current_link);
/* we need to delete it */
if (g_remove(autostart_path) != 0) {
g_warning("could not remove '%s'", autostart_path);
}
}
g_free(current_link);
}
if (!symlink_to_desktop) {
g_debug("creating symlink");
/* make sure the directory exists */
gchar *autostart_dir = g_strconcat(g_get_user_config_dir(), "/autostart", NULL);
if (g_mkdir_with_parents(autostart_dir, 0700) != 0)
g_warning("'%s' dir doesn't exist and could not be created", autostart_dir);
g_free(autostart_dir);
if (symlink(desktop_path, autostart_path) != 0) {
g_warning("could not create symlink: %s", strerror(errno));
}
}
for (int i = 0; i < num_paths; ++i) {
g_free(desktop_paths[i]);
}
} else {
g_debug("disabling autostart");
/* need to remove symlink or .desktop, if it exists
* G_FILE_TEST_IS_REGULAR will test for both file and symlink, since it
* follows symlinks */
if (g_file_test(autostart_path, G_FILE_TEST_IS_REGULAR)) {
g_debug("'%s' exists, removing", autostart_path);
if (g_remove(autostart_path) != 0) {
g_warning("could not remove '%s'", autostart_path);
}
} else {
g_debug("'%s' doesn't exist, nothing to do", autostart_path);
}
}
g_free(autostart_path);
}
GSettingsSchema *
get_ring_schema()
{
static std::unique_ptr<GSettingsSchema, decltype(g_settings_schema_unref )&>
ring_schema(nullptr, g_settings_schema_unref);
if (ring_schema.get() == nullptr) {
GSettingsSchema *schema = NULL;
/* find gschema.compiled by checking the following dirs in order:
* - current bin dir
* - install data dir
* - default dir
* note that the install and default dir may be the same
*/
GError *error = NULL;
/* try local first */
g_debug("looking for schema locally");
gchar *schema_dir_local = g_strconcat(".", NULL);
GSettingsSchemaSource *schema_source_local = NULL;
schema_source_local = g_settings_schema_source_new_from_directory(
schema_dir_local,
g_settings_schema_source_get_default(),
FALSE,
&error);
if (!error) {
schema = g_settings_schema_source_lookup(schema_source_local,
RING_CLIENT_APP_ID,
TRUE);
g_settings_schema_source_unref(schema_source_local);
} else {
g_debug("error looking up ring schema locally: %s", error->message);
g_clear_error(&error);
}
g_free(schema_dir_local);
if (!schema) {
/* try install dir */
g_debug("looking for schema in insall dir");
gchar *schema_dir_install = g_strconcat(RING_CLIENT_INSTALL, "/share/glib-2.0/schemas", NULL);
GSettingsSchemaSource *schema_source_install = NULL;
schema_source_install = g_settings_schema_source_new_from_directory(
schema_dir_install,
g_settings_schema_source_get_default(),
TRUE,
&error);
if (!error) {
schema = g_settings_schema_source_lookup(schema_source_install,
RING_CLIENT_APP_ID,
TRUE);
g_settings_schema_source_unref(schema_source_install);
} else {
g_debug("error looking up ring schema in install dir: %s", error->message);
g_clear_error(&error);
}
g_free(schema_dir_install);
}
if (!schema) {
/* try default dir */
g_debug("looking for schema in default dir");
schema = g_settings_schema_source_lookup(g_settings_schema_source_get_default(),
RING_CLIENT_APP_ID,
TRUE);
}
ring_schema.reset(schema);
}
return ring_schema.get();
}
/*
* Copyright (C) 2015 Savoir-Faire Linux Inc.
* Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
*
* 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 3 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#ifndef _FILES_H
#define _FILES_H
#include <gio/gio.h>
G_BEGIN_DECLS
void autostart_symlink(gboolean autostart);
GSettingsSchema *get_ring_schema();
G_END_DECLS
#endif /* _FILES_H */
......@@ -19,13 +19,59 @@
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<!-- start ring settings -->
<child>
<object class="GtkFrame" id="frame_ring_settings">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<!-- start frame label -->
<child type="label">
<object class="GtkLabel" id="label_ring_settings">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Ring Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
<!-- end frame label -->
<!-- start box ring settings-->
<child>
<object class="GtkBox" id="box_ring_settings">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="margin_left">10</property>
<property name="margin_top">10</property>
<child>
<object class="GtkCheckButton" id="checkbutton_autostart">
<property name="label" translatable="yes">Start Ring on login</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
</child>
</object>
</child>
<!-- end box ring settings -->
</object>
</child>
<!-- start history settings -->
<child>
<object class="GtkFrame" id="frame_history_settings">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<!-- start frame label -->
<child type="label">
<object class="GtkLabel" id="label_history_settings">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;History Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
<!-- end frame label -->
<!-- start grid history settings-->
<child>
<object class="GtkGrid" id="grid_history_settings">
......@@ -85,16 +131,6 @@
</object>
</child>
<!-- end grid history settings -->
<!-- start frame label -->
<child type="label">
<object class="GtkLabel" id="label_history_settings">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;History Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
<!-- end frame label -->
</object>
</child>
<!-- end history settings -->
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment