1 /*
2     This file is part of darktable,
3     Copyright (C) 2013-2020 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "common/dbus.h"
20 #include "common/darktable.h"
21 #include "control/conf.h"
22 #include "control/control.h"
23 #include "common/file_location.h"
24 
25 #ifdef USE_LUA
26 #include "lua/call.h"
27 #endif
28 
29 /* Introspection data for the service we are exporting */
30 static const gchar introspection_xml[] = "<node>"
31                                          "  <interface name='org.darktable.service.Remote'>"
32                                          "    <method name='Quit' />"
33                                          "    <method name='Open'>"
34                                          "      <arg type='s' name='FileName' direction='in'/>"
35                                          "      <arg type='i' name='id' direction='out' />"
36                                          "    </method>"
37 #ifdef USE_LUA
38                                          "    <method name='Lua'>"
39                                          "      <arg type='s' name='Command' direction='in'/>"
40                                          "      <arg type='s' name='Result' direction='out' />"
41                                          "    </method>"
42 #endif
43                                          "    <property type='s' name='DataDir' access='read'/>"
44                                          "    <property type='s' name='ConfigDir' access='read'/>"
45                                          "    <property type='b' name='LuaEnabled' access='read'/>"
46                                          "  </interface>"
47                                          "</node>";
48 
49 
50 #ifdef USE_LUA
dbus_lua_call_finished(lua_State * L,int result,void * data)51 static void dbus_lua_call_finished(lua_State* L,int result,void* data)
52 {
53   GDBusMethodInvocation *invocation = (GDBusMethodInvocation*)data;
54   if(result == LUA_OK)
55   {
56     if(lua_isnil(L, -1))
57     {
58       g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", ""));
59     }
60     else
61     {
62       const char *checkres = luaL_checkstring(L, -1);
63       g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", checkres));
64     }
65   }
66   else
67   {
68     const char *msg = luaL_checkstring(L, -1);
69     g_dbus_method_invocation_return_dbus_error(invocation, "org.darktable.Error.LuaError", msg);
70     dt_lua_check_print_error(L,result);
71   }
72 }
73 #endif
74 
_handle_method_call(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)75 static void _handle_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path,
76                                 const gchar *interface_name, const gchar *method_name, GVariant *parameters,
77                                 GDBusMethodInvocation *invocation, gpointer user_data)
78 {
79   if(!g_strcmp0(method_name, "Quit"))
80   {
81     g_dbus_method_invocation_return_value(invocation, NULL);
82     dt_control_quit();
83   }
84   else if(!g_strcmp0(method_name, "Open"))
85   {
86     const gchar *filename;
87     g_variant_get(parameters, "(&s)", &filename);
88     int32_t id = dt_load_from_string(filename, TRUE, NULL);
89     g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", id));
90   }
91 #ifdef USE_LUA
92   else if(!g_strcmp0(method_name, "Lua"))
93   {
94     const gchar *command;
95     g_variant_get(parameters, "(&s)", &command);
96     dt_lua_async_call_string(command, 1,dbus_lua_call_finished,invocation);
97     // we don't finish the invocation, the async task will do this for us
98   }
99 #endif
100 }
101 
102 // TODO: expose the conf? partly? completely?
103 
_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)104 static GVariant *_handle_get_property(GDBusConnection *connection, const gchar *sender,
105                                       const gchar *object_path, const gchar *interface_name,
106                                       const gchar *property_name, GError **error, gpointer user_data)
107 {
108   GVariant *ret;
109 
110   ret = NULL;
111   if(!g_strcmp0(property_name, "DataDir"))
112   {
113     gchar datadir[PATH_MAX] = { 0 };
114     dt_loc_get_datadir(datadir, sizeof(datadir));
115     ret = g_variant_new_string(datadir);
116   }
117   else if(!g_strcmp0(property_name, "ConfigDir"))
118   {
119     gchar configdir[PATH_MAX] = { 0 };
120     dt_loc_get_user_config_dir(configdir, sizeof(configdir));
121     ret = g_variant_new_string(configdir);
122   }
123   else if(!g_strcmp0(property_name, "LuaEnabled"))
124   {
125 #ifdef USE_LUA
126     ret = g_variant_new_boolean(TRUE);
127 #else
128     ret = g_variant_new_boolean(FALSE);
129 #endif
130   }
131   return ret;
132 }
133 
134 // static gboolean
135 // _handle_set_property(GDBusConnection  *connection,
136 //                      const gchar      *sender,
137 //                      const gchar      *object_path,
138 //                      const gchar      *interface_name,
139 //                      const gchar      *property_name,
140 //                      GVariant         *value,
141 //                      GError          **error,
142 //                      gpointer          user_data)
143 // {
144 //   return *error == NULL;
145 // }
146 
147 static const GDBusInterfaceVTable interface_vtable = { _handle_method_call, _handle_get_property,
148                                                        //   _handle_set_property
149                                                        NULL };
150 
_on_bus_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)151 static void _on_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data)
152 {
153   dt_dbus_t *dbus = (dt_dbus_t *)user_data;
154 
155   dbus->registration_id
156       = g_dbus_connection_register_object(connection, "/darktable", dbus->introspection_data->interfaces[0],
157                                           &interface_vtable, dbus, /* user_data */
158                                           NULL,                    /* user_data_free_func */
159                                           NULL);                   /* GError** */
160 
161   if(dbus->registration_id == 0)
162     dbus->connected
163         = 0; // technically we are connected, but we are not exporting anything. or something like that
164 }
165 
_on_name_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)166 static void _on_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data)
167 {
168   dt_dbus_t *dbus = (dt_dbus_t *)user_data;
169   dbus->connected = 1;
170 }
171 
_on_name_lost(GDBusConnection * connection,const gchar * name,gpointer user_data)172 static void _on_name_lost(GDBusConnection *connection, const gchar *name, gpointer user_data)
173 {
174   dt_dbus_t *dbus = (dt_dbus_t *)user_data;
175   dbus->connected = 0;
176 }
177 
dt_dbus_init()178 struct dt_dbus_t *dt_dbus_init()
179 {
180   dt_dbus_t *dbus = (dt_dbus_t *)g_malloc0(sizeof(dt_dbus_t));
181   if(!dbus) return NULL;
182 
183   dbus->introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
184 
185   if(dbus->introspection_data == NULL) return dbus;
186 
187   dbus->owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
188                                   "org.darktable.service", // FIXME
189                                   G_BUS_NAME_OWNER_FLAGS_NONE, _on_bus_acquired, _on_name_acquired,
190                                   _on_name_lost, dbus, NULL);
191 
192   dbus->dbus_connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
193   g_object_set(G_OBJECT(dbus->dbus_connection), "exit-on-close", FALSE, (gchar *)0);
194 
195   return dbus;
196 }
197 
dt_dbus_destroy(const dt_dbus_t * dbus)198 void dt_dbus_destroy(const dt_dbus_t *dbus)
199 {
200   g_bus_unown_name(dbus->owner_id);
201   g_dbus_node_info_unref(dbus->introspection_data);
202   g_object_unref(G_OBJECT(dbus->dbus_connection));
203 
204   g_free((dt_dbus_t *)dbus);
205 }
206 
dt_dbus_connected(const dt_dbus_t * dbus)207 gboolean dt_dbus_connected(const dt_dbus_t *dbus)
208 {
209   return dbus->connected;
210 }
211 
212 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
213 // vim: shiftwidth=2 expandtab tabstop=2 cindent
214 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
215