| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ash/display/display_util.h" | 5 #include "ash/display/display_util.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "ash/common/new_window_delegate.h" | 9 #include "ash/common/new_window_delegate.h" |
| 10 #include "ash/common/system/system_notifier.h" | 10 #include "ash/common/system/system_notifier.h" |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 52 WmShell::Get()->new_window_delegate()->OpenFeedbackPage(); | 52 WmShell::Get()->new_window_delegate()->OpenFeedbackPage(); |
| 53 } | 53 } |
| 54 | 54 |
| 55 private: | 55 private: |
| 56 // Private destructor since NotificationDelegate is ref-counted. | 56 // Private destructor since NotificationDelegate is ref-counted. |
| 57 ~DisplayErrorNotificationDelegate() override = default; | 57 ~DisplayErrorNotificationDelegate() override = default; |
| 58 | 58 |
| 59 DISALLOW_COPY_AND_ASSIGN(DisplayErrorNotificationDelegate); | 59 DISALLOW_COPY_AND_ASSIGN(DisplayErrorNotificationDelegate); |
| 60 }; | 60 }; |
| 61 | 61 |
| 62 // List of value UI Scale values. Scales for 2x are equivalent to 640, | |
| 63 // 800, 1024, 1280, 1440, 1600 and 1920 pixel width respectively on | |
| 64 // 2560 pixel width 2x density display. Please see crbug.com/233375 | |
| 65 // for the full list of resolutions. | |
| 66 const float kUIScalesFor2x[] = {0.5f, 0.625f, 0.8f, 1.0f, | |
| 67 1.125f, 1.25f, 1.5f, 2.0f}; | |
| 68 const float kUIScalesFor1_25x[] = {0.5f, 0.625f, 0.8f, 1.0f, 1.25f}; | |
| 69 const float kUIScalesFor1280[] = {0.5f, 0.625f, 0.8f, 1.0f, 1.125f}; | |
| 70 const float kUIScalesFor1366[] = {0.5f, 0.6f, 0.75f, 1.0f, 1.125f}; | |
| 71 | |
| 72 std::vector<float> GetScalesForDisplay( | |
| 73 const scoped_refptr<display::ManagedDisplayMode>& native_mode) { | |
| 74 #define ASSIGN_ARRAY(v, a) v.assign(a, a + arraysize(a)) | |
| 75 | |
| 76 std::vector<float> ret; | |
| 77 if (native_mode->device_scale_factor() == 2.0f) { | |
| 78 ASSIGN_ARRAY(ret, kUIScalesFor2x); | |
| 79 return ret; | |
| 80 } else if (native_mode->device_scale_factor() == 1.25f) { | |
| 81 ASSIGN_ARRAY(ret, kUIScalesFor1_25x); | |
| 82 return ret; | |
| 83 } | |
| 84 switch (native_mode->size().width()) { | |
| 85 case 1280: | |
| 86 ASSIGN_ARRAY(ret, kUIScalesFor1280); | |
| 87 break; | |
| 88 case 1366: | |
| 89 ASSIGN_ARRAY(ret, kUIScalesFor1366); | |
| 90 break; | |
| 91 default: | |
| 92 ASSIGN_ARRAY(ret, kUIScalesFor1280); | |
| 93 #if defined(OS_CHROMEOS) | |
| 94 if (base::SysInfo::IsRunningOnChromeOS()) | |
| 95 NOTREACHED() << "Unknown resolution:" << native_mode->size().ToString(); | |
| 96 #endif | |
| 97 } | |
| 98 return ret; | |
| 99 } | |
| 100 | |
| 101 struct ScaleComparator { | |
| 102 explicit ScaleComparator(float s) : scale(s) {} | |
| 103 | |
| 104 bool operator()( | |
| 105 const scoped_refptr<display::ManagedDisplayMode>& mode) const { | |
| 106 const float kEpsilon = 0.0001f; | |
| 107 return std::abs(scale - mode->ui_scale()) < kEpsilon; | |
| 108 } | |
| 109 float scale; | |
| 110 }; | |
| 111 | 62 |
| 112 void ConvertPointFromScreenToNative(aura::WindowTreeHost* host, | 63 void ConvertPointFromScreenToNative(aura::WindowTreeHost* host, |
| 113 gfx::Point* point) { | 64 gfx::Point* point) { |
| 114 ::wm::ConvertPointFromScreen(host->window(), point); | 65 ::wm::ConvertPointFromScreen(host->window(), point); |
| 115 host->ConvertPointToNativeScreen(point); | 66 host->ConvertPointToNativeScreen(point); |
| 116 } | 67 } |
| 117 | 68 |
| 118 scoped_refptr<display::ManagedDisplayMode> GetDisplayModeForUIScale( | 69 scoped_refptr<display::ManagedDisplayMode> GetDisplayModeForUIScale( |
| 119 const display::ManagedDisplayInfo& info, | 70 const display::ManagedDisplayInfo& info, |
| 120 float ui_scale) { | 71 float ui_scale) { |
| 121 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = | 72 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = |
| 122 info.display_modes(); | 73 info.display_modes(); |
| 123 auto iter = std::find_if( | 74 auto iter = std::find_if( |
| 124 modes.begin(), modes.end(), | 75 modes.begin(), modes.end(), |
| 125 [ui_scale](const scoped_refptr<display::ManagedDisplayMode>& mode) { | 76 [ui_scale](const scoped_refptr<display::ManagedDisplayMode>& mode) { |
| 126 return mode->ui_scale() == ui_scale; | 77 return mode->ui_scale() == ui_scale; |
| 127 }); | 78 }); |
| 128 if (iter == modes.end()) | 79 if (iter == modes.end()) |
| 129 return scoped_refptr<display::ManagedDisplayMode>(); | 80 return scoped_refptr<display::ManagedDisplayMode>(); |
| 130 return *iter; | 81 return *iter; |
| 131 } | 82 } |
| 132 | 83 |
| 133 scoped_refptr<display::ManagedDisplayMode> FindNextMode( | |
| 134 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes, | |
| 135 size_t index, | |
| 136 bool up) { | |
| 137 DCHECK_LT(index, modes.size()); | |
| 138 size_t new_index = index; | |
| 139 if (up && (index + 1 < modes.size())) | |
| 140 ++new_index; | |
| 141 else if (!up && index != 0) | |
| 142 --new_index; | |
| 143 return modes[new_index]; | |
| 144 } | |
| 145 | |
| 146 } // namespace | 84 } // namespace |
| 147 | 85 |
| 148 display::ManagedDisplayInfo::ManagedDisplayModeList | |
| 149 CreateInternalManagedDisplayModeList( | |
| 150 const scoped_refptr<display::ManagedDisplayMode>& native_mode) { | |
| 151 display::ManagedDisplayInfo::ManagedDisplayModeList display_mode_list; | |
| 152 | |
| 153 float native_ui_scale = (native_mode->device_scale_factor() == 1.25f) | |
| 154 ? 1.0f | |
| 155 : native_mode->device_scale_factor(); | |
| 156 for (float ui_scale : GetScalesForDisplay(native_mode)) { | |
| 157 scoped_refptr<display::ManagedDisplayMode> mode( | |
| 158 new display::ManagedDisplayMode( | |
| 159 native_mode->size(), native_mode->refresh_rate(), | |
| 160 native_mode->is_interlaced(), ui_scale == native_ui_scale, ui_scale, | |
| 161 native_mode->device_scale_factor())); | |
| 162 display_mode_list.push_back(mode); | |
| 163 } | |
| 164 return display_mode_list; | |
| 165 } | |
| 166 | |
| 167 display::ManagedDisplayInfo::ManagedDisplayModeList | |
| 168 CreateUnifiedManagedDisplayModeList( | |
| 169 const scoped_refptr<display::ManagedDisplayMode>& native_mode, | |
| 170 const std::set<std::pair<float, float>>& dsf_scale_list) { | |
| 171 display::ManagedDisplayInfo::ManagedDisplayModeList display_mode_list; | |
| 172 | |
| 173 for (auto& pair : dsf_scale_list) { | |
| 174 gfx::SizeF scaled_size(native_mode->size()); | |
| 175 scaled_size.Scale(pair.second); | |
| 176 scoped_refptr<display::ManagedDisplayMode> mode( | |
| 177 new display::ManagedDisplayMode( | |
| 178 gfx::ToFlooredSize(scaled_size), native_mode->refresh_rate(), | |
| 179 native_mode->is_interlaced(), false /* native */, | |
| 180 native_mode->ui_scale(), pair.first /* device_scale_factor */)); | |
| 181 display_mode_list.push_back(mode); | |
| 182 } | |
| 183 // Sort the mode by the size in DIP. | |
| 184 std::sort(display_mode_list.begin(), display_mode_list.end(), | |
| 185 [](const scoped_refptr<display::ManagedDisplayMode>& a, | |
| 186 const scoped_refptr<display::ManagedDisplayMode>& b) { | |
| 187 return a->GetSizeInDIP(false).GetArea() < | |
| 188 b->GetSizeInDIP(false).GetArea(); | |
| 189 }); | |
| 190 return display_mode_list; | |
| 191 } | |
| 192 | |
| 193 scoped_refptr<display::ManagedDisplayMode> GetDisplayModeForResolution( | |
| 194 const display::ManagedDisplayInfo& info, | |
| 195 const gfx::Size& resolution) { | |
| 196 if (display::Display::IsInternalDisplayId(info.id())) | |
| 197 return scoped_refptr<display::ManagedDisplayMode>(); | |
| 198 | |
| 199 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = | |
| 200 info.display_modes(); | |
| 201 DCHECK_NE(0u, modes.size()); | |
| 202 scoped_refptr<display::ManagedDisplayMode> target_mode; | |
| 203 display::ManagedDisplayInfo::ManagedDisplayModeList::const_iterator iter = | |
| 204 std::find_if( | |
| 205 modes.begin(), modes.end(), | |
| 206 [resolution](const scoped_refptr<display::ManagedDisplayMode>& mode) { | |
| 207 return mode->size() == resolution; | |
| 208 }); | |
| 209 if (iter == modes.end()) { | |
| 210 LOG(WARNING) << "Unsupported resolution was requested:" | |
| 211 << resolution.ToString(); | |
| 212 return scoped_refptr<display::ManagedDisplayMode>(); | |
| 213 } | |
| 214 return *iter; | |
| 215 } | |
| 216 | |
| 217 scoped_refptr<display::ManagedDisplayMode> GetDisplayModeForNextUIScale( | |
| 218 const display::ManagedDisplayInfo& info, | |
| 219 bool up) { | |
| 220 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); | |
| 221 if (!display_manager->IsActiveDisplayId(info.id()) || | |
| 222 !display::Display::IsInternalDisplayId(info.id())) { | |
| 223 return scoped_refptr<display::ManagedDisplayMode>(); | |
| 224 } | |
| 225 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = | |
| 226 info.display_modes(); | |
| 227 ScaleComparator comparator(info.configured_ui_scale()); | |
| 228 auto iter = std::find_if(modes.begin(), modes.end(), comparator); | |
| 229 return FindNextMode(modes, iter - modes.begin(), up); | |
| 230 } | |
| 231 | |
| 232 scoped_refptr<display::ManagedDisplayMode> GetDisplayModeForNextResolution( | |
| 233 const display::ManagedDisplayInfo& info, | |
| 234 bool up) { | |
| 235 if (display::Display::IsInternalDisplayId(info.id())) | |
| 236 return scoped_refptr<display::ManagedDisplayMode>(); | |
| 237 | |
| 238 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = | |
| 239 info.display_modes(); | |
| 240 scoped_refptr<display::ManagedDisplayMode> tmp = | |
| 241 new display::ManagedDisplayMode(info.size_in_pixel(), 0.0, false, false, | |
| 242 1.0, info.device_scale_factor()); | |
| 243 gfx::Size resolution = tmp->GetSizeInDIP(false); | |
| 244 | |
| 245 auto iter = std::find_if( | |
| 246 modes.begin(), modes.end(), | |
| 247 [resolution](const scoped_refptr<display::ManagedDisplayMode>& mode) { | |
| 248 return mode->GetSizeInDIP(false) == resolution; | |
| 249 }); | |
| 250 return FindNextMode(modes, iter - modes.begin(), up); | |
| 251 } | |
| 252 | |
| 253 bool SetDisplayUIScale(int64_t id, float ui_scale) { | 86 bool SetDisplayUIScale(int64_t id, float ui_scale) { |
| 254 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); | 87 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); |
| 255 if (!display_manager->IsActiveDisplayId(id) || | 88 if (!display_manager->IsActiveDisplayId(id) || |
| 256 !display::Display::IsInternalDisplayId(id)) { | 89 !display::Display::IsInternalDisplayId(id)) { |
| 257 return false; | 90 return false; |
| 258 } | 91 } |
| 259 const display::ManagedDisplayInfo& info = display_manager->GetDisplayInfo(id); | 92 const display::ManagedDisplayInfo& info = display_manager->GetDisplayInfo(id); |
| 260 | 93 |
| 261 scoped_refptr<display::ManagedDisplayMode> mode = | 94 scoped_refptr<display::ManagedDisplayMode> mode = |
| 262 GetDisplayModeForUIScale(info, ui_scale); | 95 GetDisplayModeForUIScale(info, ui_scale); |
| 263 if (!mode) | 96 if (!mode) |
| 264 return false; | 97 return false; |
| 265 return display_manager->SetDisplayMode(id, mode); | 98 return display_manager->SetDisplayMode(id, mode); |
| 266 } | 99 } |
| 267 | 100 |
| 268 bool HasDisplayModeForUIScale(const display::ManagedDisplayInfo& info, | |
| 269 float ui_scale) { | |
| 270 ScaleComparator comparator(ui_scale); | |
| 271 const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = | |
| 272 info.display_modes(); | |
| 273 return std::find_if(modes.begin(), modes.end(), comparator) != modes.end(); | |
| 274 } | |
| 275 | |
| 276 bool ComputeBoundary(const display::Display& a_display, | |
| 277 const display::Display& b_display, | |
| 278 gfx::Rect* a_edge_in_screen, | |
| 279 gfx::Rect* b_edge_in_screen) { | |
| 280 const gfx::Rect& a_bounds = a_display.bounds(); | |
| 281 const gfx::Rect& b_bounds = b_display.bounds(); | |
| 282 | |
| 283 // Find touching side. | |
| 284 int rx = std::max(a_bounds.x(), b_bounds.x()); | |
| 285 int ry = std::max(a_bounds.y(), b_bounds.y()); | |
| 286 int rr = std::min(a_bounds.right(), b_bounds.right()); | |
| 287 int rb = std::min(a_bounds.bottom(), b_bounds.bottom()); | |
| 288 | |
| 289 display::DisplayPlacement::Position position; | |
| 290 if ((rb - ry) == 0) { | |
| 291 // top bottom | |
| 292 if (a_bounds.bottom() == b_bounds.y()) { | |
| 293 position = display::DisplayPlacement::BOTTOM; | |
| 294 } else if (a_bounds.y() == b_bounds.bottom()) { | |
| 295 position = display::DisplayPlacement::TOP; | |
| 296 } else { | |
| 297 return false; | |
| 298 } | |
| 299 } else { | |
| 300 // left right | |
| 301 if (a_bounds.right() == b_bounds.x()) { | |
| 302 position = display::DisplayPlacement::RIGHT; | |
| 303 } else if (a_bounds.x() == b_bounds.right()) { | |
| 304 position = display::DisplayPlacement::LEFT; | |
| 305 } else { | |
| 306 DCHECK_NE(rr, rx); | |
| 307 return false; | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 switch (position) { | |
| 312 case display::DisplayPlacement::TOP: | |
| 313 case display::DisplayPlacement::BOTTOM: { | |
| 314 int left = std::max(a_bounds.x(), b_bounds.x()); | |
| 315 int right = std::min(a_bounds.right(), b_bounds.right()); | |
| 316 if (position == display::DisplayPlacement::TOP) { | |
| 317 a_edge_in_screen->SetRect(left, a_bounds.y(), right - left, 1); | |
| 318 b_edge_in_screen->SetRect(left, b_bounds.bottom() - 1, right - left, 1); | |
| 319 } else { | |
| 320 a_edge_in_screen->SetRect(left, a_bounds.bottom() - 1, right - left, 1); | |
| 321 b_edge_in_screen->SetRect(left, b_bounds.y(), right - left, 1); | |
| 322 } | |
| 323 break; | |
| 324 } | |
| 325 case display::DisplayPlacement::LEFT: | |
| 326 case display::DisplayPlacement::RIGHT: { | |
| 327 int top = std::max(a_bounds.y(), b_bounds.y()); | |
| 328 int bottom = std::min(a_bounds.bottom(), b_bounds.bottom()); | |
| 329 if (position == display::DisplayPlacement::LEFT) { | |
| 330 a_edge_in_screen->SetRect(a_bounds.x(), top, 1, bottom - top); | |
| 331 b_edge_in_screen->SetRect(b_bounds.right() - 1, top, 1, bottom - top); | |
| 332 } else { | |
| 333 a_edge_in_screen->SetRect(a_bounds.right() - 1, top, 1, bottom - top); | |
| 334 b_edge_in_screen->SetRect(b_bounds.x(), top, 1, bottom - top); | |
| 335 } | |
| 336 break; | |
| 337 } | |
| 338 } | |
| 339 return true; | |
| 340 } | |
| 341 | |
| 342 gfx::Rect GetNativeEdgeBounds(AshWindowTreeHost* ash_host, | 101 gfx::Rect GetNativeEdgeBounds(AshWindowTreeHost* ash_host, |
| 343 const gfx::Rect& bounds_in_screen) { | 102 const gfx::Rect& bounds_in_screen) { |
| 344 aura::WindowTreeHost* host = ash_host->AsWindowTreeHost(); | 103 aura::WindowTreeHost* host = ash_host->AsWindowTreeHost(); |
| 345 gfx::Rect native_bounds = host->GetBounds(); | 104 gfx::Rect native_bounds = host->GetBounds(); |
| 346 native_bounds.Inset(ash_host->GetHostInsets()); | 105 native_bounds.Inset(ash_host->GetHostInsets()); |
| 347 gfx::Point start_in_native = bounds_in_screen.origin(); | 106 gfx::Point start_in_native = bounds_in_screen.origin(); |
| 348 gfx::Point end_in_native = bounds_in_screen.bottom_right(); | 107 gfx::Point end_in_native = bounds_in_screen.bottom_right(); |
| 349 | 108 |
| 350 ConvertPointFromScreenToNative(host, &start_in_native); | 109 ConvertPointFromScreenToNative(host, &start_in_native); |
| 351 ConvertPointFromScreenToNative(host, &end_in_native); | 110 ConvertPointFromScreenToNative(host, &end_in_native); |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 409 &new_point_in_screen); | 168 &new_point_in_screen); |
| 410 } else { | 169 } else { |
| 411 new_point_in_screen = point_in_native; | 170 new_point_in_screen = point_in_native; |
| 412 host->ConvertPointFromNativeScreen(&new_point_in_screen); | 171 host->ConvertPointFromNativeScreen(&new_point_in_screen); |
| 413 ::wm::ConvertPointToScreen(host->window(), &new_point_in_screen); | 172 ::wm::ConvertPointToScreen(host->window(), &new_point_in_screen); |
| 414 } | 173 } |
| 415 aura::Env::GetInstance()->set_last_mouse_location(new_point_in_screen); | 174 aura::Env::GetInstance()->set_last_mouse_location(new_point_in_screen); |
| 416 } | 175 } |
| 417 } | 176 } |
| 418 | 177 |
| 419 int FindDisplayIndexContainingPoint( | |
| 420 const std::vector<display::Display>& displays, | |
| 421 const gfx::Point& point_in_screen) { | |
| 422 auto iter = std::find_if(displays.begin(), displays.end(), | |
| 423 [point_in_screen](const display::Display& display) { | |
| 424 return display.bounds().Contains(point_in_screen); | |
| 425 }); | |
| 426 return iter == displays.end() ? -1 : (iter - displays.begin()); | |
| 427 } | |
| 428 | |
| 429 display::DisplayIdList CreateDisplayIdList(const display::DisplayList& list) { | |
| 430 return GenerateDisplayIdList( | |
| 431 list.begin(), list.end(), | |
| 432 [](const display::Display& display) { return display.id(); }); | |
| 433 } | |
| 434 | |
| 435 void SortDisplayIdList(display::DisplayIdList* ids) { | |
| 436 std::sort(ids->begin(), ids->end(), | |
| 437 [](int64_t a, int64_t b) { return CompareDisplayIds(a, b); }); | |
| 438 } | |
| 439 | |
| 440 std::string DisplayIdListToString(const display::DisplayIdList& list) { | |
| 441 std::stringstream s; | |
| 442 const char* sep = ""; | |
| 443 for (int64_t id : list) { | |
| 444 s << sep << id; | |
| 445 sep = ","; | |
| 446 } | |
| 447 return s.str(); | |
| 448 } | |
| 449 | |
| 450 bool CompareDisplayIds(int64_t id1, int64_t id2) { | |
| 451 DCHECK_NE(id1, id2); | |
| 452 // Output index is stored in the first 8 bits. See GetDisplayIdFromEDID | |
| 453 // in edid_parser.cc. | |
| 454 int index_1 = id1 & 0xFF; | |
| 455 int index_2 = id2 & 0xFF; | |
| 456 DCHECK_NE(index_1, index_2) << id1 << " and " << id2; | |
| 457 return display::Display::IsInternalDisplayId(id1) || | |
| 458 (index_1 < index_2 && !display::Display::IsInternalDisplayId(id2)); | |
| 459 } | |
| 460 | 178 |
| 461 #if defined(OS_CHROMEOS) | 179 #if defined(OS_CHROMEOS) |
| 462 void ShowDisplayErrorNotification(int message_id) { | 180 void ShowDisplayErrorNotification(int message_id) { |
| 463 // Always remove the notification to make sure the notification appears | 181 // Always remove the notification to make sure the notification appears |
| 464 // as a popup in any situation. | 182 // as a popup in any situation. |
| 465 message_center::MessageCenter::Get()->RemoveNotification( | 183 message_center::MessageCenter::Get()->RemoveNotification( |
| 466 kDisplayErrorNotificationId, false /* by_user */); | 184 kDisplayErrorNotificationId, false /* by_user */); |
| 467 | 185 |
| 468 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); | 186 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 469 std::unique_ptr<message_center::Notification> notification( | 187 std::unique_ptr<message_center::Notification> notification( |
| (...skipping 17 matching lines...) Expand all Loading... |
| 487 message_center::NotificationList::Notifications notifications = | 205 message_center::NotificationList::Notifications notifications = |
| 488 message_center::MessageCenter::Get()->GetVisibleNotifications(); | 206 message_center::MessageCenter::Get()->GetVisibleNotifications(); |
| 489 for (auto* const notification : notifications) { | 207 for (auto* const notification : notifications) { |
| 490 if (notification->id() == kDisplayErrorNotificationId) | 208 if (notification->id() == kDisplayErrorNotificationId) |
| 491 return notification->message(); | 209 return notification->message(); |
| 492 } | 210 } |
| 493 return base::string16(); | 211 return base::string16(); |
| 494 } | 212 } |
| 495 | 213 |
| 496 } // namespace ash | 214 } // namespace ash |
| OLD | NEW |