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