2 * Copyright Neil Brown ©2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Core per-window functionality.
7 * Provide a pane that is instantiated between the root and any window
8 * stack, to provide common functionality. These includes:
10 * - setting per-window attributes
11 * - registering and forwarding per-window notifications
12 * - Being an intermediary for per-window selections.
14 * ==============================================================
15 * Allow any pane to "claim ownership" of "the selection", or to
16 * "commit" the selection. A pane can also "discard" the selection,
17 * but that only works if the pane owns it.
19 * This can be used for mouse-based copy/paste and interaction with the
20 * X11 "PRIMARY" clipboard.
21 * When a selection is made in any pane it claims "the selection".
22 * When a mouse-based paste request is made, the receiving pane can ask for
23 * the selection to be "commited", and then access the most recent copy-buffer.
24 * The owner of a selection will, if the selection is still valid, call
25 * copy:save to save the selected content.
26 * When a "paste" request is made where the location is based on the "point"
27 * (current cursor) it is unlikely that a selection in the same pane should be
28 * used - if there is one it is more likely to be intended to receive the paste.
29 * So the target pane can first "discard" the selection, then "commit", then call
30 * "copy:get". If the selection is in this pane, the "discard" will succeed,
31 * the "commit" will be a no-op, and the top copy buf will be used.
32 * If the selection is in another pane (or another app via X11), the "discard"
33 * will fail (wrong owner), the "commit" will succeed and copy the selection,
34 * and the "copy:get" will get it.
36 * Operations are "selection:claim", "selection:commit" and "selection:discard".
37 * When the selection is claimed, the old owner gets called (not notified)
38 * "Notify:selection:claimed", and when a commit request is made,
39 * "Notify:selection:commit" is sent.
41 * A client can declare itself to be a fall-back handler by calling
42 * select:claim with num==1. Then if any other client discards its selection,
43 * the ownership reverse to the fallback. The fallback typically provides
44 * access to some selection external to edlib, such as the x11 selections.
52 #define PANE_DATA_TYPE struct window_data
57 struct pane *sel_owner;
59 struct pane *sel_owner_fallback;
61 #include "core-pane.h"
63 DEF_CMD(request_notify)
65 pane_add_notify(ci->focus, ci->home, ksuffix(ci, "Window:request:"));
71 /* Window:notify:... */
72 return home_pane_notify(ci->home, ksuffix(ci, "Window:notify:"),
74 ci->num, ci->mark, ci->str,
75 ci->num2, ci->mark2, ci->str2, ci->comm2);
80 const char *val = ksuffix(ci, "Window:set:");
87 attr_set_str(&ci->home->attrs, val, ci->str);
92 DEF_CMD(selection_claim)
94 struct window_data *wd = ci->home->data;
96 if (wd->sel_owner && wd->sel_owner != ci->focus) {
97 call("Notify:selection:claimed", wd->sel_owner);
98 //pane_drop_notifiers(ci->home, "Notify:Close", wd->sel_owner);
100 wd->sel_owner = ci->focus;
102 wd->sel_owner_fallback = ci->focus;
103 wd->sel_committed = 0;
104 pane_add_notify(ci->home, ci->focus, "Notify:Close");
108 DEF_CMD(selection_commit)
110 struct window_data *wd = ci->home->data;
112 if (wd->sel_owner && !wd->sel_committed) {
113 if (call("Notify:selection:commit", wd->sel_owner) != 2)
114 wd->sel_committed = 1;
119 DEF_CMD(selection_discard)
121 struct window_data *wd = ci->home->data;
122 struct pane *op, *fp;
126 if (wd->sel_owner_fallback == ci->focus)
127 wd->sel_owner_fallback = NULL;
128 /* Don't require exactly same pane for sel_owner,
129 * but ensure they have the same focus.
131 op = pane_focus(wd->sel_owner);
132 fp = pane_focus(ci->focus);
136 wd->sel_owner = wd->sel_owner_fallback;
137 wd->sel_committed = 0;
143 /* This is a helper for Draw:image which interprets str2
144 * with other values and calls comm2 with:
145 * "width" returns image width
146 * "height" returns image height
147 * "scale" num=new width, num2=new height
148 * "crop" x,y is top-left num,num2 - width,height
149 * These numbers apply after scaling.
150 * "draw" num,num2 = offset
151 * "cursor" x,y=pos, num,num2=size
154 * 'str2' container 'mode' information.
155 * By default the image is placed centrally in the pane
156 * and scaled to use either fully height or fully width.
157 * Various letters modify this:
158 * 'S' - stretch to use full height *and* full width
159 * 'L' - place on left if full width isn't used
160 * 'R' - place on right if full width isn't used
161 * 'T' - place at top if full height isn't used
162 * 'B' - place at bottom if full height isn't used.
164 * Also a suffix ":NNxNN" will be parse and the two numbers used
165 * to give number of rows and cols to overlay on the image for
166 * the purpose of cursor positioning. If these are present and
167 * p->cx,cy are not negative, draw a cursor at p->cx,cy highlighting
170 * num,num2, if both positive, override the automatic scaling.
171 * The image is scaled to this many pixels.
172 * x,y is top-left pixel in the scaled image to start display at.
173 * Negative values allow a margin between pane edge and this image.
175 struct pane *p = ci->focus;
176 const char *mode = ci->str2 ?: "";
177 bool stretch = strchr(mode, 'S');
189 pxl = pane_attr_get(p, "Display:pixels");
190 if (sscanf(pxl ?: "1x1", "%hdx%hx", &px, &py) != 2)
195 if (ci->num > 0 && ci->num2 > 0) {
198 } else if (ci->num > 0) {
199 int iw = comm_call(ci->comm2, "width", p);
200 int ih = comm_call(ci->comm2, "height", p);
202 if (iw <= 0 || ih <= 0)
205 w = iw * ci->num / 1024;
206 h = ih * ci->num / 1024;
207 } else if (!stretch) {
208 int iw = comm_call(ci->comm2, "width", p);
209 int ih = comm_call(ci->comm2, "height", p);
211 if (iw <= 0 || ih <= 0)
214 if (iw * h > ih * w) {
215 /* Image is wider than space, use less height */
217 if (strchr(mode, 'B'))
220 else if (!strchr(mode, 'T'))
223 /* Round up to pixels-per-cell */
224 h = ((ih + py - 1) / py) * py;
226 /* image is too tall, use less width */
228 if (strchr(mode, 'R'))
231 else if (!strchr(mode, 'L'))
233 w = ((iw + px - 1) / px) * px;
237 comm_call(ci->comm2, "scale", p, w, NULL, NULL, h);
260 comm_call(ci->comm2, "crop", p, w, NULL, NULL, h, NULL, NULL, cix, ciy);
261 comm_call(ci->comm2, "draw", p, x + xo, NULL, NULL, y + yo);
265 char *cl = strchr(mode, ':');
266 if (cl && sscanf(cl, ":%dx%d", &cols, &rows) == 2)
267 comm_call(ci->comm2, "cursor", p,
268 w/cols, NULL, NULL, h/rows, NULL, NULL,
269 p->cx + xo, p->cy + yo);
274 DEF_CMD(window_activate_display)
276 /* Given a display attached to the root, integrate it
277 * into a full initial stack of panes.
278 * The display is the focus of this pane. This doc to
279 * attach there is the focus in the command.
281 struct pane *disp = ci->home->focus;
283 bool display_added = False;
287 if (!disp || !list_empty(&disp->children))
289 ip = pane_attr_get(disp, "window-initial-panes");
295 for (t = strtok_r(ip, " \t\n", &save);
297 t = strtok_r(NULL, " \t\n", &save)) {
300 if (strcmp(t, "DISPLAY") == 0) {
301 if (!display_added) {
302 pane_reparent(disp, p);
304 display_added = True;
307 m = strconcat(NULL, "attach-", t);
308 p2 = call_ret(pane, m, p);
315 if (p && ci->focus != disp)
316 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
318 comm_call(ci->comm2, "cb", p);
322 DEF_CMD(close_notify)
324 struct window_data *wd = ci->home->data;
326 if (wd->sel_owner_fallback == ci->focus)
327 wd->sel_owner_fallback = NULL;
329 if (wd->sel_owner == ci->focus)
330 wd->sel_owner = wd->sel_owner_fallback;
334 static struct map *window_map;
335 DEF_LOOKUP_CMD(window_handle, window_map);
337 DEF_CMD(window_attach)
341 p = pane_register(pane_root(ci->focus), 0, &window_handle.c);
344 comm_call(ci->comm2, "cb", p);
348 DEF_CMD(window_close)
350 pane_close(ci->home);
354 void window_setup(struct pane *ed safe)
356 window_map = key_alloc();
358 key_add_prefix(window_map, "Window:request:", &request_notify);
359 key_add_prefix(window_map, "Window:notify:", &send_notify);
361 key_add(window_map, "Window:close", &window_close);
363 key_add_prefix(window_map, "Window:set:", &window_set);
365 key_add(window_map, "selection:claim", &selection_claim);
366 key_add(window_map, "selection:commit", &selection_commit);
367 key_add(window_map, "selection:discard", &selection_discard);
368 key_add(window_map, "Notify:Close", &close_notify);
370 key_add(window_map, "Draw:scale-image", &scale_image);
371 key_add(window_map, "Window:activate-display",
372 &window_activate_display);
374 call_comm("global-set-command", ed, &window_attach, 0, NULL,
375 "attach-window-core");