From 0970b75122c51bb621b9435aa558b2c74ff52e9f Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Thu, 8 Oct 2020 16:01:38 +0200 Subject: Adaptations for Chromium 84 Change-Id: I359805d0bea84147fca6de2e2c7b17b4dcb17bc7 Reviewed-by: Peter Varga --- src/core/render_widget_host_view_qt.cpp | 43 +++++++++++++++++---------------- 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'src/core/render_widget_host_view_qt.cpp') diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 0f53228c7..3610dd96e 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -936,16 +936,16 @@ void RenderWidgetHostViewQt::OnGestureEvent(const ui::GestureEventData& gesture) if (m_touchSelectionController && m_touchSelectionControllerClient) { switch (event.GetType()) { - case blink::WebInputEvent::kGestureLongPress: + case blink::WebInputEvent::Type::kGestureLongPress: m_touchSelectionController->HandleLongPressEvent(event.TimeStamp(), event.PositionInWidget()); break; - case blink::WebInputEvent::kGestureTap: + case blink::WebInputEvent::Type::kGestureTap: m_touchSelectionController->HandleTapEvent(event.PositionInWidget(), event.data.tap.tap_count); break; - case blink::WebInputEvent::kGestureScrollBegin: + case blink::WebInputEvent::Type::kGestureScrollBegin: m_touchSelectionControllerClient->onScrollBegin(); break; - case blink::WebInputEvent::kGestureScrollEnd: + case blink::WebInputEvent::Type::kGestureScrollEnd: m_touchSelectionControllerClient->onScrollEnd(); break; default: @@ -1231,9 +1231,10 @@ void RenderWidgetHostViewQt::closePopup() host()->LostFocus(); } -void RenderWidgetHostViewQt::ProcessAckedTouchEvent(const content::TouchEventWithLatencyInfo &touch, content::InputEventAckState ack_result) { +void RenderWidgetHostViewQt::ProcessAckedTouchEvent(const content::TouchEventWithLatencyInfo &touch, blink::mojom::InputEventResultState ack_result) +{ Q_UNUSED(touch); - const bool eventConsumed = ack_result == content::INPUT_EVENT_ACK_STATE_CONSUMED; + const bool eventConsumed = ack_result == blink::mojom::InputEventResultState::kConsumed; const bool isSetNonBlocking = content::InputEventAckStateIsSetNonBlocking(ack_result); m_gestureProvider.OnTouchEventAck(touch.event.unique_touch_event_id, eventConsumed, isSetNonBlocking); } @@ -1329,17 +1330,17 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) return; content::NativeWebKeyboardEvent webEvent = WebEventFactory::toWebKeyboardEvent(ev); - if (webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && !m_editCommand.empty()) { + if (webEvent.GetType() == blink::WebInputEvent::Type::kRawKeyDown && !m_editCommand.empty()) { ui::LatencyInfo latency; latency.set_source_event_type(ui::SourceEventType::KEY_PRESS); - content::EditCommands commands; - commands.emplace_back(m_editCommand, ""); + std::vector commands; + commands.emplace_back(blink::mojom::EditCommand::New(m_editCommand, "")); m_editCommand.clear(); - host()->ForwardKeyboardEventWithCommands(webEvent, latency, &commands, nullptr); + host()->ForwardKeyboardEventWithCommands(webEvent, latency, std::move(commands), nullptr); return; } - bool keyDownTextInsertion = webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && webEvent.text[0]; + bool keyDownTextInsertion = webEvent.GetType() == blink::WebInputEvent::Type::kRawKeyDown && webEvent.text[0]; webEvent.skip_in_browser = keyDownTextInsertion; host()->ForwardKeyboardEvent(webEvent); @@ -1348,7 +1349,7 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) // The RawKeyDown is skipped on the way back (see above). // The same os_event will be set on both NativeWebKeyboardEvents. webEvent.skip_in_browser = false; - webEvent.SetType(blink::WebInputEvent::kChar); + webEvent.SetType(blink::WebInputEvent::Type::kChar); host()->ForwardKeyboardEvent(webEvent); } } @@ -1530,7 +1531,7 @@ void RenderWidgetHostViewQt::handleWheelEvent(QWheelEvent *ev) m_pendingWheelEvents.append(WebEventFactory::toWebWheelEvent(ev)); } -void RenderWidgetHostViewQt::WheelEventAck(const blink::WebMouseWheelEvent &event, content::InputEventAckState /*ack_result*/) +void RenderWidgetHostViewQt::WheelEventAck(const blink::WebMouseWheelEvent &event, blink::mojom::InputEventResultState /*ack_result*/) { if (event.phase == blink::WebMouseWheelEvent::kPhaseEnded) return; @@ -1544,14 +1545,14 @@ void RenderWidgetHostViewQt::WheelEventAck(const blink::WebMouseWheelEvent &even } } -void RenderWidgetHostViewQt::GestureEventAck(const blink::WebGestureEvent &event, content::InputEventAckState ack_result) +void RenderWidgetHostViewQt::GestureEventAck(const blink::WebGestureEvent &event, blink::mojom::InputEventResultState ack_result) { // Forward unhandled scroll events back as wheel events - if (event.GetType() != blink::WebInputEvent::kGestureScrollUpdate) + if (event.GetType() != blink::WebInputEvent::Type::kGestureScrollUpdate) return; switch (ack_result) { - case content::INPUT_EVENT_ACK_STATE_NOT_CONSUMED: - case content::INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: + case blink::mojom::InputEventResultState::kNotConsumed: + case blink::mojom::InputEventResultState::kNoConsumerExists: WebEventFactory::sendUnhandledWheelEvent(event, delegate()); break; default: @@ -1755,14 +1756,14 @@ void RenderWidgetHostViewQt::handlePointerEvent(T *event) // Currently WebMouseEvent is a subclass of WebPointerProperties, so basically // tablet events are mouse events with extra properties. blink::WebMouseEvent webEvent = WebEventFactory::toWebMouseEvent(event); - if ((webEvent.GetType() == blink::WebInputEvent::kMouseDown || webEvent.GetType() == blink::WebInputEvent::kMouseUp) + if ((webEvent.GetType() == blink::WebInputEvent::Type::kMouseDown || webEvent.GetType() == blink::WebInputEvent::Type::kMouseUp) && webEvent.button == blink::WebMouseEvent::Button::kNoButton) { // Blink can only handle the 5 main mouse-buttons and may assert when processing mouse-down for no button. LOG(INFO) << "Unhandled mouse button"; return; } - if (webEvent.GetType() == blink::WebInputEvent::kMouseDown) { + if (webEvent.GetType() == blink::WebInputEvent::Type::kMouseDown) { if (event->button() != m_clickHelper.lastPressButton || (event->timestamp() - m_clickHelper.lastPressTimestamp > static_cast(qGuiApp->styleHints()->mouseDoubleClickInterval())) || (event->pos() - m_clickHelper.lastPressPosition).manhattanLength() > qGuiApp->styleHints()->startDragDistance() @@ -1775,7 +1776,7 @@ void RenderWidgetHostViewQt::handlePointerEvent(T *event) m_clickHelper.lastPressPosition = QPointF(event->pos()).toPoint(); } - if (webEvent.GetType() == blink::WebInputEvent::kMouseUp) + if (webEvent.GetType() == blink::WebInputEvent::Type::kMouseUp) webEvent.click_count = m_clickHelper.clickCounter; webEvent.movement_x = event->globalX() - m_previousMousePosition.x(); @@ -1786,7 +1787,7 @@ void RenderWidgetHostViewQt::handlePointerEvent(T *event) else m_previousMousePosition = event->globalPos(); - if (m_imeInProgress && webEvent.GetType() == blink::WebInputEvent::kMouseDown) { + if (m_imeInProgress && webEvent.GetType() == blink::WebInputEvent::Type::kMouseDown) { m_imeInProgress = false; // Tell input method to commit the pre-edit string entered so far, and finish the // composition operation. -- cgit v1.2.3 From ffb13e6927ae14a7185bb45ee0dfd03973e5ebb0 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Tue, 13 Oct 2020 08:38:03 +0200 Subject: Adaptations for Chromium 85 Change-Id: I33c1af7c431055d95e0fb540246765cce684de15 Reviewed-by: Peter Varga --- src/core/render_widget_host_view_qt.cpp | 39 +++++++++++---------------------- 1 file changed, 13 insertions(+), 26 deletions(-) (limited to 'src/core/render_widget_host_view_qt.cpp') diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 3610dd96e..93281f4c9 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -60,6 +60,7 @@ #include "content/browser/compositor/image_transport_factory.h" #include "content/browser/compositor/surface_utils.h" #include "content/browser/frame_host/frame_tree.h" +#include "content/browser/frame_host/frame_tree_node.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/renderer_host/input/synthetic_gesture_target.h" #include "content/browser/renderer_host/render_view_host_delegate.h" @@ -773,7 +774,7 @@ void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputMana Q_UNUSED(updated_view); Q_UNUSED(did_update_state); - const content::TextInputState *state = text_input_manager_->GetTextInputState(); + const ui::mojom::TextInputState *state = text_input_manager_->GetTextInputState(); if (!state) { m_delegate->inputMethodStateChanged(false /*editorVisible*/, false /*passwordInput*/); m_delegate->setInputMethodHints(Qt::ImhNone); @@ -788,14 +789,14 @@ void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputMana #endif m_surroundingText = toQt(state->value); // Remove IME composition text from the surrounding text - if (state->composition_start != -1 && state->composition_end != -1) - m_surroundingText.remove(state->composition_start, state->composition_end - state->composition_start); + if (state->composition.has_value()) + m_surroundingText.remove(state->composition->start(), state->composition->end() - state->composition->start()); // In case of text selection, the update is expected in RenderWidgetHostViewQt::selectionChanged(). if (GetSelectedText().empty()) { // At this point it is unknown whether the text input state has been updated due to a text selection. // Keep the cursor position updated for cursor movements too. - m_cursorPosition = state->selection_start; + m_cursorPosition = state->selection.start(); m_delegate->inputMethodStateChanged(type != ui::TEXT_INPUT_TYPE_NONE, type == ui::TEXT_INPUT_TYPE_PASSWORD); } @@ -805,7 +806,7 @@ void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputMana } // Ignore selection change triggered by ime composition unless it clears an actual text selection - if (state->composition_start != -1 && m_emptyPreviousSelection) { + if (state->composition.has_value() && m_emptyPreviousSelection) { m_imState = 0; return; } @@ -877,7 +878,7 @@ void RenderWidgetHostViewQt::selectionChanged() // RenderWidgetHostViewQt::OnUpdateTextInputStateCalled() does not update the cursor position // if the selection is cleared because TextInputState changes before the TextSelection change. Q_ASSERT(text_input_manager_->GetTextInputState()); - m_cursorPosition = text_input_manager_->GetTextInputState()->selection_start; + m_cursorPosition = text_input_manager_->GetTextInputState()->selection.start(); m_delegate->inputMethodStateChanged(true /*editorVisible*/, type == ui::TEXT_INPUT_TYPE_PASSWORD); m_anchorPositionWithinSelection = m_cursorPosition; @@ -1434,9 +1435,8 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) } if (hasSelection) { - content::mojom::FrameInputHandler *frameInputHandler = getFrameInputHandler(); - if (frameInputHandler) - frameInputHandler->SetEditableSelectionOffsets(selectionRange.start(), selectionRange.end()); + if (auto *frameWidgetInputHandler = getFrameWidgetInputHandler()) + frameWidgetInputHandler->SetEditableSelectionOffsets(selectionRange.start(), selectionRange.end()); } int replacementLength = ev->replacementLength(); @@ -1829,26 +1829,13 @@ void RenderWidgetHostViewQt::handleFocusEvent(QFocusEvent *ev) } } -content::RenderFrameHost *RenderWidgetHostViewQt::getFocusedFrameHost() +blink::mojom::FrameWidgetInputHandler *RenderWidgetHostViewQt::getFrameWidgetInputHandler() { - content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(host()); - if (!viewHost) + auto *focused_widget = GetFocusedWidget(); + if (!focused_widget) return nullptr; - content::FrameTreeNode *focusedFrame = viewHost->GetDelegate()->GetFrameTree()->GetFocusedFrame(); - if (!focusedFrame) - return nullptr; - - return focusedFrame->current_frame_host(); -} - -content::mojom::FrameInputHandler *RenderWidgetHostViewQt::getFrameInputHandler() -{ - content::RenderFrameHostImpl *frameHost = static_cast(getFocusedFrameHost()); - if (!frameHost) - return nullptr; - - return frameHost->GetFrameInputHandler(); + return focused_widget->GetFrameWidgetInputHandler(); } ui::TextInputType RenderWidgetHostViewQt::getTextInputType() const -- cgit v1.2.3 From 7869ec5823da36a3ce33b379d3d664204756cad5 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Thu, 22 Oct 2020 14:18:16 +0200 Subject: Adaptations for Chromium 86 Change-Id: I7e0ebecdbb68cfff0b574c966f3fa80d28680e1c Reviewed-by: Peter Varga --- src/core/render_widget_host_view_qt.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/core/render_widget_host_view_qt.cpp') diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 93281f4c9..a76896c00 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -260,9 +260,9 @@ private: const QTouchEvent::TouchPoint& touchPoint(size_t i) const { return touchPoints[i].second; } }; -static content::ScreenInfo screenInfoFromQScreen(QScreen *screen) +static blink::ScreenInfo screenInfoFromQScreen(QScreen *screen) { - content::ScreenInfo r; + blink::ScreenInfo r; if (screen) { r.device_scale_factor = screen->devicePixelRatio(); r.depth_per_component = 8; @@ -758,7 +758,7 @@ void RenderWidgetHostViewQt::DisplayTooltipText(const base::string16 &tooltip_te m_adapterClient->setToolTip(toQt(tooltip_text)); } -void RenderWidgetHostViewQt::GetScreenInfo(content::ScreenInfo *results) +void RenderWidgetHostViewQt::GetScreenInfo(blink::ScreenInfo *results) { *results = m_screenInfo; } @@ -1048,7 +1048,7 @@ void RenderWidgetHostViewQt::visualPropertiesChanged() m_windowRectInDips = toGfx(m_delegate->windowGeometry()); QWindow *window = m_delegate->window(); - content::ScreenInfo oldScreenInfo = m_screenInfo; + blink::ScreenInfo oldScreenInfo = m_screenInfo; m_screenInfo = screenInfoFromQScreen(window ? window->screen() : nullptr); if (m_viewRectInDips != oldViewRect || m_windowRectInDips != oldWindowRect) -- cgit v1.2.3 From c9d902ca6ca3b1aa2e2762329d18c226d26520af Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Fri, 6 Nov 2020 09:16:15 +0100 Subject: Adaptations for Chromium 87 Change-Id: Ic4ffd98e02f986dbaf986405360e727c813e696e Reviewed-by: Peter Varga --- src/core/render_widget_host_view_qt.cpp | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'src/core/render_widget_host_view_qt.cpp') diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index a76896c00..d85542bb0 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -59,15 +59,15 @@ #include "components/viz/host/host_frame_sink_manager.h" #include "content/browser/compositor/image_transport_factory.h" #include "content/browser/compositor/surface_utils.h" -#include "content/browser/frame_host/frame_tree.h" -#include "content/browser/frame_host/frame_tree_node.h" -#include "content/browser/frame_host/render_frame_host_impl.h" +#include "content/browser/renderer_host/frame_tree.h" +#include "content/browser/renderer_host/frame_tree_node.h" #include "content/browser/renderer_host/input/synthetic_gesture_target.h" +#include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/browser/renderer_host/render_view_host_delegate.h" #include "content/browser/renderer_host/render_view_host_impl.h" -#include "content/common/content_switches_internal.h" #include "content/browser/renderer_host/render_widget_host_input_event_router.h" #include "content/browser/renderer_host/ui_events_helper.h" +#include "content/common/content_switches_internal.h" #include "content/common/cursors/webcursor.h" #include "content/common/input_messages.h" #include "third_party/skia/include/core/SkColor.h" @@ -362,6 +362,7 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget config.enable_longpress_drag_selection = false; m_touchSelectionController.reset(new ui::TouchSelectionController(m_touchSelectionControllerClient.get(), config)); + host()->render_frame_metadata_provider()->AddObserver(this); host()->render_frame_metadata_provider()->ReportAllFrameSubmissionsForTesting(true); host()->SetView(this); @@ -972,7 +973,7 @@ viz::ScopedSurfaceIdAllocator RenderWidgetHostViewQt::DidUpdateVisualProperties( void RenderWidgetHostViewQt::OnDidUpdateVisualPropertiesComplete(const cc::RenderFrameMetadata &metadata) { - synchronizeVisualProperties(metadata.local_surface_id_allocation); + synchronizeVisualProperties(metadata.local_surface_id); } void RenderWidgetHostViewQt::OnDidFirstVisuallyNonEmptyPaint() @@ -1012,18 +1013,18 @@ QSGNode *RenderWidgetHostViewQt::updatePaintNode(QSGNode *oldNode) void RenderWidgetHostViewQt::notifyShown() { // Handle possible frame eviction: - if (!m_dfhLocalSurfaceIdAllocator.HasValidLocalSurfaceIdAllocation()) + if (!m_dfhLocalSurfaceIdAllocator.HasValidLocalSurfaceId()) m_dfhLocalSurfaceIdAllocator.GenerateId(); if (m_visible) return; m_visible = true; - host()->WasShown(base::nullopt); + host()->WasShown(nullptr); m_delegatedFrameHost->AttachToCompositor(m_uiCompositor.get()); - m_delegatedFrameHost->WasShown(GetLocalSurfaceIdAllocation().local_surface_id(), + m_delegatedFrameHost->WasShown(GetLocalSurfaceId(), m_viewRectInDips.size(), - base::nullopt); + nullptr); } void RenderWidgetHostViewQt::notifyHidden() @@ -1236,7 +1237,7 @@ void RenderWidgetHostViewQt::ProcessAckedTouchEvent(const content::TouchEventWit { Q_UNUSED(touch); const bool eventConsumed = ack_result == blink::mojom::InputEventResultState::kConsumed; - const bool isSetNonBlocking = content::InputEventAckStateIsSetNonBlocking(ack_result); + const bool isSetNonBlocking = content::InputEventResultStateIsSetNonBlocking(ack_result); m_gestureProvider.OnTouchEventAck(touch.event.unique_touch_event_id, eventConsumed, isSetNonBlocking); } @@ -1856,9 +1857,9 @@ const viz::FrameSinkId &RenderWidgetHostViewQt::GetFrameSinkId() const return m_delegatedFrameHost->frame_sink_id(); } -const viz::LocalSurfaceIdAllocation &RenderWidgetHostViewQt::GetLocalSurfaceIdAllocation() const +const viz::LocalSurfaceId &RenderWidgetHostViewQt::GetLocalSurfaceId() const { - return m_dfhLocalSurfaceIdAllocator.GetCurrentLocalSurfaceIdAllocation(); + return m_dfhLocalSurfaceIdAllocator.GetCurrentLocalSurfaceId(); } void RenderWidgetHostViewQt::TakeFallbackContentFrom(content::RenderWidgetHostView *view) @@ -1888,8 +1889,6 @@ void RenderWidgetHostViewQt::ResetFallbackToFirstNavigationSurface() void RenderWidgetHostViewQt::OnRenderFrameMetadataChangedAfterActivation() { - content::RenderWidgetHostViewBase::OnRenderFrameMetadataChangedAfterActivation(); - const cc::RenderFrameMetadata &metadata = host()->render_frame_metadata_provider()->LastRenderFrameMetadata(); if (metadata.selection.start != m_selectionStart || metadata.selection.end != m_selectionEnd) { m_selectionStart = metadata.selection.start; @@ -1907,7 +1906,7 @@ void RenderWidgetHostViewQt::OnRenderFrameMetadataChangedAfterActivation() m_adapterClient->updateContentsSize(toQt(m_lastContentsSize)); } -void RenderWidgetHostViewQt::synchronizeVisualProperties(const base::Optional &childSurfaceId) +void RenderWidgetHostViewQt::synchronizeVisualProperties(const base::Optional &childSurfaceId) { if (childSurfaceId) m_dfhLocalSurfaceIdAllocator.UpdateFromChild(*childSurfaceId); @@ -1921,9 +1920,9 @@ void RenderWidgetHostViewQt::synchronizeVisualProperties(const base::OptionalSetScaleAndSize( m_screenInfo.device_scale_factor, viewSizeInPixels, - m_uiCompositorLocalSurfaceIdAllocator.GetCurrentLocalSurfaceIdAllocation()); + m_uiCompositorLocalSurfaceIdAllocator.GetCurrentLocalSurfaceId()); m_delegatedFrameHost->EmbedSurface( - m_dfhLocalSurfaceIdAllocator.GetCurrentLocalSurfaceIdAllocation().local_surface_id(), + m_dfhLocalSurfaceIdAllocator.GetCurrentLocalSurfaceId(), viewSizeInDips, cc::DeadlinePolicy::UseDefaultDeadline()); -- cgit v1.2.3 From 2b6f6ad066123253b65449033eddcee8b20c470b Mon Sep 17 00:00:00 2001 From: Peter Varga Date: Wed, 28 Oct 2020 15:59:18 +0100 Subject: Enable hangout services extension [ChangeLog] Enable hangout services extension and implement its WebRTC desktop capture extension API dependency. Fixes: QTBUG-85731 Task-number: QTBUG-51185 Task-number: QTBUG-61676 Change-Id: I7a659c2b0039243ac8d8c58685716ffc55265e3b Reviewed-by: Allan Sandfeld Jensen --- src/core/render_widget_host_view_qt.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/core/render_widget_host_view_qt.cpp') diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index d85542bb0..877f3c630 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -419,6 +419,9 @@ void RenderWidgetHostViewQt::InitAsFullscreen(content::RenderWidgetHostView*) void RenderWidgetHostViewQt::SetSize(const gfx::Size &sizeInDips) { + if (!m_delegate) + return; + m_delegate->resize(sizeInDips.width(), sizeInDips.height()); } -- cgit v1.2.3