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
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 | ) |
0 | 0 | usr/lib/*/libxapp.so.1* |
1 | 1 | usr/libexec/xapps/sn-watcher/* |
2 | usr/lib/*/gtk-3.0/modules | |
3 | /etc/X11⏎ |
10 | 10 | main_xml: 'xapp-docs.xml', |
11 | 11 | scan_args: ['--rebuild-types'], |
12 | 12 | 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 | ) |
15 | 15 | |
16 | 16 | <chapter> |
17 | 17 | <title>API reference</title> |
18 | <xi:include href="xml/xapp-favorites.xml"/> | |
18 | 19 | <xi:include href="xml/xapp-gtk-window.xml"/> |
19 | 20 | <xi:include href="xml/xapp-icon-chooser-button.xml"/> |
20 | 21 | <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 |
0 | 0 | glib_min_ver = '>=2.37.3' |
1 | 1 | |
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 | ||
2 | 6 | 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 | |
6 | 10 | libdeps += dependency('gdk-pixbuf-2.0', version: '>=2.22.0', required: true) |
7 | 11 | libdeps += dependency('cairo', required: true) |
8 | 12 | libdeps += dependency('x11', required: true) |
9 | 13 | |
14 | favorite_vfs_sources = [ | |
15 | 'favorite-vfs-file.c', | |
16 | 'favorite-vfs-file-enumerator.c', | |
17 | 'favorite-vfs-file-monitor.c' | |
18 | ] | |
19 | ||
10 | 20 | xapp_headers = [ |
21 | 'xapp-favorites.h', | |
11 | 22 | 'xapp-gtk-window.h', |
12 | 23 | 'xapp-icon-chooser-button.h', |
13 | 24 | 'xapp-icon-chooser-dialog.h', |
20 | 31 | ] |
21 | 32 | |
22 | 33 | xapp_sources = [ |
34 | 'xapp-favorites.c', | |
23 | 35 | 'xapp-glade-catalog.c', |
24 | 36 | 'xapp-gtk-window.c', |
25 | 37 | 'xapp-icon-chooser-button.c', |
82 | 94 | ) |
83 | 95 | |
84 | 96 | 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, | |
86 | 98 | include_directories: [top_inc], |
87 | 99 | version: meson.project_version(), |
88 | 100 | soversion: '1', |
133 | 145 | metadata_dirs: meson.current_source_dir(), |
134 | 146 | install: true |
135 | 147 | ) |
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 (¶m_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 | } |
49 | 49 | subdir('icons') |
50 | 50 | subdir('status-applets') |
51 | 51 | subdir('scripts') |
52 | subdir('data') | |
52 | 53 | endif |
53 | 54 | |
54 | 55 | if get_option('status-notifier') and not app_lib_only |
1 | 1 | <schemalist> |
2 | 2 | <schema id="org.x.apps" path="/org/x/apps/"> |
3 | 3 | <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> | |
4 | 16 | </schema> |
5 | 17 | |
6 | 18 | <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() |