1 /*
2 * LavaLauncher - A simple launcher panel for Wayland
3 *
4 * Copyright (C) 2020 Leon Henrik Plickat
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #define _POSIX_C_SOURCE 200809L
21
22 #include<stdio.h>
23 #include<stdlib.h>
24 #include<stdbool.h>
25 #include<stdint.h>
26 #include<unistd.h>
27 #include<string.h>
28 #include<errno.h>
29 #include<cairo/cairo.h>
30
31 #if SVG_SUPPORT
32 #include<librsvg-2.0/librsvg/rsvg.h>
33 #endif
34
35 #include"str.h"
36 #include"lavalauncher.h"
37 #include"types/image_t.h"
38
39 /* Returns: -1 On error
40 * 0 If the file is not a PNG file
41 * 1 If the file is a PNG file
42 */
is_png_file(const char * path)43 static int is_png_file (const char *path)
44 {
45 const size_t buffer_size = 8;
46
47 FILE *file;
48 if ( NULL == (file = fopen(path, "r")) )
49 {
50 log_message(0, "ERROR: Can not open file: %s\n"
51 "ERROR: fopen: %s\n", path, strerror(errno));
52 return -1;
53 }
54
55 unsigned char buffer[buffer_size];
56 size_t ret = fread(buffer, sizeof(unsigned char), buffer_size, file);
57 fclose(file);
58
59 if ( ret == 0 )
60 {
61 log_message(0, "ERROR: fread() failed when trying to fetch file magic.\n");
62 return -1;
63 }
64
65 unsigned char png_magic[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
66 for (int i = 0; i < 8; i++) if ( buffer[i] != png_magic[i] )
67 return 0;
68
69 return 1;
70 }
71
load_image(image_t * image,const char * path)72 static bool load_image (image_t *image, const char *path)
73 {
74 if (access(path, F_OK))
75 {
76 log_message(0, "ERROR: File does not exist: %s\n", path);
77 return false;
78 }
79 if (access(path, R_OK))
80 {
81 log_message(0, "ERROR: File can not be read: %s\n"
82 "INFO: Check the files permissions, owner and group.\n",
83 path);
84 return false;
85 }
86 if ( errno != 0 )
87 {
88 log_message(0, "ERROR: access: %s\n", strerror(errno));
89 return false;
90 }
91
92 /* PNG */
93 int ret;
94 ret = is_png_file(path);
95 if ( ret == 1 )
96 {
97 if ( NULL == (image->cairo_surface = cairo_image_surface_create_from_png(path)) )
98 {
99 log_message(0, "ERROR: Failed loading image: %s\n"
100 "ERROR: cairo_image_surface_create_from_png: %s\n",
101 path, strerror(errno));
102 return false;
103 }
104 else
105 return true;
106 }
107 else if ( ret == -1 )
108 return false;
109
110 #if SVG_SUPPORT
111 /* SVG */
112 GError *gerror = NULL;
113 if ( NULL != (image->rsvg_handle = rsvg_handle_new_from_file(path, &gerror)) )
114 return true;
115 else if ( gerror->domain != 123 )
116 {
117 /* The domain 123 is an XML parse error. Receiving it means that
118 * the file is likely not an SVG image, a case which must be
119 * handled differently than other errors.
120 */
121 log_message(0, "ERROR: Failed to load image: %s\n"
122 "ERROR: rsvg_handle_new_from_file: %d: %s\n",
123 path, gerror->domain, gerror->message);
124 return false;
125 }
126 #endif
127
128 log_message(0, "ERROR: Unsupported file type: %s\n"
129 #if SVG_SUPPORT
130 "INFO: LavaLauncher supports PNG and SVG images.\n",
131 #else
132 "INFO: LavaLauncher supports PNG images.\n"
133 "INFO: LavaLauncher has been compiled without SVG support.\n",
134 #endif
135 path);
136
137 return false;
138 }
139
image_t_create_from_file(const char * path)140 image_t *image_t_create_from_file (const char *path)
141 {
142 TRY_NEW(image_t, image, NULL);
143
144 image->cairo_surface = NULL;
145 image->references = 1;
146 #if SVG_SUPPORT
147 image->rsvg_handle = NULL;
148 #endif
149
150 if (load_image(image, path))
151 return image;
152
153 free(image);
154 return NULL;
155 }
156
image_t_reference(image_t * image)157 image_t *image_t_reference (image_t *image)
158 {
159 image->references++;
160 return image;
161 }
162
image_t_destroy(image_t * image)163 void image_t_destroy (image_t *image)
164 {
165 if ( --image->references > 0 )
166 return;
167
168 if ( image->cairo_surface != NULL )
169 cairo_surface_destroy(image->cairo_surface);
170
171 #if SVG_SUPPORT
172 if ( image->rsvg_handle != NULL )
173 g_object_unref(image->rsvg_handle);
174 #endif
175
176 free(image);
177 }
178
image_t_draw_to_cairo(cairo_t * cairo,image_t * image,uint32_t x,uint32_t y,uint32_t width,uint32_t height)179 void image_t_draw_to_cairo (cairo_t *cairo, image_t *image,
180 uint32_t x, uint32_t y, uint32_t width, uint32_t height)
181 {
182 cairo_save(cairo);
183 cairo_translate(cairo, x, y);
184
185 if ( image->cairo_surface != NULL )
186 {
187 int sw = cairo_image_surface_get_width(image->cairo_surface);
188 int sh = cairo_image_surface_get_height(image->cairo_surface);
189
190 cairo_scale(cairo, (float)width / (float)sw, (float)height / (float)sh);
191 cairo_set_source_surface(cairo, image->cairo_surface, 0, 0);
192 cairo_paint(cairo);
193 }
194 #if SVG_SUPPORT
195 else if ( image->rsvg_handle != NULL )
196 {
197 // TODO maybe set DPI?
198 gboolean has_width, has_height, has_viewbox;
199 RsvgLength rsvg_width, rsvg_height;
200 RsvgRectangle viewbox;
201 rsvg_handle_get_intrinsic_dimensions(image->rsvg_handle,
202 &has_width, &rsvg_width, &has_height, &rsvg_height,
203 &has_viewbox, &viewbox);
204 if ( ! has_width || ! has_height )
205 log_message(0, "ERROR: Can not render SVG image without width or height.\n");
206 else if ( rsvg_width.length == 0 || rsvg_height.length == 0 )
207 log_message(0, "ERROR: Can not render SVG image with width or height of 0.\n");
208 else if ( rsvg_width.unit != RSVG_UNIT_PX || rsvg_height.unit != RSVG_UNIT_PX )
209 log_message(0, "ERROR: Can not render SVG image whichs width or height are not defined in pixels.\n");
210 else
211 {
212 cairo_scale(cairo, (float)width / rsvg_width.length,
213 (float)width / rsvg_height.length);
214 GError *gerror = NULL;
215 rsvg_handle_render_document(image->rsvg_handle, cairo,
216 &viewbox, &gerror);
217 // TODO check value of gerror
218 }
219 }
220 #endif
221
222 cairo_restore(cairo);
223 }
224
225