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