xgetshape

changeset 0:2f02f100b20f

getting the window shape of another window
author John Tsiombikas <nuclear@member.fsf.org>
date Tue, 03 Nov 2015 00:42:08 +0200
parents
children 9b560415bad4
files .hgignore Makefile src/image.c src/image.h src/main.c src/texture.c src/texture.h
diffstat 7 files changed, 527 insertions(+), 0 deletions(-) [+]
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/.hgignore	Tue Nov 03 00:42:08 2015 +0200
     1.3 @@ -0,0 +1,5 @@
     1.4 +\.o$
     1.5 +\.swp$
     1.6 +\.d$
     1.7 +\.ppm$
     1.8 +^xgetshape$
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/Makefile	Tue Nov 03 00:42:08 2015 +0200
     2.3 @@ -0,0 +1,13 @@
     2.4 +src = $(wildcard src/*.c)
     2.5 +obj = $(src:.c=.o)
     2.6 +bin = xgetshape
     2.7 +
     2.8 +CFLAGS = -pedantic -Wall -g
     2.9 +LDFLAGS = -lGL -lX11 -lXext
    2.10 +
    2.11 +$(bin): $(obj)
    2.12 +	$(CC) -o $@ $(obj) $(LDFLAGS)
    2.13 +
    2.14 +.PHONY: clean
    2.15 +clean:
    2.16 +	rm -f $(obj) $(bin)
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/src/image.c	Tue Nov 03 00:42:08 2015 +0200
     3.3 @@ -0,0 +1,122 @@
     3.4 +#include <stdio.h>
     3.5 +#include <stdlib.h>
     3.6 +#include <string.h>
     3.7 +#include <errno.h>
     3.8 +#include "image.h"
     3.9 +
    3.10 +int image_create(struct image *img, int w, int h)
    3.11 +{
    3.12 +	img->width = w;
    3.13 +	img->height = h;
    3.14 +	if(!(img->pixels = malloc(w * h * 4))) {
    3.15 +		return -1;
    3.16 +	}
    3.17 +	return 0;
    3.18 +}
    3.19 +
    3.20 +void image_destroy(struct image *img)
    3.21 +{
    3.22 +	free(img->pixels);
    3.23 +	img->pixels = 0;
    3.24 +}
    3.25 +
    3.26 +int image_save(struct image *img, const char *rgbname, const char *aname)
    3.27 +{
    3.28 +	int i;
    3.29 +	FILE *fp;
    3.30 +	unsigned char *pptr;
    3.31 +
    3.32 +	if(rgbname) {
    3.33 +		if(!(fp = fopen(rgbname, "wb"))) {
    3.34 +			fprintf(stderr, "failed to open %s for writing: %s\n", rgbname, strerror(errno));
    3.35 +			return -1;
    3.36 +		}
    3.37 +
    3.38 +		fprintf(fp, "P6\n%d %d\n255\n", img->width, img->height);
    3.39 +		pptr = img->pixels;
    3.40 +		for(i=0; i<img->width * img->height; i++) {
    3.41 +			fputc(*pptr++, fp);
    3.42 +			fputc(*pptr++, fp);
    3.43 +			fputc(*pptr++, fp);
    3.44 +			++pptr;
    3.45 +		}
    3.46 +		fclose(fp);
    3.47 +	}
    3.48 +	if(aname) {
    3.49 +		if(!(fp = fopen(aname, "wb"))) {
    3.50 +			fprintf(stderr, "failed to open %s for writing: %s\n", rgbname, strerror(errno));
    3.51 +			return -1;
    3.52 +		}
    3.53 +
    3.54 +		fprintf(fp, "P6\n%d %d\n255\n", img->width, img->height);
    3.55 +		pptr = img->pixels;
    3.56 +		for(i=0; i<img->width * img->height; i++) {
    3.57 +			int c = pptr[3];
    3.58 +			pptr += 4;
    3.59 +			fputc(c, fp);
    3.60 +			fputc(c, fp);
    3.61 +			fputc(c, fp);
    3.62 +		}
    3.63 +		fclose(fp);
    3.64 +	}
    3.65 +	return 0;
    3.66 +}
    3.67 +
    3.68 +int image_clear(struct image *img, int r, int g, int b, int a)
    3.69 +{
    3.70 +	return image_fillrect(img, 0, 0, img->width, img->height, r, g, b, a);
    3.71 +}
    3.72 +
    3.73 +int image_fillrect(struct image *img, int x, int y, int w, int h, int r, int g, int b, int a)
    3.74 +{
    3.75 +	int i, j;
    3.76 +	unsigned char *pptr;
    3.77 +
    3.78 +	if(x < 0) {
    3.79 +		w += x;
    3.80 +		x = 0;
    3.81 +	}
    3.82 +	if(y < 0) {
    3.83 +		h += y;
    3.84 +		y = 0;
    3.85 +	}
    3.86 +	if(x + w >= img->width) {
    3.87 +		w = img->width - x;
    3.88 +	}
    3.89 +	if(y + h >= img->height) {
    3.90 +		h = img->height - y;
    3.91 +	}
    3.92 +	if(w <= 0 || h <= 0) {
    3.93 +		return -1;
    3.94 +	}
    3.95 +
    3.96 +	pptr = img->pixels + (y * img->width + x) * 4;
    3.97 +	for(i=0; i<h; i++) {
    3.98 +		for(j=0; j<w; j++) {
    3.99 +			pptr[0] = r;
   3.100 +			pptr[1] = g;
   3.101 +			pptr[2] = b;
   3.102 +			pptr[3] = a;
   3.103 +			pptr += 4;
   3.104 +		}
   3.105 +		pptr += (img->width - w) * 4;
   3.106 +	}
   3.107 +	return 0;
   3.108 +}
   3.109 +
   3.110 +int image_chess(struct image *img, int sz, int r0, int g0, int b0, int r1, int g1, int b1)
   3.111 +{
   3.112 +	int i, j;
   3.113 +	unsigned char *pptr = img->pixels;
   3.114 +
   3.115 +	for(i=0; i<img->height; i++) {
   3.116 +		for(j=0; j<img->width; j++) {
   3.117 +			int chess = ((i / sz) & 1) == ((j / sz) & 1);
   3.118 +			*pptr++ = chess ? r0 : r1;
   3.119 +			*pptr++ = chess ? g0 : g1;
   3.120 +			*pptr++ = chess ? b0 : b1;
   3.121 +			*pptr++ = 255;
   3.122 +		}
   3.123 +	}
   3.124 +	return 0;
   3.125 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/image.h	Tue Nov 03 00:42:08 2015 +0200
     4.3 @@ -0,0 +1,18 @@
     4.4 +#ifndef IMAGE_H_
     4.5 +#define IMAGE_H_
     4.6 +
     4.7 +struct image {
     4.8 +	int width, height;
     4.9 +	unsigned char *pixels;
    4.10 +};
    4.11 +
    4.12 +int image_create(struct image *img, int w, int h);
    4.13 +void image_destroy(struct image *img);
    4.14 +
    4.15 +int image_save(struct image *img, const char *rgbname, const char *aname);
    4.16 +
    4.17 +int image_clear(struct image *img, int r, int g, int b, int a);
    4.18 +int image_fillrect(struct image *img, int x, int y, int w, int h, int r, int g, int b, int a);
    4.19 +int image_chess(struct image *img, int sz, int r0, int g0, int b0, int r1, int g1, int b1);
    4.20 +
    4.21 +#endif	/* IMAGE_H_ */
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/src/main.c	Tue Nov 03 00:42:08 2015 +0200
     5.3 @@ -0,0 +1,323 @@
     5.4 +#include <stdio.h>
     5.5 +#include <stdlib.h>
     5.6 +#include <X11/Xlib.h>
     5.7 +#include <X11/extensions/shape.h>
     5.8 +#include <GL/glx.h>
     5.9 +#include "image.h"
    5.10 +#include "texture.h"
    5.11 +
    5.12 +void redraw(void);
    5.13 +void reshape(int x, int y);
    5.14 +int handle_event(XEvent *ev);
    5.15 +
    5.16 +/* X helper functions */
    5.17 +Window create_window(int xsz, int ysz);
    5.18 +void set_window_title(Window win, const char *title);
    5.19 +int get_window_shape(Window win, struct image *img);
    5.20 +
    5.21 +Display *dpy;
    5.22 +Window win;
    5.23 +GLXContext ctx;
    5.24 +int width, height;
    5.25 +int mapped;
    5.26 +int redisp_pending;
    5.27 +Atom xa_wm_prot, xa_wm_del_win;
    5.28 +struct image img;
    5.29 +struct texture tex;
    5.30 +struct texture chess_tex;
    5.31 +float aspect;
    5.32 +
    5.33 +unsigned int target_xid = 0;
    5.34 +
    5.35 +int main(int argc, char **argv)
    5.36 +{
    5.37 +	int event_base, error_base;
    5.38 +	char *endp;
    5.39 +	struct image chess_img;
    5.40 +
    5.41 +	if(!argv[1]) {
    5.42 +		fprintf(stderr, "pass the window id to use\n");
    5.43 +		return 1;
    5.44 +	}
    5.45 +	if(!(target_xid = strtol(argv[1], &endp, 0))) {
    5.46 +		fprintf(stderr, "invalid argument: %s\n", argv[1]);
    5.47 +		return 1;
    5.48 +	}
    5.49 +
    5.50 +	if(!(dpy = XOpenDisplay(0))) {
    5.51 +		fprintf(stderr, "failed to open display\n");
    5.52 +		return 1;
    5.53 +	}
    5.54 +	xa_wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False);
    5.55 +	xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    5.56 +
    5.57 +	if(!XShapeQueryExtension(dpy, &event_base, &error_base)) {
    5.58 +		fprintf(stderr, "shape extension unsupported by this X server\n");
    5.59 +		XCloseDisplay(dpy);
    5.60 +		return 1;
    5.61 +	}
    5.62 +	if(get_window_shape(target_xid, &img) == -1) {
    5.63 +		XCloseDisplay(dpy);
    5.64 +		return 1;
    5.65 +	}
    5.66 +
    5.67 +	if(!(win = create_window(1280, 800))) {
    5.68 +		return 1;
    5.69 +	}
    5.70 +
    5.71 +	image_texture(&tex, &img);
    5.72 +
    5.73 +	image_create(&chess_img, 256, 256);
    5.74 +	image_chess(&chess_img, 8, 255, 128, 64, 64, 128, 255);
    5.75 +	image_texture(&chess_tex, &chess_img);
    5.76 +	image_destroy(&chess_img);
    5.77 +
    5.78 +	for(;;) {
    5.79 +		XEvent ev;
    5.80 +		XNextEvent(dpy, &ev);
    5.81 +
    5.82 +		if(handle_event(&ev) == -1) {
    5.83 +			break;
    5.84 +		}
    5.85 +	}
    5.86 +
    5.87 +	XDestroyWindow(dpy, win);
    5.88 +	XCloseDisplay(dpy);
    5.89 +	return 0;
    5.90 +}
    5.91 +
    5.92 +void redraw(void)
    5.93 +{
    5.94 +	glClearColor(0.4, 0.4, 0.4, 1);
    5.95 +	glClear(GL_COLOR_BUFFER_BIT);
    5.96 +
    5.97 +	glMatrixMode(GL_MODELVIEW);
    5.98 +	glLoadIdentity();
    5.99 +
   5.100 +	glEnable(GL_TEXTURE_2D);
   5.101 +	glBindTexture(GL_TEXTURE_2D, chess_tex.id);
   5.102 +
   5.103 +
   5.104 +	/* background */
   5.105 +	glBegin(GL_QUADS);
   5.106 +	glTexCoord2f(0, 0); glVertex2f(-aspect, -1);
   5.107 +	glTexCoord2f(1, 0); glVertex2f(aspect, -1);
   5.108 +	glTexCoord2f(1, 1); glVertex2f(aspect, 1);
   5.109 +	glTexCoord2f(0, 1); glVertex2f(-aspect, 1);
   5.110 +	glEnd();
   5.111 +
   5.112 +	/* draw the window shape */
   5.113 +	glEnable(GL_BLEND);
   5.114 +	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   5.115 +
   5.116 +	glBindTexture(GL_TEXTURE_2D, tex.id);
   5.117 +	glMatrixMode(GL_TEXTURE);
   5.118 +	glLoadIdentity();
   5.119 +	glScalef((float)tex.width / (float)tex.tex_width,
   5.120 +			(float)tex.height / (float)tex.tex_height,
   5.121 +			1.0);
   5.122 +
   5.123 +	glMatrixMode(GL_MODELVIEW);
   5.124 +	glPushMatrix();
   5.125 +	glScalef(0.7, 0.7, 0.7);
   5.126 +
   5.127 +	/* shadow */
   5.128 +	glPushMatrix();
   5.129 +	glTranslatef(0.1, -0.1, 0);
   5.130 +	glBegin(GL_QUADS);
   5.131 +	glColor3f(0, 0, 0);
   5.132 +	glTexCoord2f(0, 1); glVertex2f(-1, -1);
   5.133 +	glTexCoord2f(1, 1); glVertex2f(1, -1);
   5.134 +	glTexCoord2f(1, 0); glVertex2f(1, 1);
   5.135 +	glTexCoord2f(0, 0); glVertex2f(-1, 1);
   5.136 +	glEnd();
   5.137 +	glPopMatrix();
   5.138 +
   5.139 +	/* window */
   5.140 +	glBegin(GL_QUADS);
   5.141 +	glColor3f(1, 1, 0);
   5.142 +	glTexCoord2f(0, 1); glVertex2f(-1, -1);
   5.143 +	glTexCoord2f(1, 1); glVertex2f(1, -1);
   5.144 +	glTexCoord2f(1, 0); glVertex2f(1, 1);
   5.145 +	glTexCoord2f(0, 0); glVertex2f(-1, 1);
   5.146 +	glEnd();
   5.147 +
   5.148 +	glMatrixMode(GL_TEXTURE);
   5.149 +	glLoadIdentity();
   5.150 +	glMatrixMode(GL_MODELVIEW);
   5.151 +	glPopMatrix();
   5.152 +
   5.153 +	glDisable(GL_BLEND);
   5.154 +
   5.155 +	glXSwapBuffers(dpy, win);
   5.156 +}
   5.157 +
   5.158 +void reshape(int x, int y)
   5.159 +{
   5.160 +	aspect = (float)x / (float)y;
   5.161 +	glViewport(0, 0, x, y);
   5.162 +
   5.163 +	glMatrixMode(GL_PROJECTION);
   5.164 +	glLoadIdentity();
   5.165 +	glScalef(1.0 / aspect, 1.0, 1.0);
   5.166 +}
   5.167 +
   5.168 +int handle_event(XEvent *ev)
   5.169 +{
   5.170 +	switch(ev->type) {
   5.171 +	case MapNotify:
   5.172 +	case UnmapNotify:
   5.173 +		mapped = ev->type == MapNotify ? 1 : 0;
   5.174 +		break;
   5.175 +
   5.176 +	case Expose:
   5.177 +		if(mapped && ev->xexpose.count == 0) {
   5.178 +			redraw();
   5.179 +		}
   5.180 +		break;
   5.181 +
   5.182 +	case KeyPress:
   5.183 +		{
   5.184 +			KeySym sym = XLookupKeysym(&ev->xkey, 0);
   5.185 +
   5.186 +			switch(sym) {
   5.187 +			case XK_Escape:
   5.188 +				return -1;
   5.189 +			}
   5.190 +		}
   5.191 +		break;
   5.192 +
   5.193 +	case ConfigureNotify:
   5.194 +		if(ev->xconfigure.width != width || ev->xconfigure.height != height) {
   5.195 +			width = ev->xconfigure.width;
   5.196 +			height = ev->xconfigure.height;
   5.197 +			reshape(width, height);
   5.198 +		}
   5.199 +		break;
   5.200 +
   5.201 +	case ClientMessage:
   5.202 +		if(ev->xclient.message_type == xa_wm_prot) {
   5.203 +			if(ev->xclient.data.l[0] == xa_wm_del_win) {
   5.204 +				return -1;
   5.205 +			}
   5.206 +		}
   5.207 +		break;
   5.208 +	}
   5.209 +	return 0;
   5.210 +}
   5.211 +
   5.212 +Window create_window(int xsz, int ysz)
   5.213 +{
   5.214 +	Window w, root;
   5.215 +	XVisualInfo *vis;
   5.216 +	XClassHint chint;
   5.217 +	XSetWindowAttributes xattr;
   5.218 +	unsigned int evmask, xattr_mask;
   5.219 +	int scr;
   5.220 +	int glxattr[] = {
   5.221 +		GLX_RGBA, GLX_DOUBLEBUFFER,
   5.222 +		GLX_RED_SIZE, 8,
   5.223 +		GLX_GREEN_SIZE, 8,
   5.224 +		GLX_BLUE_SIZE, 8,
   5.225 +		GLX_DEPTH_SIZE, 24,
   5.226 +		GLX_USE_GL, 1,
   5.227 +		None
   5.228 +	};
   5.229 +
   5.230 +	scr = DefaultScreen(dpy);
   5.231 +	root = RootWindow(dpy, scr);
   5.232 +
   5.233 +	if(!(vis = glXChooseVisual(dpy, scr, glxattr))) {
   5.234 +		printf("failed to find a suitable visual\n");
   5.235 +		return 0;
   5.236 +	}
   5.237 +	if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
   5.238 +		XFree(vis);
   5.239 +		return 0;
   5.240 +	}
   5.241 +
   5.242 +	xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr);
   5.243 +	xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone);
   5.244 +	xattr_mask = CWColormap | CWBackPixel | CWBorderPixel;
   5.245 +
   5.246 +	if(!(w = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput,
   5.247 +					vis->visual, xattr_mask, &xattr))) {
   5.248 +		printf("failed to create window\n");
   5.249 +		glXDestroyContext(dpy, ctx);
   5.250 +		XFree(vis);
   5.251 +		return 0;
   5.252 +	}
   5.253 +	XFree(vis);
   5.254 +
   5.255 +	evmask = StructureNotifyMask | VisibilityChangeMask | KeyPressMask |
   5.256 +		ExposureMask;
   5.257 +	XSelectInput(dpy, w, evmask);
   5.258 +
   5.259 +	XSetWMProtocols(dpy, w, &xa_wm_del_win, 1);
   5.260 +
   5.261 +	chint.res_name = chint.res_class = "xgetshape";
   5.262 +	XSetClassHint(dpy, w, &chint);
   5.263 +
   5.264 +	set_window_title(w, "GL xgetshape");
   5.265 +
   5.266 +	glXMakeCurrent(dpy, w, ctx);
   5.267 +	XMapWindow(dpy, w);
   5.268 +	return w;
   5.269 +}
   5.270 +
   5.271 +void set_window_title(Window win, const char *title)
   5.272 +{
   5.273 +	XTextProperty wm_name;
   5.274 +	XStringListToTextProperty((char**)&title, 1, &wm_name);
   5.275 +	XSetWMName(dpy, win, &wm_name);
   5.276 +	XSetWMIconName(dpy, win, &wm_name);
   5.277 +	XFree(wm_name.value);
   5.278 +}
   5.279 +
   5.280 +int get_window_shape(Window win, struct image *img)
   5.281 +{
   5.282 +	Bool buse, cuse;
   5.283 +	int bx, by, cx, cy;
   5.284 +	unsigned int bw, bh, cw, ch;
   5.285 +	int kind;
   5.286 +	int x, y, w, h;
   5.287 +	XRectangle *rects;
   5.288 +	int i, rect_count, rect_order;
   5.289 +
   5.290 +	XShapeQueryExtents(dpy, win, &buse, &bx, &by, &bw, &bh,
   5.291 +			&cuse, &cx, &cy, &bw, &bh);
   5.292 +	if(cuse) {
   5.293 +		x = cx;
   5.294 +		y = cy;
   5.295 +		w = cw;
   5.296 +		h = ch;
   5.297 +		kind = ShapeClip;
   5.298 +	} else if(buse) {
   5.299 +		x = bx;
   5.300 +		y = by;
   5.301 +		w = bw;
   5.302 +		h = bh;
   5.303 +		kind = ShapeBounding;
   5.304 +	} else {
   5.305 +		fprintf(stderr, "XShapeQueryExtents returned no extents\n");
   5.306 +		return -1;
   5.307 +	}
   5.308 +
   5.309 +	if(image_create(img, w, h) == -1) {
   5.310 +		fprintf(stderr, "failed to create shape image (%dx%d)\n", w, h);
   5.311 +		return -1;
   5.312 +	}
   5.313 +	image_clear(img, 0, 0, 0, 0);
   5.314 +
   5.315 +	if(!(rects = XShapeGetRectangles(dpy, win, kind, &rect_count, &rect_order))) {
   5.316 +		fprintf(stderr, "failed to get the shape rectangles\n");
   5.317 +		image_destroy(img);
   5.318 +		return -1;
   5.319 +	}
   5.320 +
   5.321 +	for(i=0; i<rect_count; i++) {
   5.322 +		image_fillrect(img, rects[i].x, rects[i].y, rects[i].width, rects[i].height,
   5.323 +				255, 255, 255, 255);
   5.324 +	}
   5.325 +	return 0;
   5.326 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/src/texture.c	Tue Nov 03 00:42:08 2015 +0200
     6.3 @@ -0,0 +1,33 @@
     6.4 +#include <GL/gl.h>
     6.5 +#include "texture.h"
     6.6 +
     6.7 +static int next_pow2(int x);
     6.8 +
     6.9 +int image_texture(struct texture *tex, struct image *img)
    6.10 +{
    6.11 +	glGenTextures(1, &tex->id);
    6.12 +	glBindTexture(GL_TEXTURE_2D, tex->id);
    6.13 +	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    6.14 +	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    6.15 +
    6.16 +	tex->width = img->width;
    6.17 +	tex->height = img->height;
    6.18 +	tex->tex_width = next_pow2(tex->width);
    6.19 +	tex->tex_height = next_pow2(tex->height);
    6.20 +
    6.21 +	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->tex_width, tex->tex_height, 0,
    6.22 +			GL_RGBA, GL_UNSIGNED_BYTE, 0);
    6.23 +	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex->width, tex->height,
    6.24 +			GL_RGBA, GL_UNSIGNED_BYTE, img->pixels);
    6.25 +	return 0;
    6.26 +}
    6.27 +
    6.28 +static int next_pow2(int x)
    6.29 +{
    6.30 +	--x;
    6.31 +	x |= x >> 1;
    6.32 +	x |= x >> 2;
    6.33 +	x |= x >> 4;
    6.34 +	x |= x >> 8;
    6.35 +	return x + 1;
    6.36 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/src/texture.h	Tue Nov 03 00:42:08 2015 +0200
     7.3 @@ -0,0 +1,13 @@
     7.4 +#ifndef TEXTURE_H_
     7.5 +#define TEXTURE_H_
     7.6 +
     7.7 +#include "image.h"
     7.8 +
     7.9 +struct texture {
    7.10 +	unsigned int id;
    7.11 +	int width, height, tex_width, tex_height;
    7.12 +};
    7.13 +
    7.14 +int image_texture(struct texture *tex, struct image *img);
    7.15 +
    7.16 +#endif	/* TEXTURE_H_ */