1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * Copyright (C) Bastien Nocera 2019 <hadess@hadess.net>
4 * Copyright (C) Simon Wenner 2011 <simon@wenner.ch>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
19 */
20
21 #include "config.h"
22
23 #include <unistd.h>
24
25 #include <glib.h>
26 #include <glib-object.h>
27 #include <glib/gi18n-lib.h>
28 #include <glib/gstdio.h>
29 #include <gmodule.h>
30 #include <errno.h>
31 #include <libpeas/peas-extension-base.h>
32 #include <libpeas/peas-object-module.h>
33 #include <libpeas/peas-activatable.h>
34
35 #include "totem-plugin.h"
36 #include "totem-interface.h"
37 #include "backend/bacon-video-widget.h"
38
39 #define TOTEM_TYPE_ROTATION_PLUGIN (totem_rotation_plugin_get_type ())
40 #define TOTEM_ROTATION_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_ROTATION_PLUGIN, TotemRotationPlugin))
41 #define TOTEM_ROTATION_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_ROTATION_PLUGIN, TotemRotationPluginClass))
42 #define TOTEM_IS_ROTATION_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_ROTATION_PLUGIN))
43 #define TOTEM_IS_ROTATION_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_ROTATION_PLUGIN))
44 #define TOTEM_ROTATION_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_ROTATION_PLUGIN, TotemRotationPluginClass))
45
46 #define GIO_ROTATION_FILE_ATTRIBUTE "metadata::totem::rotation"
47 #define STATE_COUNT 4
48
49 typedef struct {
50 TotemObject *totem;
51 GtkWidget *bvw;
52
53 GCancellable *cancellable;
54 GSimpleAction *rotate_left_action;
55 GSimpleAction *rotate_right_action;
56 } TotemRotationPluginPrivate;
57
TOTEM_PLUGIN_REGISTER(TOTEM_TYPE_ROTATION_PLUGIN,TotemRotationPlugin,totem_rotation_plugin)58 TOTEM_PLUGIN_REGISTER(TOTEM_TYPE_ROTATION_PLUGIN, TotemRotationPlugin, totem_rotation_plugin)
59
60 static void
61 store_state_cb (GObject *source_object,
62 GAsyncResult *res,
63 gpointer user_data)
64 {
65 GError *error = NULL;
66
67 if (!g_file_set_attributes_finish (G_FILE (source_object), res, NULL, &error)) {
68 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED) &&
69 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
70 g_warning ("Could not store file attribute: %s", error->message);
71 }
72 g_error_free (error);
73 }
74 }
75
76 static void
store_state(TotemRotationPlugin * pi)77 store_state (TotemRotationPlugin *pi)
78 {
79 TotemRotationPluginPrivate *priv = pi->priv;
80 BvwRotation rotation;
81 char *rotation_s;
82 GFileInfo *info;
83 char *mrl;
84 GFile *file;
85
86 rotation = bacon_video_widget_get_rotation (BACON_VIDEO_WIDGET (priv->bvw));
87 rotation_s = g_strdup_printf ("%u", rotation);
88 info = g_file_info_new ();
89 g_file_info_set_attribute_string (info, GIO_ROTATION_FILE_ATTRIBUTE, rotation_s);
90 g_free (rotation_s);
91
92 mrl = totem_object_get_current_mrl (priv->totem);
93 file = g_file_new_for_uri (mrl);
94 g_free (mrl);
95 g_file_set_attributes_async (file,
96 info,
97 G_FILE_QUERY_INFO_NONE,
98 G_PRIORITY_DEFAULT,
99 priv->cancellable,
100 store_state_cb,
101 pi);
102 g_object_unref (file);
103 }
104
105 static void
restore_state_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)106 restore_state_cb (GObject *source_object,
107 GAsyncResult *res,
108 gpointer user_data)
109 {
110 TotemRotationPlugin *pi;
111 GError *error = NULL;
112 GFileInfo *info;
113 const char *rotation_s;
114 BvwRotation rotation;
115
116 info = g_file_query_info_finish (G_FILE (source_object), res, &error);
117 if (info == NULL) {
118 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED) &&
119 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
120 g_warning ("Could not query file attribute: %s", error->message);
121 }
122 g_error_free (error);
123 return;
124 }
125
126 pi = user_data;
127
128 rotation_s = g_file_info_get_attribute_string (info, GIO_ROTATION_FILE_ATTRIBUTE);
129 if (!rotation_s)
130 goto out;
131
132 rotation = atoi (rotation_s);
133 bacon_video_widget_set_rotation (BACON_VIDEO_WIDGET (pi->priv->bvw), rotation);
134
135 out:
136 g_object_unref (info);
137 }
138
139 static void
restore_state(TotemRotationPlugin * pi)140 restore_state (TotemRotationPlugin *pi)
141 {
142 TotemRotationPluginPrivate *priv = pi->priv;
143 char *mrl;
144 GFile *file;
145
146 mrl = totem_object_get_current_mrl (priv->totem);
147 file = g_file_new_for_uri (mrl);
148 g_free (mrl);
149
150 g_file_query_info_async (file,
151 GIO_ROTATION_FILE_ATTRIBUTE,
152 G_FILE_QUERY_INFO_NONE,
153 G_PRIORITY_DEFAULT,
154 priv->cancellable,
155 restore_state_cb,
156 pi);
157 g_object_unref (file);
158 }
159
160 static void
update_state(TotemRotationPlugin * pi,const char * mrl)161 update_state (TotemRotationPlugin *pi,
162 const char *mrl)
163 {
164 TotemRotationPluginPrivate *priv = pi->priv;
165
166 if (mrl == NULL) {
167 bacon_video_widget_set_rotation (BACON_VIDEO_WIDGET (priv->bvw),
168 BVW_ROTATION_R_ZERO);
169 g_simple_action_set_enabled (priv->rotate_left_action, FALSE);
170 g_simple_action_set_enabled (priv->rotate_right_action, FALSE);
171 } else {
172 g_simple_action_set_enabled (priv->rotate_left_action, TRUE);
173 g_simple_action_set_enabled (priv->rotate_right_action, TRUE);
174 restore_state (pi);
175 }
176 }
177
178 static void
cb_rotate_left(GSimpleAction * simple,GVariant * parameter,gpointer user_data)179 cb_rotate_left (GSimpleAction *simple,
180 GVariant *parameter,
181 gpointer user_data)
182 {
183 TotemRotationPlugin *pi = user_data;
184 TotemRotationPluginPrivate *priv = pi->priv;
185 int state;
186
187 state = (bacon_video_widget_get_rotation (BACON_VIDEO_WIDGET (priv->bvw)) - 1) % STATE_COUNT;
188 bacon_video_widget_set_rotation (BACON_VIDEO_WIDGET (priv->bvw), state);
189 store_state (pi);
190 }
191
192 static void
cb_rotate_right(GSimpleAction * simple,GVariant * parameter,gpointer user_data)193 cb_rotate_right (GSimpleAction *simple,
194 GVariant *parameter,
195 gpointer user_data)
196 {
197 TotemRotationPlugin *pi = user_data;
198 TotemRotationPluginPrivate *priv = pi->priv;
199 int state;
200
201 state = (bacon_video_widget_get_rotation (BACON_VIDEO_WIDGET (priv->bvw)) + 1) % STATE_COUNT;
202 bacon_video_widget_set_rotation (BACON_VIDEO_WIDGET (priv->bvw), state);
203 store_state (pi);
204 }
205
206 static void
totem_rotation_file_closed(TotemObject * totem,TotemRotationPlugin * pi)207 totem_rotation_file_closed (TotemObject *totem,
208 TotemRotationPlugin *pi)
209 {
210 update_state (pi, NULL);
211 }
212
213 static void
totem_rotation_file_opened(TotemObject * totem,const char * mrl,TotemRotationPlugin * pi)214 totem_rotation_file_opened (TotemObject *totem,
215 const char *mrl,
216 TotemRotationPlugin *pi)
217 {
218 update_state (pi, mrl);
219 }
220
221 static void
impl_activate(PeasActivatable * plugin)222 impl_activate (PeasActivatable *plugin)
223 {
224 TotemRotationPlugin *pi = TOTEM_ROTATION_PLUGIN (plugin);
225 TotemRotationPluginPrivate *priv = pi->priv;
226 GMenu *menu;
227 GMenuItem *item;
228 char *mrl;
229 const char * const rotate_cw[]= { "<Primary>r", NULL };
230 const char * const rotate_ccw[]= { "<Primary><Shift>r", NULL };
231
232 priv->totem = g_object_get_data (G_OBJECT (plugin), "object");
233 priv->bvw = totem_object_get_video_widget (priv->totem);
234 priv->cancellable = g_cancellable_new ();
235
236 g_signal_connect (priv->totem,
237 "file-opened",
238 G_CALLBACK (totem_rotation_file_opened),
239 plugin);
240 g_signal_connect (priv->totem,
241 "file-closed",
242 G_CALLBACK (totem_rotation_file_closed),
243 plugin);
244
245 /* add UI */
246 menu = totem_object_get_menu_section (priv->totem, "rotation-placeholder");
247
248 priv->rotate_left_action = g_simple_action_new ("rotate-left", NULL);
249 g_signal_connect (G_OBJECT (priv->rotate_left_action), "activate",
250 G_CALLBACK (cb_rotate_left), pi);
251 g_action_map_add_action (G_ACTION_MAP (priv->totem),
252 G_ACTION (priv->rotate_left_action));
253 gtk_application_set_accels_for_action (GTK_APPLICATION (priv->totem),
254 "app.rotate-left",
255 rotate_ccw);
256
257 priv->rotate_right_action = g_simple_action_new ("rotate-right", NULL);
258 g_signal_connect (G_OBJECT (priv->rotate_right_action), "activate",
259 G_CALLBACK (cb_rotate_right), pi);
260 g_action_map_add_action (G_ACTION_MAP (priv->totem),
261 G_ACTION (priv->rotate_right_action));
262 gtk_application_set_accels_for_action (GTK_APPLICATION (priv->totem),
263 "app.rotate-right",
264 rotate_cw);
265
266 item = g_menu_item_new (_("_Rotate ↷"), "app.rotate-right");
267 g_menu_item_set_attribute (item, "accel", "s", "<Primary>R");
268 g_menu_append_item (G_MENU (menu), item);
269
270 item = g_menu_item_new (_("Rotate ↶"), "app.rotate-left");
271 g_menu_item_set_attribute (item, "accel", "s", "<Primary><Shift>R");
272 g_menu_append_item (G_MENU (menu), item);
273
274 mrl = totem_object_get_current_mrl (priv->totem);
275 update_state (pi, mrl);
276 g_free (mrl);
277 }
278
279 static void
impl_deactivate(PeasActivatable * plugin)280 impl_deactivate (PeasActivatable *plugin)
281 {
282 TotemRotationPlugin *pi = TOTEM_ROTATION_PLUGIN (plugin);
283 TotemRotationPluginPrivate *priv = pi->priv;
284 const char * const accels[] = { NULL };
285
286 if (priv->cancellable != NULL) {
287 g_cancellable_cancel (priv->cancellable);
288 g_clear_object (&priv->cancellable);
289 }
290
291 g_signal_handlers_disconnect_by_func (priv->totem, totem_rotation_file_opened, plugin);
292 g_signal_handlers_disconnect_by_func (priv->totem, totem_rotation_file_closed, plugin);
293
294 gtk_application_set_accels_for_action (GTK_APPLICATION (priv->totem),
295 "app.rotate-right",
296 accels);
297 gtk_application_set_accels_for_action (GTK_APPLICATION (priv->totem),
298 "app.rotate-left",
299 accels);
300
301 totem_object_empty_menu_section (priv->totem, "rotation-placeholder");
302 g_action_map_remove_action (G_ACTION_MAP (priv->totem), "rotate-left");
303 g_action_map_remove_action (G_ACTION_MAP (priv->totem), "rotate-right");
304
305 bacon_video_widget_set_rotation (BACON_VIDEO_WIDGET (priv->bvw),
306 BVW_ROTATION_R_ZERO);
307
308 priv->totem = NULL;
309 priv->bvw = NULL;
310 }
311