vfs: check userland buffers before reading them.
[haiku.git] / src / preferences / screen / ScreenWindow.cpp
blob496e6cad358b81ebfca4502a17bec0bf8398d9b5
1 /*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * Andrew Bachmann
8 * Stefano Ceccherini, burton666@libero.it
9 * Alexandre Deckner, alex@zappotek.com
10 * Axel Dörfler, axeld@pinc-software.de
11 * Rene Gollent, rene@gollent.com
12 * Thomas Kurschel
13 * Rafael Romo
14 * John Scipione, jscipione@gmail.com
18 #include "ScreenWindow.h"
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <strings.h>
24 #include <Alert.h>
25 #include <Application.h>
26 #include <Box.h>
27 #include <Button.h>
28 #include <Catalog.h>
29 #include <ControlLook.h>
30 #include <Directory.h>
31 #include <File.h>
32 #include <FindDirectory.h>
33 #include <InterfaceDefs.h>
34 #include <LayoutBuilder.h>
35 #include <MenuBar.h>
36 #include <MenuItem.h>
37 #include <MenuField.h>
38 #include <Messenger.h>
39 #include <Path.h>
40 #include <PopUpMenu.h>
41 #include <Screen.h>
42 #include <SpaceLayoutItem.h>
43 #include <Spinner.h>
44 #include <String.h>
45 #include <StringView.h>
46 #include <Roster.h>
47 #include <Window.h>
49 #include <InterfacePrivate.h>
51 #include "AlertWindow.h"
52 #include "Constants.h"
53 #include "RefreshWindow.h"
54 #include "MonitorView.h"
55 #include "ScreenSettings.h"
56 #include "Utility.h"
58 /* Note, this headers defines a *private* interface to the Radeon accelerant.
59 * It's a solution that works with the current BeOS interface that Haiku
60 * adopted.
61 * However, it's not a nice and clean solution. Don't use this header in any
62 * application if you can avoid it. No other driver is using this, or should
63 * be using this.
64 * It will be replaced as soon as we introduce an updated accelerant interface
65 * which may even happen before R1 hits the streets.
67 #include "multimon.h" // the usual: DANGER WILL, ROBINSON!
70 #undef B_TRANSLATION_CONTEXT
71 #define B_TRANSLATION_CONTEXT "Screen"
74 const char* kBackgroundsSignature = "application/x-vnd.Haiku-Backgrounds";
76 // list of officially supported colour spaces
77 static const struct {
78 color_space space;
79 int32 bits_per_pixel;
80 const char* label;
81 } kColorSpaces[] = {
82 { B_CMAP8, 8, B_TRANSLATE("8 bits/pixel, 256 colors") },
83 { B_RGB15, 15, B_TRANSLATE("15 bits/pixel, 32768 colors") },
84 { B_RGB16, 16, B_TRANSLATE("16 bits/pixel, 65536 colors") },
85 { B_RGB24, 24, B_TRANSLATE("24 bits/pixel, 16 Million colors") },
86 { B_RGB32, 32, B_TRANSLATE("32 bits/pixel, 16 Million colors") }
88 static const int32 kColorSpaceCount
89 = sizeof(kColorSpaces) / sizeof(kColorSpaces[0]);
91 // list of standard refresh rates
92 static const int32 kRefreshRates[] = { 60, 70, 72, 75, 80, 85, 95, 100 };
93 static const int32 kRefreshRateCount
94 = sizeof(kRefreshRates) / sizeof(kRefreshRates[0]);
96 // list of combine modes
97 static const struct {
98 combine_mode mode;
99 const char *name;
100 } kCombineModes[] = {
101 { kCombineDisable, B_TRANSLATE("disable") },
102 { kCombineHorizontally, B_TRANSLATE("horizontally") },
103 { kCombineVertically, B_TRANSLATE("vertically") }
105 static const int32 kCombineModeCount
106 = sizeof(kCombineModes) / sizeof(kCombineModes[0]);
109 static BString
110 tv_standard_to_string(uint32 mode)
112 switch (mode) {
113 case 0: return "disabled";
114 case 1: return "NTSC";
115 case 2: return "NTSC Japan";
116 case 3: return "PAL BDGHI";
117 case 4: return "PAL M";
118 case 5: return "PAL N";
119 case 6: return "SECAM";
120 case 101: return "NTSC 443";
121 case 102: return "PAL 60";
122 case 103: return "PAL NC";
123 default:
125 BString name;
126 name << "??? (" << mode << ")";
128 return name;
134 static void
135 resolution_to_string(screen_mode& mode, BString &string)
137 string << mode.width << " x " << mode.height;
141 static void
142 refresh_rate_to_string(float refresh, BString &string,
143 bool appendUnit = true, bool alwaysWithFraction = false)
145 snprintf(string.LockBuffer(32), 32, "%.*g", refresh >= 100.0 ? 4 : 3,
146 refresh);
147 string.UnlockBuffer();
149 if (appendUnit)
150 string << " " << B_TRANSLATE("Hz");
154 static const char*
155 screen_errors(status_t status)
157 switch (status) {
158 case B_ENTRY_NOT_FOUND:
159 return B_TRANSLATE("Unknown mode");
160 // TODO: add more?
162 default:
163 return strerror(status);
168 // #pragma mark - ScreenWindow
171 ScreenWindow::ScreenWindow(ScreenSettings* settings)
173 BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Screen"),
174 B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE
175 | B_AUTO_UPDATE_SIZE_LIMITS, B_ALL_WORKSPACES),
176 fIsVesa(false),
177 fBootWorkspaceApplied(false),
178 fOtherRefresh(NULL),
179 fScreenMode(this),
180 fUndoScreenMode(this),
181 fModified(false)
183 BScreen screen(this);
185 accelerant_device_info info;
186 if (screen.GetDeviceInfo(&info) == B_OK
187 && !strcasecmp(info.chipset, "VESA"))
188 fIsVesa = true;
190 _UpdateOriginal();
191 _BuildSupportedColorSpaces();
192 fActive = fSelected = fOriginal;
194 fSettings = settings;
196 // we need the "Current Workspace" first to get its height
198 BPopUpMenu* popUpMenu = new BPopUpMenu(B_TRANSLATE("Current workspace"),
199 true, true);
200 fAllWorkspacesItem = new BMenuItem(B_TRANSLATE("All workspaces"),
201 new BMessage(WORKSPACE_CHECK_MSG));
202 popUpMenu->AddItem(fAllWorkspacesItem);
203 BMenuItem *item = new BMenuItem(B_TRANSLATE("Current workspace"),
204 new BMessage(WORKSPACE_CHECK_MSG));
206 popUpMenu->AddItem(item);
207 fAllWorkspacesItem->SetMarked(true);
209 BMenuField* workspaceMenuField = new BMenuField("WorkspaceMenu", NULL,
210 popUpMenu);
211 workspaceMenuField->ResizeToPreferred();
213 // box on the left with workspace count and monitor view
215 BBox* screenBox = new BBox("screen box");
216 BGroupLayout* layout = new BGroupLayout(B_VERTICAL, B_USE_SMALL_SPACING);
217 layout->SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING,
218 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING);
219 screenBox->SetLayout(layout);
221 fMonitorInfo = new BStringView("monitor info", "");
222 fMonitorInfo->SetAlignment(B_ALIGN_CENTER);
223 screenBox->AddChild(fMonitorInfo);
225 fDeviceInfo = new BStringView("monitor info", "");
226 fDeviceInfo->SetAlignment(B_ALIGN_CENTER);
227 screenBox->AddChild(fDeviceInfo);
229 float scaling = std::max(1.0f, be_plain_font->Size() / 12.0f);
230 fMonitorView = new MonitorView(BRect(0.0, 0.0, 80.0 * scaling,
231 80.0 * scaling), "monitor", screen.Frame().IntegerWidth() + 1,
232 screen.Frame().IntegerHeight() + 1);
233 screenBox->AddChild(fMonitorView);
235 BStringView* workspaces = new BStringView("workspaces",
236 B_TRANSLATE("Workspaces"));
237 workspaces->SetAlignment(B_ALIGN_CENTER);
239 fColumnsControl = new BSpinner("columns", B_TRANSLATE("Columns:"),
240 new BMessage(kMsgWorkspaceColumnsChanged));
241 fColumnsControl->SetAlignment(B_ALIGN_RIGHT);
242 fColumnsControl->SetRange(1, 32);
244 fRowsControl = new BSpinner("rows", B_TRANSLATE("Rows:"),
245 new BMessage(kMsgWorkspaceRowsChanged));
246 fRowsControl->SetAlignment(B_ALIGN_RIGHT);
247 fRowsControl->SetRange(1, 32);
249 uint32 columns;
250 uint32 rows;
251 BPrivate::get_workspaces_layout(&columns, &rows);
252 fColumnsControl->SetValue(columns);
253 fRowsControl->SetValue(rows);
255 screenBox->AddChild(BLayoutBuilder::Group<>()
256 .AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
257 .Add(workspaces)
258 .AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
259 // columns
260 .Add(fColumnsControl->CreateLabelLayoutItem(), 0, 0)
261 .Add(fColumnsControl->CreateTextViewLayoutItem(), 1, 0)
262 // rows
263 .Add(fRowsControl->CreateLabelLayoutItem(), 0, 1)
264 .Add(fRowsControl->CreateTextViewLayoutItem(), 1, 1)
265 .End()
266 .End()
267 .View());
269 fBackgroundsButton = new BButton("BackgroundsButton",
270 B_TRANSLATE("Set background" B_UTF8_ELLIPSIS),
271 new BMessage(BUTTON_LAUNCH_BACKGROUNDS_MSG));
272 fBackgroundsButton->SetFontSize(be_plain_font->Size() * 0.9);
273 screenBox->AddChild(fBackgroundsButton);
275 // box on the right with screen resolution, etc.
277 BBox* controlsBox = new BBox("controls box");
278 controlsBox->SetLabel(workspaceMenuField);
279 BGroupView* outerControlsView = new BGroupView(B_VERTICAL);
280 outerControlsView->GroupLayout()->SetInsets(B_USE_DEFAULT_SPACING,
281 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING);
282 controlsBox->AddChild(outerControlsView);
284 fResolutionMenu = new BPopUpMenu("resolution", true, true);
286 uint16 maxWidth = 0;
287 uint16 maxHeight = 0;
288 uint16 previousWidth = 0;
289 uint16 previousHeight = 0;
290 for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
291 screen_mode mode = fScreenMode.ModeAt(i);
293 if (mode.width == previousWidth && mode.height == previousHeight)
294 continue;
296 previousWidth = mode.width;
297 previousHeight = mode.height;
298 if (maxWidth < mode.width)
299 maxWidth = mode.width;
300 if (maxHeight < mode.height)
301 maxHeight = mode.height;
303 BMessage* message = new BMessage(POP_RESOLUTION_MSG);
304 message->AddInt32("width", mode.width);
305 message->AddInt32("height", mode.height);
307 BString name;
308 name << mode.width << " x " << mode.height;
310 fResolutionMenu->AddItem(new BMenuItem(name.String(), message));
313 fMonitorView->SetMaxResolution(maxWidth, maxHeight);
315 fResolutionField = new BMenuField("ResolutionMenu",
316 B_TRANSLATE("Resolution:"), fResolutionMenu);
317 fResolutionField->SetAlignment(B_ALIGN_RIGHT);
319 fColorsMenu = new BPopUpMenu("colors", true, false);
321 for (int32 i = 0; i < kColorSpaceCount; i++) {
322 if ((fSupportedColorSpaces & (1 << i)) == 0)
323 continue;
325 BMessage* message = new BMessage(POP_COLORS_MSG);
326 message->AddInt32("bits_per_pixel", kColorSpaces[i].bits_per_pixel);
327 message->AddInt32("space", kColorSpaces[i].space);
329 BMenuItem* item = new BMenuItem(kColorSpaces[i].label, message);
330 if (kColorSpaces[i].space == screen.ColorSpace())
331 fUserSelectedColorSpace = item;
333 fColorsMenu->AddItem(item);
336 fColorsField = new BMenuField("ColorsMenu", B_TRANSLATE("Colors:"),
337 fColorsMenu);
338 fColorsField->SetAlignment(B_ALIGN_RIGHT);
340 fRefreshMenu = new BPopUpMenu("refresh rate", true, true);
342 float min, max;
343 if (fScreenMode.GetRefreshLimits(fActive, min, max) != B_OK) {
344 // if we couldn't obtain the refresh limits, reset to the default
345 // range. Constraints from detected monitors will fine-tune this
346 // later.
347 min = kRefreshRates[0];
348 max = kRefreshRates[kRefreshRateCount - 1];
351 if (min == max) {
352 // This is a special case for drivers that only support a single
353 // frequency, like the VESA driver
354 BString name;
355 refresh_rate_to_string(min, name);
356 BMessage *message = new BMessage(POP_REFRESH_MSG);
357 message->AddFloat("refresh", min);
358 BMenuItem *item = new BMenuItem(name.String(), message);
359 fRefreshMenu->AddItem(item);
360 item->SetEnabled(false);
361 } else {
362 monitor_info info;
363 if (fScreenMode.GetMonitorInfo(info) == B_OK) {
364 min = max_c(info.min_vertical_frequency, min);
365 max = min_c(info.max_vertical_frequency, max);
368 for (int32 i = 0; i < kRefreshRateCount; ++i) {
369 if (kRefreshRates[i] < min || kRefreshRates[i] > max)
370 continue;
372 BString name;
373 name << kRefreshRates[i] << " " << B_TRANSLATE("Hz");
375 BMessage *message = new BMessage(POP_REFRESH_MSG);
376 message->AddFloat("refresh", kRefreshRates[i]);
378 fRefreshMenu->AddItem(new BMenuItem(name.String(), message));
381 fOtherRefresh = new BMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
382 new BMessage(POP_OTHER_REFRESH_MSG));
383 fRefreshMenu->AddItem(fOtherRefresh);
386 fRefreshField = new BMenuField("RefreshMenu", B_TRANSLATE("Refresh rate:"),
387 fRefreshMenu);
388 fRefreshField->SetAlignment(B_ALIGN_RIGHT);
390 if (_IsVesa())
391 fRefreshField->Hide();
393 // enlarged area for multi-monitor settings
395 bool dummy;
396 uint32 dummy32;
397 bool multiMonSupport;
398 bool useLaptopPanelSupport;
399 bool tvStandardSupport;
401 multiMonSupport = TestMultiMonSupport(&screen) == B_OK;
402 useLaptopPanelSupport = GetUseLaptopPanel(&screen, &dummy) == B_OK;
403 tvStandardSupport = GetTVStandard(&screen, &dummy32) == B_OK;
405 // even if there is no support, we still create all controls
406 // to make sure we don't access NULL pointers later on
408 fCombineMenu = new BPopUpMenu("CombineDisplays",
409 true, true);
411 for (int32 i = 0; i < kCombineModeCount; i++) {
412 BMessage *message = new BMessage(POP_COMBINE_DISPLAYS_MSG);
413 message->AddInt32("mode", kCombineModes[i].mode);
415 fCombineMenu->AddItem(new BMenuItem(kCombineModes[i].name,
416 message));
419 fCombineField = new BMenuField("CombineMenu",
420 B_TRANSLATE("Combine displays:"), fCombineMenu);
421 fCombineField->SetAlignment(B_ALIGN_RIGHT);
423 if (!multiMonSupport)
424 fCombineField->Hide();
426 fSwapDisplaysMenu = new BPopUpMenu("SwapDisplays",
427 true, true);
429 // !order is important - we rely that boolean value == idx
430 BMessage *message = new BMessage(POP_SWAP_DISPLAYS_MSG);
431 message->AddBool("swap", false);
432 fSwapDisplaysMenu->AddItem(new BMenuItem(B_TRANSLATE("no"), message));
434 message = new BMessage(POP_SWAP_DISPLAYS_MSG);
435 message->AddBool("swap", true);
436 fSwapDisplaysMenu->AddItem(new BMenuItem(B_TRANSLATE("yes"), message));
438 fSwapDisplaysField = new BMenuField("SwapMenu",
439 B_TRANSLATE("Swap displays:"), fSwapDisplaysMenu);
440 fSwapDisplaysField->SetAlignment(B_ALIGN_RIGHT);
442 if (!multiMonSupport)
443 fSwapDisplaysField->Hide();
445 fUseLaptopPanelMenu = new BPopUpMenu("UseLaptopPanel",
446 true, true);
448 // !order is important - we rely that boolean value == idx
449 message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
450 message->AddBool("use", false);
451 fUseLaptopPanelMenu->AddItem(new BMenuItem(B_TRANSLATE("if needed"),
452 message));
454 message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
455 message->AddBool("use", true);
456 fUseLaptopPanelMenu->AddItem(new BMenuItem(B_TRANSLATE("always"),
457 message));
459 fUseLaptopPanelField = new BMenuField("UseLaptopPanel",
460 B_TRANSLATE("Use laptop panel:"), fUseLaptopPanelMenu);
461 fUseLaptopPanelField->SetAlignment(B_ALIGN_RIGHT);
463 if (!useLaptopPanelSupport)
464 fUseLaptopPanelField->Hide();
466 fTVStandardMenu = new BPopUpMenu("TVStandard", true, true);
468 // arbitrary limit
469 uint32 i;
470 for (i = 0; i < 100; ++i) {
471 uint32 mode;
472 if (GetNthSupportedTVStandard(&screen, i, &mode) != B_OK)
473 break;
475 BString name = tv_standard_to_string(mode);
477 message = new BMessage(POP_TV_STANDARD_MSG);
478 message->AddInt32("tv_standard", mode);
480 fTVStandardMenu->AddItem(new BMenuItem(name.String(), message));
483 fTVStandardField = new BMenuField("tv standard",
484 B_TRANSLATE("Video format:"), fTVStandardMenu);
485 fTVStandardField->SetAlignment(B_ALIGN_RIGHT);
487 if (!tvStandardSupport || i == 0)
488 fTVStandardField->Hide();
491 BLayoutBuilder::Group<>(outerControlsView)
492 .AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
493 .Add(fResolutionField->CreateLabelLayoutItem(), 0, 0)
494 .Add(fResolutionField->CreateMenuBarLayoutItem(), 1, 0)
495 .Add(fColorsField->CreateLabelLayoutItem(), 0, 1)
496 .Add(fColorsField->CreateMenuBarLayoutItem(), 1, 1)
497 .Add(fRefreshField->CreateLabelLayoutItem(), 0, 2)
498 .Add(fRefreshField->CreateMenuBarLayoutItem(), 1, 2)
499 .Add(fCombineField->CreateLabelLayoutItem(), 0, 3)
500 .Add(fCombineField->CreateMenuBarLayoutItem(), 1, 3)
501 .Add(fSwapDisplaysField->CreateLabelLayoutItem(), 0, 4)
502 .Add(fSwapDisplaysField->CreateMenuBarLayoutItem(), 1, 4)
503 .Add(fUseLaptopPanelField->CreateLabelLayoutItem(), 0, 5)
504 .Add(fUseLaptopPanelField->CreateMenuBarLayoutItem(), 1, 5)
505 .Add(fTVStandardField->CreateLabelLayoutItem(), 0, 6)
506 .Add(fTVStandardField->CreateMenuBarLayoutItem(), 1, 6)
507 .End();
509 fBrightnessSlider = new BSlider("brightness", "Brightness",
510 NULL, 0, 255, B_HORIZONTAL);
512 status_t result = screen.GetBrightness(&fOriginalBrightness);
513 if (result == B_OK) {
514 fBrightnessSlider->SetModificationMessage(
515 new BMessage(SLIDER_BRIGHTNESS_MSG));
516 fBrightnessSlider->SetValue(fOriginalBrightness * 255);
517 } else {
518 // The driver does not support changing the brightness,
519 // so hide the slider
520 fBrightnessSlider->Hide();
522 fOriginalBrightness = -1;
525 // TODO: we don't support getting the screen's preferred settings
526 /* fDefaultsButton = new BButton(buttonRect, "DefaultsButton", "Defaults",
527 new BMessage(BUTTON_DEFAULTS_MSG));*/
529 fApplyButton = new BButton("ApplyButton", B_TRANSLATE("Apply"),
530 new BMessage(BUTTON_APPLY_MSG));
531 fApplyButton->SetEnabled(false);
532 BLayoutBuilder::Group<>(outerControlsView)
533 .AddGlue()
534 .AddGroup(B_HORIZONTAL)
535 .AddGlue()
536 .Add(fApplyButton);
538 fRevertButton = new BButton("RevertButton", B_TRANSLATE("Revert"),
539 new BMessage(BUTTON_REVERT_MSG));
540 fRevertButton->SetEnabled(false);
542 BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING)
543 .AddGroup(B_HORIZONTAL)
544 .AddGroup(B_VERTICAL, 0, 1)
545 .AddStrut(floorf(controlsBox->TopBorderOffset()) - 1)
546 .Add(screenBox)
547 .End()
548 .AddGroup(B_VERTICAL, 0, 1)
549 .Add(controlsBox, 2)
550 .Add(fBrightnessSlider)
551 .End()
552 .End()
553 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
554 .Add(fRevertButton)
555 .AddGlue()
556 .End()
557 .SetInsets(B_USE_WINDOW_SPACING);
559 _UpdateControls();
560 _UpdateMonitor();
562 MoveOnScreen();
566 ScreenWindow::~ScreenWindow()
568 delete fSettings;
572 bool
573 ScreenWindow::QuitRequested()
575 fSettings->SetWindowFrame(Frame());
577 // Write mode of workspace 0 (the boot workspace) to the vesa settings file
578 screen_mode vesaMode;
579 if (fBootWorkspaceApplied && fScreenMode.Get(vesaMode, 0) == B_OK) {
580 status_t status = _WriteVesaModeFile(vesaMode);
581 if (status < B_OK) {
582 BString warning = B_TRANSLATE("Could not write VESA mode settings"
583 " file:\n\t");
584 warning << strerror(status);
585 BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
586 warning.String(), B_TRANSLATE("OK"), NULL,
587 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
588 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
589 alert->Go();
593 be_app->PostMessage(B_QUIT_REQUESTED);
595 return BWindow::QuitRequested();
599 /*! Update resolution list according to combine mode
600 (some resolutions may not be combinable due to memory restrictions).
602 void
603 ScreenWindow::_CheckResolutionMenu()
605 for (int32 i = 0; i < fResolutionMenu->CountItems(); i++)
606 fResolutionMenu->ItemAt(i)->SetEnabled(false);
608 for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
609 screen_mode mode = fScreenMode.ModeAt(i);
610 if (mode.combine != fSelected.combine)
611 continue;
613 BString name;
614 name << mode.width << " x " << mode.height;
616 BMenuItem *item = fResolutionMenu->FindItem(name.String());
617 if (item != NULL)
618 item->SetEnabled(true);
623 /*! Update color and refresh options according to current mode
624 (a color space is made active if there is any mode with
625 given resolution and this colour space; same applies for
626 refresh rate, though "Other…" is always possible)
628 void
629 ScreenWindow::_CheckColorMenu()
631 int32 supportsAnything = false;
632 int32 index = 0;
634 for (int32 i = 0; i < kColorSpaceCount; i++) {
635 if ((fSupportedColorSpaces & (1 << i)) == 0)
636 continue;
638 bool supported = false;
640 for (int32 j = 0; j < fScreenMode.CountModes(); j++) {
641 screen_mode mode = fScreenMode.ModeAt(j);
643 if (fSelected.width == mode.width
644 && fSelected.height == mode.height
645 && kColorSpaces[i].space == mode.space
646 && fSelected.combine == mode.combine) {
647 supportsAnything = true;
648 supported = true;
649 break;
653 BMenuItem* item = fColorsMenu->ItemAt(index++);
654 if (item)
655 item->SetEnabled(supported);
658 fColorsField->SetEnabled(supportsAnything);
660 if (!supportsAnything)
661 return;
663 // Make sure a valid item is selected
665 BMenuItem* item = fColorsMenu->FindMarked();
666 bool changed = false;
668 if (item != fUserSelectedColorSpace) {
669 if (fUserSelectedColorSpace != NULL
670 && fUserSelectedColorSpace->IsEnabled()) {
671 fUserSelectedColorSpace->SetMarked(true);
672 item = fUserSelectedColorSpace;
673 changed = true;
676 if (item != NULL && !item->IsEnabled()) {
677 // find the next best item
678 int32 index = fColorsMenu->IndexOf(item);
679 bool found = false;
681 for (int32 i = index + 1; i < fColorsMenu->CountItems(); i++) {
682 item = fColorsMenu->ItemAt(i);
683 if (item->IsEnabled()) {
684 found = true;
685 break;
688 if (!found) {
689 // search backwards as well
690 for (int32 i = index - 1; i >= 0; i--) {
691 item = fColorsMenu->ItemAt(i);
692 if (item->IsEnabled())
693 break;
697 item->SetMarked(true);
698 changed = true;
701 if (changed) {
702 // Update selected space
704 BMessage* message = item->Message();
705 int32 space;
706 if (message->FindInt32("space", &space) == B_OK) {
707 fSelected.space = (color_space)space;
708 _UpdateColorLabel();
714 /*! Enable/disable refresh options according to current mode. */
715 void
716 ScreenWindow::_CheckRefreshMenu()
718 float min, max;
719 if (fScreenMode.GetRefreshLimits(fSelected, min, max) != B_OK || min == max)
720 return;
722 for (int32 i = fRefreshMenu->CountItems(); i-- > 0;) {
723 BMenuItem* item = fRefreshMenu->ItemAt(i);
724 BMessage* message = item->Message();
725 float refresh;
726 if (message != NULL && message->FindFloat("refresh", &refresh) == B_OK)
727 item->SetEnabled(refresh >= min && refresh <= max);
732 /*! Activate appropriate menu item according to selected refresh rate */
733 void
734 ScreenWindow::_UpdateRefreshControl()
736 for (int32 i = 0; i < fRefreshMenu->CountItems(); i++) {
737 BMenuItem* item = fRefreshMenu->ItemAt(i);
738 if (item->Message()->FindFloat("refresh") == fSelected.refresh) {
739 item->SetMarked(true);
740 // "Other" items only contains a refresh rate when active
741 fOtherRefresh->SetLabel(B_TRANSLATE("Other" B_UTF8_ELLIPSIS));
742 return;
746 // this is a non-standard refresh rate
747 if (fOtherRefresh != NULL) {
748 fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh);
749 fOtherRefresh->SetMarked(true);
751 BString string;
752 refresh_rate_to_string(fSelected.refresh, string);
753 fRefreshMenu->Superitem()->SetLabel(string.String());
755 string.Append(B_TRANSLATE("/other" B_UTF8_ELLIPSIS));
756 fOtherRefresh->SetLabel(string.String());
761 void
762 ScreenWindow::_UpdateMonitorView()
764 BMessage updateMessage(UPDATE_DESKTOP_MSG);
765 updateMessage.AddInt32("width", fSelected.width);
766 updateMessage.AddInt32("height", fSelected.height);
768 PostMessage(&updateMessage, fMonitorView);
772 void
773 ScreenWindow::_UpdateControls()
775 _UpdateWorkspaceButtons();
777 BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays);
778 if (item != NULL && !item->IsMarked())
779 item->SetMarked(true);
781 item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel);
782 if (item != NULL && !item->IsMarked())
783 item->SetMarked(true);
785 for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) {
786 item = fTVStandardMenu->ItemAt(i);
788 uint32 tvStandard;
789 item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard);
790 if (tvStandard == fSelected.tv_standard) {
791 if (!item->IsMarked())
792 item->SetMarked(true);
793 break;
797 _CheckResolutionMenu();
798 _CheckColorMenu();
799 _CheckRefreshMenu();
801 BString string;
802 resolution_to_string(fSelected, string);
803 item = fResolutionMenu->FindItem(string.String());
805 if (item != NULL) {
806 if (!item->IsMarked())
807 item->SetMarked(true);
808 } else {
809 // this is bad luck - if mode has been set via screen references,
810 // this case cannot occur; there are three possible solutions:
811 // 1. add a new resolution to list
812 // - we had to remove it as soon as a "valid" one is selected
813 // - we don't know which frequencies/bit depths are supported
814 // - as long as we haven't the GMT formula to create
815 // parameters for any resolution given, we cannot
816 // really set current mode - it's just not in the list
817 // 2. choose nearest resolution
818 // - probably a good idea, but implies coding and testing
819 // 3. choose lowest resolution
820 // - do you really think we are so lazy? yes, we are
821 item = fResolutionMenu->ItemAt(0);
822 if (item)
823 item->SetMarked(true);
825 // okay - at least we set menu label to active resolution
826 fResolutionMenu->Superitem()->SetLabel(string.String());
829 // mark active combine mode
830 for (int32 i = 0; i < kCombineModeCount; i++) {
831 if (kCombineModes[i].mode == fSelected.combine) {
832 item = fCombineMenu->ItemAt(i);
833 if (item != NULL && !item->IsMarked())
834 item->SetMarked(true);
835 break;
839 item = fColorsMenu->ItemAt(0);
841 for (int32 i = 0, index = 0; i < kColorSpaceCount; i++) {
842 if ((fSupportedColorSpaces & (1 << i)) == 0)
843 continue;
845 if (kColorSpaces[i].space == fSelected.space) {
846 item = fColorsMenu->ItemAt(index);
847 break;
850 index++;
853 if (item != NULL && !item->IsMarked())
854 item->SetMarked(true);
856 _UpdateColorLabel();
857 _UpdateMonitorView();
858 _UpdateRefreshControl();
860 _CheckApplyEnabled();
864 /*! Reflect active mode in chosen settings */
865 void
866 ScreenWindow::_UpdateActiveMode()
868 _UpdateActiveMode(current_workspace());
872 void
873 ScreenWindow::_UpdateActiveMode(int32 workspace)
875 // Usually, this function gets called after a mode
876 // has been set manually; still, as the graphics driver
877 // is free to fiddle with mode passed, we better ask
878 // what kind of mode we actually got
879 if (fScreenMode.Get(fActive, workspace) == B_OK) {
880 fSelected = fActive;
882 _UpdateMonitor();
883 _BuildSupportedColorSpaces();
884 _UpdateControls();
889 void
890 ScreenWindow::_UpdateWorkspaceButtons()
892 uint32 columns;
893 uint32 rows;
894 BPrivate::get_workspaces_layout(&columns, &rows);
896 // Set the max values enabling/disabling the up/down arrows
898 if (rows == 1)
899 fColumnsControl->SetMaxValue(32);
900 else if (rows == 2)
901 fColumnsControl->SetMaxValue(16);
902 else if (rows <= 4)
903 fColumnsControl->SetMaxValue(8);
904 else if (rows <= 8)
905 fColumnsControl->SetMaxValue(4);
906 else if (rows <= 16)
907 fColumnsControl->SetMaxValue(2);
908 else if (rows <= 32)
909 fColumnsControl->SetMaxValue(1);
911 if (columns == 1)
912 fRowsControl->SetMaxValue(32);
913 else if (columns == 2)
914 fRowsControl->SetMaxValue(16);
915 else if (columns <= 4)
916 fRowsControl->SetMaxValue(8);
917 else if (columns <= 8)
918 fRowsControl->SetMaxValue(4);
919 else if (columns <= 16)
920 fRowsControl->SetMaxValue(2);
921 else if (columns <= 32)
922 fRowsControl->SetMaxValue(1);
926 void
927 ScreenWindow::ScreenChanged(BRect frame, color_space mode)
929 // move window on screen, if necessary
930 if (frame.right <= Frame().right
931 && frame.bottom <= Frame().bottom) {
932 MoveTo((frame.Width() - Frame().Width()) / 2,
933 (frame.Height() - Frame().Height()) / 2);
938 void
939 ScreenWindow::WorkspaceActivated(int32 workspace, bool state)
941 if (fScreenMode.GetOriginalMode(fOriginal, workspace) == B_OK) {
942 _UpdateActiveMode(workspace);
944 BMessage message(UPDATE_DESKTOP_COLOR_MSG);
945 PostMessage(&message, fMonitorView);
950 void
951 ScreenWindow::MessageReceived(BMessage* message)
953 switch (message->what) {
954 case WORKSPACE_CHECK_MSG:
955 _CheckApplyEnabled();
956 break;
958 case kMsgWorkspaceColumnsChanged:
960 uint32 newColumns = (uint32)fColumnsControl->Value();
962 uint32 rows;
963 BPrivate::get_workspaces_layout(NULL, &rows);
964 BPrivate::set_workspaces_layout(newColumns, rows);
966 _UpdateWorkspaceButtons();
967 fRowsControl->SetValue(rows);
968 // enables/disables up/down arrows
969 _CheckApplyEnabled();
971 break;
974 case kMsgWorkspaceRowsChanged:
976 uint32 newRows = (uint32)fRowsControl->Value();
978 uint32 columns;
979 BPrivate::get_workspaces_layout(&columns, NULL);
980 BPrivate::set_workspaces_layout(columns, newRows);
982 _UpdateWorkspaceButtons();
983 fColumnsControl->SetValue(columns);
984 // enables/disables up/down arrows
985 _CheckApplyEnabled();
986 break;
989 case POP_RESOLUTION_MSG:
991 message->FindInt32("width", &fSelected.width);
992 message->FindInt32("height", &fSelected.height);
994 _CheckColorMenu();
995 _CheckRefreshMenu();
997 _UpdateMonitorView();
998 _UpdateRefreshControl();
1000 _CheckApplyEnabled();
1001 break;
1004 case POP_COLORS_MSG:
1006 int32 space;
1007 if (message->FindInt32("space", &space) != B_OK)
1008 break;
1010 int32 index;
1011 if (message->FindInt32("index", &index) == B_OK
1012 && fColorsMenu->ItemAt(index) != NULL)
1013 fUserSelectedColorSpace = fColorsMenu->ItemAt(index);
1015 fSelected.space = (color_space)space;
1016 _UpdateColorLabel();
1018 _CheckApplyEnabled();
1019 break;
1022 case POP_REFRESH_MSG:
1024 message->FindFloat("refresh", &fSelected.refresh);
1025 fOtherRefresh->SetLabel(B_TRANSLATE("Other" B_UTF8_ELLIPSIS));
1026 // revert "Other…" label - it might have a refresh rate prefix
1028 _CheckApplyEnabled();
1029 break;
1032 case POP_OTHER_REFRESH_MSG:
1034 // make sure menu shows something useful
1035 _UpdateRefreshControl();
1037 float min = 0, max = 999;
1038 fScreenMode.GetRefreshLimits(fSelected, min, max);
1039 if (min < gMinRefresh)
1040 min = gMinRefresh;
1041 if (max > gMaxRefresh)
1042 max = gMaxRefresh;
1044 monitor_info info;
1045 if (fScreenMode.GetMonitorInfo(info) == B_OK) {
1046 min = max_c(info.min_vertical_frequency, min);
1047 max = min_c(info.max_vertical_frequency, max);
1050 RefreshWindow *fRefreshWindow = new RefreshWindow(
1051 fRefreshField->ConvertToScreen(B_ORIGIN), fSelected.refresh,
1052 min, max);
1053 fRefreshWindow->Show();
1054 break;
1057 case SET_CUSTOM_REFRESH_MSG:
1059 // user pressed "done" in "Other…" refresh dialog;
1060 // select the refresh rate chosen
1061 message->FindFloat("refresh", &fSelected.refresh);
1063 _UpdateRefreshControl();
1064 _CheckApplyEnabled();
1065 break;
1068 case POP_COMBINE_DISPLAYS_MSG:
1070 // new combine mode has bee chosen
1071 int32 mode;
1072 if (message->FindInt32("mode", &mode) == B_OK)
1073 fSelected.combine = (combine_mode)mode;
1075 _CheckResolutionMenu();
1076 _CheckApplyEnabled();
1077 break;
1080 case POP_SWAP_DISPLAYS_MSG:
1081 message->FindBool("swap", &fSelected.swap_displays);
1082 _CheckApplyEnabled();
1083 break;
1085 case POP_USE_LAPTOP_PANEL_MSG:
1086 message->FindBool("use", &fSelected.use_laptop_panel);
1087 _CheckApplyEnabled();
1088 break;
1090 case POP_TV_STANDARD_MSG:
1091 message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard);
1092 _CheckApplyEnabled();
1093 break;
1095 case BUTTON_LAUNCH_BACKGROUNDS_MSG:
1096 if (be_roster->Launch(kBackgroundsSignature) == B_ALREADY_RUNNING) {
1097 app_info info;
1098 be_roster->GetAppInfo(kBackgroundsSignature, &info);
1099 be_roster->ActivateApp(info.team);
1101 break;
1103 case BUTTON_DEFAULTS_MSG:
1105 // TODO: get preferred settings of screen
1106 fSelected.width = 640;
1107 fSelected.height = 480;
1108 fSelected.space = B_CMAP8;
1109 fSelected.refresh = 60.0;
1110 fSelected.combine = kCombineDisable;
1111 fSelected.swap_displays = false;
1112 fSelected.use_laptop_panel = false;
1113 fSelected.tv_standard = 0;
1115 // TODO: workspace defaults
1117 _UpdateControls();
1118 break;
1121 case BUTTON_UNDO_MSG:
1122 fUndoScreenMode.Revert();
1123 _UpdateActiveMode();
1124 break;
1126 case BUTTON_REVERT_MSG:
1128 fModified = false;
1129 fBootWorkspaceApplied = false;
1131 // ScreenMode::Revert() assumes that we first set the correct
1132 // number of workspaces
1134 BPrivate::set_workspaces_layout(fOriginalWorkspacesColumns,
1135 fOriginalWorkspacesRows);
1136 _UpdateWorkspaceButtons();
1138 fScreenMode.Revert();
1139 _UpdateActiveMode();
1141 BScreen screen(this);
1142 screen.SetBrightness(fOriginalBrightness);
1143 fBrightnessSlider->SetValue(fOriginalBrightness * 255);
1144 break;
1147 case BUTTON_APPLY_MSG:
1148 _Apply();
1149 break;
1151 case MAKE_INITIAL_MSG:
1152 // user pressed "keep" in confirmation dialog
1153 fModified = true;
1154 _UpdateActiveMode();
1155 break;
1157 case UPDATE_DESKTOP_COLOR_MSG:
1158 PostMessage(message, fMonitorView);
1159 break;
1161 case SLIDER_BRIGHTNESS_MSG:
1163 BScreen screen(this);
1164 screen.SetBrightness(message->FindInt32("be:value") / 255.f);
1165 _CheckApplyEnabled();
1166 break;
1169 default:
1170 BWindow::MessageReceived(message);
1175 status_t
1176 ScreenWindow::_WriteVesaModeFile(const screen_mode& mode) const
1178 BPath path;
1179 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
1180 if (status < B_OK)
1181 return status;
1183 path.Append("kernel/drivers");
1184 status = create_directory(path.Path(), 0755);
1185 if (status < B_OK)
1186 return status;
1188 path.Append("vesa");
1189 BFile file;
1190 status = file.SetTo(path.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE);
1191 if (status < B_OK)
1192 return status;
1194 char buffer[256];
1195 snprintf(buffer, sizeof(buffer), "mode %" B_PRId32 " %" B_PRId32 " %"
1196 B_PRId32 "\n", mode.width, mode.height, mode.BitsPerPixel());
1198 ssize_t bytesWritten = file.Write(buffer, strlen(buffer));
1199 if (bytesWritten < B_OK)
1200 return bytesWritten;
1202 return B_OK;
1206 void
1207 ScreenWindow::_BuildSupportedColorSpaces()
1209 fSupportedColorSpaces = 0;
1211 for (int32 i = 0; i < kColorSpaceCount; i++) {
1212 for (int32 j = 0; j < fScreenMode.CountModes(); j++) {
1213 if (fScreenMode.ModeAt(j).space == kColorSpaces[i].space) {
1214 fSupportedColorSpaces |= 1 << i;
1215 break;
1222 void
1223 ScreenWindow::_CheckApplyEnabled()
1225 bool applyEnabled = true;
1227 if (fSelected == fActive) {
1228 applyEnabled = false;
1229 if (fAllWorkspacesItem->IsMarked()) {
1230 screen_mode screenMode;
1231 const int32 workspaceCount = count_workspaces();
1232 for (int32 i = 0; i < workspaceCount; i++) {
1233 fScreenMode.Get(screenMode, i);
1234 if (screenMode != fSelected) {
1235 applyEnabled = true;
1236 break;
1242 fApplyButton->SetEnabled(applyEnabled);
1244 uint32 columns;
1245 uint32 rows;
1246 BPrivate::get_workspaces_layout(&columns, &rows);
1248 BScreen screen(this);
1249 float brightness = -1;
1250 screen.GetBrightness(&brightness);
1252 fRevertButton->SetEnabled(columns != fOriginalWorkspacesColumns
1253 || rows != fOriginalWorkspacesRows
1254 || brightness != fOriginalBrightness
1255 || fSelected != fOriginal);
1259 void
1260 ScreenWindow::_UpdateOriginal()
1262 BPrivate::get_workspaces_layout(&fOriginalWorkspacesColumns,
1263 &fOriginalWorkspacesRows);
1265 fScreenMode.Get(fOriginal);
1266 fScreenMode.UpdateOriginalModes();
1270 void
1271 ScreenWindow::_UpdateMonitor()
1273 monitor_info info;
1274 float diagonalInches;
1275 status_t status = fScreenMode.GetMonitorInfo(info, &diagonalInches);
1276 if (status == B_OK) {
1277 char text[512];
1278 snprintf(text, sizeof(text), "%s%s%s %g\"", info.vendor,
1279 info.name[0] ? " " : "", info.name, diagonalInches);
1281 fMonitorInfo->SetText(text);
1283 if (fMonitorInfo->IsHidden(fMonitorInfo))
1284 fMonitorInfo->Show();
1285 } else {
1286 if (!fMonitorInfo->IsHidden(fMonitorInfo))
1287 fMonitorInfo->Hide();
1290 // Add info about the graphics device
1292 accelerant_device_info deviceInfo;
1294 if (fScreenMode.GetDeviceInfo(deviceInfo) == B_OK) {
1295 BString deviceString;
1297 if (deviceInfo.name[0] && deviceInfo.chipset[0]) {
1298 deviceString.SetToFormat("%s (%s)", deviceInfo.name,
1299 deviceInfo.chipset);
1300 } else if (deviceInfo.name[0] || deviceInfo.chipset[0]) {
1301 deviceString
1302 = deviceInfo.name[0] ? deviceInfo.name : deviceInfo.chipset;
1305 fDeviceInfo->SetText(deviceString);
1309 char text[512];
1310 size_t length = 0;
1311 text[0] = 0;
1313 if (status == B_OK) {
1314 if (info.min_horizontal_frequency != 0
1315 && info.min_vertical_frequency != 0
1316 && info.max_pixel_clock != 0) {
1317 length = snprintf(text, sizeof(text),
1318 B_TRANSLATE("Horizonal frequency:\t%lu - %lu kHz\n"
1319 "Vertical frequency:\t%lu - %lu Hz\n\n"
1320 "Maximum pixel clock:\t%g MHz"),
1321 info.min_horizontal_frequency, info.max_horizontal_frequency,
1322 info.min_vertical_frequency, info.max_vertical_frequency,
1323 info.max_pixel_clock / 1000.0);
1325 if (info.serial_number[0] && length < sizeof(text)) {
1326 length += snprintf(text + length, sizeof(text) - length,
1327 B_TRANSLATE("%sSerial no.: %s"), length ? "\n\n" : "",
1328 info.serial_number);
1329 if (info.produced.week != 0 && info.produced.year != 0
1330 && length < sizeof(text)) {
1331 length += snprintf(text + length, sizeof(text) - length,
1332 " (%u/%u)", info.produced.week, info.produced.year);
1337 if (text[0])
1338 fMonitorView->SetToolTip(text);
1342 void
1343 ScreenWindow::_UpdateColorLabel()
1345 BString string;
1346 string << fSelected.BitsPerPixel() << " " << B_TRANSLATE("bits/pixel");
1347 fColorsMenu->Superitem()->SetLabel(string.String());
1351 void
1352 ScreenWindow::_Apply()
1354 // make checkpoint, so we can undo these changes
1355 fUndoScreenMode.UpdateOriginalModes();
1357 status_t status = fScreenMode.Set(fSelected);
1358 if (status == B_OK) {
1359 // use the mode that has eventually been set and
1360 // thus we know to be working; it can differ from
1361 // the mode selected by user due to hardware limitation
1362 display_mode newMode;
1363 BScreen screen(this);
1364 screen.GetMode(&newMode);
1366 if (fAllWorkspacesItem->IsMarked()) {
1367 int32 originatingWorkspace = current_workspace();
1368 const int32 workspaceCount = count_workspaces();
1369 for (int32 i = 0; i < workspaceCount; i++) {
1370 if (i != originatingWorkspace)
1371 screen.SetMode(i, &newMode, true);
1373 fBootWorkspaceApplied = true;
1374 } else {
1375 if (current_workspace() == 0)
1376 fBootWorkspaceApplied = true;
1379 fActive = fSelected;
1381 // TODO: only show alert when this is an unknown mode
1382 BAlert* window = new AlertWindow(this);
1383 window->Go(NULL);
1384 } else {
1385 char message[256];
1386 snprintf(message, sizeof(message),
1387 B_TRANSLATE("The screen mode could not be set:\n\t%s\n"),
1388 screen_errors(status));
1389 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), message,
1390 B_TRANSLATE("OK"), NULL, NULL,
1391 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1392 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1393 alert->Go();