1/*
2 * Shutdown (/restart) bits
3 */
4
5#include "ctwm.h"
6
7#include <stdlib.h>
8#include <stdio.h>
9#include <unistd.h>
10
11#include "animate.h"
12#ifdef CAPTIVE
13#include "captive.h"
14#endif
15#include "colormaps.h"
16#include "ctwm_atoms.h"
17#include "ctwm_shutdown.h"
18#include "screen.h"
19#ifdef SESSION
20#include "session.h"
21#endif
22#ifdef SOUNDS
23# include "sound.h"
24#endif
25#include "otp.h"
26#include "win_ops.h"
27#include "win_utils.h"
28
29
30static void RestoreForShutdown(Time mytime);
31
32
33/**
34 * Put a window back where it should be if we don't (any longer) control
35 * it and reparent it back up to the root.  This leaves it where it was
36 * before we started (well, adjusted by any moves we've made to it
37 * since), and placed so that if we restart and take it back over, it'll
38 * wind up right where it is now, so restarting doesn't shift windows all
39 * over the place.
40 */
41void
42RestoreWinConfig(TwmWindow *tmp)
43{
44	int gravx, gravy;
45	int newx, newy;
46
47	// Things adjusting by the border have to move our border size, but
48	// subtract away from that the old border we're restoring.
49	const int borders = tmp->frame_bw + tmp->frame_bw3D - tmp->old_bw;
50
51	// If this window is "unmapped" by moving it way offscreen, and is in
52	// that state, move it back onto the window.
53	if(tmp->UnmapByMovingFarAway && !visible(tmp)) {
54		XMoveWindow(dpy, tmp->frame, tmp->frame_x, tmp->frame_y);
55	}
56
57	// If it's squeezed, unsqueeze it.
58	if(tmp->squeezed) {
59		Squeeze(tmp);
60	}
61
62	// This is apparently our standard "is this window still around?"
63	// idiom.
64	if(XGetGeometry(dpy, tmp->w, &JunkRoot, &JunkX, &JunkY,
65	                &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth) == 0) {
66		// Well, give up...
67		return;
68	}
69
70	// Get gravity bits to know how to move stuff around when we take
71	// away the decorations.
72	GetGravityOffsets(tmp, &gravx, &gravy);
73
74	// We want to move the window to where it should be "outside" of our
75	// frame.  This varies depending on the window gravity detail, and we
76	// have to account for that, since on re-startup we'll be doing it to
77	// resposition it after we re-wrap it.
78	//
79	// e.g., in simple "NorthWest" gravity, we just made the frame start
80	// where the window did, and shifted the app window right (by the
81	// border width) and down (by the border width + titlebar).  However,
82	// "SouthEast" gravity means the bottom right of the frame is where
83	// the windows' was, and the window itself shifted left/up by the
84	// border.  Compare e.g. an xterm with -geometry "+0+0" with one
85	// using "-0-0" as an easy trick to make windows with different
86	// geoms.
87	newx = tmp->frame_x;
88	newy = tmp->frame_y;
89
90
91	// So, first consider the north/south gravity.  If gravity is North,
92	// we put the top of the frame where the window was and shifted
93	// everything inside down, so the top of the frame now is where the
94	// window should be put.  With South-y gravity, the window should
95	// wind up at the bottom of the frame, which means we need to shift
96	// it down by the titlebar height, plus twice the border width.  But
97	// if the vertical gravity is neutral, then the window needs to wind
98	// up staying right where it is, because we built the frame around it
99	// without moving it.
100	//
101	// Previous code here (and code elsewhere) expressed this calculation
102	// by the rather confusing idiom ((gravy + 1) * border_width), which
103	// gives the right answer, but is confusing as hell...
104	if(gravy < 0) {
105		// North; where the frame starts (already set)
106	}
107	else if(gravy > 0) {
108		// South; shift down title + 2*border
109		newy += tmp->title_height + 2 * borders;
110	}
111	else {
112		// Neutral; down by the titlebar + border.
113		newy += tmp->title_height + borders;
114	}
115
116
117	// Now east/west.  West means align with the frame start, east means
118	// align with the frame right edge, neutral means where it already
119	// is.
120	if(gravx < 0) {
121		// West; it's already correct
122	}
123	else if(gravx > 0) {
124		// East; shift over by 2*border
125		newx += 2 * borders;
126	}
127	else {
128		// Neutral; over by the left border
129		newx += borders;
130	}
131
132
133#ifdef WINBOX
134	// If it's in a WindowBox, reparent the frame back up to our real root
135	if(tmp->winbox && tmp->winbox->twmwin && tmp->frame) {
136		int xbox, ybox;
137		unsigned int j_bw;
138
139		// XXX This isn't right, right?  This will give coords relative
140		// to the window box, but we're using them relative to the real
141		// screen root?
142		if(XGetGeometry(dpy, tmp->frame, &JunkRoot, &xbox, &ybox,
143		                &JunkWidth, &JunkHeight, &j_bw, &JunkDepth)) {
144			ReparentWindow(dpy, tmp, WinWin, Scr->Root, xbox, ybox);
145		}
146	}
147#endif
148
149
150	// Restore the original window border if there were one
151	if(tmp->old_bw) {
152		XSetWindowBorderWidth(dpy, tmp->w, tmp->old_bw);
153	}
154
155	// Reparent and move back to where it otter be
156	XReparentWindow(dpy, tmp->w, Scr->Root, newx, newy);
157
158	// If it came with a pre-made icon window, hide it
159	if(tmp->wmhints->flags & IconWindowHint) {
160		XUnmapWindow(dpy, tmp->wmhints->icon_window);
161	}
162
163	// Done
164	return;
165}
166
167
168/**
169 * Restore some window positions/etc in preparation for going away.
170 */
171static void
172RestoreForShutdown(Time mytime)
173{
174	ScreenInfo *savedScr = Scr;  // We need Scr flipped around...
175
176	XGrabServer(dpy);
177
178	for(int scrnum = 0; scrnum < NumScreens; scrnum++) {
179		if((Scr = ScreenList[scrnum]) == NULL) {
180			continue;
181		}
182
183		// Force reinstalling any colormaps
184		InstallColormaps(0, &Scr->RootColormaps);
185
186		// Pull all the windows out of their frames and reposition them
187		// where the frame was, with approprate adjustments for gravity.
188		// This will preserve their positions when we restart, or put
189		// them back where they were before we started.  Do it from the
190		// bottom up of our stacking order to preserve the stacking.
191		for(TwmWindow *tw = OtpBottomWin() ; tw != NULL
192		                ; tw = OtpNextWinUp(tw)) {
193			if(tw->isiconmgr || tw->iswspmgr || tw->isoccupy) {
194				// Don't bother with internals...
195				continue;
196			}
197			RestoreWinConfig(tw);
198		}
199	}
200
201	XUngrabServer(dpy);
202
203	// Restore
204	Scr = savedScr;
205
206	// Focus away from any windows
207	SetFocus(NULL, mytime);
208}
209
210
211/**
212 * Cleanup and exit ctwm
213 */
214void
215DoShutdown(void)
216{
217
218#ifdef SOUNDS
219	// Denounce ourselves
220	play_exit_sound();
221#endif
222
223	// Restore windows/colormaps for our absence.
224	RestoreForShutdown(CurrentTime);
225
226#ifdef EWMH
227	// Clean up EWMH properties
228	EwmhTerminate();
229#endif
230
231	// Clean up our list of workspaces
232	XDeleteProperty(dpy, Scr->Root, XA_WM_WORKSPACESLIST);
233
234#ifdef CAPTIVE
235	// Shut down captive stuff
236	if(CLarg.is_captive) {
237		RemoveFromCaptiveList(Scr->captivename);
238	}
239#endif
240
241	// Close up shop
242	XCloseDisplay(dpy);
243	exit(0);
244}
245
246
247/**
248 * exec() ourself to restart.
249 */
250void
251DoRestart(Time t)
252{
253	// Don't try to do any further animating
254	StopAnimation();
255	XSync(dpy, 0);
256
257	// Replace all the windows/colormaps as if we were going away.  'cuz
258	// we are.
259	RestoreForShutdown(t);
260	XSync(dpy, 0);
261
262#ifdef SESSION
263	// Shut down session management connection cleanly.
264	shutdown_session();
265#endif
266
267	// Re-run ourself
268	fprintf(stderr, "%s:  restarting:  %s\n", ProgramName, *Argv);
269	execvp(*Argv, Argv);
270
271	// Uh oh, we shouldn't get here...
272	fprintf(stderr, "%s:  unable to restart:  %s\n", ProgramName, *Argv);
273
274	// We should probably un-RestoreForShutdown() etc.  If that exec
275	// fails, we're in a really weird state...
276	XBell(dpy, 0);
277}
278