1 /* 2 * Copyright 2010-2011, Hamish Morrison, hamish@lavabit.com 3 * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "SettingsWindow.h" 9 #include "Settings.h" 10 11 #include <Application.h> 12 #include <Alert.h> 13 #include <Box.h> 14 #include <Button.h> 15 #include <Catalog.h> 16 #include <CheckBox.h> 17 #include <GroupLayout.h> 18 #include <GroupLayoutBuilder.h> 19 #include <Locale.h> 20 #include <StringView.h> 21 #include <String.h> 22 #include <Slider.h> 23 #include <PopUpMenu.h> 24 #include <MenuItem.h> 25 #include <MenuField.h> 26 #include <Screen.h> 27 #include <FindDirectory.h> 28 #include <Path.h> 29 #include <Volume.h> 30 #include <VolumeRoster.h> 31 32 #include <stdio.h> 33 34 #undef B_TRANSLATION_CONTEXT 35 #define B_TRANSLATION_CONTEXT "SettingsWindow" 36 37 38 static const uint32 kMsgDefaults = 'dflt'; 39 static const uint32 kMsgRevert = 'rvrt'; 40 static const uint32 kMsgSliderUpdate = 'slup'; 41 static const uint32 kMsgSwapEnabledUpdate = 'swen'; 42 static const uint32 kMsgVolumeSelected = 'vlsl'; 43 static const int64 kMegaByte = 1024 * 1024; 44 45 46 class SizeSlider : public BSlider { 47 public: 48 SizeSlider(const char* name, const char* label, 49 BMessage* message, int32 min, int32 max, uint32 flags); 50 virtual ~SizeSlider(); 51 52 virtual const char* UpdateText() const; 53 54 private: 55 mutable BString fText; 56 }; 57 58 59 SizeSlider::SizeSlider(const char* name, const char* label, 60 BMessage* message, int32 min, int32 max, uint32 flags) 61 : BSlider(name, label, message, min, max, B_HORIZONTAL, B_BLOCK_THUMB, flags) 62 { 63 rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR); 64 UseFillColor(true, &color); 65 } 66 67 68 SizeSlider::~SizeSlider() 69 { 70 } 71 72 73 const char* 74 byte_string(int64 size) 75 { 76 double value = 1. * size; 77 static char string[256]; 78 79 if (value < 1024) 80 snprintf(string, sizeof(string), B_TRANSLATE("%Ld B"), size); 81 else { 82 static const char *units[] = { 83 B_TRANSLATE_MARK("KB"), 84 B_TRANSLATE_MARK("MB"), 85 B_TRANSLATE_MARK("GB"), 86 NULL 87 }; 88 int32 i = -1; 89 90 do { 91 value /= 1024.0; 92 i++; 93 } while (value >= 1024 && units[i + 1]); 94 95 off_t rounded = off_t(value * 100LL); 96 snprintf(string, sizeof(string), "%g %s", rounded / 100.0, 97 B_TRANSLATE_NOCOLLECT(units[i])); 98 } 99 100 return string; 101 } 102 103 104 const char* 105 SizeSlider::UpdateText() const 106 { 107 fText = byte_string(Value() * kMegaByte); 108 return fText.String(); 109 } 110 111 112 class VolumeMenuItem : public BMenuItem { 113 public: 114 VolumeMenuItem(const char* label, BMessage* message, BVolume* volume); 115 BVolume* Volume() { return fVolume; } 116 117 private: 118 BVolume* fVolume; 119 }; 120 121 122 VolumeMenuItem::VolumeMenuItem(const char* label, BMessage* message, 123 BVolume* volume) 124 : BMenuItem(label, message) 125 { 126 fVolume = volume; 127 } 128 129 130 SettingsWindow::SettingsWindow() 131 : 132 BWindow(BRect(0, 0, 269, 172), B_TRANSLATE_SYSTEM_NAME("VirtualMemory"), 133 B_TITLED_WINDOW, B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS 134 | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS), 135 fLocked(false) 136 137 { 138 BView* view = new BGroupView(); 139 140 fSwapEnabledCheckBox = new BCheckBox("enable swap", 141 B_TRANSLATE("Enable virtual memory"), 142 new BMessage(kMsgSwapEnabledUpdate)); 143 144 BBox* box = new BBox("box", B_FOLLOW_LEFT_RIGHT); 145 box->SetLabel(fSwapEnabledCheckBox); 146 147 system_info info; 148 get_system_info(&info); 149 150 BString string = B_TRANSLATE("Physical memory: "); 151 string << byte_string((off_t)info.max_pages * B_PAGE_SIZE); 152 BStringView* memoryView = new BStringView("physical memory", string.String()); 153 154 string = B_TRANSLATE("Current swap file size: "); 155 string << byte_string(fSettings.SwapSize()); 156 BStringView* swapfileView = new BStringView("current swap size", string.String()); 157 158 BPopUpMenu* menu = new BPopUpMenu("invalid"); 159 160 // collect volumes 161 // TODO: listen to volume changes! 162 // TODO: accept dropped volumes 163 164 BVolumeRoster volumeRoster; 165 BVolume* volume = new BVolume(); 166 char name[B_FILE_NAME_LENGTH]; 167 while (volumeRoster.GetNextVolume(volume) == B_OK) { 168 if (!volume->IsPersistent() || volume->GetName(name) != B_OK || !name[0]) 169 continue; 170 VolumeMenuItem* item = new VolumeMenuItem(name, 171 new BMessage(kMsgVolumeSelected), volume); 172 menu->AddItem(item); 173 volume = new BVolume(); 174 } 175 176 fVolumeMenuField = new BMenuField("volumes", B_TRANSLATE("Use volume:"), menu); 177 178 fSizeSlider = new SizeSlider("size slider", 179 B_TRANSLATE("Requested swap file size:"), new BMessage(kMsgSliderUpdate), 180 1, 1, B_WILL_DRAW | B_FRAME_EVENTS); 181 fSizeSlider->SetViewColor(255, 0, 255); 182 183 fWarningStringView = new BStringView("", ""); 184 fWarningStringView->SetAlignment(B_ALIGN_CENTER); 185 186 view->SetLayout(new BGroupLayout(B_HORIZONTAL)); 187 view->AddChild(BGroupLayoutBuilder(B_VERTICAL, 10) 188 .AddGroup(B_HORIZONTAL) 189 .Add(memoryView) 190 .AddGlue() 191 .End() 192 .AddGroup(B_HORIZONTAL) 193 .Add(swapfileView) 194 .AddGlue() 195 .End() 196 #ifdef SWAP_VOLUME_IMPLEMENTED 197 .AddGroup(B_HORIZONTAL) 198 .Add(fVolumeMenuField) 199 .AddGlue() 200 .End() 201 #else 202 .AddGlue() 203 #endif 204 .Add(fSizeSlider) 205 .Add(fWarningStringView) 206 .SetInsets(10, 10, 10, 10) 207 ); 208 box->AddChild(view); 209 210 fDefaultsButton = new BButton("defaults", B_TRANSLATE("Defaults"), 211 new BMessage(kMsgDefaults)); 212 213 fRevertButton = new BButton("revert", B_TRANSLATE("Revert"), 214 new BMessage(kMsgRevert)); 215 fRevertButton->SetEnabled(false); 216 217 SetLayout(new BGroupLayout(B_HORIZONTAL)); 218 AddChild(BGroupLayoutBuilder(B_VERTICAL, 10) 219 .Add(box) 220 .AddGroup(B_HORIZONTAL, 10) 221 .Add(fDefaultsButton) 222 .Add(fRevertButton) 223 .AddGlue() 224 .End() 225 .SetInsets(10, 10, 10, 10) 226 ); 227 228 BScreen screen; 229 BRect screenFrame = screen.Frame(); 230 if (!screenFrame.Contains(fSettings.WindowPosition())) 231 CenterOnScreen(); 232 else 233 MoveTo(fSettings.WindowPosition()); 234 235 #ifdef SWAP_VOLUME_IMPLEMENTED 236 // Validate the volume specified in settings file 237 status_t result = fSettings.SwapVolume().InitCheck(); 238 239 if (result != B_OK) { 240 BAlert* alert = new BAlert("VirtualMemory", B_TRANSLATE( 241 "The swap volume specified in the settings file is invalid.\n" 242 "You can keep the current setting or switch to the " 243 "default swap volume."), 244 B_TRANSLATE("Keep"), B_TRANSLATE("Switch"), NULL, 245 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 246 alert->SetShortcut(0, B_ESCAPE); 247 int32 choice = alert->Go(); 248 if (choice == 1) { 249 BVolumeRoster volumeRoster; 250 BVolume bootVolume; 251 volumeRoster.GetBootVolume(&bootVolume); 252 fSettings.SetSwapVolume(bootVolume); 253 } 254 } 255 #endif 256 257 _Update(); 258 } 259 260 261 SettingsWindow::~SettingsWindow() 262 { 263 } 264 265 266 void 267 SettingsWindow::MessageReceived(BMessage* message) 268 { 269 switch (message->what) { 270 case kMsgRevert: 271 fSettings.RevertSwapChanges(); 272 _Update(); 273 break; 274 case kMsgDefaults: 275 _SetSwapDefaults(); 276 _Update(); 277 break; 278 case kMsgSliderUpdate: 279 fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte); 280 _Update(); 281 break; 282 case kMsgVolumeSelected: 283 fSettings.SetSwapVolume(*((VolumeMenuItem*)fVolumeMenuField->Menu() 284 ->FindMarked())->Volume()); 285 _Update(); 286 break; 287 case kMsgSwapEnabledUpdate: 288 { 289 int32 value; 290 if (message->FindInt32("be:value", &value) != B_OK) 291 break; 292 293 if (value == 0) { 294 // print out warning, give the user the time to think about it :) 295 // ToDo: maybe we want to remove this possibility in the GUI 296 // as Be did, but I thought a proper warning could be helpful 297 // (for those that want to change that anyway) 298 BAlert* alert = new BAlert("VirtualMemory", 299 B_TRANSLATE( 300 "Disabling virtual memory will have unwanted effects on " 301 "system stability once the memory is used up.\n" 302 "Virtual memory does not affect system performance " 303 "until this point is reached.\n\n" 304 "Are you really sure you want to turn it off?"), 305 B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL, 306 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 307 alert->SetShortcut(1, B_ESCAPE); 308 int32 choice = alert->Go(); 309 if (choice == 1) { 310 fSwapEnabledCheckBox->SetValue(1); 311 break; 312 } 313 } 314 315 fSettings.SetSwapEnabled(value != 0); 316 _Update(); 317 break; 318 } 319 320 default: 321 BWindow::MessageReceived(message); 322 } 323 } 324 325 326 bool 327 SettingsWindow::QuitRequested() 328 { 329 fSettings.SetWindowPosition(Frame().LeftTop()); 330 be_app->PostMessage(B_QUIT_REQUESTED); 331 return true; 332 } 333 334 335 void 336 SettingsWindow::_Update() 337 { 338 if ((fSwapEnabledCheckBox->Value() != 0) != fSettings.SwapEnabled()) 339 fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled()); 340 341 #ifdef SWAP_VOLUME_IMPLEMENTED 342 if (fVolumeMenuField->IsEnabled() != fSettings.SwapEnabled()) 343 fVolumeMenuField->SetEnabled(fSettings.SwapEnabled()); 344 VolumeMenuItem* selectedVolumeItem = 345 (VolumeMenuItem*)fVolumeMenuField->Menu()->FindMarked(); 346 if (selectedVolumeItem == NULL) { 347 VolumeMenuItem* currentVolumeItem; 348 int32 items = fVolumeMenuField->Menu()->CountItems(); 349 for (int32 index = 0; index < items; ++index) { 350 currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index)); 351 if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) { 352 currentVolumeItem->SetMarked(true); 353 break; 354 } 355 } 356 } else if (*selectedVolumeItem->fVolume != fSettings.SwapVolume()) { 357 VolumeMenuItem* currentVolumeItem; 358 int32 items = fVolumeMenuField->Menu()->CountItems(); 359 for (int32 index = 0; index < items; ++index) { 360 currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index)); 361 if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) { 362 currentVolumeItem->SetMarked(true); 363 break; 364 } 365 } 366 } 367 #endif 368 369 fWarningStringView->SetText(""); 370 fLocked = false; 371 372 if (fSettings.IsRevertible()) 373 fWarningStringView->SetText( 374 B_TRANSLATE("Changes will take effect on restart!")); 375 if (fRevertButton->IsEnabled() != fSettings.IsRevertible()) 376 fRevertButton->SetEnabled(fSettings.IsRevertible()); 377 378 off_t minSize, maxSize; 379 if (_GetSwapFileLimits(minSize, maxSize) == B_OK) { 380 // round to nearest MB -- slider steps in whole MBs 381 (minSize >>= 20) <<= 20; 382 (maxSize >>= 20) <<= 20; 383 BString minLabel, maxLabel; 384 minLabel << byte_string(minSize); 385 maxLabel << byte_string(maxSize); 386 if (minLabel != fSizeSlider->MinLimitLabel() 387 || maxLabel != fSizeSlider->MaxLimitLabel()) { 388 fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String()); 389 fSizeSlider->SetLimits(minSize / kMegaByte, maxSize / kMegaByte); 390 } 391 } else if (fSettings.SwapEnabled()) { 392 fWarningStringView->SetText( 393 B_TRANSLATE("Insufficient space for a swap file.")); 394 fLocked = true; 395 } 396 if (fSizeSlider->Value() != fSettings.SwapSize() / kMegaByte) 397 fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte); 398 if (fSizeSlider->IsEnabled() != fSettings.SwapEnabled() || fLocked) 399 { 400 fSizeSlider->SetEnabled(fSettings.SwapEnabled() && !fLocked); 401 fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte); 402 } 403 } 404 405 406 status_t 407 SettingsWindow::_GetSwapFileLimits(off_t& minSize, off_t& maxSize) 408 { 409 minSize = kMegaByte; 410 411 // maximum size is the free space on the current volume 412 // (minus some safety offset, depending on the disk size) 413 off_t freeSpace = fSettings.SwapVolume().FreeBytes(); 414 off_t safetyFreeSpace = fSettings.SwapVolume().Capacity() / 100; 415 if (safetyFreeSpace > 1024 * kMegaByte) 416 safetyFreeSpace = 1024 * kMegaByte; 417 418 // check if there already is a page file on this disk and 419 // adjust the free space accordingly 420 BPath path; 421 if (find_directory(B_COMMON_VAR_DIRECTORY, &path, false, 422 &fSettings.SwapVolume()) == B_OK) { 423 path.Append("swap"); 424 BEntry swap(path.Path()); 425 426 off_t size; 427 if (swap.GetSize(&size) == B_OK) { 428 // If swap file exists, forget about safety space; 429 // disk may have filled after creation of swap file. 430 safetyFreeSpace = 0; 431 freeSpace += size; 432 } 433 } 434 435 maxSize = freeSpace - safetyFreeSpace; 436 if (maxSize < minSize) { 437 maxSize = 0; 438 minSize = 0; 439 return B_ERROR; 440 } 441 442 return B_OK; 443 } 444 445 446 void 447 SettingsWindow::_SetSwapDefaults() 448 { 449 fSettings.SetSwapEnabled(true); 450 451 BVolumeRoster volumeRoster; 452 BVolume temporaryVolume; 453 volumeRoster.GetBootVolume(&temporaryVolume); 454 fSettings.SetSwapVolume(temporaryVolume); 455 456 system_info info; 457 get_system_info(&info); 458 459 off_t defaultSize = (off_t)info.max_pages * B_PAGE_SIZE; 460 off_t minSize, maxSize; 461 _GetSwapFileLimits(minSize, maxSize); 462 463 if (defaultSize > maxSize / 2) 464 defaultSize = maxSize / 2; 465 466 fSettings.SetSwapSize(defaultSize); 467 } 468 469