xref: /haiku/src/preferences/virtualmemory/SettingsWindow.cpp (revision 220c5364ab445eb20376f964ade2f7d3a09e163a)
1 /*
2  * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de
3  * All rights reserved. Distributed under the terms of the MIT License.
4  *
5  * Copyright 2010-2012 Haiku, Inc. All rights reserved.
6  * Distributed under the terms of the MIT License.
7  *
8  * Authors:
9  *		Hamish Morrison, hamish@lavabit.com
10  *		Alexander von Gluck, kallisti5@unixzen.com
11  */
12 
13 
14 #include "SettingsWindow.h"
15 
16 #include <Application.h>
17 #include <Alert.h>
18 #include <Box.h>
19 #include <Button.h>
20 #include <Catalog.h>
21 #include <CheckBox.h>
22 #include <Directory.h>
23 #include <FindDirectory.h>
24 #include <LayoutBuilder.h>
25 #include <MenuItem.h>
26 #include <MenuField.h>
27 #include <NodeMonitor.h>
28 #include <Path.h>
29 #include <PopUpMenu.h>
30 #include <Screen.h>
31 #include <StringForSize.h>
32 #include <StringView.h>
33 #include <String.h>
34 #include <Slider.h>
35 #include <system_info.h>
36 #include <Volume.h>
37 #include <VolumeRoster.h>
38 
39 #include "Settings.h"
40 
41 
42 #undef B_TRANSLATION_CONTEXT
43 #define B_TRANSLATION_CONTEXT "SettingsWindow"
44 
45 
46 static const uint32 kMsgDefaults = 'dflt';
47 static const uint32 kMsgRevert = 'rvrt';
48 static const uint32 kMsgSliderUpdate = 'slup';
49 static const uint32 kMsgSwapEnabledUpdate = 'swen';
50 static const uint32 kMsgSwapAutomaticUpdate = 'swat';
51 static const uint32 kMsgVolumeSelected = 'vlsl';
52 static const off_t kMegaByte = 1024 * 1024;
53 static dev_t gBootDev = -1;
54 
55 
56 SizeSlider::SizeSlider(const char* name, const char* label,
57 	BMessage* message, int32 min, int32 max, uint32 flags)
58 	:
59 	BSlider(name, label, message, min, max, B_HORIZONTAL,
60 		B_BLOCK_THUMB, flags)
61 {
62 	rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
63 	UseFillColor(true, &color);
64 }
65 
66 
67 const char*
68 SizeSlider::UpdateText() const
69 {
70 	return string_for_size(Value() * kMegaByte, fText, sizeof(fText));
71 }
72 
73 
74 VolumeMenuItem::VolumeMenuItem(BVolume volume, BMessage* message)
75 	:
76 	BMenuItem("", message),
77 	fVolume(volume)
78 {
79 	GenerateLabel();
80 }
81 
82 
83 void
84 VolumeMenuItem::MessageReceived(BMessage* message)
85 {
86 	if (message->what == B_NODE_MONITOR) {
87 		int32 code;
88 		if (message->FindInt32("opcode", &code) == B_OK)
89 			if (code == B_ENTRY_MOVED)
90 				GenerateLabel();
91 	}
92 }
93 
94 
95 void
96 VolumeMenuItem::GenerateLabel()
97 {
98 	char name[B_FILE_NAME_LENGTH + 1];
99 	fVolume.GetName(name);
100 
101 	BDirectory dir;
102 	if (fVolume.GetRootDirectory(&dir) == B_OK) {
103 		BEntry entry;
104 		if (dir.GetEntry(&entry) == B_OK) {
105 			BPath path;
106 			if (entry.GetPath(&path) == B_OK) {
107 				BString label;
108 				label << name << " (" << path.Path() << ")";
109 				SetLabel(label);
110 				return;
111 			}
112 		}
113 	}
114 
115 	SetLabel(name);
116 }
117 
118 
119 SettingsWindow::SettingsWindow()
120 	:
121 	BWindow(BRect(0, 0, 269, 172), B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
122 		B_TITLED_WINDOW, B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS
123 		| B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
124 
125 {
126 	gBootDev = dev_for_path("/boot");
127 	BAlignment align(B_ALIGN_LEFT, B_ALIGN_MIDDLE);
128 
129 	if (fSettings.ReadWindowSettings() != B_OK)
130 		CenterOnScreen();
131 	else
132 		MoveTo(fSettings.WindowPosition());
133 
134 	status_t result = fSettings.ReadSwapSettings();
135 	if (result == kErrorSettingsNotFound)
136 		fSettings.DefaultSwapSettings(false);
137 	else if (result == kErrorSettingsInvalid) {
138 		int32 choice = (new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
139 			B_TRANSLATE("The settings specified in the settings file "
140 			"are invalid. You can load the defaults or quit."),
141 			B_TRANSLATE("Load defaults"), B_TRANSLATE("Quit")))->Go();
142 		if (choice == 1) {
143 			be_app->PostMessage(B_QUIT_REQUESTED);
144 			return;
145 		}
146 		fSettings.DefaultSwapSettings(false);
147 	} else if (result == kErrorVolumeNotFound) {
148 		int32 choice = (new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
149 			B_TRANSLATE("The volume specified in the settings file "
150 			"could not be found. You can use the boot volume or quit."),
151 			B_TRANSLATE("Use boot volume"), B_TRANSLATE("Quit")))->Go();
152 		if (choice == 1) {
153 			be_app->PostMessage(B_QUIT_REQUESTED);
154 			return;
155 		}
156 		fSettings.SetSwapVolume(gBootDev, false);
157 	}
158 
159 	fSwapEnabledCheckBox = new BCheckBox("enable swap",
160 		B_TRANSLATE("Enable virtual memory"),
161 		new BMessage(kMsgSwapEnabledUpdate));
162 	fSwapEnabledCheckBox->SetExplicitAlignment(align);
163 
164 	fSwapAutomaticCheckBox = new BCheckBox("auto swap",
165 		B_TRANSLATE("Automatic swap management"),
166 		new BMessage(kMsgSwapAutomaticUpdate));
167 	fSwapEnabledCheckBox->SetExplicitAlignment(align);
168 
169 	fSwapUsageBar = new BStatusBar("swap usage");
170 
171 	BPopUpMenu* menu = new BPopUpMenu("volume menu");
172 	fVolumeMenuField = new BMenuField("volume menu field",
173 		B_TRANSLATE("Use volume:"), menu);
174 	fVolumeMenuField->SetExplicitAlignment(align);
175 
176 	BVolumeRoster roster;
177 	BVolume vol;
178 	while (roster.GetNextVolume(&vol) == B_OK) {
179 		if (!vol.IsPersistent() || vol.IsReadOnly() || vol.IsRemovable()
180 			|| vol.IsShared())
181 			continue;
182 		_AddVolumeMenuItem(vol.Device());
183 	}
184 
185 	watch_node(NULL, B_WATCH_MOUNT, this, this);
186 
187 	fSizeSlider = new SizeSlider("size slider",
188 		B_TRANSLATE("Requested swap file size:"),
189 		new BMessage(kMsgSliderUpdate),	0, 0, B_WILL_DRAW | B_FRAME_EVENTS);
190 	fSizeSlider->SetViewColor(255, 0, 255);
191 	fSizeSlider->SetExplicitAlignment(align);
192 
193 	fWarningStringView = new BStringView("warning",
194 		B_TRANSLATE("Changes will take effect upon reboot."));
195 
196 	BBox* box = new BBox("box");
197 	box->SetLabel(fSwapEnabledCheckBox);
198 
199 	box->AddChild(BLayoutBuilder::Group<>(B_VERTICAL)
200 		.SetInsets(B_USE_DEFAULT_SPACING)
201 		.Add(fSwapUsageBar)
202 		.Add(fSwapAutomaticCheckBox)
203 		.Add(fVolumeMenuField)
204 		.Add(fSizeSlider)
205 		.Add(fWarningStringView)
206 		.View());
207 
208 	fDefaultsButton = new BButton("defaults", B_TRANSLATE("Defaults"),
209 		new BMessage(kMsgDefaults));
210 
211 	fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
212 		new BMessage(kMsgRevert));
213 	fRevertButton->SetEnabled(false);
214 
215 	BLayoutBuilder::Group<>(this, B_VERTICAL)
216 		.SetInsets(B_USE_WINDOW_SPACING)
217 		.Add(box)
218 		.AddGroup(B_HORIZONTAL)
219 			.Add(fDefaultsButton)
220 			.Add(fRevertButton)
221 			.AddGlue()
222 		.End();
223 
224 	BScreen screen;
225 	BRect screenFrame = screen.Frame();
226 	if (!screenFrame.Contains(fSettings.WindowPosition()))
227 		CenterOnScreen();
228 
229 #ifdef SWAP_VOLUME_IMPLEMENTED
230 	// Validate the volume specified in settings file
231 	status_t result = fSettings.SwapVolume().InitCheck();
232 
233 	if (result != B_OK) {
234 		BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
235 			B_TRANSLATE("The swap volume specified in the settings file is ",
236 			"invalid.\n You can keep the current setting or switch to the "
237 			"default swap volume."),
238 			B_TRANSLATE("Keep"), B_TRANSLATE("Switch"), NULL,
239 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
240 		alert->SetShortcut(0, B_ESCAPE);
241 		int32 choice = alert->Go();
242 		if (choice == 1) {
243 			BVolumeRoster volumeRoster;
244 			BVolume bootVolume;
245 			volumeRoster.GetBootVolume(&bootVolume);
246 			fSettings.SetSwapVolume(bootVolume);
247 		}
248 	}
249 #endif
250 
251 	_Update();
252 
253 	// TODO: We may want to run this at an interval
254 	_UpdateSwapInfo();
255 }
256 
257 
258 void
259 SettingsWindow::MessageReceived(BMessage* message)
260 {
261 	switch (message->what) {
262 		case B_NODE_MONITOR:
263 		{
264 			int32 opcode;
265 			if (message->FindInt32("opcode", &opcode) != B_OK)
266 				break;
267 			dev_t device;
268 			if (opcode == B_DEVICE_MOUNTED
269 				&& message->FindInt32("new device", &device) == B_OK) {
270 				BVolume vol(device);
271 				if (!vol.IsPersistent() || vol.IsReadOnly()
272 					|| vol.IsRemovable() || vol.IsShared()) {
273 					break;
274 				}
275 				_AddVolumeMenuItem(device);
276 			} else if (opcode == B_DEVICE_UNMOUNTED
277 				&& message->FindInt32("device", &device) == B_OK) {
278 				_RemoveVolumeMenuItem(device);
279 			}
280 			_Update();
281 			break;
282 		}
283 		case kMsgRevert:
284 			fSettings.RevertSwapSettings();
285 			_Update();
286 			break;
287 		case kMsgDefaults:
288 			fSettings.DefaultSwapSettings();
289 			_Update();
290 			break;
291 		case kMsgSliderUpdate:
292 			_RecordChoices();
293 			_Update();
294 			break;
295 		case kMsgVolumeSelected:
296 			_RecordChoices();
297 			_Update();
298 			break;
299 		case kMsgSwapEnabledUpdate:
300 		{
301 			if (fSwapEnabledCheckBox->Value() == 0) {
302 				// print out warning, give the user the
303 				// time to think about it :)
304 				// ToDo: maybe we want to remove this possibility in the GUI
305 				// as Be did, but I thought a proper warning could be helpful
306 				// (for those that want to change that anyway)
307 				BAlert* alert = new BAlert(
308 					B_TRANSLATE_SYSTEM_NAME("VirtualMemory"), B_TRANSLATE(
309 					"Disabling virtual memory will have unwanted effects on "
310 					"system stability once the memory is used up.\n"
311 					"Virtual memory does not affect system performance "
312 					"until this point is reached.\n\n"
313 					"Are you really sure you want to turn it off?"),
314 					B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL,
315 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
316 				alert->SetShortcut(1, B_ESCAPE);
317 				int32 choice = alert->Go();
318 				if (choice == 1) {
319 					fSwapEnabledCheckBox->SetValue(1);
320 					break;
321 				}
322 			}
323 
324 			_RecordChoices();
325 			_Update();
326 			break;
327 		}
328 		case kMsgSwapAutomaticUpdate:
329 		{
330 			_RecordChoices();
331 			_Update();
332 			break;
333 		}
334 
335 		default:
336 			BWindow::MessageReceived(message);
337 	}
338 }
339 
340 
341 bool
342 SettingsWindow::QuitRequested()
343 {
344 	fSettings.SetWindowPosition(Frame().LeftTop());
345 
346 	_RecordChoices();
347 	fSettings.WriteWindowSettings();
348 	fSettings.WriteSwapSettings();
349 	be_app->PostMessage(B_QUIT_REQUESTED);
350 	return true;
351 }
352 
353 
354 status_t
355 SettingsWindow::_AddVolumeMenuItem(dev_t device)
356 {
357 	if (_FindVolumeMenuItem(device) != NULL)
358 		return B_ERROR;
359 
360 	VolumeMenuItem* item = new VolumeMenuItem(device,
361 		new BMessage(kMsgVolumeSelected));
362 
363 	fs_info info;
364 	if (fs_stat_dev(device, &info) == 0) {
365 		node_ref node;
366 		node.device = info.dev;
367 		node.node = info.root;
368 		AddHandler(item);
369 		watch_node(&node, B_WATCH_NAME, item);
370 	}
371 
372 	fVolumeMenuField->Menu()->AddItem(item);
373 	return B_OK;
374 }
375 
376 
377 status_t
378 SettingsWindow::_RemoveVolumeMenuItem(dev_t device)
379 {
380 	VolumeMenuItem* item = _FindVolumeMenuItem(device);
381 	if (item != NULL) {
382 		fVolumeMenuField->Menu()->RemoveItem(item);
383 		delete item;
384 		return B_OK;
385 	}
386 	return B_ERROR;
387 }
388 
389 
390 VolumeMenuItem*
391 SettingsWindow::_FindVolumeMenuItem(dev_t device)
392 {
393 	VolumeMenuItem* item = NULL;
394 	int32 count = fVolumeMenuField->Menu()->CountItems();
395 	for (int i = 0; i < count; i++) {
396 		item = (VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(i);
397 		if (item->Volume().Device() == device)
398 			return item;
399 	}
400 
401 	return NULL;
402 }
403 
404 
405 void
406 SettingsWindow::_RecordChoices()
407 {
408 	fSettings.SetSwapAutomatic(fSwapAutomaticCheckBox->Value());
409 	fSettings.SetSwapEnabled(fSwapEnabledCheckBox->Value());
410 	fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
411 	fSettings.SetSwapVolume(((VolumeMenuItem*)fVolumeMenuField
412 		->Menu()->FindMarked())->Volume().Device());
413 }
414 
415 
416 void
417 SettingsWindow::_Update()
418 {
419 	fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
420 	fSwapAutomaticCheckBox->SetValue(fSettings.SwapAutomatic());
421 
422 	VolumeMenuItem* item = _FindVolumeMenuItem(fSettings.SwapVolume());
423 	if (item != NULL) {
424 		fSizeSlider->SetEnabled(true);
425 		item->SetMarked(true);
426 		BEntry swapFile;
427 		if (gBootDev == item->Volume().Device())
428 			swapFile.SetTo("/var/swap");
429 		else {
430 			BDirectory root;
431 			item->Volume().GetRootDirectory(&root);
432 			swapFile.SetTo(&root, "swap");
433 		}
434 
435 		off_t swapFileSize = 0;
436 		swapFile.GetSize(&swapFileSize);
437 
438 		char sizeStr[16];
439 
440 		off_t freeSpace = item->Volume().FreeBytes() + swapFileSize;
441 		off_t safeSpace = freeSpace - (off_t)(0.15 * freeSpace);
442 		(safeSpace >>= 20) <<= 20;
443 		off_t minSize = B_PAGE_SIZE + kMegaByte;
444 		(minSize >>= 20) <<= 20;
445 		BString minLabel, maxLabel;
446 		minLabel << string_for_size(minSize, sizeStr, sizeof(sizeStr));
447 		maxLabel << string_for_size(safeSpace, sizeStr, sizeof(sizeStr));
448 
449 		fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String());
450 		fSizeSlider->SetLimits(minSize / kMegaByte, safeSpace / kMegaByte);
451 		fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte);
452 	} else
453 		fSizeSlider->SetEnabled(false);
454 
455 	bool revertable = fSettings.IsRevertable();
456 	if (revertable)
457 		fWarningStringView->Show();
458 	else
459 		fWarningStringView->Hide();
460 
461 	fRevertButton->SetEnabled(revertable);
462 	fDefaultsButton->SetEnabled(fSettings.IsDefaultable());
463 
464 	// Automatic Swap depends on swap being enabled
465 	fSwapAutomaticCheckBox->SetEnabled(fSettings.SwapEnabled());
466 
467 	// Manual swap settings depend on enabled swap
468 	// and automatic swap being disabled
469 	fSizeSlider->SetEnabled(fSettings.SwapEnabled()
470 		&& !fSwapAutomaticCheckBox->Value());
471 	fVolumeMenuField->SetEnabled(fSettings.SwapEnabled()
472 		&& !fSwapAutomaticCheckBox->Value());
473 }
474 
475 
476 void
477 SettingsWindow::_UpdateSwapInfo()
478 {
479 	system_info info;
480 	get_system_info(&info);
481 
482 	off_t currentSwapSize = info.max_swap_pages * B_PAGE_SIZE;
483 	off_t currentSwapUsed
484 		= (info.max_swap_pages - info.free_swap_pages) * B_PAGE_SIZE;
485 
486 	char sizeStr[16];
487 	BString swapSizeStr = string_for_size(currentSwapSize, sizeStr,
488 		sizeof(sizeStr));
489 	BString swapUsedStr = string_for_size(currentSwapUsed, sizeStr,
490 		sizeof(sizeStr));
491 
492 	BString string = swapUsedStr << " / " << swapSizeStr;
493 
494 	fSwapUsageBar->SetMaxValue(currentSwapSize / kMegaByte);
495 	fSwapUsageBar->Update(currentSwapUsed / kMegaByte,
496 		B_TRANSLATE("Current Swap:"), string.String());
497 }
498