Codebase list xapp / c9c885a
Add XAppFavorites. Favorite files are stored as a set of (uri, mimetype). During runtime, however, a 'display_name' field is computed and is also available. If the file's basename is unique among all favorites, this display_name will equal the file's basename. Otherwise, the file's parent folder is also displayed as part of the display_name. - Favorites are stored in dconf. - Interaction is via an XAppFavorites (singleton) class which allows adding and removing, and activating, along with lookups. - Favorites can be listed in their entirety or filtered by a mimetype or list of mimetypes (so apps that handle specific mimetypes can retrieve only favorites they can handle). - Utility functions are provided to retrieve a GtkMenu containing menuitems corresponding to favorites, as well as a GtkActionGroup for the same. The menu one is maintenance-free and will update its children if the underlying list of favorites changes. The action group is not. Favorites uri: - A 'favorites' uri scheme and GFile implementation is added using a GtkModule to allow programs to access favorites as though it was an actual folder. - The module also adds a "Favorites" shortcut to all Gtk3 file-picker dialogs to allow selection there. Issues/TODOs: - When implemented in other apps, activating/opening a favorite file will add it to the recent file list, unless specifically filtered out in the app. This isn't desirable, since by definition a favorite file is probably also a recent file, so they needn't be shown in both lists. - While the favorites:/// uri can be accessed by any Gtk3-based file manager, it's a poor experience unless specific handling is added. - The icons in this commit need to be replaced, except perhaps for the two symbolic actions. Initially, support is added to: - nemo (full support, adding, removing, sidebar, etc...). - xviewer (menu access to favorites). - xreader (menu access and launch landing page view). - pix (Favorites entry in the sidebar tree). - xed (menu access to favorites). - cinnamon (dedicated favorites applet, and a category in the menu applet). - warpinator (menu access in the 'send files' popup as well as the status icon menu). Michael Webster 3 years ago
23 changed file(s) with 4997 addition(s) and 5 deletion(s). Raw diff Collapse all Expand all
0 # This file is sourced by Xsession(5), not executed.
1
2 if [ -z "$GTK_MODULES" ] ; then
3 GTK_MODULES="xapp-gtk3-module"
4 else
5 GTK_MODULES="$GTK_MODULES:xapp-gtk3-module"
6 fi
7
8 export GTK_MODULES
0 install_data(['80xapp-gtk3-module'],
1 install_dir: join_paths(get_option('sysconfdir'), 'X11', 'Xsession.d')
2 )
00 usr/lib/*/libxapp.so.1*
11 usr/libexec/xapps/sn-watcher/*
2 usr/lib/*/gtk-3.0/modules
3 /etc/X11
1010 main_xml: 'xapp-docs.xml',
1111 scan_args: ['--rebuild-types'],
1212 mkdb_args: ['--xml-mode', '--output-format=xml'],
13 )
13 ignore_headers: ['favorite-vfs-file.h', 'favorite-vfs-file-enumerator.h', 'favorite-vfs-file-monitor.h']
14 )
1515
1616 <chapter>
1717 <title>API reference</title>
18 <xi:include href="xml/xapp-favorites.xml"/>
1819 <xi:include href="xml/xapp-gtk-window.xml"/>
1920 <xi:include href="xml/xapp-icon-chooser-button.xml"/>
2021 <xi:include href="xml/xapp-icon-chooser-dialog.xml"/>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:dc="http://purl.org/dc/elements/1.1/"
3 xmlns:cc="http://creativecommons.org/ns#"
4 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
5 xmlns:svg="http://www.w3.org/2000/svg"
6 xmlns="http://www.w3.org/2000/svg"
7 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
8 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
9 id="svg4060"
10 height="16"
11 width="16"
12 version="1.1"
13 sodipodi:docname="xapp-favorite-symbolic.svg"
14 inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
15 <sodipodi:namedview
16 pagecolor="#ffffff"
17 bordercolor="#666666"
18 borderopacity="1"
19 objecttolerance="10"
20 gridtolerance="10"
21 guidetolerance="10"
22 inkscape:pageopacity="0"
23 inkscape:pageshadow="2"
24 inkscape:window-width="1920"
25 inkscape:window-height="989"
26 id="namedview6"
27 showgrid="true"
28 inkscape:zoom="41.7193"
29 inkscape:cx="0.93745069"
30 inkscape:cy="8.3555532"
31 inkscape:window-x="0"
32 inkscape:window-y="27"
33 inkscape:window-maximized="1"
34 inkscape:current-layer="svg4060">
35 <inkscape:grid
36 type="xygrid"
37 id="grid816" />
38 </sodipodi:namedview>
39 <defs
40 id="defs4062" />
41 <metadata
42 id="metadata4065">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title />
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <path
54 id="path3649"
55 style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.92937016;marker:none;enable-background:accumulate"
56 d="M 9.087891,10.5 5.9983503,8.874203 2.9075508,10.497604 3.4990553,7.0568778 1,4.6190107 4.4551098,4.1183218 6.0014083,0.98823644 7.5452799,4.1195202 11,4.6228883 8.4990549,7.0588163 Z M 11,9.0000004 V 11 H 9.0000002 v 2 H 11 v 2 h 2 v -2 h 2 V 11 H 13 V 9.0000004 Z" />
57 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:dc="http://purl.org/dc/elements/1.1/"
3 xmlns:cc="http://creativecommons.org/ns#"
4 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
5 xmlns:svg="http://www.w3.org/2000/svg"
6 xmlns="http://www.w3.org/2000/svg"
7 xmlns:xlink="http://www.w3.org/1999/xlink"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 id="svg4060"
11 height="16"
12 width="16"
13 version="1.1"
14 sodipodi:docname="xapp-unfavorite-symbolic.svg"
15 inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
16 <sodipodi:namedview
17 pagecolor="#ffffff"
18 bordercolor="#666666"
19 borderopacity="1"
20 objecttolerance="10"
21 gridtolerance="10"
22 guidetolerance="10"
23 inkscape:pageopacity="0"
24 inkscape:pageshadow="2"
25 inkscape:window-width="1920"
26 inkscape:window-height="989"
27 id="namedview6"
28 showgrid="true"
29 inkscape:zoom="41.7193"
30 inkscape:cx="1.4574888"
31 inkscape:cy="7.7311177"
32 inkscape:window-x="0"
33 inkscape:window-y="27"
34 inkscape:window-maximized="1"
35 inkscape:current-layer="svg4060">
36 <inkscape:grid
37 type="xygrid"
38 id="grid816" />
39 </sodipodi:namedview>
40 <defs
41 id="defs4062">
42 <linearGradient
43 inkscape:collect="always"
44 xlink:href="#linearGradient5261"
45 id="linearGradient5263"
46 x1="1.013729"
47 y1="8"
48 x2="15"
49 y2="8"
50 gradientUnits="userSpaceOnUse"
51 gradientTransform="matrix(1.1111111,0,0,1.1111111,-0.11111111,-0.0555556)" />
52 <linearGradient
53 inkscape:collect="always"
54 id="linearGradient5261">
55 <stop
56 style="stop-color:#bebebe;stop-opacity:1;"
57 offset="0"
58 id="stop5257" />
59 <stop
60 style="stop-color:#bebebe;stop-opacity:0;"
61 offset="1"
62 id="stop5259" />
63 </linearGradient>
64 </defs>
65 <metadata
66 id="metadata4065">
67 <rdf:RDF>
68 <cc:Work
69 rdf:about="">
70 <dc:format>image/svg+xml</dc:format>
71 <dc:type
72 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
73 <dc:title />
74 </cc:Work>
75 </rdf:RDF>
76 </metadata>
77 <path
78 id="path836"
79 style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient5263);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.20698357;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
80 d="M 6.00217,0.98828111 4.4548611,4.1176211 1,4.6189233 3.5,7.0559889 2.9075522,10.49783 5.99783,8.8745656 9.0881078,10.5 8.5,7.05816 11,4.6232633 7.5451389,4.1197911 Z M 11,11 v 0 H 9.0000002 v 2 H 11 v 0 h 2 v 0 h 2 v -2 h -2 v 0 z" />
81 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
11 sodipodi:docname="xapp-favorites-symbolic.svg"
12 id="svg4"
13 version="1.1"
14 width="48"
15 height="48">
16 <metadata
17 id="metadata10">
18 <rdf:RDF>
19 <cc:Work
20 rdf:about="">
21 <dc:format>image/svg+xml</dc:format>
22 <dc:type
23 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
24 <dc:title></dc:title>
25 </cc:Work>
26 </rdf:RDF>
27 </metadata>
28 <defs
29 id="defs8">
30 <linearGradient
31 id="linearGradient7722"
32 osb:paint="gradient">
33 <stop
34 style="stop-color:#000000;stop-opacity:1;"
35 offset="0"
36 id="stop7718" />
37 <stop
38 style="stop-color:#000000;stop-opacity:0;"
39 offset="1"
40 id="stop7720" />
41 </linearGradient>
42 <filter
43 style="color-interpolation-filters:sRGB;"
44 inkscape:label="Feather"
45 id="filter1726">
46 <feGaussianBlur
47 stdDeviation="5"
48 result="blur"
49 id="feGaussianBlur1718" />
50 <feComposite
51 in="SourceGraphic"
52 in2="blur"
53 operator="atop"
54 result="composite1"
55 id="feComposite1720" />
56 <feComposite
57 in2="composite1"
58 operator="in"
59 result="composite2"
60 id="feComposite1722" />
61 <feComposite
62 in2="composite2"
63 operator="in"
64 result="composite3"
65 id="feComposite1724" />
66 </filter>
67 </defs>
68 <sodipodi:namedview
69 pagecolor="#ffffff"
70 bordercolor="#666666"
71 borderopacity="1"
72 objecttolerance="10"
73 gridtolerance="10"
74 guidetolerance="10"
75 inkscape:pageopacity="0"
76 inkscape:pageshadow="2"
77 inkscape:window-width="1920"
78 inkscape:window-height="989"
79 id="namedview6"
80 showgrid="false"
81 inkscape:zoom="12.525139"
82 inkscape:cx="27.539377"
83 inkscape:cy="20.700947"
84 inkscape:window-x="0"
85 inkscape:window-y="27"
86 inkscape:window-maximized="1"
87 inkscape:current-layer="svg4" />
88 <path
89 d="M 23.953096,2.4741988 17.470049,15.628515 3,17.78943 13.429644,28.03116 10.9867,42.5 23.953096,35.641717 36.91799,42.5 34.476548,28.03116 45,17.78943 30.435543,15.628515 Z"
90 id="path2"
91 style="fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.12517142;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;paint-order:normal"
92 inkscape:connector-curvature="0"
93 sodipodi:nodetypes="ccccccccccc" />
94 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:xlink="http://www.w3.org/1999/xlink"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
12 sodipodi:docname="xapp-favorites.svg"
13 id="svg4"
14 version="1.1"
15 width="96"
16 height="96"
17 style="enable-background:new">
18 <metadata
19 id="metadata10">
20 <rdf:RDF>
21 <cc:Work
22 rdf:about="">
23 <dc:format>image/svg+xml</dc:format>
24 <dc:type
25 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
26 <dc:title></dc:title>
27 </cc:Work>
28 </rdf:RDF>
29 </metadata>
30 <defs
31 id="defs8">
32 <linearGradient
33 x1="48"
34 y1="90"
35 x2="48"
36 gradientUnits="userSpaceOnUse"
37 y2="5.9877"
38 id="linearGradient6452">
39 <stop
40 id="stop6448"
41 offset="0"
42 stop-color="#b4b4b4" />
43 <stop
44 id="stop6450"
45 offset="1"
46 stop-color="#e6e6e6" />
47 </linearGradient>
48 <linearGradient
49 id="linearGradient4984"
50 osb:paint="solid">
51 <stop
52 style="stop-color:#ffffff;stop-opacity:1;"
53 offset="0"
54 id="stop4982" />
55 </linearGradient>
56 <linearGradient
57 id="linearGradient7722"
58 osb:paint="gradient">
59 <stop
60 style="stop-color:#000000;stop-opacity:1;"
61 offset="0"
62 id="stop7718" />
63 <stop
64 style="stop-color:#000000;stop-opacity:0;"
65 offset="1"
66 id="stop7720" />
67 </linearGradient>
68 <filter
69 style="color-interpolation-filters:sRGB"
70 inkscape:label="Feather"
71 id="filter1726">
72 <feGaussianBlur
73 stdDeviation="5"
74 result="blur"
75 id="feGaussianBlur1718" />
76 <feComposite
77 in="SourceGraphic"
78 in2="blur"
79 operator="atop"
80 result="composite1"
81 id="feComposite1720" />
82 <feComposite
83 in2="composite1"
84 operator="in"
85 result="composite2"
86 id="feComposite1722" />
87 <feComposite
88 in2="composite2"
89 operator="in"
90 result="composite3"
91 id="feComposite1724" />
92 </filter>
93 <filter
94 style="color-interpolation-filters:sRGB"
95 inkscape:label="Drop Shadow"
96 id="filter1795">
97 <feFlood
98 flood-opacity="0.498039"
99 flood-color="rgb(0,0,0)"
100 result="flood"
101 id="feFlood1785" />
102 <feComposite
103 in="flood"
104 in2="SourceGraphic"
105 operator="in"
106 result="composite1"
107 id="feComposite1787" />
108 <feGaussianBlur
109 in="composite1"
110 stdDeviation="1"
111 result="blur"
112 id="feGaussianBlur1789" />
113 <feOffset
114 dx="1"
115 dy="1"
116 result="offset"
117 id="feOffset1791" />
118 <feComposite
119 in="SourceGraphic"
120 in2="offset"
121 operator="over"
122 result="composite2"
123 id="feComposite1793" />
124 </filter>
125 <filter
126 style="color-interpolation-filters:sRGB"
127 inkscape:label="Drop Shadow"
128 id="filter1819">
129 <feFlood
130 flood-opacity="0.498039"
131 flood-color="rgb(0,0,0)"
132 result="flood"
133 id="feFlood1809" />
134 <feComposite
135 in="flood"
136 in2="SourceGraphic"
137 operator="in"
138 result="composite1"
139 id="feComposite1811" />
140 <feGaussianBlur
141 in="composite1"
142 stdDeviation="1"
143 result="blur"
144 id="feGaussianBlur1813" />
145 <feOffset
146 dx="1"
147 dy="1"
148 result="offset"
149 id="feOffset1815" />
150 <feComposite
151 in="SourceGraphic"
152 in2="offset"
153 operator="over"
154 result="composite2"
155 id="feComposite1817" />
156 </filter>
157 <filter
158 style="color-interpolation-filters:sRGB"
159 inkscape:label="Drop Shadow"
160 id="filter1720">
161 <feFlood
162 flood-opacity="0.498039"
163 flood-color="rgb(0,0,0)"
164 result="flood"
165 id="feFlood1710" />
166 <feComposite
167 in="flood"
168 in2="SourceGraphic"
169 operator="in"
170 result="composite1"
171 id="feComposite1712" />
172 <feGaussianBlur
173 in="composite1"
174 stdDeviation="1"
175 result="blur"
176 id="feGaussianBlur1714" />
177 <feOffset
178 dx="1"
179 dy="1"
180 result="offset"
181 id="feOffset1716" />
182 <feComposite
183 in="SourceGraphic"
184 in2="offset"
185 operator="over"
186 result="composite2"
187 id="feComposite1718" />
188 </filter>
189 <filter
190 style="color-interpolation-filters:sRGB"
191 inkscape:label="Drop Shadow"
192 id="filter1740">
193 <feFlood
194 flood-opacity="0.498039"
195 flood-color="rgb(0,0,0)"
196 result="flood"
197 id="feFlood1730" />
198 <feComposite
199 in="flood"
200 in2="SourceGraphic"
201 operator="in"
202 result="composite1"
203 id="feComposite1732" />
204 <feGaussianBlur
205 in="composite1"
206 stdDeviation="1"
207 result="blur"
208 id="feGaussianBlur1734" />
209 <feOffset
210 dx="1"
211 dy="1"
212 result="offset"
213 id="feOffset1736" />
214 <feComposite
215 in="SourceGraphic"
216 in2="offset"
217 operator="over"
218 result="composite2"
219 id="feComposite1738" />
220 </filter>
221 <linearGradient
222 id="g"
223 y2="90.238998"
224 xlink:href="#a"
225 gradientUnits="userSpaceOnUse"
226 x2="32.250999"
227 gradientTransform="matrix(1.0238,0,0,-1.0119,-1.1429,98.071)"
228 y1="6.1317"
229 x1="32.250999" />
230 <linearGradient
231 id="a"
232 y2="7.0165"
233 gradientUnits="userSpaceOnUse"
234 x2="45.448002"
235 gradientTransform="matrix(1.0059,0,0,0.99417,100,0)"
236 y1="92.540001"
237 x1="45.448002">
238 <stop
239 offset="0"
240 id="stop3979" />
241 <stop
242 stop-opacity=".58824"
243 offset="1"
244 id="stop3981" />
245 </linearGradient>
246 <linearGradient
247 id="h"
248 y2="5.9995999"
249 xlink:href="#a"
250 gradientUnits="userSpaceOnUse"
251 x2="32.250999"
252 gradientTransform="matrix(1.0238,0,0,1.0119,-1.1429,0.929)"
253 y1="90"
254 x1="32.250999" />
255 <linearGradient
256 id="i"
257 y2="6"
258 xlink:href="#a"
259 gradientUnits="userSpaceOnUse"
260 x2="32.250999"
261 gradientTransform="translate(0,1)"
262 y1="90"
263 x1="32.250999" />
264 <radialGradient
265 id="e"
266 gradientUnits="userSpaceOnUse"
267 cy="78.287003"
268 cx="47.098"
269 gradientTransform="matrix(1.383,-1.035e-8,0,1.3124,-18.038997,-24.219)"
270 r="38.957001">
271 <stop
272 stop-color="#42394b"
273 offset="0"
274 id="stop4006" />
275 <stop
276 offset="1"
277 id="stop4008" />
278 </radialGradient>
279 <filter
280 style="color-interpolation-filters:sRGB"
281 id="o"
282 height="1.0908"
283 width="1.3035001"
284 y="-0.045378"
285 x="-0.15174">
286 <feGaussianBlur
287 stdDeviation="1.1526305"
288 id="feGaussianBlur3973" />
289 </filter>
290 <linearGradient
291 id="b"
292 y2="52.101002"
293 xlink:href="#d"
294 gradientUnits="userSpaceOnUse"
295 x2="19.941999"
296 gradientTransform="matrix(0,-0.65823,0.65616,0,-10.186,94.127)"
297 y1="52.101002"
298 x1="80.710999" />
299 <linearGradient
300 id="d"
301 stop-color="#fff">
302 <stop
303 offset="0"
304 id="stop3968" />
305 <stop
306 stop-opacity="0"
307 offset="1"
308 id="stop3970" />
309 </linearGradient>
310 <filter
311 style="color-interpolation-filters:sRGB"
312 id="p"
313 height="1.3025"
314 width="1.0888"
315 y="-0.15126"
316 x="-0.044411998">
317 <feGaussianBlur
318 stdDeviation="1.1526305"
319 id="feGaussianBlur3976" />
320 </filter>
321 <linearGradient
322 id="c"
323 y2="52.101002"
324 xlink:href="#d"
325 gradientUnits="userSpaceOnUse"
326 x2="19.941999"
327 gradientTransform="matrix(0.65823,0,0,0.65616,2.8732,25.814)"
328 y1="52.101002"
329 x1="80.710999" />
330 <linearGradient
331 id="j"
332 y2="5.9877"
333 gradientUnits="userSpaceOnUse"
334 x2="48"
335 y1="90"
336 x1="48">
337 <stop
338 stop-color="#b4b4b4"
339 offset="0"
340 id="stop3999" />
341 <stop
342 stop-color="#e6e6e6"
343 offset="1"
344 id="stop4001" />
345 </linearGradient>
346 <linearGradient
347 id="m"
348 stop-color="#fff"
349 y2="63.893002"
350 gradientUnits="userSpaceOnUse"
351 x2="36.356998"
352 y1="6"
353 x1="36.356998">
354 <stop
355 offset="0"
356 id="stop3994" />
357 <stop
358 stop-opacity="0"
359 offset="1"
360 id="stop3996" />
361 </linearGradient>
362 <linearGradient
363 id="n"
364 y2="83.294998"
365 gradientUnits="userSpaceOnUse"
366 x2="43.179001"
367 gradientTransform="matrix(1.0294,0,0,1.0294,-1.4118,-1.4118)"
368 y1="13"
369 x1="43.179001">
370 <stop
371 stop-color="#919191"
372 offset="0"
373 id="stop3989" />
374 <stop
375 stop-color="#fdfdfd"
376 offset="1"
377 id="stop3991" />
378 </linearGradient>
379 <radialGradient
380 id="f"
381 stop-color="#fff"
382 gradientUnits="userSpaceOnUse"
383 cy="90.171997"
384 cx="48"
385 gradientTransform="matrix(1.1573,0,0,0.99591,-7.551,0.19713)"
386 r="42">
387 <stop
388 offset="0"
389 id="stop3984" />
390 <stop
391 stop-opacity="0"
392 offset="1"
393 id="stop3986" />
394 </radialGradient>
395 <linearGradient
396 inkscape:collect="always"
397 xlink:href="#a"
398 id="linearGradient4912"
399 gradientUnits="userSpaceOnUse"
400 gradientTransform="matrix(1.0238,0,0,-1.0119,-1.1429,98.071)"
401 x1="32.250999"
402 y1="6.1317"
403 x2="32.250999"
404 y2="90.238998" />
405 <linearGradient
406 inkscape:collect="always"
407 xlink:href="#a"
408 id="linearGradient4914"
409 gradientUnits="userSpaceOnUse"
410 gradientTransform="matrix(1.0238,0,0,-1.0119,-1.1429,98.071)"
411 x1="32.250999"
412 y1="6.1317"
413 x2="32.250999"
414 y2="90.238998" />
415 <linearGradient
416 inkscape:collect="always"
417 xlink:href="#a"
418 id="linearGradient4916"
419 gradientUnits="userSpaceOnUse"
420 gradientTransform="matrix(1.0238,0,0,-1.0119,-1.1429,98.071)"
421 x1="32.250999"
422 y1="6.1317"
423 x2="32.250999"
424 y2="90.238998" />
425 <radialGradient
426 inkscape:collect="always"
427 xlink:href="#linearGradient6452"
428 id="radialGradient6446"
429 cx="24.000002"
430 cy="23.915552"
431 fx="24.000002"
432 fy="23.915552"
433 r="23.37389"
434 gradientTransform="matrix(0.50559953,-0.01484073,0.01430171,0.48723605,11.523578,12.619211)"
435 gradientUnits="userSpaceOnUse" />
436 </defs>
437 <sodipodi:namedview
438 pagecolor="#ffffff"
439 bordercolor="#666666"
440 borderopacity="1"
441 objecttolerance="10"
442 gridtolerance="10"
443 guidetolerance="10"
444 inkscape:pageopacity="0"
445 inkscape:pageshadow="2"
446 inkscape:window-width="1920"
447 inkscape:window-height="989"
448 id="namedview6"
449 showgrid="false"
450 inkscape:zoom="4.4283054"
451 inkscape:cx="46.062124"
452 inkscape:cy="35.733194"
453 inkscape:window-x="0"
454 inkscape:window-y="27"
455 inkscape:window-maximized="1"
456 inkscape:current-layer="svg4" />
457 <ellipse
458 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.95995587;stroke-opacity:1;filter:url(#filter1819)"
459 id="path1698"
460 cx="-62.545948"
461 cy="154.09407"
462 rx="21"
463 ry="20.501823" />
464 <rect
465 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-opacity:1;filter:url(#filter1740)"
466 id="rect1728"
467 width="40"
468 height="40"
469 x="-129.49153"
470 y="121.61999"
471 rx="6"
472 ry="6" />
473 <path
474 d="M 93.79561,48.452021 87.457596,61.312062 73.31126,63.424634 83.50758,73.437241 81.119287,87.5824 93.79561,80.877544 106.47046,87.5824 104.08364,73.437241 114.37167,63.424634 100.13303,61.312062 Z"
475 id="path2-3"
476 style="fill:none;fill-opacity:0.94117647;stroke:#000000;stroke-width:2.93992543;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
477 inkscape:connector-curvature="0"
478 sodipodi:nodetypes="ccccccccccc" />
479 <g
480 style="fill:url(#g)"
481 id="g4022">
482 <path
483 style="opacity:0.07999998;fill:url(#linearGradient4912)"
484 inkscape:connector-curvature="0"
485 d="M 12,95.031 C 6.4889,95.031 1.969,90.511 1.969,85 V 14 C 1.969,8.4889 6.4892,3.969 12,3.969 h 72 c 5.5111,0 10.031,4.5202 10.031,10.031 v 71 c 0,5.5111 -4.5202,10.031 -10.031,10.031 z"
486 id="path4016" />
487 <path
488 style="opacity:0.1;fill:url(#linearGradient4914)"
489 inkscape:connector-curvature="0"
490 d="m 12,94.031 c -4.9716,0 -9.0312,-4.0596 -9.0312,-9.0312 v -71 C 2.9688,9.028 7.0284,4.9684 12,4.9684 h 72 c 4.9716,0 9.0312,4.0596 9.0312,9.0312 v 71 c 0,4.9716 -4.0596,9.0312 -9.0312,9.0312 H 12 Z"
491 id="path4018" />
492 <path
493 style="opacity:0.2;fill:url(#linearGradient4916)"
494 inkscape:connector-curvature="0"
495 d="M 12,93 C 7.5909,93 4,89.409 4,85 V 14 C 4,9.5909 7.5909,6 12,6 h 72 c 4.4091,0 8,3.5909 8,8 v 71 c 0,4.4091 -3.5909,8 -8,8 z"
496 id="path4020" />
497 </g>
498 <rect
499 style="opacity:0.3;fill:url(#h)"
500 rx="7"
501 ry="7"
502 height="85"
503 width="86"
504 y="6.9999986"
505 x="4.9999986"
506 id="rect4024" />
507 <rect
508 style="opacity:0.45;fill:url(#i)"
509 rx="6"
510 ry="6"
511 height="84"
512 width="84"
513 y="6.9999986"
514 x="5.9999986"
515 id="rect4026" />
516 <path
517 inkscape:connector-curvature="0"
518 style="opacity:0.15"
519 d="m 19,13 c -3.2894,0 -6,2.7106 -6,6 v 58 c 0,3.2894 2.7106,6 6,6 h 58 c 3.2894,0 6,-2.7106 6,-6 V 19 c 0,-3.2894 -2.7106,-6 -6,-6 z m 0,4 h 58 c 1.1426,0 2,0.85741 2,2 v 58 c 0,1.1426 -0.85741,2 -2,2 H 19 c -1.1426,0 -2,-0.85741 -2,-2 V 19 c 0,-1.1426 0.85741,-2 2,-2 z"
520 id="path4058" />
521 <path
522 inkscape:connector-curvature="0"
523 style="opacity:0.3"
524 d="m 17,14 c -1.662,0 -3,1.338 -3,3 v 62 c 0,1.662 1.338,3 3,3 h 62 c 1.662,0 3,-1.338 3,-3 V 17 c 0,-1.662 -1.338,-3 -3,-3 z m 0,1.9375 h 62 c 0.61816,0 1.0625,0.44434 1.0625,1.0625 v 62 c 0,0.61816 -0.44434,1.0625 -1.0625,1.0625 H 17 c -0.61816,0 -1.0625,-0.44434 -1.0625,-1.0625 V 17 c 0,-0.61816 0.44434,-1.0625 1.0625,-1.0625 z"
525 id="path4060" />
526 <path
527 inkscape:connector-curvature="0"
528 style="opacity:0.6"
529 d="m 17,14 c -1.662,0 -3,1.338 -3,3 v 62 c 0,1.662 1.338,3 3,3 h 62 c 1.662,0 3,-1.338 3,-3 V 17 c 0,-1.662 -1.338,-3 -3,-3 z m 0,0.96875 h 62 c 1.1401,0 2.0312,0.89117 2.0312,2.0312 v 62 c 0,1.1401 -0.89117,2.0312 -2.0312,2.0312 H 17 c -1.1401,0 -2.0312,-0.89117 -2.0312,-2.0312 v -62 c 0,-1.1401 0.89117,-2.0312 2.0312,-2.0312 z"
530 id="path4062" />
531 <path
532 inkscape:connector-curvature="0"
533 style="fill:url(#j)"
534 d="M 12,6 C 8.676,6 6,8.676 6,12 v 72 c 0,3.324 2.676,6 6,6 h 72 c 3.324,0 6,-2.676 6,-6 V 12 C 90,8.676 87.324,6 84,6 Z m 5,7 h 62 c 2.216,0 4,1.784 4,4 v 62 c 10e-7,2.216 -1.784,4 -4,4 H 17 c -2.216,10e-7 -4,-1.784 -4,-4 V 17 c 0,-2.216 1.784,-4 4,-4 z"
535 id="path4064" />
536 <path
537 inkscape:connector-curvature="0"
538 style="opacity:0.5;fill:url(#m)"
539 d="M 12,6 C 8.676,6 6,8.676 6,12 v 72 c 0,0.33472 0.04135,0.6507 0.09375,0.96875 0.0487,0.2956 0.09704,0.59692 0.1875,0.875 0.0099,0.03038 0.02089,0.0636 0.03125,0.09375 0.09887,0.28777 0.23488,0.54745 0.375,0.8125 0.14459,0.27351 0.31562,0.53562 0.5,0.78125 0.18438,0.24564 0.37378,0.47347 0.59375,0.6875 0.43995,0.42806 0.94291,0.81453 1.5,1.0938 0.27854,0.13961 0.5734698,0.24695 0.875,0.34375 -0.2562002,-0.10022 -0.48671,-0.23627 -0.71875,-0.375 -0.0074,-0.0044 -0.02387,0.0045 -0.03125,0 -0.0319,-0.019 -0.0622,-0.042 -0.0937,-0.062 -0.1204,-0.077 -0.231,-0.164 -0.3437,-0.25 -0.1062,-0.081 -0.2133,-0.161 -0.3126,-0.25 -0.1778,-0.162 -0.3473,-0.346 -0.4999,-0.531 -0.1075,-0.131 -0.2183,-0.266 -0.3124,-0.407 -0.0251,-0.038 -0.0385,-0.086 -0.0626,-0.125 -0.0647,-0.103 -0.1302,-0.204 -0.1874,-0.312 -0.1011,-0.195 -0.2057,-0.416 -0.2813,-0.625 -0.008,-0.022 -0.0236,-0.041 -0.0313,-0.063 -0.0318,-0.092 -0.0358,-0.187 -0.0624,-0.281 -0.0304,-0.107 -0.0704,-0.203 -0.0938,-0.313 -0.0729,-0.341 -0.125,-0.698 -0.125,-1.062 v -72 c 0,-2.782 2.2182,-5 5,-5 h 72 c 2.7818,0 5,2.2182 5,5 v 72 c 0,0.3643 -0.05212,0.72099 -0.125,1.0625 -0.04415,0.20689 -0.08838,0.39766 -0.15625,0.59375 -0.0077,0.02195 -0.0233,0.04069 -0.03125,0.0625 -0.06274,0.17374 -0.13838,0.36745 -0.21875,0.53125 -0.04158,0.0828 -0.07904,0.16995 -0.125,0.25 -0.0546,0.09721 -0.12677,0.18835 -0.1875,0.28125 -0.09411,0.14096 -0.20492,0.276 -0.3125,0.40625 -0.14317,0.17445 -0.30314,0.347 -0.46875,0.5 -0.01117,0.0102 -0.01998,0.02115 -0.03125,0.03125 -0.13839,0.12556 -0.28509,0.23444 -0.4375,0.34375 -0.10257,0.07315 -0.20432,0.15336 -0.3125,0.21875 -0.0074,0.0045 -0.02384,-0.0044 -0.03125,0 -0.23204,0.13873 -0.46255,0.27478 -0.71875,0.375 0.30153,-0.0968 0.59646,-0.20414 0.875,-0.34375 0.55709,-0.27922 1.0601,-0.66569 1.5,-1.0938 0.21997,-0.21403 0.40937,-0.44186 0.59375,-0.6875 0.18438,-0.24564 0.35541,-0.50774 0.5,-0.78125 0.14012,-0.26505 0.27614,-0.52473 0.375,-0.8125 0.01041,-0.03078 0.02133,-0.06274 0.03125,-0.09375 0.09046,-0.27808 0.1388,-0.5794 0.1875,-0.875 0.053,-0.318 0.094,-0.634 0.094,-0.969 V 12 c 0,-3.324 -2.676,-6 -6,-6 h -72 z"
540 id="path4066" />
541 <path
542 inkscape:connector-curvature="0"
543 style="fill:url(#n)"
544 d="m 17,12 c -2.7527,0 -5,2.2473 -5,5 v 62 c 0,2.7527 2.2473,5 5,5 h 62 c 2.7527,0 5,-2.2473 5,-5 V 17 c 0,-2.7527 -2.2473,-5 -5,-5 z m 0,2 h 62 c 1.6793,0 3,1.3207 3,3 v 62 c 10e-7,1.6793 -1.3207,3 -3,3 H 17 c -1.6793,0 -3,-1.3207 -3,-3 V 17 c 0,-1.6793 1.3207,-3 3,-3 z"
545 id="path4068" />
546 <path
547 inkscape:connector-curvature="0"
548 style="opacity:0.4;fill:url(#f)"
549 d="M 12,90 C 8.676,90 6,87.324 6,84 V 12 c 0,-0.33472 0.04135,-0.6507 0.09375,-0.96875 0.0487,-0.2956 0.09704,-0.59692 0.1875,-0.875 0.0099,-0.03 0.0209,-0.063 0.0313,-0.094 0.0989,-0.2873 0.2349,-0.547 0.375,-0.812 0.1446,-0.2735 0.3156,-0.5356 0.5,-0.7812 0.1844,-0.2457 0.3738,-0.4735 0.5937,-0.6876 0.44,-0.428 0.943,-0.8145 1.5,-1.0937 0.2786,-0.1396 0.5734998,-0.2469 0.8748,-0.3437 -0.2560002,0.1002 -0.4865,0.2362 -0.7185,0.375 -0.0074,0.0044 -0.02387,-0.0045 -0.03125,0 -0.03193,0.0193 -0.06229,0.04251 -0.09375,0.0625 -0.1204,0.0767 -0.23102,0.16351 -0.34375,0.25 -0.10617,0.0808 -0.21328,0.16111 -0.3125,0.25 -0.1779,0.1614 -0.3474,0.3453 -0.5,0.5312 -0.1075,0.1303 -0.2183,0.2653 -0.3124,0.4063 -0.0251,0.0383 -0.0385,0.0858 -0.0626,0.125 -0.0647,0.103 -0.1302,0.2045 -0.1874,0.3124 -0.1011,0.1948 -0.2057,0.4158 -0.2813,0.625 -0.008,0.0219 -0.0236,0.0406 -0.0313,0.0626 -0.0318,0.0919 -0.0358,0.1868 -0.0624,0.2812 -0.0304,0.1066 -0.0704,0.203 -0.0938,0.3125 -0.0729,0.3415 -0.125,0.6985 -0.125,1.0625 v 72 c 0,2.7818 2.2182,5 5,5 h 72 c 2.7818,0 5,-2.2182 5,-5 v -72 c 0,-0.364 -0.052,-0.721 -0.125,-1.0625 -0.044,-0.2069 -0.088,-0.3977 -0.156,-0.5937 -0.008,-0.022 -0.024,-0.0407 -0.031,-0.0626 -0.063,-0.1737 -0.139,-0.3674 -0.219,-0.5312 -0.042,-0.0828 -0.079,-0.17 -0.125,-0.25 -0.055,-0.0972 -0.127,-0.1884 -0.188,-0.2812 -0.094,-0.141 -0.205,-0.276 -0.312,-0.4063 -0.143,-0.1745 -0.303,-0.347 -0.469,-0.5 -0.011,-0.0102 -0.02,-0.0211 -0.031,-0.0313 -0.139,-0.1255 -0.285,-0.2344 -0.438,-0.3437 -0.102,-0.0731 -0.204,-0.1534 -0.312,-0.2187 -0.0074,-0.0045 -0.02384,0.0044 -0.03125,0 -0.23204,-0.13873 -0.46255,-0.27478 -0.71875,-0.375 0.30153,0.0968 0.59646,0.20414 0.875,0.34375 0.55709,0.27922 1.0601,0.66569 1.5,1.0938 0.21997,0.21403 0.40937,0.44186 0.59375,0.6875 0.18438,0.24564 0.35541,0.50774 0.5,0.78125 0.14012,0.26505 0.27614,0.52473 0.375,0.8125 0.01041,0.03078 0.02133,0.06274 0.03125,0.09375 0.09046,0.27808 0.1388,0.5794 0.1875,0.875 0.053,0.318 0.094,0.634 0.094,0.969 v 72 c 0,3.324 -2.676,6 -6,6 h -72 z"
550 id="path4070" />
551 <path
552 d="M 23.956495,5.3528801 17.94324,17.553973 4.521759,19.558298 l 9.673863,9.499563 -2.265917,13.420364 12.02679,-6.3613 12.025397,6.3613 -2.264523,-13.420364 9.760873,-9.499563 -13.509048,-2.004325 z"
553 id="path2"
554 style="fill:url(#radialGradient6446);fill-opacity:1;stroke:#000000;stroke-width:1.0436362;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;paint-order:normal;filter:url(#filter1795)"
555 inkscape:connector-curvature="0"
556 sodipodi:nodetypes="ccccccccccc"
557 transform="matrix(1.4973973,0,0,1.4973973,12.062463,10.33408)" />
558 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
11 sodipodi:docname="emblem-xapp-favorite.svg"
12 id="svg4"
13 version="1.1"
14 width="48"
15 height="48"
16 enable-background="new">
17 <metadata
18 id="metadata10">
19 <rdf:RDF>
20 <cc:Work
21 rdf:about="">
22 <dc:format>image/svg+xml</dc:format>
23 <dc:type
24 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25 <dc:title />
26 </cc:Work>
27 </rdf:RDF>
28 </metadata>
29 <defs
30 id="defs8">
31 <linearGradient
32 id="linearGradient7722"
33 osb:paint="gradient">
34 <stop
35 style="stop-color:#000000;stop-opacity:1;"
36 offset="0"
37 id="stop7718" />
38 <stop
39 style="stop-color:#000000;stop-opacity:0;"
40 offset="1"
41 id="stop7720" />
42 </linearGradient>
43 <filter
44 style="color-interpolation-filters:sRGB;"
45 inkscape:label="Feather"
46 id="filter1726">
47 <feGaussianBlur
48 stdDeviation="5"
49 result="blur"
50 id="feGaussianBlur1718" />
51 <feComposite
52 in="SourceGraphic"
53 in2="blur"
54 operator="atop"
55 result="composite1"
56 id="feComposite1720" />
57 <feComposite
58 in2="composite1"
59 operator="in"
60 result="composite2"
61 id="feComposite1722" />
62 <feComposite
63 in2="composite2"
64 operator="in"
65 result="composite3"
66 id="feComposite1724" />
67 </filter>
68 <filter
69 inkscape:collect="always"
70 style="color-interpolation-filters:sRGB"
71 id="filter1694"
72 x="-0.02929493"
73 width="1.0585899"
74 y="-0.030739846"
75 height="1.0614797">
76 <feGaussianBlur
77 inkscape:collect="always"
78 stdDeviation="0.54683814"
79 id="feGaussianBlur1696" />
80 </filter>
81 <filter
82 style="color-interpolation-filters:sRGB;"
83 inkscape:label="Drop Shadow"
84 id="filter1795">
85 <feFlood
86 flood-opacity="0.498039"
87 flood-color="rgb(0,0,0)"
88 result="flood"
89 id="feFlood1785" />
90 <feComposite
91 in="flood"
92 in2="SourceGraphic"
93 operator="in"
94 result="composite1"
95 id="feComposite1787" />
96 <feGaussianBlur
97 in="composite1"
98 stdDeviation="1"
99 result="blur"
100 id="feGaussianBlur1789" />
101 <feOffset
102 dx="1"
103 dy="1"
104 result="offset"
105 id="feOffset1791" />
106 <feComposite
107 in="SourceGraphic"
108 in2="offset"
109 operator="over"
110 result="composite2"
111 id="feComposite1793" />
112 </filter>
113 <filter
114 style="color-interpolation-filters:sRGB;"
115 inkscape:label="Drop Shadow"
116 id="filter1819">
117 <feFlood
118 flood-opacity="0.498039"
119 flood-color="rgb(0,0,0)"
120 result="flood"
121 id="feFlood1809" />
122 <feComposite
123 in="flood"
124 in2="SourceGraphic"
125 operator="in"
126 result="composite1"
127 id="feComposite1811" />
128 <feGaussianBlur
129 in="composite1"
130 stdDeviation="1"
131 result="blur"
132 id="feGaussianBlur1813" />
133 <feOffset
134 dx="1"
135 dy="1"
136 result="offset"
137 id="feOffset1815" />
138 <feComposite
139 in="SourceGraphic"
140 in2="offset"
141 operator="over"
142 result="composite2"
143 id="feComposite1817" />
144 </filter>
145 </defs>
146 <sodipodi:namedview
147 pagecolor="#ffffff"
148 bordercolor="#666666"
149 borderopacity="1"
150 objecttolerance="10"
151 gridtolerance="10"
152 guidetolerance="10"
153 inkscape:pageopacity="0"
154 inkscape:pageshadow="2"
155 inkscape:window-width="1920"
156 inkscape:window-height="989"
157 id="namedview6"
158 showgrid="false"
159 inkscape:zoom="12.525139"
160 inkscape:cx="10.514129"
161 inkscape:cy="19.752735"
162 inkscape:window-x="0"
163 inkscape:window-y="27"
164 inkscape:window-maximized="1"
165 inkscape:current-layer="svg4" />
166 <ellipse
167 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.95995587;stroke-opacity:1;filter:url(#filter1819)"
168 id="path1698"
169 cx="24"
170 cy="24.498177"
171 rx="21"
172 ry="20.501823" />
173 <path
174 d="m -38.085272,-1.7272848 -6.915242,14.0312568 -15.434703,2.304974 11.124942,10.924498 -2.605805,15.433419 13.830808,-7.315495 13.829207,7.315495 -2.604201,-15.433419 11.225004,-10.924498 -15.535406,-2.304974 z"
175 id="path2-2"
176 style="fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:1.20018172;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;paint-order:normal;filter:url(#filter1694)"
177 inkscape:connector-curvature="0"
178 sodipodi:nodetypes="ccccccccccc" />
179 <path
180 d="M 23.956495,5.3528801 17.94324,17.553973 4.521759,19.558298 l 9.673863,9.499563 -2.265917,13.420364 12.02679,-6.3613 12.025397,6.3613 -2.264523,-13.420364 9.760873,-9.499563 -13.509048,-2.004325 z"
181 id="path2"
182 style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.0436362;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;paint-order:normal;filter:url(#filter1795)"
183 inkscape:connector-curvature="0"
184 sodipodi:nodetypes="ccccccccccc"
185 transform="matrix(0.90919255,0,0,0.90919255,2.1793771,1.0885881)" />
186 <path
187 d="M 8.0836672,-3.5187526 1.7456531,9.3412879 -12.400683,11.45386 -2.2043633,21.466467 -4.5926558,35.611626 8.0836672,28.90677 20.758521,35.611626 18.371698,21.466467 28.659726,11.45386 14.421094,9.3412879 Z"
188 id="path2-3"
189 style="fill:none;fill-opacity:0.94117647;stroke:#000000;stroke-width:2.93992543;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
190 inkscape:connector-curvature="0"
191 sodipodi:nodetypes="ccccccccccc" />
192 </svg>
0 #include "xapp-favorites.h"
1 #include "favorite-vfs-file-enumerator.h"
2 #include "favorite-vfs-file.h"
3
4 typedef struct
5 {
6 GFile *file;
7
8 GList *uris;
9 gchar *attributes;
10 GFileQueryInfoFlags flags;
11
12 GList *current_pos;
13 } FavoriteVfsFileEnumeratorPrivate;
14
15 struct _FavoriteVfsFileEnumerator
16 {
17 GObject parent_instance;
18 FavoriteVfsFileEnumeratorPrivate *priv;
19 };
20
21 G_DEFINE_TYPE_WITH_PRIVATE(FavoriteVfsFileEnumerator,
22 favorite_vfs_file_enumerator,
23 G_TYPE_FILE_ENUMERATOR)
24
25 static GFileInfo *
26 next_file (GFileEnumerator *enumerator,
27 GCancellable *cancellable,
28 GError **error)
29 {
30 FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (enumerator);
31 FavoriteVfsFileEnumeratorPrivate *priv = favorite_vfs_file_enumerator_get_instance_private (self);
32 GFileInfo *info;
33
34 if (cancellable)
35 {
36 if (g_cancellable_is_cancelled (cancellable))
37 {
38 *error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Enumerate canceled");
39 return NULL;
40 }
41 }
42
43 info = NULL;
44
45 while (priv->current_pos != NULL && info == NULL)
46 {
47 GFile *file;
48 gchar *uri;
49
50 uri = path_to_fav_uri ((const gchar *) priv->current_pos->data);
51 if (!xapp_favorites_find_by_display_name (xapp_favorites_get_default (), (gchar *) priv->current_pos->data))
52 {
53 *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "File not found");
54
55 g_warn_if_reached ();
56 }
57 else
58 {
59 file = g_file_new_for_uri (uri);
60
61 info = g_file_query_info (file,
62 priv->attributes,
63 priv->flags,
64 cancellable,
65 error);
66 }
67
68 g_free (uri);
69 g_object_unref (file);
70 }
71
72 if (priv->current_pos)
73 {
74 priv->current_pos = priv->current_pos->next;
75 }
76
77 return info;
78 }
79
80 static void
81 next_async_op_free (GList *files)
82 {
83 g_list_free_full (files, g_object_unref);
84 }
85
86 static void
87 next_files_async_thread (GTask *task,
88 gpointer source_object,
89 gpointer task_data,
90 GCancellable *cancellable)
91 {
92 FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (source_object);
93 GList *ret;
94 GError *error = NULL;
95 gint i, n_requested;
96
97 n_requested = GPOINTER_TO_INT (g_task_get_task_data (task));
98
99 ret = NULL;
100
101 for (i = 0; i < n_requested; i++)
102 {
103 GFileInfo *info = NULL;
104 // g_clear_error (&error);
105
106 if (g_cancellable_set_error_if_cancelled (cancellable, &error))
107 {
108 g_task_return_error (task, error);
109 return;
110 }
111 else
112 {
113 info = next_file (G_FILE_ENUMERATOR (self), cancellable, &error);
114 }
115
116 if (info != NULL)
117 {
118 ret = g_list_prepend (ret, info);
119 }
120 else
121 {
122 if (error)
123 {
124 g_critical ("ERROR: %s\n", error->message);
125 }
126 break;
127 }
128 }
129
130 if (error)
131 {
132 g_task_return_error (task, error);
133 }
134 else
135 {
136 ret = g_list_reverse (ret);
137 g_task_return_pointer (task, ret, (GDestroyNotify) next_async_op_free);
138 }
139 }
140
141 static void
142 next_files_async (GFileEnumerator *enumerator,
143 gint num_files,
144 gint io_priority,
145 GCancellable *cancellable,
146 GAsyncReadyCallback callback,
147 gpointer user_data)
148 {
149 // FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (enumerator);
150
151 GTask *task;
152 task = g_task_new (enumerator, cancellable, callback, user_data);
153 g_task_set_priority (task, io_priority);
154 g_task_set_task_data (task, GINT_TO_POINTER (num_files), NULL);
155
156 g_task_run_in_thread (task, next_files_async_thread);
157 g_object_unref (task);
158 }
159
160 static GList *
161 next_files_finished (GFileEnumerator *enumerator,
162 GAsyncResult *result,
163 GError **error)
164 {
165 g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL);
166 g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
167
168 return (GList *) g_task_propagate_pointer (G_TASK (result), error);
169 }
170
171 static gboolean
172 close_fn (GFileEnumerator *enumerator,
173 GCancellable *cancellable,
174 GError **error)
175 {
176 // FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (enumerator);
177
178 return TRUE;
179 }
180
181 static void
182 favorite_vfs_file_enumerator_init (FavoriteVfsFileEnumerator *self)
183 {
184 }
185
186 static void
187 favorite_vfs_file_enumerator_dispose (GObject *object)
188 {
189 G_OBJECT_CLASS (favorite_vfs_file_enumerator_parent_class)->dispose (object);
190 }
191
192 static void
193 favorite_vfs_file_enumerator_finalize (GObject *object)
194 {
195 FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR(object);
196 FavoriteVfsFileEnumeratorPrivate *priv = favorite_vfs_file_enumerator_get_instance_private(self);
197
198 g_list_free_full (priv->uris, (GDestroyNotify) g_free);
199 g_free (priv->attributes);
200 g_object_unref (priv->file);
201
202 G_OBJECT_CLASS (favorite_vfs_file_enumerator_parent_class)->finalize (object);
203 }
204
205 static void
206 favorite_vfs_file_enumerator_class_init (FavoriteVfsFileEnumeratorClass *klass)
207 {
208 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
209 GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass);
210
211 gobject_class->dispose = favorite_vfs_file_enumerator_dispose;
212 gobject_class->finalize = favorite_vfs_file_enumerator_finalize;
213
214 enumerator_class->next_file = next_file;
215 enumerator_class->next_files_async = next_files_async;
216 enumerator_class->next_files_finish = next_files_finished;
217 enumerator_class->close_fn = close_fn;
218 }
219
220 GFileEnumerator *
221 favorite_vfs_file_enumerator_new (GFile *file,
222 const gchar *attributes,
223 GFileQueryInfoFlags flags,
224 GList *uris)
225 {
226 FavoriteVfsFileEnumerator *enumerator = g_object_new (FAVORITE_TYPE_VFS_FILE_ENUMERATOR, NULL);
227 FavoriteVfsFileEnumeratorPrivate *priv = favorite_vfs_file_enumerator_get_instance_private(enumerator);
228
229 priv->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
230 priv->current_pos = priv->uris;
231
232 priv->file = g_object_ref (file);
233 priv->attributes = g_strdup (attributes);
234 priv->flags = flags;
235
236 return G_FILE_ENUMERATOR (enumerator);
237 }
238
0 #ifndef FAVORITE_VFS_FILE_ENUMERATOR_H
1 #define FAVORITE_VFS_FILE_ENUMERATOR_H
2
3 #include <glib-object.h>
4 #include <gio/gio.h>
5
6 G_BEGIN_DECLS
7
8 #define FAVORITE_TYPE_VFS_FILE_ENUMERATOR favorite_vfs_file_enumerator_get_type()
9
10 G_DECLARE_FINAL_TYPE (FavoriteVfsFileEnumerator, favorite_vfs_file_enumerator, \
11 FAVORITE, VFS_FILE_ENUMERATOR, \
12 GFileEnumerator)
13
14 GFileEnumerator *
15 favorite_vfs_file_enumerator_new (GFile *file,
16 const gchar *attributes,
17 GFileQueryInfoFlags flags,
18 GList *favorites);
19
20 G_END_DECLS
21
22 #endif // FAVORITE_VFS_FILE_ENUMERATOR_H
0 #include "xapp-favorites.h"
1 #include "favorite-vfs-file.h"
2 #include "favorite-vfs-file-monitor.h"
3
4 typedef struct
5 {
6 gulong changed_handler_id;
7 GHashTable *file_monitors;
8 GList *infos;
9
10 GVolumeMonitor *mount_mon;
11 } FavoriteVfsFileMonitorPrivate;
12
13 struct _FavoriteVfsFileMonitor
14 {
15 GObject parent_instance;
16 FavoriteVfsFileMonitorPrivate *priv;
17 };
18
19 G_DEFINE_TYPE_WITH_PRIVATE(FavoriteVfsFileMonitor, \
20 favorite_vfs_file_monitor, \
21 G_TYPE_FILE_MONITOR)
22
23 GFile *_favorite_vfs_file_new_for_info (XAppFavoriteInfo *info);
24 void _xapp_favorites_rename (XAppFavorites *favorites,
25 const gchar *old_uri,
26 const gchar *new_uri);
27
28 // static void
29 // rename_favorite (GFile *old_file,
30 // GFile *new_file)
31 // {
32 // gchar *old_file_uri, *new_file_uri;
33
34 // old_file_uri = g_file_get_uri (old_file);
35 // new_file_uri = g_file_get_uri (new_file);
36
37 // _xapp_favorites_rename (xapp_favorites_get_default (),
38 // old_file_uri,
39 // new_file_uri);
40
41 // g_free (old_file_uri);
42 // g_free (new_file_uri);
43 // }
44
45 static void
46 favorite_real_file_changed (GFileMonitor *rfmonitor,
47 GFile *file,
48 GFile *other_file,
49 GFileMonitorEvent event_type,
50 gpointer user_data)
51 {
52 // Disabled
53 return;
54
55 // g_return_if_fail (FAVORITE_IS_VFS_FILE_MONITOR (user_data));
56 // FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (user_data);
57 // FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
58
59 // g_debug ("real file changed: %s: %d", g_file_get_uri (file), event_type);
60
61 // switch (event_type)
62 // {
63 // case G_FILE_MONITOR_EVENT_MOVED_OUT:
64 // break;
65 // {
66 // gchar *uri = g_file_get_uri (file);
67
68 // g_debug ("Deleted: %s\n", uri);
69
70 // xapp_favorites_remove (xapp_favorites_get_default (), uri);
71 // g_free (uri);
72 // }
73
74 // break;
75 // case G_FILE_MONITOR_EVENT_RENAMED:
76 // break;
77 // {
78 // gchar *uri = g_file_get_uri (file);
79
80 // g_debug ("Renamed: %s\n", uri);
81
82 // rename_favorite (file, other_file);
83 // }
84 // break;
85 // case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
86 // case G_FILE_MONITOR_EVENT_CHANGED:
87 // {
88 // gchar *uri = g_file_get_uri (file);
89
90 // GList *iter;
91
92 // for (iter = priv->infos; iter != NULL; iter = iter->next)
93 // {
94 // XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data;
95
96 // if (g_strcmp0 (uri, info->uri) == 0)
97 // {
98 // GFile *fav_file;
99 // gchar *uri;
100
101 // uri = path_to_fav_uri (info->display_name);
102 // fav_file = g_file_new_for_uri (uri);
103
104 // g_debug ("Changed: %s", uri);
105 // g_free (uri);
106
107 // g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
108 // fav_file,
109 // NULL,
110 // G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED);
111 // g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
112 // fav_file,
113 // NULL,
114 // G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
115 // g_object_unref (fav_file);
116
117 // break;
118 // }
119 // }
120 // }
121 // break;
122 // case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
123 // case G_FILE_MONITOR_EVENT_CREATED:
124 // // case G_FILE_MONITOR_EVENT_CHANGED:
125 // case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
126 // case G_FILE_MONITOR_EVENT_UNMOUNTED:
127 // case G_FILE_MONITOR_EVENT_MOVED:
128 // case G_FILE_MONITOR_EVENT_MOVED_IN:
129 // case G_FILE_MONITOR_EVENT_DELETED:
130 // break;
131 // default:
132 // g_warn_if_reached ();
133 // }
134 }
135
136 static void
137 unmonitor_files (FavoriteVfsFileMonitor *monitor)
138 {
139 /* Disabled. See below */
140 return;
141
142 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
143
144 if (priv->file_monitors != NULL)
145 {
146 g_hash_table_destroy (priv->file_monitors);
147 priv->file_monitors = NULL;
148 }
149 }
150
151 static void
152 monitor_files (FavoriteVfsFileMonitor *monitor)
153 {
154
155 /* Disabled - this isn't necessary right now but could be expanded to help
156 * support less integrated apps. */
157 return;
158
159 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
160 GList *iter;
161
162 priv->file_monitors = g_hash_table_new_full (g_str_hash, g_str_equal,
163 g_free, (GDestroyNotify) g_object_unref);
164
165 for (iter = priv->infos; iter != NULL; iter = iter->next)
166 {
167 XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data;
168 GFileMonitor *real_monitor;
169 GFile *real_file;
170 GError *error;
171
172 g_debug ("Monitoring real file: %s\n", info->uri);
173
174 error = NULL;
175 real_file = g_file_new_for_uri (info->uri);
176 real_monitor = g_file_monitor (real_file,
177 G_FILE_MONITOR_WATCH_MOVES,
178 NULL,
179 &error);
180 g_object_unref (real_file);
181
182 if (real_monitor == NULL)
183 {
184 if (error != NULL)
185 {
186 g_warning ("Unable to add file monitor for '%s': %s", info->uri, error->message);
187 g_error_free (error);
188 }
189
190 continue;
191 }
192
193 g_hash_table_insert (priv->file_monitors,
194 (gpointer) g_strdup (info->uri),
195 (gpointer) real_monitor);
196
197 g_signal_connect (real_monitor,
198 "changed",
199 G_CALLBACK (favorite_real_file_changed),
200 monitor);
201 }
202 }
203
204 static gint
205 find_info_by_uri (gconstpointer ptr_a,
206 gconstpointer ptr_b)
207 {
208 XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr_a;
209 const gchar *uri = (gchar *) ptr_b;
210
211 return g_strcmp0 (info->uri, uri);
212 }
213
214 static void
215 favorites_changed (XAppFavorites *favorites,
216 gpointer user_data)
217 {
218 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
219 g_return_if_fail (FAVORITE_IS_VFS_FILE_MONITOR (user_data));
220
221 FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (user_data);
222 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
223 GList *added, *removed;
224 GList *iter, *new_infos;
225
226 if (g_file_monitor_is_cancelled (G_FILE_MONITOR (monitor)))
227 {
228 return;
229 }
230
231 added = removed = NULL;
232
233 new_infos = xapp_favorites_get_favorites (favorites, NULL);
234
235 for (iter = priv->infos; iter != NULL; iter = iter->next)
236 {
237 XAppFavoriteInfo *old_info = (XAppFavoriteInfo *) iter->data;
238 GList *res = g_list_find_custom (new_infos,
239 (gpointer) old_info->uri,
240 (GCompareFunc) find_info_by_uri);
241 if (res == NULL)
242 {
243 removed = g_list_prepend (removed, old_info);
244 }
245 }
246
247 for (iter = new_infos; iter != NULL; iter = iter->next)
248 {
249 XAppFavoriteInfo *new_info = (XAppFavoriteInfo *) iter->data;
250 GList *res = g_list_find_custom (priv->infos,
251 (gpointer) new_info->uri,
252 (GCompareFunc) find_info_by_uri);
253 if (res == NULL)
254 {
255 added = g_list_prepend (added, new_info);
256 }
257 }
258
259 for (iter = added; iter != NULL; iter = iter->next)
260 {
261 XAppFavoriteInfo *added_info = (XAppFavoriteInfo *) iter->data;
262
263 GFile *file = _favorite_vfs_file_new_for_info (added_info);
264
265 g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
266 file,
267 NULL,
268 G_FILE_MONITOR_EVENT_CREATED);
269 g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
270 file,
271 NULL,
272 G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
273 g_object_unref (file);
274 }
275
276 for (iter = removed; iter != NULL; iter = iter->next)
277 {
278 XAppFavoriteInfo *removed_info = (XAppFavoriteInfo *) iter->data;
279
280 GFile *file = _favorite_vfs_file_new_for_info (removed_info);
281
282 g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
283 file,
284 NULL,
285 G_FILE_MONITOR_EVENT_DELETED);
286 g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
287 file,
288 NULL,
289 G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
290 g_object_unref (file);
291 }
292
293 GList *tmp = priv->infos;
294 priv->infos = new_infos;
295
296 g_list_free_full (tmp, (GDestroyNotify) xapp_favorite_info_free);
297
298 //FIXME: add/remove individually
299 unmonitor_files (monitor);
300 monitor_files (monitor);
301 }
302
303 static void
304 mounts_changed (GVolumeMonitor *mount_mon,
305 GMount *mount,
306 gpointer user_data)
307 {
308 g_return_if_fail (FAVORITE_IS_VFS_FILE_MONITOR (user_data));
309
310 FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (user_data);
311 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
312
313 GFile *root;
314 GList *iter, *mount_favorites;
315
316 root = g_mount_get_root (mount);
317 mount_favorites = NULL;
318
319 // Find any favorites that are descendent from root.
320
321 for (iter = priv->infos; iter != NULL; iter = iter->next)
322 {
323 XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data;
324 GFile *fav_file = g_file_new_for_uri (info->uri);
325 gchar *relpath;
326
327 relpath = g_file_get_relative_path (root, fav_file);
328
329 if (relpath != NULL)
330 {
331 mount_favorites = g_list_prepend (mount_favorites, info);
332 }
333
334 g_free (relpath);
335 g_object_unref (fav_file);
336 }
337
338 if (mount_favorites != NULL)
339 {
340 for (iter = mount_favorites; iter != NULL; iter = iter->next) {
341 XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data;
342 GFile *fav_file;
343 gchar *uri;
344
345 uri = path_to_fav_uri (info->display_name);
346 fav_file = g_file_new_for_uri (uri);
347
348 g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
349 fav_file,
350 NULL,
351 G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED);
352 g_file_monitor_emit_event (G_FILE_MONITOR (monitor),
353 fav_file,
354 NULL,
355 G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
356
357 g_free (uri);
358 g_object_unref (fav_file);
359 }
360
361 g_list_free (mount_favorites);
362 }
363
364 g_object_unref (root);
365
366 unmonitor_files (monitor);
367 monitor_files (monitor);
368 }
369
370 static gboolean
371 favorite_vfs_file_monitor_cancel (GFileMonitor* gfilemon)
372 {
373 FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (gfilemon);
374 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
375
376 if (priv->changed_handler_id > 0)
377 {
378 g_signal_handler_disconnect (xapp_favorites_get_default (), priv->changed_handler_id);
379 }
380
381 return TRUE;
382 }
383
384 static void
385 favorite_vfs_file_monitor_init (FavoriteVfsFileMonitor *monitor)
386 {
387 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor);
388
389 priv->mount_mon = g_volume_monitor_get ();
390 g_signal_connect (priv->mount_mon,
391 "mount-added",
392 G_CALLBACK (mounts_changed),
393 monitor);
394 g_signal_connect (priv->mount_mon,
395 "mount-removed",
396 G_CALLBACK (mounts_changed),
397 monitor);
398
399 priv->infos = xapp_favorites_get_favorites (xapp_favorites_get_default (), NULL);
400 priv->changed_handler_id = g_signal_connect (xapp_favorites_get_default (),
401 "changed",
402 G_CALLBACK (favorites_changed),
403 monitor);
404
405 monitor_files (monitor);
406 }
407
408 static void
409 favorite_vfs_file_monitor_dispose (GObject *object)
410 {
411 FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR(object);
412 FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private(monitor);
413
414 unmonitor_files (monitor);
415
416 g_signal_handlers_disconnect_by_func (priv->mount_mon, mounts_changed, monitor);
417 g_clear_object (&priv->mount_mon);
418
419 if (priv->infos != NULL)
420 {
421 g_list_free_full (priv->infos, (GDestroyNotify) xapp_favorite_info_free);
422 priv->infos = NULL;
423 }
424
425 G_OBJECT_CLASS (favorite_vfs_file_monitor_parent_class)->dispose (object);
426 }
427
428 static void
429 favorite_vfs_file_monitor_finalize (GObject *object)
430 {
431 // FavoriteVfsFileMonitor *self = FAVORITE_VFS_FILE_MONITOR(object);
432 // FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private(self);
433
434 G_OBJECT_CLASS (favorite_vfs_file_monitor_parent_class)->finalize (object);
435 }
436
437 static void
438 favorite_vfs_file_monitor_class_init (FavoriteVfsFileMonitorClass *klass)
439 {
440 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
441 GFileMonitorClass *monitor_class = G_FILE_MONITOR_CLASS (klass);
442
443 gobject_class->dispose = favorite_vfs_file_monitor_dispose;
444 gobject_class->finalize = favorite_vfs_file_monitor_finalize;
445
446 monitor_class->cancel = favorite_vfs_file_monitor_cancel;
447 }
448
449 GFileMonitor *
450 favorite_vfs_file_monitor_new (void)
451 {
452 return G_FILE_MONITOR (g_object_new (FAVORITE_TYPE_VFS_FILE_MONITOR, NULL));
453 }
454
0 #ifndef __FAVORITE_VFS_FILE_MONITOR_H__
1 #define __FAVORITE_VFS_FILE_MONITOR_H__
2
3 #include <gio/gio.h>
4 #include <glib-object.h>
5
6 G_BEGIN_DECLS
7
8 #define FAVORITE_TYPE_VFS_FILE_MONITOR (favorite_vfs_file_monitor_get_type ())
9
10 G_DECLARE_FINAL_TYPE (FavoriteVfsFileMonitor, favorite_vfs_file_monitor,
11 FAVORITE, VFS_FILE_MONITOR, GFileMonitor)
12
13 GFileMonitor *favorite_vfs_file_monitor_new (void);
14
15 G_END_DECLS
16
17 #endif /* __FAVORITE_VFS_FILE_MONITOR_H__ */
0 #include <config.h>
1 #include <glib/gi18n-lib.h>
2
3 #include "favorite-vfs-file.h"
4 #include "favorite-vfs-file-enumerator.h"
5 #include "favorite-vfs-file-monitor.h"
6
7 #define FAVORITES_SCHEMA "org.x.apps.favorites"
8 #define FAVORITE_DCONF_METADATA_KEY "root-metadata"
9
10 static GSettings *settings = NULL;
11
12 typedef struct
13 {
14 gchar *uri; // favorites://foo
15
16 XAppFavoriteInfo *info;
17 } FavoriteVfsFilePrivate;
18
19 struct _FavoriteVfsFile
20 {
21 GObject parent_instance;
22 FavoriteVfsFilePrivate *priv;
23 };
24
25 GList *_xapp_favorites_get_display_names (XAppFavorites *favorites);
26 static void favorite_vfs_file_gfile_iface_init (GFileIface *iface);
27
28 gchar *
29 path_to_fav_uri (const gchar *path)
30 {
31 g_return_val_if_fail (path != NULL, NULL);
32
33 return g_strconcat (ROOT_URI, path, NULL);
34 }
35
36 gchar *
37 fav_uri_to_display_name (const gchar *uri)
38 {
39 g_return_val_if_fail (uri != NULL, NULL);
40 g_return_val_if_fail (g_str_has_prefix (uri, ROOT_URI), NULL);
41
42 const gchar *ptr;
43
44 ptr = uri + strlen (ROOT_URI);
45
46 if (ptr[0] == '/')
47 {
48 ptr++;
49 }
50
51 return g_strdup (ptr);
52 }
53
54 G_DEFINE_TYPE_EXTENDED (FavoriteVfsFile,
55 favorite_vfs_file,
56 G_TYPE_OBJECT,
57 0,
58 G_ADD_PRIVATE (FavoriteVfsFile)
59 G_IMPLEMENT_INTERFACE (G_TYPE_FILE, favorite_vfs_file_gfile_iface_init))
60
61 static gboolean
62 is_root_file (FavoriteVfsFile *file)
63 {
64 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (file);
65
66 return g_strcmp0 (priv->uri, ROOT_URI) == 0;
67 }
68
69 static GFile *
70 file_dup (GFile *file)
71 {
72 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
73
74 return favorite_vfs_file_new_for_uri (priv->uri);
75 }
76
77 static guint
78 file_hash (GFile *file)
79 {
80 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
81
82 return g_str_hash (priv->uri);
83 }
84
85 static gboolean
86 file_equal (GFile *file1,
87 GFile *file2)
88 {
89 FavoriteVfsFilePrivate *priv1 = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file1));
90 FavoriteVfsFilePrivate *priv2 = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file2));
91
92 return g_strcmp0 (priv1->uri, priv2->uri) == 0;
93 }
94
95 static gboolean
96 file_is_native (GFile *file)
97 {
98 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
99
100 if (priv->info != NULL && priv->info->uri != NULL)
101 {
102 GFile *real_file;
103 gboolean is_really_native;
104
105 real_file = g_file_new_for_uri (priv->info->uri);
106 is_really_native = g_file_is_native (real_file);
107
108 g_object_unref (real_file);
109 return is_really_native;
110 }
111
112 return FALSE;
113 }
114
115 static gboolean
116 file_has_uri_scheme (GFile *file,
117 const gchar *uri_scheme)
118 {
119 return g_strcmp0 (uri_scheme, URI_SCHEME) == 0;
120 }
121
122 static gchar *
123 file_get_uri_scheme (GFile *file)
124 {
125 return g_strdup (URI_SCHEME);
126 }
127
128 static gchar *
129 file_get_basename (GFile *file)
130 {
131 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
132
133 if (priv->info == NULL)
134 {
135 return g_strdup ("/");
136 }
137
138 return g_strdup (priv->info->display_name);
139 }
140
141 static gchar *
142 file_get_path (GFile *file)
143 {
144 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
145
146 if (file_is_native (file))
147 {
148 GFile *real_file;
149 gchar *ret;
150
151 real_file = g_file_new_for_uri (priv->info->uri);
152
153 // file can't be native without an info, so we don't need to check for null here
154 ret = g_file_get_path (real_file);
155 g_object_unref (real_file);
156
157 return ret;
158 }
159
160 // GtkFileChooser checks for a path (and not null) before allowing a shortcut to
161 // be added using gtk_file_chooser_add_shortcut_folder_uri(). Even though this / doesn't
162 // make much sense for favorites:/// it allows it to be added to the file chooser without
163 // being forced to override its 'local-only' property.
164 if (is_root_file (FAVORITE_VFS_FILE (file)))
165 {
166 return g_strdup ("/");
167 }
168
169 return NULL;
170 }
171
172 static gchar *
173 file_get_uri (GFile *file)
174 {
175 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
176
177 return g_strdup (priv->uri);
178 }
179
180 static gchar *
181 file_get_parse_name (GFile *file)
182 {
183 return file_get_uri (file);
184 }
185
186 static GFile *
187 file_get_parent (GFile *file)
188 {
189 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
190
191 // We're only ever one level deep.
192 if (priv->info != NULL)
193 {
194 return g_file_new_for_uri (ROOT_URI);
195 }
196
197 return NULL;
198 }
199
200 static gboolean
201 file_prefix_matches (GFile *parent,
202 GFile *descendant)
203 {
204 g_autofree gchar *puri = NULL;
205 g_autofree gchar *duri = NULL;
206 gchar *ptr = NULL;
207
208 puri = g_file_get_uri (parent);
209 duri = g_file_get_uri (descendant);
210
211 ptr = g_strstr_len (puri, -1, duri);
212
213 if ((ptr == puri) && ptr[strlen (duri) + 1] == '/')
214 {
215 return TRUE;
216 }
217
218 return FALSE;
219 }
220
221 static gchar *
222 file_get_relative_path (GFile *parent,
223 GFile *descendant)
224 {
225 g_autofree gchar *puri = NULL;
226 g_autofree gchar *duri = NULL;
227 g_autofree gchar *rpath = NULL;
228 gchar *ptr = NULL;
229
230 puri = g_file_get_uri (parent);
231 duri = g_file_get_uri (descendant);
232
233 ptr = g_strstr_len (puri, -1, duri);
234
235 if ((ptr == puri) && ptr[strlen (duri) + 1] == '/')
236 {
237 rpath = g_strdup (puri + strlen (duri) + 1);
238
239 return rpath;
240 }
241
242 return NULL;
243 }
244
245 static GFile *
246 file_resolve_relative_path (GFile *file,
247 const gchar *relative_path)
248 {
249 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
250 GFile *relative_file;
251 gchar *uri;
252
253 if (g_path_is_absolute (relative_path))
254 {
255 return g_file_new_for_path (relative_path);
256 }
257
258 if (priv->info != NULL && priv->info->uri != NULL)
259 {
260 GFile *real_file;
261 real_file = g_file_new_for_uri (priv->info->uri);
262
263 relative_file = g_file_resolve_relative_path (real_file,
264 relative_path);
265
266 g_object_unref (real_file);
267 return relative_file;
268 }
269
270 if (g_strcmp0 (relative_path, ".") == 0)
271 {
272 relative_file = file_dup (file);
273
274 return relative_file;
275 }
276
277 uri = path_to_fav_uri (relative_path);
278
279 relative_file = g_file_new_for_uri (uri);
280 g_free (uri);
281
282 return relative_file;
283 }
284
285 static GFile *
286 file_get_child_for_display_name (GFile *file,
287 const char *display_name,
288 GError **error)
289 {
290 return g_file_get_child (file, display_name);
291 }
292
293 static GFile *
294 file_set_display_name (GFile *file,
295 const gchar *display_name,
296 GCancellable *cancellable,
297 GError **error)
298 {
299 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
300
301 if (priv->info != NULL && priv->info->uri != NULL)
302 {
303 GFile *real_file;
304 GFile *ret;
305
306 real_file = g_file_new_for_uri (priv->info->uri);
307 ret = g_file_set_display_name (real_file,
308 display_name,
309 cancellable,
310 error);
311 g_object_unref (real_file);
312
313 return ret;
314 }
315
316 g_set_error_literal (error, G_IO_ERROR,
317 G_IO_ERROR_NOT_SUPPORTED,
318 "Can't rename file");
319
320 return NULL;
321 }
322
323 static GFileEnumerator *
324 file_enumerate_children(GFile *file,
325 const char *attributes,
326 GFileQueryInfoFlags flags,
327 GCancellable *cancellable,
328 GError **error)
329 {
330 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
331 GFileEnumerator *enumerator;
332
333 if (priv->info != NULL && priv->info->uri != NULL)
334 {
335 GFile *real_file;
336 real_file = g_file_new_for_uri (priv->info->uri);
337
338 enumerator = g_file_enumerate_children (real_file,
339 attributes,
340 flags,
341 cancellable,
342 error);
343
344 g_object_unref (real_file);
345 return enumerator;
346 }
347
348 GList *uris;
349
350 uris = _xapp_favorites_get_display_names (xapp_favorites_get_default ());
351 enumerator = favorite_vfs_file_enumerator_new (file,
352 attributes,
353 flags,
354 uris);
355
356 g_list_free (uris);
357
358 return enumerator;
359 }
360
361 static GFileInfo *
362 file_query_info (GFile *file,
363 const char *attributes,
364 GFileQueryInfoFlags flags,
365 GCancellable *cancellable,
366 GError **error)
367 {
368 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
369 GFileInfo *info;
370 GIcon *icon;
371
372 if (priv->info != NULL)
373 {
374 if (!priv->info->uri)
375 {
376 if (error != NULL)
377 {
378 *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "File not found");
379 }
380 return NULL;
381 }
382
383 GFile *real_file = g_file_new_for_uri (priv->info->uri);
384
385 info = g_file_query_info (real_file, attributes, flags, cancellable, error);
386
387 if (info != NULL)
388 {
389 gchar *local_path;
390
391 g_file_info_set_display_name (info, priv->info->display_name);
392 g_file_info_set_name (info, priv->info->display_name);
393 g_file_info_set_is_symlink (info, TRUE);
394
395 local_path = g_file_get_path (real_file);
396
397 if (local_path != NULL)
398 {
399 g_file_info_set_symlink_target (info, local_path);
400 g_free (local_path);
401 }
402 else
403 {
404 g_file_info_set_symlink_target (info, priv->info->uri);
405 }
406
407 // Recent sets this also. If it's set, this uri is used to display the "location"
408 // for the file (the directory in which real file resides).
409 g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, priv->info->uri);
410
411 g_file_info_set_attribute_string (info, FAVORITE_AVAILABLE_METADATA_KEY, META_TRUE);
412 }
413 else
414 {
415 g_clear_error (error);
416 gchar *content_type;
417
418 info = g_file_info_new ();
419
420 g_file_info_set_display_name (info, priv->info->display_name);
421 g_file_info_set_name (info, priv->info->display_name);
422 g_file_info_set_is_symlink (info, TRUE);
423
424 g_file_info_set_symlink_target (info, priv->info->uri);
425
426 /* Prevent showing a 'thumbnailing' icon */
427 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED, TRUE);
428
429 /* This will keep the sort position the same for missing or unmounted files */
430 g_file_info_set_attribute_string (info, FAVORITE_METADATA_KEY, META_TRUE);
431 g_file_info_set_attribute_string (info, FAVORITE_AVAILABLE_METADATA_KEY, META_FALSE);
432 g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, priv->info->uri);
433
434 content_type = g_content_type_from_mime_type (priv->info->cached_mimetype);
435
436 icon = g_content_type_get_icon (content_type);
437 g_file_info_set_icon (info, icon);
438 g_object_unref (icon);
439
440 icon = g_content_type_get_symbolic_icon (content_type);
441 g_file_info_set_symbolic_icon (info, icon);
442 g_object_unref (icon);
443
444 g_free (content_type);
445 }
446
447 g_object_unref (real_file);
448
449 return info;
450 }
451
452 if (is_root_file (FAVORITE_VFS_FILE (file)))
453 {
454 GFileAttributeMatcher *matcher = g_file_attribute_matcher_new (attributes);
455
456 info = g_file_info_new ();
457
458 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_NAME))
459 g_file_info_set_name (info, "/");
460
461 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
462 g_file_info_set_display_name (info, _("Favorites"));
463
464 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_TYPE))
465 g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
466
467 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_ICON))
468 {
469 icon = g_themed_icon_new ("xapp-favorites");
470 g_file_info_set_icon (info, icon);
471
472 g_object_unref (icon);
473 }
474
475 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON))
476 {
477 icon = g_themed_icon_new ("xapp-favorites-symbolic");
478 g_file_info_set_symbolic_icon (info, icon);
479 g_object_unref (icon);
480 }
481
482 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_GVFS_BACKEND))
483 g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_GVFS_BACKEND, "favorites");
484
485 if (g_file_attribute_matcher_matches (matcher, FAVORITE_AVAILABLE_METADATA_KEY))
486 g_file_info_set_attribute_string (info, FAVORITE_AVAILABLE_METADATA_KEY, META_TRUE);
487
488 if (g_file_attribute_matcher_enumerate_namespace (matcher, "metadata"))
489 {
490 gchar **entries = g_settings_get_strv (settings, FAVORITE_DCONF_METADATA_KEY);
491
492 if (entries != NULL)
493 {
494 gint i;
495
496 for (i = 0; entries[i] != NULL; i++)
497 {
498 gchar **t_n_v;
499
500 t_n_v = g_strsplit (entries[i], "==", 3);
501
502 if (g_strv_length (t_n_v) == 3)
503 {
504 if (g_strcmp0 (t_n_v[0], "string") == 0)
505 {
506 g_file_info_set_attribute_string (info, t_n_v[1], t_n_v[2]);
507 }
508 else
509 if (g_strcmp0 (t_n_v[0], "strv") == 0)
510 {
511 gchar **members = g_strsplit (t_n_v[2], "|", -1);
512
513 g_file_info_set_attribute_stringv (info, t_n_v[1], members);
514
515 g_strfreev (members);
516 }
517 }
518
519 g_strfreev (t_n_v);
520 }
521 }
522
523 g_strfreev (entries);
524 }
525
526 g_file_attribute_matcher_unref (matcher);
527 }
528
529 return info;
530 }
531
532 GFileInfo *
533 file_query_filesystem_info (GFile *file,
534 const char *attributes,
535 GCancellable *cancellable,
536 GError **error)
537 {
538 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
539
540 if (priv->info != NULL && priv->info->uri != NULL)
541 {
542 GFileInfo *info;
543 GFile *real_file;
544 real_file = g_file_new_for_uri (priv->info->uri);
545
546 info = g_file_query_filesystem_info (real_file,
547 attributes,
548 cancellable,
549 error);
550
551 g_object_unref (real_file);
552 return info;
553 }
554
555 GFileInfo *info;
556 GFileAttributeMatcher *matcher;
557
558 info = g_file_info_new ();
559
560 matcher = g_file_attribute_matcher_new (attributes);
561
562 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE))
563 {
564 g_file_info_set_attribute_string (info,
565 G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "favorites");
566 }
567
568 if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
569 {
570 g_file_info_set_attribute_boolean (info,
571 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
572 }
573
574 g_file_attribute_matcher_unref (matcher);
575
576 return info;
577 }
578
579 GMount *
580 file_find_enclosing_mount (GFile *file,
581 GCancellable *cancellable,
582 GError **error)
583 {
584 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
585
586 if (priv->info != NULL && priv->info->uri != NULL)
587 {
588 GMount *mount;
589 GFile *real_file;
590 real_file = g_file_new_for_uri (priv->info->uri);
591
592 mount = g_file_find_enclosing_mount (real_file,
593 cancellable,
594 error);
595
596 g_object_unref (real_file);
597 return mount;
598 }
599
600 g_set_error_literal (error, G_IO_ERROR,
601 G_IO_ERROR_NOT_SUPPORTED,
602 "Can't find favorite file enclosing mount");
603
604 return NULL;
605 }
606
607 GFileAttributeInfoList *
608 file_query_settable_attributes (GFile *file,
609 GCancellable *cancellable,
610 GError **error)
611 {
612 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
613
614 if (priv->info != NULL && priv->info->uri != NULL)
615 {
616 GFileAttributeInfoList *list;
617 GFile *real_file;
618 real_file = g_file_new_for_uri (priv->info->uri);
619
620 list = g_file_query_settable_attributes (real_file,
621 cancellable,
622 error);
623
624 g_object_unref (real_file);
625 return list;
626 }
627
628 return g_file_attribute_info_list_new ();
629 }
630
631 GFileAttributeInfoList *
632 file_query_writable_namespaces (GFile *file,
633 GCancellable *cancellable,
634 GError **error)
635 {
636 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
637 GFileAttributeInfoList *list;
638
639 if (priv->info != NULL && priv->info->uri != NULL)
640 {
641 GFile *real_file;
642 real_file = g_file_new_for_uri (priv->info->uri);
643
644 list = g_file_query_writable_namespaces (real_file,
645 cancellable,
646 error);
647
648 g_object_unref (real_file);
649 return list;
650 }
651
652 list = g_file_attribute_info_list_new ();
653
654 g_file_attribute_info_list_add (list,
655 "metadata",
656 G_FILE_ATTRIBUTE_TYPE_STRING,
657 G_FILE_ATTRIBUTE_INFO_NONE);
658
659 return list;
660 }
661
662 static void
663 remove_root_metadata (const gchar *attr_name)
664 {
665 GPtrArray *new_array;
666 gchar **old_metadata, **new_metadata;
667 gint i;
668
669 old_metadata = g_settings_get_strv (settings, FAVORITE_DCONF_METADATA_KEY);
670
671 if (old_metadata == NULL)
672 {
673 return;
674 }
675
676 new_array = g_ptr_array_new ();
677
678 for (i = 0; old_metadata[i] != NULL; i++)
679 {
680 gchar **t_n_v;
681
682 t_n_v = g_strsplit (old_metadata[i], "==", 3);
683
684 if (g_strcmp0 (t_n_v[1], attr_name) != 0)
685 {
686 g_ptr_array_add (new_array, g_strdup (old_metadata[i]));
687 }
688
689 g_strfreev (t_n_v);
690 }
691
692 g_ptr_array_add (new_array, NULL);
693 g_strfreev (old_metadata);
694
695 new_metadata = (gchar **) g_ptr_array_free (new_array, FALSE);
696
697 g_settings_set_strv (settings, FAVORITE_DCONF_METADATA_KEY, (const gchar * const *) new_metadata);
698 g_strfreev (new_metadata);
699 }
700
701 static void
702 set_or_update_root_metadata (const gchar *attr_name,
703 const gpointer value,
704 GFileAttributeType type)
705 {
706 GPtrArray *new_array;
707 gchar **old_metadata, **new_metadata;
708 gint i;
709 gchar *entry;
710 gboolean exists;
711
712 old_metadata = g_settings_get_strv (settings, FAVORITE_DCONF_METADATA_KEY);
713
714 if (old_metadata == NULL)
715 {
716 return;
717 }
718
719 switch (type)
720 {
721 case G_FILE_ATTRIBUTE_TYPE_STRING:
722 {
723 entry = g_strdup_printf ("string==%s==%s", attr_name, (gchar *) value);
724 break;
725 }
726 case G_FILE_ATTRIBUTE_TYPE_STRINGV:
727 {
728 gchar *val_strv = g_strjoinv ("|", (gchar **) value);
729 entry = g_strdup_printf ("strv==%s==%s", attr_name, val_strv);
730 g_free (val_strv);
731 break;
732 }
733 default:
734 break;
735 }
736
737 exists = FALSE;
738 new_array = g_ptr_array_new ();
739
740 for (i = 0; old_metadata[i] != NULL; i++)
741 {
742 gchar **t_n_v;
743
744 t_n_v = g_strsplit (old_metadata[i], "==", 3);
745
746 if (g_strcmp0 (t_n_v[1], attr_name) == 0)
747 {
748 g_ptr_array_add (new_array, entry);
749 exists = TRUE;
750 }
751 else
752 {
753 g_ptr_array_add (new_array, g_strdup (old_metadata[i]));
754 }
755
756 g_strfreev (t_n_v);
757 }
758
759 if (!exists)
760 {
761 g_ptr_array_add (new_array, entry);
762 }
763
764 g_ptr_array_add (new_array, NULL);
765 g_strfreev (old_metadata);
766
767 new_metadata = (gchar **) g_ptr_array_free (new_array, FALSE);
768
769 g_settings_set_strv (settings, FAVORITE_DCONF_METADATA_KEY, (const gchar * const *) new_metadata);
770 g_strfreev (new_metadata);
771 }
772
773 gboolean
774 file_set_attribute (GFile *file,
775 const gchar *attribute,
776 GFileAttributeType type,
777 gpointer value_p,
778 GFileQueryInfoFlags flags,
779 GCancellable *cancellable,
780 GError **error)
781 {
782 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
783 gboolean ret;
784
785 if (priv->info != NULL && priv->info->uri != NULL)
786 {
787 GFile *real_file;
788 gboolean ret;
789 real_file = g_file_new_for_uri (priv->info->uri);
790
791 ret = g_file_set_attribute (real_file,
792 attribute,
793 type,
794 value_p,
795 flags,
796 cancellable,
797 error);
798
799 g_object_unref (real_file);
800 return ret;
801 }
802
803 ret = FALSE;
804
805 if (!is_root_file (FAVORITE_VFS_FILE (file)))
806 {
807 g_set_error (error, G_IO_ERROR,
808 G_IO_ERROR_NOT_SUPPORTED,
809 "Can't set attributes for %s - only the root (favorites:///) is supported.", priv->uri);
810 }
811 else
812 {
813 if (g_str_has_prefix (attribute, "metadata"))
814 {
815 if (type == G_FILE_ATTRIBUTE_TYPE_INVALID || value_p == NULL || ((char *) value_p)[0] == '\0')
816 {
817 // unset metadata
818 remove_root_metadata (attribute);
819 ret = TRUE;
820 }
821 else
822 {
823 if (type == G_FILE_ATTRIBUTE_TYPE_STRING || type == G_FILE_ATTRIBUTE_TYPE_STRINGV)
824 {
825 set_or_update_root_metadata (attribute, (gchar *) value_p, type);
826 ret = TRUE;
827 }
828 else
829 {
830 g_set_error (error, G_IO_ERROR,
831 G_IO_ERROR_NOT_SUPPORTED,
832 "Can't set attribute '%s' for favorites:/// file "
833 "(only string-type metadata are allowed).", attribute);
834 }
835 }
836 }
837 else
838 {
839 g_set_error (error, G_IO_ERROR,
840 G_IO_ERROR_NOT_SUPPORTED,
841 "Can't set attribute '%s' for favorites:/// file "
842 "(only 'metadata' namespace is allowed).", attribute);
843 }
844 }
845
846 return ret;
847 }
848
849 static gboolean
850 file_set_attributes_from_info (GFile *file,
851 GFileInfo *info,
852 GFileQueryInfoFlags flags,
853 GCancellable *cancellable,
854 GError **error)
855 {
856 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
857 gboolean res;
858
859 if (priv->info != NULL && priv->info->uri != NULL)
860 {
861 GFile *real_file;
862 gboolean ret = FALSE;
863 real_file = g_file_new_for_uri (priv->info->uri);
864
865 ret = g_file_set_attributes_from_info (real_file, info, flags, cancellable, error);
866
867 g_object_unref (real_file);
868 return ret;
869 }
870
871 res = TRUE;
872
873 if (g_file_info_has_namespace (info, "metadata"))
874 {
875 GFileAttributeType type;
876 gchar **attributes;
877 gpointer value_p;
878 gint i;
879
880 attributes = g_file_info_list_attributes (info, "metadata");
881
882 for (i = 0; attributes[i] != NULL; i++)
883 {
884 if (g_file_info_get_attribute_data (info, attributes[i], &type, &value_p, NULL))
885 {
886 if (!file_set_attribute (file,
887 attributes[i],
888 type,
889 value_p,
890 flags,
891 cancellable,
892 error))
893 {
894 g_file_info_set_attribute_status (info, attributes[i], G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING);
895 error = NULL; // from gvfs gdaemonvfs.c - ignore subsequent errors iterating thru attribute list.
896 res = FALSE;
897 }
898 else
899 {
900 g_file_info_set_attribute_status (info, attributes[i], G_FILE_ATTRIBUTE_STATUS_SET);
901 }
902 }
903 }
904
905 g_strfreev (attributes);
906 }
907
908 return res;
909 }
910
911 static GFileInputStream *
912 file_read_fn (GFile *file,
913 GCancellable *cancellable,
914 GError **error)
915 {
916 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
917
918 if (priv->info != NULL && priv->info->uri != NULL)
919 {
920 GFile *real_file = g_file_new_for_uri (priv->info->uri);
921
922 GFileInputStream *stream;
923
924 stream = g_file_read (real_file, cancellable, error);
925
926 g_object_unref (real_file);
927 return stream;
928 }
929
930 g_set_error_literal (error, G_IO_ERROR,
931 G_IO_ERROR_NOT_SUPPORTED,
932 _("Operation not supported"));
933
934 return NULL;
935 }
936
937 GFileOutputStream *
938 file_append_to (GFile *file,
939 GFileCreateFlags flags,
940 GCancellable *cancellable,
941 GError **error)
942 {
943 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
944
945 if (priv->info != NULL && priv->info->uri != NULL)
946 {
947 GFile *real_file = g_file_new_for_uri (priv->info->uri);
948
949 GFileOutputStream *stream;
950
951 stream = g_file_append_to (real_file,
952 flags,
953 cancellable,
954 error);
955
956 g_object_unref (real_file);
957 return stream;
958 }
959
960 g_set_error_literal (error, G_IO_ERROR,
961 G_IO_ERROR_NOT_SUPPORTED,
962 _("Operation not supported"));
963 return NULL;
964 }
965
966 GFileOutputStream *
967 file_create (GFile *file,
968 GFileCreateFlags flags,
969 GCancellable *cancellable,
970 GError **error)
971 {
972 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
973
974 if (priv->info != NULL && priv->info->uri != NULL)
975 {
976 GFile *real_file = g_file_new_for_uri (priv->info->uri);
977
978 GFileOutputStream *stream;
979
980 stream = g_file_create (real_file,
981 flags,
982 cancellable,
983 error);
984
985 g_object_unref (real_file);
986 return stream;
987 }
988
989 g_set_error_literal (error, G_IO_ERROR,
990 G_IO_ERROR_NOT_SUPPORTED,
991 _("Operation not supported"));
992
993 return NULL;
994 }
995
996 static GFileOutputStream *
997 file_replace (GFile *file,
998 const char *etag,
999 gboolean make_backup,
1000 GFileCreateFlags flags,
1001 GCancellable *cancellable,
1002 GError **error)
1003 {
1004 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1005
1006 if (priv->info != NULL && priv->info->uri != NULL)
1007 {
1008 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1009
1010 GFileOutputStream *stream;
1011
1012 stream = g_file_replace (real_file,
1013 etag,
1014 make_backup,
1015 flags,
1016 cancellable,
1017 error);
1018
1019 g_object_unref (real_file);
1020 return stream;
1021 }
1022
1023 g_set_error_literal (error, G_IO_ERROR,
1024 G_IO_ERROR_NOT_SUPPORTED,
1025 _("Operation not supported"));
1026
1027 return NULL;
1028 }
1029
1030 static GFileIOStream *
1031 file_open_readwrite (GFile *file,
1032 GCancellable *cancellable,
1033 GError **error)
1034 {
1035 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1036
1037 if (priv->info != NULL && priv->info->uri != NULL)
1038 {
1039 GFileIOStream *res;
1040 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1041
1042 res = g_file_open_readwrite (real_file,
1043 cancellable,
1044 error);
1045
1046 g_object_unref (real_file);
1047 return res;
1048 }
1049
1050 g_set_error_literal (error, G_IO_ERROR,
1051 G_IO_ERROR_NOT_SUPPORTED,
1052 _("Operation not supported"));
1053
1054 return NULL;
1055 }
1056
1057 static GFileIOStream *
1058 file_create_readwrite (GFile *file,
1059 GFileCreateFlags flags,
1060 GCancellable *cancellable,
1061 GError **error)
1062 {
1063 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1064
1065 if (priv->info != NULL && priv->info->uri != NULL)
1066 {
1067 GFileIOStream *res;
1068 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1069
1070 res = g_file_create_readwrite (real_file,
1071 flags,
1072 cancellable,
1073 error);
1074
1075 g_object_unref (real_file);
1076 return res;
1077 }
1078
1079 g_set_error_literal (error, G_IO_ERROR,
1080 G_IO_ERROR_NOT_SUPPORTED,
1081 _("Operation not supported"));
1082
1083 return NULL;
1084 }
1085
1086 static GFileIOStream *
1087 file_replace_readwrite (GFile *file,
1088 const char *etag,
1089 gboolean make_backup,
1090 GFileCreateFlags flags,
1091 GCancellable *cancellable,
1092 GError **error)
1093 {
1094 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1095
1096 if (priv->info != NULL && priv->info->uri != NULL)
1097 {
1098 GFileIOStream *res;
1099 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1100
1101 res = g_file_replace_readwrite (real_file,
1102 etag,
1103 make_backup,
1104 flags,
1105 cancellable,
1106 error);
1107
1108 g_object_unref (real_file);
1109 return res;
1110 }
1111
1112 g_set_error_literal (error, G_IO_ERROR,
1113 G_IO_ERROR_NOT_SUPPORTED,
1114 _("Operation not supported"));
1115
1116 return NULL;
1117 }
1118
1119 static gboolean
1120 file_delete (GFile *file,
1121 GCancellable *cancellable,
1122 GError **error)
1123 {
1124 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1125
1126 if (priv->info != NULL && priv->info->uri != NULL)
1127 {
1128 gboolean res;
1129 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1130
1131 res = g_file_delete (real_file,
1132 cancellable,
1133 error);
1134
1135 g_object_unref (real_file);
1136 return res;
1137 }
1138
1139 g_set_error_literal (error, G_IO_ERROR,
1140 G_IO_ERROR_NOT_SUPPORTED,
1141 _("Operation not supported"));
1142
1143 return FALSE;
1144 }
1145
1146 static gboolean
1147 file_trash (GFile *file,
1148 GCancellable *cancellable,
1149 GError **error)
1150 {
1151 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1152
1153 if (priv->info != NULL && priv->info->uri != NULL)
1154 {
1155 gboolean res;
1156 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1157
1158 res = g_file_trash (real_file,
1159 cancellable,
1160 error);
1161 g_object_unref (real_file);
1162
1163 return res;
1164 }
1165
1166 g_set_error_literal (error, G_IO_ERROR,
1167 G_IO_ERROR_NOT_SUPPORTED,
1168 "Not supported");
1169
1170 return FALSE;
1171 }
1172
1173 gboolean
1174 file_move (GFile *source,
1175 GFile *destination,
1176 GFileCopyFlags flags,
1177 GCancellable *cancellable,
1178 GFileProgressCallback progress_callback,
1179 gpointer progress_callback_data,
1180 GError **error)
1181 {
1182 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (source));
1183
1184 if (priv->info != NULL && priv->info->uri != NULL)
1185 {
1186 gboolean res;
1187 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1188
1189 res = g_file_move (real_file,
1190 destination,
1191 flags,
1192 cancellable,
1193 progress_callback,
1194 progress_callback_data,
1195 error);
1196
1197 g_object_unref (real_file);
1198 return res;
1199 }
1200
1201 g_set_error_literal (error, G_IO_ERROR,
1202 G_IO_ERROR_NOT_SUPPORTED,
1203 _("Operation not supported"));
1204
1205 return FALSE;
1206 }
1207
1208 static GFileMonitor *
1209 file_monitor_dir (GFile *file,
1210 GFileMonitorFlags flags,
1211 GCancellable *cancellable,
1212 GError **error)
1213 {
1214 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1215
1216 if (priv->info != NULL && priv->info->uri != NULL)
1217 {
1218 GFile *real_file;
1219 GFileMonitor *monitor;
1220
1221 real_file = g_file_new_for_uri (priv->info->uri);
1222 monitor = g_file_monitor_directory (real_file,
1223 flags,
1224 cancellable,
1225 error);
1226 g_object_unref (real_file);
1227
1228 return monitor;
1229 }
1230 else
1231 if (is_root_file (FAVORITE_VFS_FILE (file)))
1232 {
1233 return favorite_vfs_file_monitor_new ();
1234 }
1235
1236 g_set_error_literal (error, G_IO_ERROR,
1237 G_IO_ERROR_NOT_SUPPORTED,
1238 _("Operation not supported"));
1239
1240 return NULL;
1241 }
1242
1243 static GFileMonitor *
1244 file_monitor_file (GFile *file,
1245 GFileMonitorFlags flags,
1246 GCancellable *cancellable,
1247 GError **error)
1248 {
1249 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1250
1251 if (priv->info != NULL && priv->info->uri != NULL)
1252 {
1253 GFile *real_file;
1254 GFileMonitor *monitor;
1255
1256 real_file = g_file_new_for_uri (priv->info->uri);
1257 monitor = g_file_monitor_file (real_file,
1258 flags,
1259 cancellable,
1260 error);
1261 g_object_unref (real_file);
1262
1263 return monitor;
1264 }
1265
1266 g_set_error_literal (error, G_IO_ERROR,
1267 G_IO_ERROR_NOT_SUPPORTED,
1268 _("Operation not supported"));
1269
1270 return NULL;
1271 }
1272
1273 gboolean
1274 file_measure_disk_usage (GFile *file,
1275 GFileMeasureFlags flags,
1276 GCancellable *cancellable,
1277 GFileMeasureProgressCallback progress_callback,
1278 gpointer progress_data,
1279 guint64 *disk_usage,
1280 guint64 *num_dirs,
1281 guint64 *num_files,
1282 GError **error)
1283 {
1284 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1285
1286 if (priv->info != NULL && priv->info->uri != NULL)
1287 {
1288 gboolean res;
1289 GFile *real_file = g_file_new_for_uri (priv->info->uri);
1290
1291 res = g_file_measure_disk_usage (real_file,
1292 flags,
1293 cancellable,
1294 progress_callback,
1295 progress_data,
1296 disk_usage,
1297 num_dirs,
1298 num_files,
1299 error);
1300
1301 g_object_unref (real_file);
1302 return res;
1303 }
1304
1305 g_set_error_literal (error, G_IO_ERROR,
1306 G_IO_ERROR_NOT_SUPPORTED,
1307 _("Operation not supported"));
1308
1309 return FALSE;
1310 }
1311
1312 static void favorite_vfs_file_gfile_iface_init (GFileIface *iface)
1313 {
1314 iface->dup = file_dup;
1315 iface->hash = file_hash;
1316 iface->equal = file_equal;
1317 iface->is_native = file_is_native;
1318 iface->has_uri_scheme = file_has_uri_scheme;
1319 iface->get_uri_scheme = file_get_uri_scheme;
1320 iface->get_basename = file_get_basename;
1321 iface->get_path = file_get_path;
1322 iface->get_uri = file_get_uri;
1323 iface->get_parse_name = file_get_parse_name;
1324 iface->get_parent = file_get_parent;
1325 iface->prefix_matches = file_prefix_matches;
1326 iface->get_relative_path = file_get_relative_path;
1327 iface->resolve_relative_path = file_resolve_relative_path;
1328 iface->get_child_for_display_name = file_get_child_for_display_name;
1329 iface->set_display_name = file_set_display_name;
1330 iface->enumerate_children = file_enumerate_children;
1331 iface->query_info = file_query_info;
1332 iface->query_filesystem_info = file_query_filesystem_info;
1333 iface->find_enclosing_mount = file_find_enclosing_mount;
1334 iface->query_settable_attributes = file_query_settable_attributes;
1335 iface->query_writable_namespaces = file_query_writable_namespaces;
1336 iface->set_attribute = file_set_attribute;
1337 iface->set_attributes_from_info = file_set_attributes_from_info;
1338 iface->read_fn = file_read_fn;
1339 iface->append_to = file_append_to;
1340 iface->create = file_create;
1341 iface->replace = file_replace;
1342 iface->open_readwrite = file_open_readwrite;
1343 iface->create_readwrite = file_create_readwrite;
1344 iface->replace_readwrite = file_replace_readwrite;
1345 iface->delete_file = file_delete;
1346 iface->trash = file_trash;
1347 // iface->make_directory = file_make_directory; ### Don't support
1348 // iface->make_symbolic_link = file_make_symbolic_link; ### Don't support
1349 iface->move = file_move;
1350 iface->monitor_dir = file_monitor_dir;
1351 iface->monitor_file = file_monitor_file;
1352 iface->measure_disk_usage = file_measure_disk_usage;
1353 iface->supports_thread_contexts = TRUE;
1354 }
1355
1356 static void favorite_vfs_file_dispose (GObject *object)
1357 {
1358 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (object));
1359
1360 if (priv->info != NULL)
1361 {
1362 xapp_favorite_info_free (priv->info);
1363 priv->info = NULL;
1364 }
1365
1366 g_clear_pointer (&priv->uri, g_free);
1367
1368 G_OBJECT_CLASS (favorite_vfs_file_parent_class)->dispose (object);
1369 }
1370
1371 static void
1372 ensure_metadata_store (FavoriteVfsFile *file)
1373 {
1374 if (is_root_file (file))
1375 {
1376 if (settings == NULL)
1377 {
1378 settings = g_settings_new (FAVORITES_SCHEMA);
1379 g_object_add_weak_pointer (G_OBJECT (settings), (gpointer) &settings);
1380 }
1381 else
1382 {
1383 g_object_ref (settings);
1384 }
1385 }
1386 }
1387
1388 static void favorite_vfs_file_class_init (FavoriteVfsFileClass *klass)
1389 {
1390 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1391
1392 gobject_class->dispose = favorite_vfs_file_dispose;
1393 }
1394
1395 static void favorite_vfs_file_init (FavoriteVfsFile *self)
1396 {
1397 }
1398
1399 GFile *_favorite_vfs_file_new_for_info (XAppFavoriteInfo *info)
1400 {
1401 FavoriteVfsFile *new_file;
1402
1403 new_file = g_object_new (FAVORITE_TYPE_VFS_FILE, NULL);
1404
1405 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (new_file));
1406
1407 priv->uri = path_to_fav_uri (info->display_name);
1408 priv->info = xapp_favorite_info_copy (info);
1409 ensure_metadata_store (new_file);
1410
1411 return G_FILE (new_file);
1412 }
1413
1414 gchar *favorite_vfs_file_get_real_uri (GFile *file)
1415 {
1416 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file));
1417
1418 if (priv->info != NULL && priv->info->uri != NULL)
1419 {
1420 return g_strdup (priv->info->uri);
1421 }
1422
1423 return NULL;
1424 }
1425
1426 GFile *favorite_vfs_file_new_for_uri (const char *uri)
1427 {
1428 FavoriteVfsFile *new_file;
1429 g_autofree gchar *basename = NULL;
1430
1431 new_file = g_object_new (FAVORITE_TYPE_VFS_FILE, NULL);
1432
1433 g_debug ("FavoriteVfsFile new for uri: %s", uri);
1434
1435 FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (new_file));
1436
1437 priv->uri = g_strdup (uri);
1438 ensure_metadata_store (new_file);
1439
1440 if (g_strcmp0 (uri, ROOT_URI) == 0)
1441 {
1442 priv->info = NULL;
1443 }
1444 else
1445 {
1446 gchar *display_name;
1447
1448 display_name = fav_uri_to_display_name (uri);
1449 XAppFavoriteInfo *info = xapp_favorites_find_by_display_name (xapp_favorites_get_default (),
1450 display_name);
1451
1452 if (info != NULL)
1453 {
1454 priv->info = xapp_favorite_info_copy (info);
1455 }
1456 else
1457 {
1458 info = g_slice_new0 (XAppFavoriteInfo);
1459 info->uri = g_strdup (NULL);
1460 info->display_name = g_strdup (display_name);
1461 info->cached_mimetype = NULL;
1462
1463 priv->info = info;
1464 }
1465
1466 g_free (display_name);
1467 }
1468
1469 return G_FILE (new_file);
1470 }
1471
1472 GFile *favorite_vfs_file_new (void)
1473 {
1474 return favorite_vfs_file_new_for_uri (ROOT_URI);
1475 }
1476
1477 static GFile *
1478 favorite_vfs_lookup (GVfs *vfs,
1479 const char *identifier,
1480 gpointer user_data)
1481 {
1482 if (g_str_has_prefix (identifier, ROOT_URI))
1483 {
1484 return favorite_vfs_file_new_for_uri (identifier);
1485 }
1486
1487 return NULL;
1488 }
1489
1490 void
1491 init_favorite_vfs (void)
1492 {
1493 GVfs *vfs;
1494 vfs = g_vfs_get_default ();
1495
1496 g_vfs_register_uri_scheme (vfs, "favorites",
1497 favorite_vfs_lookup, NULL, NULL,
1498 favorite_vfs_lookup, NULL, NULL);
1499 }
1500
0 #ifndef FAVORITE_VFS_FILE_H
1 #define FAVORITE_VFS_FILE_H
2
3 #include "xapp-favorites.h"
4 #include <glib-object.h>
5 #include <gio/gio.h>
6
7 G_BEGIN_DECLS
8
9 #define FAVORITE_TYPE_VFS_FILE (favorite_vfs_file_get_type ())
10
11 G_DECLARE_FINAL_TYPE (FavoriteVfsFile, favorite_vfs_file, \
12 FAVORITE, VFS_FILE, GObject)
13
14 // Initializer for favorites:/// - called when the XAppFavorites singleton is created
15 void init_favorite_vfs (void);
16
17 GFile *favorite_vfs_file_new_for_uri (const char *uri);
18 gchar *favorite_vfs_file_get_real_uri (GFile *file);
19
20 #define URI_SCHEME "favorites"
21 #define ROOT_URI ("favorites:///")
22
23 #define FAVORITE_METADATA_KEY "metadata::xapp-favorite"
24 #define FAVORITE_AVAILABLE_METADATA_KEY "metadata::xapp-favorite-available"
25
26 #define META_TRUE "true"
27 #define META_FALSE "false"
28
29 gchar *path_to_fav_uri (const gchar *path);
30 gchar *fav_uri_to_display_name (const gchar *uri);
31
32 G_END_DECLS
33
34 #endif // FAVORITE_VFS_FILE_H
00 glib_min_ver = '>=2.37.3'
11
2 gio_dep = dependency('gio-2.0', version: glib_min_ver, required: true)
3 glib_dep = dependency('glib-2.0', version: glib_min_ver, required: true)
4 gtk3_dep = dependency('gtk+-3.0', version: '>=3.3.16', required: true)
5
26 libdeps = []
3 libdeps += dependency('gio-2.0', version: glib_min_ver, required: true)
4 libdeps += dependency('glib-2.0', version: glib_min_ver, required: true)
5 libdeps += dependency('gtk+-3.0', version: '>=3.3.16', required: true)
7 libdeps += gio_dep
8 libdeps += glib_dep
9 libdeps += gtk3_dep
610 libdeps += dependency('gdk-pixbuf-2.0', version: '>=2.22.0', required: true)
711 libdeps += dependency('cairo', required: true)
812 libdeps += dependency('x11', required: true)
913
14 favorite_vfs_sources = [
15 'favorite-vfs-file.c',
16 'favorite-vfs-file-enumerator.c',
17 'favorite-vfs-file-monitor.c'
18 ]
19
1020 xapp_headers = [
21 'xapp-favorites.h',
1122 'xapp-gtk-window.h',
1223 'xapp-icon-chooser-button.h',
1324 'xapp-icon-chooser-dialog.h',
2031 ]
2132
2233 xapp_sources = [
34 'xapp-favorites.c',
2335 'xapp-glade-catalog.c',
2436 'xapp-gtk-window.c',
2537 'xapp-icon-chooser-button.c',
8294 )
8395
8496 libxapp = library('xapp',
85 sources : xapp_headers + xapp_sources + xapp_enums + dbus_headers,
97 sources : xapp_headers + xapp_sources + xapp_enums + dbus_headers + favorite_vfs_sources,
8698 include_directories: [top_inc],
8799 version: meson.project_version(),
88100 soversion: '1',
133145 metadata_dirs: meson.current_source_dir(),
134146 install: true
135147 )
148
149 gtk3_module = shared_module(
150 'xapp-gtk3-module', ['xapp-gtk3-module.c'],
151 include_directories: [top_inc],
152 dependencies: [gtk3_dep, libxapp_dep],
153 install: true,
154 install_dir: join_paths(gtk3_dep.get_pkgconfig_variable('libdir'),'gtk-3.0','modules')
155 )
0
1 #include <config.h>
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <utime.h>
12
13 #include <gtk/gtk.h>
14 #include <glib/gi18n-lib.h>
15 #include <gio/gdesktopappinfo.h>
16 #include <glib/gstdio.h>
17
18 #include "xapp-favorites.h"
19 #include "favorite-vfs-file.h"
20
21 #define FAVORITES_SCHEMA "org.x.apps.favorites"
22 #define FAVORITES_KEY "list"
23 #define SETTINGS_DELIMITER "::"
24 #define MAX_DISPLAY_URI_LENGTH 20
25
26 G_DEFINE_BOXED_TYPE (XAppFavoriteInfo, xapp_favorite_info, xapp_favorite_info_copy, xapp_favorite_info_free);
27 /**
28 * SECTION:xapp-favorites
29 * @Short_description: Keeps track of favorite files.
30 * @Title: XAppFavorites
31 *
32 * The XAppFavorites class allows applications display frequently-used files and
33 * provide a safe mechanism for launching them.
34 *
35 * A list of #XAppFavoriteInfos can be retrieved in full, or only for specific mimetypes.
36 *
37 * XAppFavorites are new for 2.0
38 */
39
40 /**
41 * xapp_favorite_info_copy:
42 * @info: The #XAppFavoriteInfo to duplicate.
43 *
44 * Makes an exact copy of an existing #XAppFavoriteInfo.
45 *
46 * Returns: (transfer full): a new #XAppFavoriteInfo. Free using #xapp_favorite_info_free.
47 *
48 * Since 2.0
49 */
50 XAppFavorites *global_favorites;
51
52 XAppFavoriteInfo *
53 xapp_favorite_info_copy (const XAppFavoriteInfo *info)
54 {
55 // g_debug ("XAppFavoriteInfo: copy");
56 g_return_val_if_fail (info != NULL, NULL);
57
58 XAppFavoriteInfo *_info = g_slice_dup (XAppFavoriteInfo, info);
59 _info->uri = g_strdup (info->uri);
60 _info->display_name = g_strdup (info->display_name);
61 _info->cached_mimetype = g_strdup (info->cached_mimetype);
62
63 return _info;
64 }
65
66 /**
67 * xapp_favorite_info_free:
68 * @info: The #XAppFavoriteInfo to free.
69 *
70 * Destroys the #XAppFavoriteInfo.
71 *
72 * Since 2.0
73 */
74 void
75 xapp_favorite_info_free (XAppFavoriteInfo *info)
76 {
77 g_debug ("XAppFavoriteInfo free (%s)", info->uri);
78 g_return_if_fail (info != NULL);
79
80 g_free (info->uri);
81 g_free (info->display_name);
82 g_free (info->cached_mimetype);
83 g_slice_free (XAppFavoriteInfo, info);
84 }
85
86 typedef struct
87 {
88 GHashTable *infos;
89 GHashTable *menus;
90
91 GSettings *settings;
92
93 gulong settings_listener_id;
94 guint changed_timer_id;
95 } XAppFavoritesPrivate;
96
97 struct _XAppFavorites
98 {
99 GObject parent_instance;
100 };
101
102 G_DEFINE_TYPE_WITH_PRIVATE (XAppFavorites, xapp_favorites, G_TYPE_OBJECT)
103
104 enum
105 {
106 CHANGED,
107 LAST_SIGNAL
108 };
109
110 static guint signals[LAST_SIGNAL] = {0, };
111
112 static void finish_add_favorite (XAppFavorites *favorites,
113 const gchar *uri,
114 const gchar *mimetype,
115 gboolean from_saved);
116
117 static gboolean
118 changed_callback (gpointer data)
119 {
120 g_return_val_if_fail (XAPP_IS_FAVORITES (data), G_SOURCE_REMOVE);
121 XAppFavorites *favorites = XAPP_FAVORITES (data);
122
123 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
124 g_debug ("XAppFavorites: list updated, emitting changed signal");
125
126 priv->changed_timer_id = 0;
127 g_signal_emit (favorites, signals[CHANGED], 0);
128
129 return G_SOURCE_REMOVE;
130 }
131
132 static void
133 queue_changed (XAppFavorites *favorites)
134 {
135 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
136
137 if (priv->changed_timer_id > 0)
138 {
139 g_source_remove (priv->changed_timer_id);
140 }
141
142 priv->changed_timer_id = g_idle_add ((GSourceFunc) changed_callback, favorites);
143 }
144
145 static void
146 sync_metadata_callback (GObject *source,
147 GAsyncResult *res,
148 gpointer user_data)
149 {
150 // Disabled
151 return;
152
153 // GFile *file;
154 // GError *error;
155
156 // file = G_FILE (source);
157 // error = NULL;
158
159 // if (!g_file_set_attributes_finish (file,
160 // res,
161 // NULL,
162 // &error))
163 // {
164 // if (error != NULL)
165 // {
166 // if (error->code != G_IO_ERROR_NOT_FOUND)
167 // {
168 // g_warning ("Could not update file metadata for favorite file '%s': %s", g_file_get_uri (file), error->message);
169 // }
170
171 // g_error_free (error);
172 // }
173 // }
174 // else
175 // {
176 // if (g_file_is_native (file))
177 // {
178 // // I can't think of any other way to touch a file so a file monitor might notice
179 // // the attribute change. It shouldn't be too much trouble since most times add/remove
180 // // will be done in the file manager (where the update can be triggered internally).
181
182 // gchar *local_path = g_file_get_path (file);
183 // g_utime (local_path, NULL);
184 // g_free (local_path);
185 // }
186 // }
187 }
188
189 static void
190 sync_file_metadata (XAppFavorites *favorites,
191 const gchar *uri,
192 gboolean is_favorite)
193 {
194 /* Disabled - this is less than optimal, and is implemented instead in
195 * nemo, currently. This could be changed later to help support other browsers.
196 * Also, this only works with local files. */
197 return;
198
199 /* borrowed from nemo-vfs-file.c */
200 GFileInfo *info;
201 GFile *file;
202
203 g_debug ("Sync metadata: %s - Favorite? %d", uri, is_favorite);
204
205 info = g_file_info_new ();
206
207 if (is_favorite) {
208 g_file_info_set_attribute_string (info, FAVORITE_METADATA_KEY, META_TRUE);
209 } else {
210 /* Unset the key */
211 g_file_info_set_attribute (info, FAVORITE_METADATA_KEY, G_FILE_ATTRIBUTE_TYPE_INVALID, NULL);
212 }
213
214 file = g_file_new_for_uri (uri);
215
216 g_file_set_attributes_async (file,
217 info,
218 0,
219 G_PRIORITY_DEFAULT,
220 NULL,
221 sync_metadata_callback,
222 favorites);
223
224 g_object_unref (file);
225 g_object_unref (info);
226 }
227
228 static void
229 store_favorites (XAppFavorites *favorites)
230 {
231 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
232 GList *iter, *keys;
233 GPtrArray *array;
234 gchar **new_settings;
235
236 array = g_ptr_array_new ();
237
238 keys = g_hash_table_get_keys (priv->infos);
239
240 for (iter = keys; iter != NULL; iter = iter->next)
241 {
242 XAppFavoriteInfo *info = (XAppFavoriteInfo *) g_hash_table_lookup (priv->infos, iter->data);
243 gchar *entry;
244
245 entry = g_strjoin (SETTINGS_DELIMITER,
246 info->uri,
247 info->cached_mimetype,
248 NULL);
249
250 g_ptr_array_add (array, entry);
251 }
252
253 g_ptr_array_add (array, NULL);
254
255 g_list_free (keys);
256
257 new_settings = (gchar **) g_ptr_array_free (array, FALSE);
258
259 g_signal_handler_block (priv->settings, priv->settings_listener_id);
260 g_settings_set_strv (priv->settings, FAVORITES_KEY, (const gchar* const*) new_settings);
261 g_signal_handler_unblock (priv->settings, priv->settings_listener_id);
262
263 g_debug ("XAppFavorites: store_favorites: favorites saved");
264
265 g_free (new_settings);
266 }
267
268 static void
269 load_favorites (XAppFavorites *favorites,
270 gboolean signal_changed)
271 {
272 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
273 gchar **raw_list;
274 gint i;
275
276 if (priv->infos != NULL)
277 {
278 g_hash_table_destroy (priv->infos);
279 }
280
281 priv->infos = g_hash_table_new_full (g_str_hash, g_str_equal,
282 g_free, (GDestroyNotify) xapp_favorite_info_free);
283
284 raw_list = g_settings_get_strv (priv->settings, FAVORITES_KEY);
285
286 if (!raw_list)
287 {
288 // no favorites
289 return;
290 }
291
292 for (i = 0; i < g_strv_length (raw_list); i++)
293 {
294 gchar **entry = g_strsplit (raw_list[i], SETTINGS_DELIMITER, 2);
295
296 finish_add_favorite (favorites,
297 entry[0], // uri
298 entry[1], // cached_mimetype
299 TRUE);
300
301 g_strfreev (entry);
302 }
303
304 g_strfreev (raw_list);
305
306 g_debug ("XAppFavorites: load_favorite: favorites loaded (%d)", i);
307
308 if (signal_changed)
309 {
310 queue_changed (favorites);
311 }
312 }
313
314 static void
315 rename_favorite (XAppFavorites *favorites,
316 const gchar *old_uri,
317 const gchar *new_uri)
318 {
319 XAppFavoriteInfo *info;
320 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
321 gchar *final_new_uri = NULL;
322
323 if (g_str_has_prefix (old_uri, ROOT_URI))
324 {
325 // Renaming occurred inside of favorites:/// we need to identify by
326 // display name.
327
328 const gchar *old_display_name = old_uri + strlen (ROOT_URI);
329 const gchar *new_display_name = new_uri + strlen (ROOT_URI);
330
331 info = xapp_favorites_find_by_display_name (favorites, old_display_name);
332
333 if (info)
334 {
335 GFile *real_file, *parent, *renamed_file;
336
337 real_file = g_file_new_for_uri (info->uri);
338 parent = g_file_get_parent (real_file);
339
340 renamed_file = g_file_get_child_for_display_name (parent,
341 new_display_name,
342 NULL);
343
344 if (renamed_file != NULL)
345 {
346 final_new_uri = g_file_get_uri (renamed_file);
347 }
348
349 g_object_unref (real_file);
350 g_object_unref (parent);
351 g_clear_object (&renamed_file);
352 }
353 }
354 else
355 {
356 info = g_hash_table_lookup (priv->infos, old_uri);
357 final_new_uri = g_strdup (new_uri);
358 }
359
360 if (info != NULL && final_new_uri != NULL)
361 {
362 gchar *mimetype = g_strdup (info->cached_mimetype);
363
364 sync_file_metadata (favorites, info->uri, FALSE);
365
366 g_hash_table_remove (priv->infos,
367 (gconstpointer) info->uri);
368
369 finish_add_favorite (favorites,
370 final_new_uri,
371 mimetype,
372 FALSE);
373
374 sync_file_metadata (favorites, final_new_uri, TRUE);
375
376 g_free (mimetype);
377 }
378
379 g_free (final_new_uri);
380 }
381
382 static void
383 remove_favorite (XAppFavorites *favorites,
384 const gchar *uri)
385 {
386 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
387 gchar *real_uri;
388
389 if (g_str_has_prefix (uri, "favorites"))
390 {
391 GFile *file = g_file_new_for_uri (uri);
392 real_uri = favorite_vfs_file_get_real_uri (file);
393
394 g_object_unref (file);
395 }
396 else
397 {
398 real_uri = g_strdup (uri);
399 }
400
401 g_debug ("XAppFavorites: remove favorite: %s", real_uri);
402
403 // It may be orphaned for some reason.. even if it's not in gsettings, still try
404 // to remove the favorite attribute.
405 sync_file_metadata (favorites, real_uri, FALSE);
406
407 if (!g_hash_table_remove (priv->infos, real_uri))
408 {
409 g_debug ("XAppFavorites: remove_favorite: could not find favorite for uri '%s'", real_uri);
410 g_free (real_uri);
411 return;
412 }
413
414 g_free (real_uri);
415
416 store_favorites (favorites);
417 queue_changed (favorites);
418 }
419
420 static void
421 deduplicate_display_names (XAppFavorites *favorites,
422 GHashTable *infos)
423 {
424 GList *fav_uris, *ptr;
425 GHashTable *lists_of_keys_by_basename = g_hash_table_new_full (g_str_hash, g_str_equal,
426 g_free, NULL);
427 GHashTableIter iter;
428
429 fav_uris = g_hash_table_get_keys (infos);
430
431 for (ptr = fav_uris; ptr != NULL; ptr = ptr->next)
432 {
433 GList *uris;
434 const gchar *uri = (gchar *) ptr->data;
435 gchar *original_display_name = g_path_get_basename (uri);
436
437 if (g_hash_table_contains (lists_of_keys_by_basename, original_display_name))
438 {
439 uris = g_hash_table_lookup (lists_of_keys_by_basename, original_display_name);
440
441 // this could be prepend, but then the value in the table would have to be replaced
442 uris = g_list_append ((GList *) uris, g_strdup (uri));
443 }
444 else
445 {
446 uris = g_list_prepend (NULL, g_strdup (uri));
447 g_hash_table_insert (lists_of_keys_by_basename,
448 g_strdup (original_display_name),
449 uris);
450 }
451
452 g_free (original_display_name);
453 }
454
455 g_list_free (fav_uris);
456
457 gpointer key, value;
458
459 g_hash_table_iter_init (&iter, lists_of_keys_by_basename);
460
461 while (g_hash_table_iter_next (&iter, &key, &value))
462 {
463 GList *same_names_list, *uri_ptr;
464 const gchar *common_display_name;
465
466 if (((GList *) value)->next == NULL)
467 {
468 // Single member of current common name list;
469 g_list_free_full ((GList *) value, g_free);
470 continue;
471 }
472 // Now we know we have a list of uris that would have identical display names
473 // Add a part of the uri after each to distinguish them.
474 common_display_name = (const gchar *) key;
475 same_names_list = (GList *) value;
476
477 for (uri_ptr = same_names_list; uri_ptr != NULL; uri_ptr = uri_ptr->next)
478 {
479 XAppFavoriteInfo *info;
480 GFile *uri_file, *home_file, *parent_file;
481 GString *new_display_string;
482 const gchar *current_uri;
483
484 current_uri = (const gchar *) uri_ptr->data;
485
486 uri_file = g_file_new_for_uri (current_uri);
487 parent_file = g_file_get_parent (uri_file);
488 home_file = g_file_new_for_path (g_get_home_dir());
489
490 new_display_string = g_string_new (common_display_name);
491 g_string_append (new_display_string, " (");
492
493 // How much effort should we put into duplicate naming? Keeping it
494 // simple like this won't work all the time.
495 gchar *parent_basename = g_file_get_basename (parent_file);
496 g_string_append (new_display_string, parent_basename);
497 g_free (parent_basename);
498
499 // TODO: ellipsized deduplication paths?
500
501 // if (g_file_has_prefix (parent_file, home_file))
502 // {
503 // gchar *home_rpath = g_file_get_relative_path (home_file, parent_file);
504 // gchar *home_basename = g_file_get_basename (home_file);
505
506 // if (strlen (home_rpath) < MAX_DISPLAY_URI_LENGTH)
507 // {
508 // g_string_append (new_display_string, home_basename);
509 // g_string_append (new_display_string, "/");
510 // g_string_append (new_display_string, home_rpath);
511 // }
512 // else
513 // {
514 // gchar *parent_basename = g_file_get_basename (parent_file);
515
516 // g_string_append (new_display_string, home_basename);
517 // g_string_append (new_display_string, "/.../");
518 // g_string_append (new_display_string, parent_basename);
519
520 // g_free (parent_basename);
521 // }
522
523 // g_free (home_rpath);
524 // g_free (home_basename);
525 // }
526 // else
527 // {
528 // GString *tmp_string = g_string_new (NULL);
529
530 // if (g_file_is_native (parent_file))
531 // {
532 // g_string_append (tmp_string, g_file_peek_path (parent_file));
533 // }
534 // else
535 // {
536 // g_string_append (tmp_string, current_uri);
537 // }
538
539 // if (tmp_string->len > MAX_DISPLAY_URI_LENGTH)
540 // {
541 // gint diff;
542 // gint replace_pos;
543
544 // diff = tmp_string->len - MAX_DISPLAY_URI_LENGTH;
545 // replace_pos = (tmp_string->len / 2) - (diff / 2) - 2;
546
547 // g_string_erase (tmp_string,
548 // replace_pos,
549 // diff);
550 // g_string_insert (tmp_string,
551 // replace_pos,
552 // "...");
553 // }
554
555 // g_string_append (new_display_string, tmp_string->str);
556 // g_string_free (tmp_string, TRUE);
557 // }
558
559 g_object_unref (uri_file);
560 g_object_unref (home_file);
561 g_object_unref (parent_file);
562
563 g_string_append (new_display_string, ")");
564
565 // Look up the info from our master table
566 info = g_hash_table_lookup (infos, current_uri);
567 g_free (info->display_name);
568
569 info->display_name = g_string_free (new_display_string, FALSE);
570 }
571
572 g_list_free_full (same_names_list, g_free);
573 }
574
575 // We freed the individual lists just above, only the keys will need
576 // freed here.
577 g_hash_table_destroy (lists_of_keys_by_basename);
578 }
579
580 static void
581 finish_add_favorite (XAppFavorites *favorites,
582 const gchar *uri,
583 const gchar *cached_mimetype,
584 gboolean from_saved)
585 {
586 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
587 XAppFavoriteInfo *info;
588 gchar *unescaped_uri;
589
590 // Check if it's there again, in case it was added while we were getting mimetype.
591 if (g_hash_table_contains (priv->infos, uri))
592 {
593 g_debug ("XAppFavorites: favorite for '%s' exists, ignoring", uri);
594 return;
595 }
596
597 info = g_slice_new0 (XAppFavoriteInfo);
598 info->uri = g_strdup (uri);
599
600 unescaped_uri = g_uri_unescape_string (uri, NULL);
601 info->display_name = g_path_get_basename (unescaped_uri);
602 g_free (unescaped_uri);
603
604 info->cached_mimetype = g_strdup (cached_mimetype);
605
606 g_hash_table_insert (priv->infos, (gpointer) g_strdup (uri), (gpointer) info);
607
608 g_debug ("XAppFavorites: added favorite: %s", uri);
609
610 deduplicate_display_names (favorites, priv->infos);
611
612 if (from_saved)
613 {
614 return;
615 }
616
617 store_favorites (favorites);
618 queue_changed (favorites);
619 }
620
621 static void
622 on_content_type_info_received (GObject *source,
623 GAsyncResult *res,
624 gpointer user_data)
625 {
626 XAppFavorites *favorites = XAPP_FAVORITES (user_data);
627 GFile *file;
628 GFileInfo *file_info;
629 GError *error;
630 gchar *cached_mimetype, *uri;
631
632 file = G_FILE (source);
633 uri = g_file_get_uri (file);
634 error = NULL;
635 cached_mimetype = NULL;
636
637 file_info = g_file_query_info_finish (file, res, &error);
638
639 if (error)
640 {
641 g_debug ("XAppFavorites: problem trying to figure out content type for uri '%s': %s",
642 uri, error->message);
643 g_error_free (error);
644 }
645
646 if (file_info)
647 {
648 cached_mimetype = g_strdup (g_file_info_get_content_type (file_info));
649
650 if (cached_mimetype == NULL)
651 {
652 cached_mimetype = g_strdup ("application/unknown");
653 }
654
655 finish_add_favorite (favorites,
656 uri,
657 cached_mimetype,
658 FALSE);
659
660 sync_file_metadata (favorites, uri, TRUE);
661 }
662
663 g_free (uri);
664 g_free (cached_mimetype);
665 g_clear_object (&file_info);
666 g_object_unref (file);
667 }
668
669 static void
670 add_favorite (XAppFavorites *favorites,
671 const gchar *uri)
672 {
673 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
674 GFile *file;
675
676 if (g_hash_table_contains (priv->infos, uri))
677 {
678 g_debug ("XAppFavorites: favorite for '%s' exists, ignoring", uri);
679 return;
680 }
681
682 file = g_file_new_for_uri (uri);
683
684 g_file_query_info_async (file,
685 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
686 G_FILE_QUERY_INFO_NONE,
687 G_PRIORITY_LOW,
688 NULL,
689 on_content_type_info_received,
690 favorites);
691 }
692
693 static void
694 on_settings_list_changed (GSettings *settings,
695 gchar *key,
696 gpointer user_data)
697 {
698 XAppFavorites *favorites = XAPP_FAVORITES (user_data);
699
700 load_favorites (favorites, TRUE);
701 }
702
703 static void
704 xapp_favorites_init (XAppFavorites *favorites)
705 {
706 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
707
708 g_debug ("XAppFavorites: init:");
709
710 init_favorite_vfs ();
711
712 priv->settings = g_settings_new (FAVORITES_SCHEMA);
713 priv->settings_listener_id = g_signal_connect (priv->settings,
714 "changed::" FAVORITES_KEY,
715 G_CALLBACK (on_settings_list_changed),
716 favorites);
717
718 load_favorites (favorites, FALSE);
719 }
720
721 static void
722 xapp_favorites_dispose (GObject *object)
723 {
724 XAppFavorites *favorites = XAPP_FAVORITES (object);
725 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
726
727 g_debug ("XAppFavorites dispose (%p)", object);
728
729 g_clear_object (&priv->settings);
730 g_clear_pointer (&priv->infos, g_hash_table_destroy);
731
732 G_OBJECT_CLASS (xapp_favorites_parent_class)->dispose (object);
733 }
734
735 static void
736 xapp_favorites_finalize (GObject *object)
737 {
738 g_debug ("XAppFavorites finalize (%p)", object);
739
740 G_OBJECT_CLASS (xapp_favorites_parent_class)->finalize (object);
741 }
742
743 static void
744 xapp_favorites_class_init (XAppFavoritesClass *klass)
745 {
746 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
747
748 gobject_class->dispose = xapp_favorites_dispose;
749 gobject_class->finalize = xapp_favorites_finalize;
750
751 /**
752 * XAppFavorites::changed:
753
754 * Notifies when the favorites list has changed.
755 */
756 signals [CHANGED] =
757 g_signal_new ("changed",
758 XAPP_TYPE_FAVORITES,
759 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
760 0,
761 NULL, NULL, NULL,
762 G_TYPE_NONE, 0);
763 }
764
765 /**
766 * xapp_favorites_get_default:
767 *
768 * Returns the #XAppFavorites instance.
769 *
770 * Returns: (transfer none): the XAppFavorites instance for the process. Do not free.
771 *
772 * Since: 2.0
773 */
774 XAppFavorites *
775 xapp_favorites_get_default (void)
776 {
777 if (global_favorites == NULL)
778 {
779 global_favorites = g_object_new (XAPP_TYPE_FAVORITES, NULL);
780 }
781
782 return global_favorites;
783 }
784
785 typedef struct {
786 GList *items;
787 const gchar **mimetypes;
788 } MatchData;
789
790 void
791 match_mimetypes (gpointer key,
792 gpointer value,
793 gpointer user_data)
794 {
795 MatchData *data = (MatchData *) user_data;
796 const XAppFavoriteInfo *info = (XAppFavoriteInfo *) value;
797
798 if (data->mimetypes == NULL)
799 {
800 data->items = g_list_prepend (data->items, xapp_favorite_info_copy (info));
801 return;
802 }
803
804 gint i;
805
806 for (i = 0; i < g_strv_length ((gchar **) data->mimetypes); i++)
807 {
808 if (g_content_type_is_mime_type (info->cached_mimetype, data->mimetypes[i]))
809 {
810 data->items = g_list_prepend (data->items, xapp_favorite_info_copy (info));
811 return;
812 }
813 }
814 }
815
816 /**
817 * xapp_favorites_get_favorites:
818 * @favorites: The #XAppFavorites
819 * @mimetypes: (nullable): The mimetypes to filter by for results
820 *
821 * Gets a list of all favorites. If mimetype is not %NULL, the list will
822 * contain only favorites with that mimetype.
823 *
824 * Returns: (element-type XAppFavoriteInfo) (transfer full): a list of #XAppFavoriteInfos.
825 Free the list with #g_list_free, free elements with #xapp_favorite_info_free.
826 *
827 * Since: 2.0
828 */
829 GList *
830 xapp_favorites_get_favorites (XAppFavorites *favorites,
831 const gchar **mimetypes)
832 {
833 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
834 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
835 GList *ret = NULL;
836 MatchData data;
837
838 data.items = NULL;
839 data.mimetypes = mimetypes;
840 g_hash_table_foreach (priv->infos,
841 (GHFunc) match_mimetypes,
842 &data);
843
844 ret = g_list_reverse (data.items);
845
846 gchar *typestring = mimetypes ? g_strjoinv (", ", (gchar **) mimetypes) : NULL;
847 g_debug ("XAppFavorites: get_favorites returning list for mimetype '%s' (%d items)",
848 typestring, g_list_length (ret));
849 g_free (typestring);
850
851 return ret;
852 }
853
854 /**
855 * xapp_favorites_get_n_favorites:
856 * @favorites: The #XAppFavorites
857 *
858 * Returns: The number of favorite files
859
860 * Since: 2.0
861 */
862 gint
863 xapp_favorites_get_n_favorites (XAppFavorites *favorites)
864 {
865 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), 0);
866 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
867 gint n;
868
869 n = g_hash_table_size (priv->infos);
870
871 g_debug ("XAppFavorites: get_n_favorites returning number of items: %d.", n);
872
873 return n;
874 }
875
876 static gboolean
877 lookup_display_name (gpointer key,
878 gpointer value,
879 gpointer user_data)
880 {
881 XAppFavoriteInfo *info = (XAppFavoriteInfo *) value;
882
883 if (g_strcmp0 (info->display_name, (const gchar *) user_data) == 0)
884 {
885 return TRUE;
886 }
887
888 return FALSE;
889 }
890
891 /**
892 * xapp_favorites_find_by_display_name:
893 * @favorites: The #XAppFavorites
894 * @display_name: (not nullable): The display name to lookup info for.
895 *
896 * Looks for an XAppFavoriteInfo that corresponds to @display_name.
897 *
898 * Returns: (transfer none): an XAppFavoriteInfo or NULL if one was not found. This is owned
899 * by the favorites manager and should not be freed.
900 *
901 * Since: 2.0
902 */
903 XAppFavoriteInfo *
904 xapp_favorites_find_by_display_name (XAppFavorites *favorites,
905 const gchar *display_name)
906 {
907 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
908 g_return_val_if_fail (display_name != NULL, NULL);
909
910 XAppFavoriteInfo *info;
911 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
912
913 info = g_hash_table_find (priv->infos,
914 (GHRFunc) lookup_display_name,
915 (gpointer) display_name);
916
917 if (info != NULL)
918 {
919 return info;
920 }
921
922 return NULL;
923 }
924
925 /**
926 * xapp_favorites_find_by_uri:
927 * @favorites: The #XAppFavorites
928 * @uri: (not nullable): The uri to lookup info for.
929 *
930 * Looks for an XAppFavoriteInfo that corresponds to @uri.
931 *
932 * Returns: (transfer none): an XAppFavoriteInfo or NULL if one was not found. This is owned
933 * by the favorites manager and should not be freed.
934 *
935 * Since: 2.0
936 */
937 XAppFavoriteInfo *
938 xapp_favorites_find_by_uri (XAppFavorites *favorites,
939 const gchar *uri)
940 {
941 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
942 g_return_val_if_fail (uri != NULL, NULL);
943
944 XAppFavoriteInfo *info;
945 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
946
947 info = g_hash_table_lookup (priv->infos, uri);
948
949 if (info != NULL)
950 {
951 return (XAppFavoriteInfo *) info;
952 }
953
954 return NULL;
955 }
956
957 /**
958 * xapp_favorites_add:
959 * @favorites: The #XAppFavorites
960 * @uri: The uri the favorite is for
961 *
962 * Adds a new favorite. If the uri already exists, this does nothing.
963 *
964 * Since: 2.0
965 */
966 void
967 xapp_favorites_add (XAppFavorites *favorites,
968 const gchar *uri)
969 {
970 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
971 g_return_if_fail (uri != NULL);
972
973 add_favorite (favorites, uri);
974 }
975
976 /**
977 * xapp_favorites_remove:
978 * @favorites: The #XAppFavorites
979 * @uri: The uri for the favorite being removed
980 *
981 * Removes a favorite from the list.
982 *
983 * Since: 2.0
984 */
985 void
986 xapp_favorites_remove (XAppFavorites *favorites,
987 const gchar *uri)
988 {
989 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
990 g_return_if_fail (uri != NULL);
991
992 remove_favorite (favorites, uri);
993 }
994
995 static void
996 launch_uri_callback (GObject *source,
997 GAsyncResult *res,
998 gpointer user_data)
999 {
1000 gchar *uri = (gchar *) user_data;
1001 GError *error;
1002
1003 error = NULL;
1004
1005 if (!g_app_info_launch_default_for_uri_finish (res, &error))
1006 {
1007 if (error)
1008 {
1009 g_debug ("XAppFavorites: launch: error opening uri '%s': %s", uri, error->message);
1010 g_error_free (error);
1011 }
1012 }
1013
1014 g_free (uri);
1015 }
1016
1017 /**
1018 * xapp_favorites_launch:
1019 * @favorites: The #XAppFavorites
1020 * @uri: The uri for the favorite to launch
1021 * @timestamp: The timestamp from an event or 0
1022 *
1023 * Opens a favorite in its default app.
1024 *
1025 * Since: 2.0
1026 */
1027 void
1028 xapp_favorites_launch (XAppFavorites *favorites,
1029 const gchar *uri,
1030 guint32 timestamp)
1031 {
1032 GdkDisplay *display;
1033 GdkAppLaunchContext *launch_context;
1034
1035 display = gdk_display_get_default ();
1036 launch_context = gdk_display_get_app_launch_context (display);
1037 gdk_app_launch_context_set_timestamp (launch_context, timestamp);
1038
1039 g_app_info_launch_default_for_uri_async (uri,
1040 G_APP_LAUNCH_CONTEXT (launch_context),
1041 NULL,
1042 launch_uri_callback,
1043 g_strdup (uri));
1044
1045 g_object_unref (launch_context);
1046 }
1047
1048 typedef struct {
1049 XAppFavorites *favorites;
1050 guint update_id;
1051
1052 GDestroyNotify destroy_func;
1053 gpointer user_data;
1054 } DestroyData;
1055
1056 typedef struct {
1057 XAppFavorites *favorites;
1058 XAppFavoritesItemSelectedCallback callback;
1059 gchar *uri;
1060 gpointer user_data;
1061 } ItemCallbackData;
1062
1063 static void
1064 cb_data_destroy_notify (gpointer callback_data,
1065 GObject *object)
1066 {
1067 DestroyData *dd = (DestroyData *) callback_data;
1068
1069 if (dd->update_id > 0)
1070 {
1071 g_signal_handler_disconnect (dd->favorites, dd->update_id);
1072 }
1073
1074 dd->destroy_func (dd->user_data);
1075
1076 g_slice_free (DestroyData, dd);
1077 }
1078
1079 static void
1080 free_item_callback_data (gpointer callback_data,
1081 GClosure *closure)
1082 {
1083 ItemCallbackData *data = (ItemCallbackData *) callback_data;
1084 g_free (data->uri);
1085 g_slice_free (ItemCallbackData, data);
1086 }
1087
1088 static void
1089 item_activated (GObject *item,
1090 gpointer user_data)
1091 {
1092 ItemCallbackData *data = (ItemCallbackData *) user_data;
1093
1094 data->callback (data->favorites,
1095 data->uri,
1096 data->user_data);
1097 }
1098
1099 static void
1100 remove_menu_item (GtkWidget *item,
1101 gpointer user_data)
1102 {
1103 gtk_container_remove (GTK_CONTAINER (user_data), item);
1104 }
1105
1106 static void
1107 populate_menu (XAppFavorites *favorites,
1108 GtkMenu *menu)
1109 {
1110 GList *fav_list, *ptr;
1111 GtkWidget *item;
1112 XAppFavoritesItemSelectedCallback callback;
1113 gpointer user_data;
1114 const gchar **mimetypes;
1115
1116 gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) remove_menu_item, menu);
1117
1118 mimetypes = (const gchar **) g_object_get_data (G_OBJECT (menu), "mimetypes");
1119 callback = g_object_get_data (G_OBJECT (menu), "activate-cb");
1120 user_data = g_object_get_data (G_OBJECT (menu), "user-data");
1121
1122 fav_list = xapp_favorites_get_favorites (favorites, mimetypes);
1123
1124 if (fav_list == NULL)
1125 {
1126 return;
1127 }
1128
1129 for (ptr = fav_list; ptr != NULL; ptr = ptr->next)
1130 {
1131 XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr->data;
1132 ItemCallbackData *data;
1133
1134 if (mimetypes != NULL)
1135 {
1136 item = gtk_menu_item_new_with_label (info->display_name);
1137 }
1138 else
1139 {
1140 GtkWidget *image;
1141 GIcon *icon;
1142
1143 icon = g_content_type_get_symbolic_icon (info->cached_mimetype);
1144 image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
1145 g_object_unref (icon);
1146
1147 item = gtk_image_menu_item_new_with_label (info->display_name);
1148 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1149 }
1150
1151 data = g_slice_new0 (ItemCallbackData);
1152 data->favorites = favorites;
1153 data->uri = g_strdup (info->uri);
1154 data->callback = callback;
1155 data->user_data = user_data;
1156
1157 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1158
1159 g_signal_connect_data (item,
1160 "activate", G_CALLBACK (item_activated),
1161 data, (GClosureNotify) free_item_callback_data, 0);
1162 }
1163
1164 gtk_widget_show_all (GTK_WIDGET (menu));
1165 }
1166
1167 static void
1168 refresh_menu_items (XAppFavorites *favorites,
1169 gpointer user_data)
1170 {
1171 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
1172 g_return_if_fail (GTK_IS_MENU (user_data));
1173
1174 GtkMenu *menu = GTK_MENU (user_data);
1175
1176 populate_menu (favorites, menu);
1177 }
1178
1179 /**
1180 * xapp_favorites_create_menu:
1181 * @favorites: The #XAppFavorites instance.
1182 * @mimetypes: (nullable): The mimetypes to filter for, or NULL to include all favorites.
1183 * @callback: (scope notified): (closure user_data): The callback to use when a menu item has been selected.
1184 * @user_data: (closure): The data to pass to the callback
1185 * @func: Destroy function for user_data
1186 *
1187 * Generates a GtkMenu widget populated with favorites. The callback will be called when
1188 * a menu item has been activated, and will include the uri of the respective item.
1189 *
1190 * Returns: (transfer full): a new #GtkMenu populated with a list of favorites, or NULL
1191 if there are no favorites.
1192 *
1193 * Since: 2.0
1194 */
1195 GtkWidget *
1196 xapp_favorites_create_menu (XAppFavorites *favorites,
1197 const gchar **mimetypes,
1198 XAppFavoritesItemSelectedCallback callback,
1199 gpointer user_data,
1200 GDestroyNotify func)
1201 {
1202 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
1203 GtkWidget *menu;
1204
1205 menu = gtk_menu_new ();
1206
1207 g_object_set_data_full (G_OBJECT (menu),
1208 "mimetype", g_strdupv ((gchar **) mimetypes),
1209 (GDestroyNotify) g_strfreev);
1210
1211 g_object_set_data (G_OBJECT (menu),
1212 "activate-cb", callback);
1213
1214 g_object_set_data (G_OBJECT (menu),
1215 "user-data", user_data);
1216
1217 populate_menu (favorites, GTK_MENU (menu));
1218
1219 DestroyData *dd = g_slice_new0 (DestroyData);
1220 dd->destroy_func = func;
1221 dd->user_data = user_data;
1222 dd->update_id = g_signal_connect (favorites,
1223 "changed",
1224 G_CALLBACK (refresh_menu_items),
1225 menu);
1226
1227 g_object_weak_ref (G_OBJECT (menu), (GWeakNotify) cb_data_destroy_notify, dd);
1228
1229 return menu;
1230 }
1231
1232 static GList *
1233 populate_action_list (XAppFavorites *favorites,
1234 const gchar **mimetypes)
1235 {
1236 GList *fav_list, *ptr;
1237 GList *actions;
1238 GtkAction *action;
1239 gint i;
1240
1241 fav_list = xapp_favorites_get_favorites (favorites, mimetypes);
1242
1243 if (fav_list == NULL)
1244 {
1245 return NULL;
1246 }
1247
1248 actions = NULL;
1249
1250 for (ptr = fav_list, i = 0; ptr != NULL; ptr = ptr->next, i++)
1251 {
1252 XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr->data;
1253
1254 if (mimetypes != NULL)
1255 {
1256 action = g_object_new (GTK_TYPE_ACTION,
1257 "name", info->uri,
1258 "label", info->display_name,
1259 NULL);
1260 }
1261 else
1262 {
1263 GIcon *icon;
1264 icon = g_content_type_get_symbolic_icon (info->cached_mimetype);
1265
1266 action = g_object_new (GTK_TYPE_ACTION,
1267 "name", info->uri,
1268 "label", info->display_name,
1269 "gicon", icon,
1270 NULL);
1271
1272 g_free (icon);
1273 }
1274
1275 actions = g_list_prepend (actions, action);
1276 }
1277
1278 actions = g_list_reverse (actions);
1279
1280 return actions;
1281 }
1282
1283 /**
1284 * xapp_favorites_create_actions:
1285 * @favorites: The #XAppFavorites instance.
1286 * @mimetypes: (nullable): The mimetypes to filter for, or NULL to include all favorites.
1287 *
1288 * Generates a list of favorite GtkActions.
1289 *
1290 * Returns: (element-type Gtk.Action) (transfer full): a new #GtkActionGroup populated with a list of favorites, or NULL
1291 if there are no favorites.
1292
1293 * Since: 2.0
1294 */
1295 GList *
1296 xapp_favorites_create_actions (XAppFavorites *favorites,
1297 const gchar **mimetypes)
1298 {
1299 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
1300 GList *actions;
1301
1302 actions = populate_action_list (favorites,
1303 mimetypes);
1304
1305 return actions;
1306 }
1307
1308 /* Used by favorite_vfs_file */
1309 GList *
1310 _xapp_favorites_get_display_names (XAppFavorites *favorites)
1311 {
1312 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
1313 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
1314 GHashTableIter iter;
1315 GList *ret;
1316 gpointer key, value;
1317
1318 ret = NULL;
1319 g_hash_table_iter_init (&iter, priv->infos);
1320
1321 while (g_hash_table_iter_next (&iter, &key, &value))
1322 {
1323 XAppFavoriteInfo *info = (XAppFavoriteInfo *) value;
1324 ret = g_list_prepend (ret, info->display_name);
1325 }
1326
1327 ret = g_list_reverse (ret);
1328 return ret;
1329 }
1330
1331 /* private for favorite-vfs-file-monitor */
1332 void
1333 _xapp_favorites_rename (XAppFavorites *favorites,
1334 const gchar *old_uri,
1335 const gchar *new_uri)
1336 {
1337 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
1338 g_return_if_fail (old_uri != NULL && new_uri != NULL);
1339
1340 rename_favorite (favorites, old_uri, new_uri);
1341 }
1342 /* private */
1343
0 #ifndef __XAPP_FAVORITES_H__
1 #define __XAPP_FAVORITES_H__
2
3 #include <stdio.h>
4 #include <gtk/gtk.h>
5
6 #include <glib-object.h>
7
8 G_BEGIN_DECLS
9
10 #define XAPP_TYPE_FAVORITE_INFO (xapp_favorite_info_get_type ())
11 typedef struct _XAppFavoriteInfo XAppFavoriteInfo;
12
13 #define XAPP_TYPE_FAVORITES (xapp_favorites_get_type ())
14
15 G_DECLARE_FINAL_TYPE (XAppFavorites, xapp_favorites, XAPP, FAVORITES, GObject)
16
17 XAppFavorites *xapp_favorites_get_default (void);
18 GList *xapp_favorites_get_favorites (XAppFavorites *favorites,
19 const gchar **mimetypes);
20 gint xapp_favorites_get_n_favorites (XAppFavorites *favorites);
21 XAppFavoriteInfo *xapp_favorites_find_by_display_name (XAppFavorites *favorites,
22 const gchar *display_name);
23 XAppFavoriteInfo *xapp_favorites_find_by_uri (XAppFavorites *favorites,
24 const gchar *uri);
25 void xapp_favorites_add (XAppFavorites *favorites,
26 const gchar *uri);
27 void xapp_favorites_remove (XAppFavorites *favorites,
28 const gchar *uri);
29 void xapp_favorites_launch (XAppFavorites *favorites,
30 const gchar *uri,
31 guint32 timestamp);
32
33 /**
34 * XAppFavoriteInfo:
35 * @uri: The uri to the favorite file.
36 * @display_name: The name for use when displaying the item. This may not exactly match
37 * the filename if there are files with the same name but in different folders.
38 * @cached_mimetype: The mimetype calculated for the uri when it was added to favorites.
39 *
40 * Information related to a single favorite file.
41 */
42 struct _XAppFavoriteInfo
43 {
44 gchar *uri;
45 gchar *display_name;
46 gchar *cached_mimetype;
47 };
48
49 GType xapp_favorite_info_get_type (void) G_GNUC_CONST;
50 XAppFavoriteInfo *xapp_favorite_info_copy (const XAppFavoriteInfo *info);
51 void xapp_favorite_info_free (XAppFavoriteInfo *info);
52
53
54 /* XAppFavoritesMenu */
55
56 /**
57 * FavoritesItemSelectedCallback
58 * @favorites: the #XAppFavorites instance
59 * @uri: the uri of the favorite that was selected
60 * @user_data: Callback data
61 *
62 * Callback when an item has been selected from the favorites
63 * #GtkMenu.
64 */
65 typedef void (* XAppFavoritesItemSelectedCallback) (XAppFavorites *favorites,
66 const gchar *uri,
67 gpointer user_data);
68
69 GtkWidget *xapp_favorites_create_menu (XAppFavorites *favorites,
70 const gchar **mimetypes,
71 XAppFavoritesItemSelectedCallback callback,
72 gpointer user_data,
73 GDestroyNotify func);
74 GList *xapp_favorites_create_actions (XAppFavorites *favorites,
75 const gchar **mimetypes);
76
77 G_END_DECLS
78
79 #endif /* __XAPP_FAVORITES_H__ */
0 /*-*- Mode: C; c-basic-offset: 8 -*-*/
1
2 /***
3 This file is part of libcanberra.
4
5 Copyright 2008 Lennart Poettering
6
7 libcanberra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation, either version 2.1 of the
10 License, or (at your option) any later version.
11
12 libcanberra is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with libcanberra. If not, see
19 <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <config.h>
23 #include <gtk/gtk.h>
24
25 #include "xapp-favorites.h"
26
27 /* Gtk module justification:
28 *
29 * The sole purpose of this module currently is to add a 'Favorites'
30 * shortcut to GtkFileChooser dialogs.
31 *
32 * In gtk_module_init, the XAppFavorites singleton is initialized, and
33 * the 'favorites' uri scheme is added to the default vfs. Ordinarily
34 * non-file:// schemes aren't supported in these dialogs unless their
35 * 'local-only' property is set to FALSE. Since favorites are shortcuts
36 * to locally-available files, we lie to the chooser setup by returning
37 * "/" instead of NULL when g_file_get_path ("favorites:///") is called.
38 */
39
40
41 /* Make sure GCC doesn't warn us about a missing prototype for this
42 * exported function */
43 void gtk_module_init (gint *argc, gchar ***argv[]);
44
45 static gboolean
46 selection_changed_cb (GSignalInvocationHint *ihint,
47 guint n_param_values,
48 const GValue *param_values,
49 gpointer data)
50 {
51 GtkFileChooser *chooser = GTK_FILE_CHOOSER (g_value_get_object (&param_values[0]));
52 GSList *list, *i;
53 gboolean already_applied = FALSE;
54 list = gtk_file_chooser_list_shortcut_folder_uris (chooser);
55
56 for (i = list; i != NULL; i = i->next)
57 {
58 if (g_strcmp0 ((gchar *) i->data, "favorites:///") == 0)
59 {
60 already_applied = TRUE;
61 break;
62 }
63 }
64
65 g_slist_free_full (list, g_free);
66
67 if (!already_applied)
68 {
69 gtk_file_chooser_add_shortcut_folder_uri (chooser, "favorites:///", NULL);
70 }
71
72 return TRUE;
73 }
74
75 static void
76 add_chooser_hook (GType type)
77 {
78 GTypeClass *type_class;
79 guint sigid;
80
81 type_class = g_type_class_ref (type);
82
83 sigid = g_signal_lookup ("selection-changed", type);
84 g_signal_add_emission_hook (sigid, 0, selection_changed_cb, NULL, NULL);
85
86 g_type_class_unref (type_class);
87 }
88
89 G_MODULE_EXPORT void gtk_module_init(gint *argc, gchar ***argv[]) {
90 xapp_favorites_get_default ();
91 add_chooser_hook (GTK_TYPE_FILE_CHOOSER_WIDGET);
92 add_chooser_hook (GTK_TYPE_FILE_CHOOSER_DIALOG);
93 add_chooser_hook (GTK_TYPE_FILE_CHOOSER_BUTTON);
94 }
95
96 G_MODULE_EXPORT gchar* g_module_check_init(GModule *module);
97
98 G_MODULE_EXPORT gchar* g_module_check_init(GModule *module) {
99 g_module_make_resident(module);
100 return NULL;
101 }
4949 subdir('icons')
5050 subdir('status-applets')
5151 subdir('scripts')
52 subdir('data')
5253 endif
5354
5455 if get_option('status-notifier') and not app_lib_only
11 <schemalist>
22 <schema id="org.x.apps" path="/org/x/apps/">
33 <child name="status-icon" schema="org.x.apps.statusicon"/>
4 <child name="favorites" schema="org.x.apps.favorites"/>
5 </schema>
6
7 <schema id="org.x.apps.favorites" path="/org/x/apps/favorites/" gettext-domain="xapps">
8 <key name="list" type="as">
9 <default>[]</default>
10 <summary>List of favorites, stored in display order, with the format of uri::mimetype</summary>
11 </key>
12 <key name="root-metadata" type="as">
13 <default>[]</default>
14 <summary>List of gvfs metadata for the favorites:/// root (for remembering sort order in nemo, etc).</summary>
15 </key>
416 </schema>
517
618 <schema id="org.x.apps.statusicon" path="/org/x/apps/statusicon/">
0 #!/usr/bin/python3
1 import gi
2 gi.require_version('Gtk', '3.0')
3 gi.require_version('XApp', '1.0')
4 from gi.repository import Gio, GLib, GObject, Gtk, XApp, Pango
5 import sys
6 import signal
7
8 signal.signal(signal.SIGINT, signal.SIG_DFL)
9
10
11 DBUS_NAME = "org.x.StatusIcon"
12 DBUS_PATH = "/org/x/StatusIcon"
13
14 class ListItem(Gtk.ListBoxRow):
15 def __init__(self, favinfo, mgr):
16 super(Gtk.ListBoxRow, self).__init__()
17 self.favinfo = favinfo
18 self.mgr = mgr
19
20 self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
21 self.add(self.box)
22
23 label = Gtk.Label(label=self.favinfo.display_name, xalign=0)
24 self.box.pack_start(label, False, False, 6)
25
26 delete = Gtk.Button.new_from_icon_name("edit-delete-symbolic", Gtk.IconSize.BUTTON)
27 delete.set_relief(Gtk.ReliefStyle.NONE)
28 self.box.pack_end(delete, False, False, 6)
29 delete.connect("clicked", self.on_delete_clicked)
30
31 self.show_all()
32
33 def on_delete_clicked(self, widget, data=None):
34 self.mgr.remove(self.favinfo.uri)
35
36 class FavoriteList(GObject.Object):
37
38 def __init__(self):
39 super(FavoriteList, self).__init__()
40 self.window = None
41 self.manager = XApp.Favorites.get_default()
42 self.manager.connect("changed", self.on_manager_changed)
43
44 self.window = Gtk.Window()
45 self.window.set_default_size(600, 400)
46 self.window.connect("destroy", self.on_window_destroy)
47 self.window_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
48 self.main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
49 margin=6,
50 spacing=0)
51
52 bar = Gtk.MenuBar()
53 menu_item = Gtk.MenuItem.new_with_label("Favorites")
54 favorites = self.manager.create_menu(None, self.favorite_menu_item_activated)
55 menu_item.set_submenu(favorites)
56 bar.append(menu_item)
57
58 self.window_box.pack_start(bar, False, False, 0)
59 self.window_box.pack_start(self.main_box, True, True, 0)
60
61 # list stuff
62 sw_frame = Gtk.Frame()
63 sw = Gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
64 sw_frame.add(sw)
65
66 self.list_box = Gtk.ListBox(activate_on_single_click=False)
67 self.list_box.connect("selected-rows-changed", self.on_selection_changed)
68 self.list_box.connect("row-activated", self.on_row_activated)
69 sw.add(self.list_box)
70
71 self.main_box.pack_start(sw_frame, True, True, 6)
72 self.window.add(self.window_box)
73
74 # controls
75
76 control_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
77 self.add_button = Gtk.Button.new_from_icon_name("list-add-symbolic", Gtk.IconSize.BUTTON)
78 self.add_button.connect("clicked", self.on_add_clicked)
79
80 control_box.pack_start(self.add_button, False, False, 6)
81
82 self.main_box.pack_start(control_box, False, False, 6)
83
84 self.window.show_all()
85
86 self.selected_uri = None
87 self.loading = False
88
89 self.load_favorites()
90
91 def favorite_menu_item_activated(self, manager, uri, data=None):
92 print("activated", uri)
93 self.manager.launch(uri, Gtk.get_current_event_time())
94
95 def load_favorites(self):
96 self.loading = True # this will preserve the last selection
97
98 for child in self.list_box.get_children():
99 self.list_box.remove(child)
100
101 previously_selected = None
102
103 favorites = self.manager.get_favorites(None)
104
105 for info in favorites:
106 row = ListItem(info, self.manager)
107
108 self.list_box.insert(row, -1)
109
110 if self.selected_uri == info.uri:
111 previously_selected = row
112
113 self.loading = False
114
115 if previously_selected:
116 self.list_box.select_row(previously_selected)
117
118 def on_row_activated(self, box, row, data=None):
119 self.manager.launch(row.favinfo.uri, Gtk.get_current_event_time())
120
121 def on_selection_changed(self, box, data=None):
122 if self.loading:
123 return
124
125 row = self.list_box.get_selected_row()
126
127 if row:
128 self.selected_uri = row.favinfo.uri
129 else:
130 self.selected_uri = None
131
132 def on_add_clicked(self, widget, data=None):
133 dialog = Gtk.FileChooserDialog(title="Add file to favorites",
134 parent=self.window,
135 action=Gtk.FileChooserAction.OPEN)
136
137 dialog.add_buttons("Add", Gtk.ResponseType.OK,
138 "Cancel", Gtk.ResponseType.CANCEL)
139
140 # dialog.add_shortcut_folder_uri ("favorites:///")
141
142 res = dialog.run()
143 if res == Gtk.ResponseType.OK:
144 uri = dialog.get_uri()
145
146 self.manager.add(uri)
147
148 dialog.destroy()
149
150 def on_manager_changed(self, manager, data=None):
151 self.load_favorites()
152
153 def on_window_destroy(self, widget, data=None):
154 Gtk.main_quit()
155
156 if __name__ == '__main__':
157 test = FavoriteList()
158 Gtk.main()