diff options
author | 2018-03-28 15:25:33 -0400 | |
---|---|---|
committer | 2018-03-28 15:25:33 -0400 | |
commit | 9070950eecded7bfa64e7bca3bb76b150ccc8b72 (patch) | |
tree | 509a9c669bf2679085e27a1ff1b0c95526abf14c /swaybg | |
parent | Merge pull request #1641 from swaywm/transformed-events (diff) | |
parent | Address review comments (diff) | |
download | sway-9070950eecded7bfa64e7bca3bb76b150ccc8b72.tar.gz sway-9070950eecded7bfa64e7bca3bb76b150ccc8b72.tar.zst sway-9070950eecded7bfa64e7bca3bb76b150ccc8b72.zip |
Merge pull request #1638 from swaywm/swaybg-layers
Reimplement swaybg using surface layers
Diffstat (limited to 'swaybg')
-rw-r--r-- | swaybg/main.c | 445 | ||||
-rw-r--r-- | swaybg/meson.build | 18 |
2 files changed, 290 insertions, 173 deletions
diff --git a/swaybg/main.c b/swaybg/main.c index 2fdd4220..f431526c 100644 --- a/swaybg/main.c +++ b/swaybg/main.c | |||
@@ -1,44 +1,61 @@ | |||
1 | #include "wayland-desktop-shell-client-protocol.h" | 1 | #include <assert.h> |
2 | #include <ctype.h> | ||
2 | #include <stdbool.h> | 3 | #include <stdbool.h> |
3 | #include <stdio.h> | 4 | #include <stdio.h> |
4 | #include <stdlib.h> | 5 | #include <stdlib.h> |
5 | #include <ctype.h> | ||
6 | #include <wayland-client.h> | ||
7 | #include <time.h> | ||
8 | #include <string.h> | 6 | #include <string.h> |
9 | #include "client/window.h" | 7 | #include <time.h> |
10 | #include "client/registry.h" | 8 | #include <wayland-client.h> |
11 | #include "client/cairo.h" | 9 | #include <wlr/util/log.h> |
12 | #include "log.h" | 10 | #include "pool-buffer.h" |
13 | #include "list.h" | 11 | #include "cairo.h" |
14 | #include "util.h" | 12 | #include "util.h" |
13 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" | ||
15 | 14 | ||
16 | list_t *surfaces; | 15 | enum background_mode { |
17 | struct registry *registry; | 16 | BACKGROUND_MODE_STRETCH, |
17 | BACKGROUND_MODE_FILL, | ||
18 | BACKGROUND_MODE_FIT, | ||
19 | BACKGROUND_MODE_CENTER, | ||
20 | BACKGROUND_MODE_TILE, | ||
21 | BACKGROUND_MODE_SOLID_COLOR, | ||
22 | }; | ||
18 | 23 | ||
19 | enum scaling_mode { | 24 | struct swaybg_args { |
20 | SCALING_MODE_STRETCH, | 25 | int output_idx; |
21 | SCALING_MODE_FILL, | 26 | const char *path; |
22 | SCALING_MODE_FIT, | 27 | enum background_mode mode; |
23 | SCALING_MODE_CENTER, | ||
24 | SCALING_MODE_TILE, | ||
25 | }; | 28 | }; |
26 | 29 | ||
27 | void sway_terminate(int exit_code) { | 30 | struct swaybg_context { |
28 | int i; | 31 | uint32_t color; |
29 | for (i = 0; i < surfaces->length; ++i) { | 32 | cairo_surface_t *image; |
30 | struct window *window = surfaces->items[i]; | 33 | }; |
31 | window_teardown(window); | 34 | |
32 | } | 35 | struct swaybg_state { |
33 | list_free(surfaces); | 36 | const struct swaybg_args *args; |
34 | registry_teardown(registry); | 37 | struct swaybg_context context; |
35 | exit(exit_code); | 38 | |
36 | } | 39 | struct wl_display *display; |
40 | struct wl_compositor *compositor; | ||
41 | struct zwlr_layer_shell_v1 *layer_shell; | ||
42 | struct wl_shm *shm; | ||
43 | |||
44 | struct wl_output *output; | ||
45 | struct wl_surface *surface; | ||
46 | struct zwlr_layer_surface_v1 *layer_surface; | ||
47 | |||
48 | bool run_display; | ||
49 | uint32_t width, height; | ||
50 | struct pool_buffer buffers[2]; | ||
51 | struct pool_buffer *current_buffer; | ||
52 | }; | ||
37 | 53 | ||
38 | bool is_valid_color(const char *color) { | 54 | bool is_valid_color(const char *color) { |
39 | int len = strlen(color); | 55 | int len = strlen(color); |
40 | if (len != 7 || color[0] != '#') { | 56 | if (len != 7 || color[0] != '#') { |
41 | sway_log(L_ERROR, "%s is not a valid color for swaybg. Color should be specified as #rrggbb (no alpha).", color); | 57 | wlr_log(L_ERROR, "%s is not a valid color for swaybg. " |
58 | "Color should be specified as #rrggbb (no alpha).", color); | ||
42 | return false; | 59 | return false; |
43 | } | 60 | } |
44 | 61 | ||
@@ -52,163 +69,245 @@ bool is_valid_color(const char *color) { | |||
52 | return true; | 69 | return true; |
53 | } | 70 | } |
54 | 71 | ||
55 | int main(int argc, const char **argv) { | 72 | static void render_image(struct swaybg_state *state) { |
56 | init_log(L_INFO); | 73 | cairo_t *cairo = state->current_buffer->cairo; |
57 | surfaces = create_list(); | 74 | cairo_surface_t *image = state->context.image; |
58 | registry = registry_poll(); | 75 | double width = cairo_image_surface_get_width(image); |
76 | double height = cairo_image_surface_get_height(image); | ||
77 | int wwidth = state->width; | ||
78 | int wheight = state->height; | ||
59 | 79 | ||
60 | if (argc != 4) { | 80 | switch (state->args->mode) { |
61 | sway_abort("Do not run this program manually. See man 5 sway and look for output options."); | 81 | case BACKGROUND_MODE_STRETCH: |
62 | } | 82 | cairo_scale(cairo, (double)wwidth / width, (double)wheight / height); |
83 | cairo_set_source_surface(cairo, image, 0, 0); | ||
84 | break; | ||
85 | case BACKGROUND_MODE_FILL: { | ||
86 | double window_ratio = (double)wwidth / wheight; | ||
87 | double bg_ratio = width / height; | ||
63 | 88 | ||
64 | if (!registry->desktop_shell) { | 89 | if (window_ratio > bg_ratio) { |
65 | sway_abort("swaybg requires the compositor to support the desktop-shell extension."); | 90 | double scale = (double)wwidth / width; |
91 | cairo_scale(cairo, scale, scale); | ||
92 | cairo_set_source_surface(cairo, image, | ||
93 | 0, (double)wheight / 2 / scale - height / 2); | ||
94 | } else { | ||
95 | double scale = (double)wheight / height; | ||
96 | cairo_scale(cairo, scale, scale); | ||
97 | cairo_set_source_surface(cairo, image, | ||
98 | (double)wwidth / 2 / scale - width / 2, 0); | ||
99 | } | ||
100 | break; | ||
66 | } | 101 | } |
102 | case BACKGROUND_MODE_FIT: { | ||
103 | double window_ratio = (double)wwidth / wheight; | ||
104 | double bg_ratio = width / height; | ||
67 | 105 | ||
68 | int desired_output = atoi(argv[1]); | 106 | if (window_ratio > bg_ratio) { |
69 | sway_log(L_INFO, "Using output %d of %d", desired_output, registry->outputs->length); | 107 | double scale = (double)wheight / height; |
70 | int i; | 108 | cairo_scale(cairo, scale, scale); |
71 | struct output_state *output = registry->outputs->items[desired_output]; | 109 | cairo_set_source_surface(cairo, image, |
72 | struct window *window = window_setup(registry, | 110 | (double)wwidth / 2 / scale - width / 2, 0); |
73 | output->width, output->height, output->scale, false); | ||
74 | if (!window) { | ||
75 | sway_abort("Failed to create surfaces."); | ||
76 | } | ||
77 | desktop_shell_set_background(registry->desktop_shell, output->output, window->surface); | ||
78 | window_make_shell(window); | ||
79 | list_add(surfaces, window); | ||
80 | |||
81 | if (strcmp(argv[3], "solid_color") == 0 && is_valid_color(argv[2])) { | ||
82 | cairo_set_source_u32(window->cairo, parse_color(argv[2])); | ||
83 | cairo_paint(window->cairo); | ||
84 | window_render(window); | ||
85 | } else { | ||
86 | #ifdef WITH_GDK_PIXBUF | ||
87 | GError *err = NULL; | ||
88 | GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(argv[2], &err); | ||
89 | if (!pixbuf) { | ||
90 | sway_abort("Failed to load background image."); | ||
91 | } | ||
92 | cairo_surface_t *image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); | ||
93 | g_object_unref(pixbuf); | ||
94 | #else | ||
95 | cairo_surface_t *image = cairo_image_surface_create_from_png(argv[2]); | ||
96 | #endif //WITH_GDK_PIXBUF | ||
97 | if (!image) { | ||
98 | sway_abort("Failed to read background image."); | ||
99 | } | ||
100 | if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { | ||
101 | sway_abort("Failed to read background image: %s." | ||
102 | #ifndef WITH_GDK_PIXBUF | ||
103 | "\nSway was compiled without gdk_pixbuf support, so only" | ||
104 | "\nPNG images can be loaded. This is the likely cause." | ||
105 | #endif //WITH_GDK_PIXBUF | ||
106 | , cairo_status_to_string(cairo_surface_status(image))); | ||
107 | } | ||
108 | double width = cairo_image_surface_get_width(image); | ||
109 | double height = cairo_image_surface_get_height(image); | ||
110 | |||
111 | const char *scaling_mode_str = argv[3]; | ||
112 | enum scaling_mode scaling_mode = SCALING_MODE_STRETCH; | ||
113 | if (strcmp(scaling_mode_str, "stretch") == 0) { | ||
114 | scaling_mode = SCALING_MODE_STRETCH; | ||
115 | } else if (strcmp(scaling_mode_str, "fill") == 0) { | ||
116 | scaling_mode = SCALING_MODE_FILL; | ||
117 | } else if (strcmp(scaling_mode_str, "fit") == 0) { | ||
118 | scaling_mode = SCALING_MODE_FIT; | ||
119 | } else if (strcmp(scaling_mode_str, "center") == 0) { | ||
120 | scaling_mode = SCALING_MODE_CENTER; | ||
121 | } else if (strcmp(scaling_mode_str, "tile") == 0) { | ||
122 | scaling_mode = SCALING_MODE_TILE; | ||
123 | } else { | 111 | } else { |
124 | sway_abort("Unsupported scaling mode: %s", scaling_mode_str); | 112 | double scale = (double)wwidth / width; |
113 | cairo_scale(cairo, scale, scale); | ||
114 | cairo_set_source_surface(cairo, image, | ||
115 | 0, (double)wheight / 2 / scale - height / 2); | ||
125 | } | 116 | } |
117 | break; | ||
118 | } | ||
119 | case BACKGROUND_MODE_CENTER: | ||
120 | cairo_set_source_surface(cairo, image, | ||
121 | (double)wwidth / 2 - width / 2, | ||
122 | (double)wheight / 2 - height / 2); | ||
123 | break; | ||
124 | case BACKGROUND_MODE_TILE: { | ||
125 | cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); | ||
126 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); | ||
127 | cairo_set_source(cairo, pattern); | ||
128 | break; | ||
129 | } | ||
130 | case BACKGROUND_MODE_SOLID_COLOR: | ||
131 | assert(0); | ||
132 | break; | ||
133 | } | ||
134 | cairo_paint(cairo); | ||
135 | } | ||
136 | |||
137 | static void render_frame(struct swaybg_state *state) { | ||
138 | state->current_buffer = get_next_buffer(state->shm, | ||
139 | state->buffers, state->width, state->height); | ||
140 | cairo_t *cairo = state->current_buffer->cairo; | ||
141 | |||
142 | switch (state->args->mode) { | ||
143 | case BACKGROUND_MODE_SOLID_COLOR: | ||
144 | cairo_set_source_u32(cairo, state->context.color); | ||
145 | cairo_paint(cairo); | ||
146 | break; | ||
147 | default: | ||
148 | render_image(state); | ||
149 | break; | ||
150 | } | ||
151 | |||
152 | wl_surface_attach(state->surface, state->current_buffer->buffer, 0, 0); | ||
153 | wl_surface_damage(state->surface, 0, 0, state->width, state->height); | ||
154 | wl_surface_commit(state->surface); | ||
155 | } | ||
126 | 156 | ||
127 | int wwidth = window->width * window->scale; | 157 | static bool prepare_context(struct swaybg_state *state) { |
128 | int wheight = window->height * window->scale; | 158 | if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { |
129 | 159 | state->context.color = parse_color(state->args->path); | |
130 | for (i = 0; i < surfaces->length; ++i) { | 160 | return is_valid_color(state->args->path); |
131 | struct window *window = surfaces->items[i]; | 161 | } |
132 | if (window_prerender(window) && window->cairo) { | 162 | #ifdef HAVE_GDK_PIXBUF |
133 | switch (scaling_mode) { | 163 | GError *err = NULL; |
134 | case SCALING_MODE_STRETCH: | 164 | GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(state->args->path, &err); |
135 | cairo_scale(window->cairo, | 165 | if (!pixbuf) { |
136 | (double) wwidth / width, | 166 | wlr_log(L_ERROR, "Failed to load background image."); |
137 | (double) wheight / height); | 167 | return false; |
138 | cairo_set_source_surface(window->cairo, image, 0, 0); | 168 | } |
139 | break; | 169 | state->context.image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); |
140 | case SCALING_MODE_FILL: | 170 | g_object_unref(pixbuf); |
141 | { | 171 | #else |
142 | double window_ratio = (double) wwidth / wheight; | 172 | state->context.image = cairo_image_surface_create_from_png( |
143 | double bg_ratio = width / height; | 173 | state->args->path); |
144 | 174 | #endif //HAVE_GDK_PIXBUF | |
145 | if (window_ratio > bg_ratio) { | 175 | if (!state->context.image) { |
146 | double scale = (double) wwidth / width; | 176 | wlr_log(L_ERROR, "Failed to read background image."); |
147 | cairo_scale(window->cairo, scale, scale); | 177 | return false; |
148 | cairo_set_source_surface(window->cairo, image, | 178 | } |
149 | 0, | 179 | if (cairo_surface_status(state->context.image) != CAIRO_STATUS_SUCCESS) { |
150 | (double) wheight/2 / scale - height/2); | 180 | wlr_log(L_ERROR, "Failed to read background image: %s." |
151 | } else { | 181 | #ifndef HAVE_GDK_PIXBUF |
152 | double scale = (double) wheight / height; | 182 | "\nSway was compiled without gdk_pixbuf support, so only" |
153 | cairo_scale(window->cairo, scale, scale); | 183 | "\nPNG images can be loaded. This is the likely cause." |
154 | cairo_set_source_surface(window->cairo, image, | 184 | #endif //HAVE_GDK_PIXBUF |
155 | (double) wwidth/2 / scale - width/2, | 185 | , cairo_status_to_string( |
156 | 0); | 186 | cairo_surface_status(state->context.image))); |
157 | } | 187 | return false; |
158 | break; | 188 | } |
159 | } | 189 | return true; |
160 | case SCALING_MODE_FIT: | 190 | } |
161 | { | 191 | |
162 | double window_ratio = (double) wwidth / wheight; | 192 | static void layer_surface_configure(void *data, |
163 | double bg_ratio = width / height; | 193 | struct zwlr_layer_surface_v1 *surface, |
164 | 194 | uint32_t serial, uint32_t width, uint32_t height) { | |
165 | if (window_ratio > bg_ratio) { | 195 | struct swaybg_state *state = data; |
166 | double scale = (double) wheight / height; | 196 | state->width = width; |
167 | cairo_scale(window->cairo, scale, scale); | 197 | state->height = height; |
168 | cairo_set_source_surface(window->cairo, image, | 198 | zwlr_layer_surface_v1_ack_configure(surface, serial); |
169 | (double) wwidth/2 / scale - width/2, | 199 | render_frame(state); |
170 | 0); | 200 | } |
171 | } else { | 201 | |
172 | double scale = (double) wwidth / width; | 202 | static void layer_surface_closed(void *data, |
173 | cairo_scale(window->cairo, scale, scale); | 203 | struct zwlr_layer_surface_v1 *surface) { |
174 | cairo_set_source_surface(window->cairo, image, | 204 | struct swaybg_state *state = data; |
175 | 0, | 205 | zwlr_layer_surface_v1_destroy(state->layer_surface); |
176 | (double) wheight/2 / scale - height/2); | 206 | wl_surface_destroy(state->surface); |
177 | } | 207 | state->run_display = false; |
178 | break; | 208 | } |
179 | } | 209 | |
180 | case SCALING_MODE_CENTER: | 210 | struct zwlr_layer_surface_v1_listener layer_surface_listener = { |
181 | cairo_set_source_surface(window->cairo, image, | 211 | .configure = layer_surface_configure, |
182 | (double) wwidth/2 - width/2, | 212 | .closed = layer_surface_closed, |
183 | (double) wheight/2 - height/2); | 213 | }; |
184 | break; | 214 | |
185 | case SCALING_MODE_TILE: | 215 | static void handle_global(void *data, struct wl_registry *registry, |
186 | { | 216 | uint32_t name, const char *interface, uint32_t version) { |
187 | cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); | 217 | struct swaybg_state *state = data; |
188 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); | 218 | if (strcmp(interface, wl_compositor_interface.name) == 0) { |
189 | cairo_set_source(window->cairo, pattern); | 219 | state->compositor = wl_registry_bind(registry, name, |
190 | break; | 220 | &wl_compositor_interface, 1); |
191 | } | 221 | } else if (strcmp(interface, wl_shm_interface.name) == 0) { |
192 | default: | 222 | state->shm = wl_registry_bind(registry, name, |
193 | sway_abort("Scaling mode '%s' not implemented yet!", scaling_mode_str); | 223 | &wl_shm_interface, 1); |
194 | } | 224 | } else if (strcmp(interface, wl_output_interface.name) == 0) { |
195 | 225 | static int output_idx = 0; | |
196 | cairo_paint(window->cairo); | 226 | if (output_idx == state->args->output_idx) { |
197 | 227 | state->output = wl_registry_bind(registry, name, | |
198 | window_render(window); | 228 | &wl_output_interface, 1); |
199 | } | ||
200 | } | 229 | } |
230 | output_idx++; | ||
231 | } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { | ||
232 | state->layer_shell = wl_registry_bind( | ||
233 | registry, name, &zwlr_layer_shell_v1_interface, 1); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | static void handle_global_remove(void *data, struct wl_registry *registry, | ||
238 | uint32_t name) { | ||
239 | // who cares | ||
240 | } | ||
241 | |||
242 | static const struct wl_registry_listener registry_listener = { | ||
243 | .global = handle_global, | ||
244 | .global_remove = handle_global_remove, | ||
245 | }; | ||
201 | 246 | ||
202 | cairo_surface_destroy(image); | 247 | int main(int argc, const char **argv) { |
248 | struct swaybg_args args = {0}; | ||
249 | struct swaybg_state state = {0}; | ||
250 | state.args = &args; | ||
251 | wlr_log_init(L_DEBUG, NULL); | ||
252 | |||
253 | if (argc != 4) { | ||
254 | wlr_log(L_ERROR, "Do not run this program manually. " | ||
255 | "See man 5 sway and look for output options."); | ||
256 | return 1; | ||
203 | } | 257 | } |
258 | args.output_idx = atoi(argv[1]); | ||
259 | args.path = argv[2]; | ||
260 | |||
261 | args.mode = BACKGROUND_MODE_STRETCH; | ||
262 | if (strcmp(argv[3], "stretch") == 0) { | ||
263 | args.mode = BACKGROUND_MODE_STRETCH; | ||
264 | } else if (strcmp(argv[3], "fill") == 0) { | ||
265 | args.mode = BACKGROUND_MODE_FILL; | ||
266 | } else if (strcmp(argv[3], "fit") == 0) { | ||
267 | args.mode = BACKGROUND_MODE_FIT; | ||
268 | } else if (strcmp(argv[3], "center") == 0) { | ||
269 | args.mode = BACKGROUND_MODE_CENTER; | ||
270 | } else if (strcmp(argv[3], "tile") == 0) { | ||
271 | args.mode = BACKGROUND_MODE_TILE; | ||
272 | } else if (strcmp(argv[3], "solid_color") == 0) { | ||
273 | args.mode = BACKGROUND_MODE_SOLID_COLOR; | ||
274 | } else { | ||
275 | wlr_log(L_ERROR, "Unsupported background mode: %s", argv[3]); | ||
276 | return 1; | ||
277 | } | ||
278 | |||
279 | if (!prepare_context(&state)) { | ||
280 | return 1; | ||
281 | } | ||
282 | |||
283 | assert(state.display = wl_display_connect(NULL)); | ||
284 | |||
285 | struct wl_registry *registry = wl_display_get_registry(state.display); | ||
286 | wl_registry_add_listener(registry, ®istry_listener, &state); | ||
287 | wl_display_roundtrip(state.display); | ||
288 | assert(state.compositor && state.layer_shell && state.output && state.shm); | ||
289 | |||
290 | assert(state.surface = wl_compositor_create_surface(state.compositor)); | ||
291 | |||
292 | state.layer_surface = zwlr_layer_shell_v1_get_layer_surface( | ||
293 | state.layer_shell, state.surface, state.output, | ||
294 | ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper"); | ||
295 | assert(state.layer_surface); | ||
204 | 296 | ||
205 | while (wl_display_dispatch(registry->display) != -1); | 297 | zwlr_layer_surface_v1_set_size(state.layer_surface, 0, 0); |
298 | zwlr_layer_surface_v1_set_anchor(state.layer_surface, | ||
299 | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | | ||
300 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | | ||
301 | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | | ||
302 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); | ||
303 | zwlr_layer_surface_v1_add_listener(state.layer_surface, | ||
304 | &layer_surface_listener, &state); | ||
305 | state.run_display = true; | ||
306 | wl_surface_commit(state.surface); | ||
307 | wl_display_roundtrip(state.display); | ||
206 | 308 | ||
207 | for (i = 0; i < surfaces->length; ++i) { | 309 | while (wl_display_dispatch(state.display) != -1 && state.run_display) { |
208 | struct window *window = surfaces->items[i]; | 310 | // This space intentionally left blank |
209 | window_teardown(window); | ||
210 | } | 311 | } |
211 | list_free(surfaces); | ||
212 | registry_teardown(registry); | ||
213 | return 0; | 312 | return 0; |
214 | } | 313 | } |
diff --git a/swaybg/meson.build b/swaybg/meson.build new file mode 100644 index 00000000..5e10f3c7 --- /dev/null +++ b/swaybg/meson.build | |||
@@ -0,0 +1,18 @@ | |||
1 | executable( | ||
2 | 'swaybg', | ||
3 | 'main.c', | ||
4 | include_directories: [sway_inc], | ||
5 | dependencies: [ | ||
6 | cairo, | ||
7 | gdk_pixbuf, | ||
8 | jsonc, | ||
9 | math, | ||
10 | pango, | ||
11 | pangocairo, | ||
12 | sway_protos, | ||
13 | wayland_client, | ||
14 | wlroots, | ||
15 | ], | ||
16 | link_with: [lib_sway_common, lib_sway_client], | ||
17 | install: true | ||
18 | ) | ||