Changeset View
Changeset View
Standalone View
Standalone View
plugins/platforms/drm/drm_output.cpp
Show First 20 Lines • Show All 56 Lines • ▼ Show 20 Line(s) | 56 | DrmOutput::DrmOutput(DrmBackend *backend) | |||
---|---|---|---|---|---|
57 | : QObject() | 57 | : QObject() | ||
58 | , m_backend(backend) | 58 | , m_backend(backend) | ||
59 | { | 59 | { | ||
60 | } | 60 | } | ||
61 | 61 | | |||
62 | DrmOutput::~DrmOutput() | 62 | DrmOutput::~DrmOutput() | ||
63 | { | 63 | { | ||
64 | hideCursor(); | 64 | hideCursor(); | ||
65 | cleanupBlackBuffer(); | 65 | m_crtc->blank(); | ||
66 | delete m_crtc; | | |||
67 | delete m_conn; | | |||
68 | delete m_waylandOutput.data(); | 66 | delete m_waylandOutput.data(); | ||
69 | delete m_waylandOutputDevice.data(); | 67 | delete m_waylandOutputDevice.data(); | ||
70 | } | 68 | } | ||
71 | 69 | | |||
72 | void DrmOutput::cleanup() | 70 | void DrmOutput::releaseGbm() | ||
73 | { | 71 | { | ||
74 | if (m_currentBuffer) { | 72 | if (DrmBuffer *b = m_crtc->current()) { | ||
75 | m_currentBuffer->releaseGbm(); | 73 | b->releaseGbm(); | ||
76 | } | | |||
77 | if (m_nextBuffer) { | | |||
78 | m_nextBuffer->releaseGbm(); | | |||
79 | } | 74 | } | ||
80 | if (m_primaryPlane) { | 75 | if (m_primaryPlane) { | ||
81 | if (m_primaryPlane->current()) { | 76 | if (m_primaryPlane->current()) { | ||
82 | m_primaryPlane->current()->releaseGbm(); | 77 | m_primaryPlane->current()->releaseGbm(); | ||
83 | } | 78 | } | ||
84 | if (m_primaryPlane->next()) { | | |||
85 | m_primaryPlane->next()->releaseGbm(); | | |||
86 | } | | |||
87 | } | 79 | } | ||
88 | } | 80 | } | ||
89 | 81 | | |||
90 | void DrmOutput::hideCursor() | 82 | void DrmOutput::hideCursor() | ||
91 | { | 83 | { | ||
92 | drmModeSetCursor(m_backend->fd(), m_crtcId, 0, 0, 0); | 84 | drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0); | ||
93 | } | | |||
94 | | ||||
95 | void DrmOutput::restoreSaved() | | |||
96 | { | | |||
97 | if (!m_savedCrtc.isNull()) { | | |||
98 | drmModeSetCrtc(m_backend->fd(), m_savedCrtc->crtc_id, m_savedCrtc->buffer_id, | | |||
99 | m_savedCrtc->x, m_savedCrtc->y, &m_connector, 1, &m_savedCrtc->mode); | | |||
100 | } | | |||
101 | } | 85 | } | ||
102 | 86 | | |||
103 | void DrmOutput::showCursor(DrmBuffer *c) | 87 | void DrmOutput::showCursor(DrmBuffer *c) | ||
104 | { | 88 | { | ||
105 | const QSize &s = c->size(); | 89 | const QSize &s = c->size(); | ||
106 | drmModeSetCursor(m_backend->fd(), m_crtcId, c->handle(), s.width(), s.height()); | 90 | drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()); | ||
107 | } | 91 | } | ||
108 | 92 | | |||
109 | void DrmOutput::moveCursor(const QPoint &globalPos) | 93 | void DrmOutput::moveCursor(const QPoint &globalPos) | ||
110 | { | 94 | { | ||
111 | const QPoint p = (globalPos - m_globalPos) * m_scale; | 95 | const QPoint p = (globalPos - m_globalPos) * m_scale; | ||
112 | drmModeMoveCursor(m_backend->fd(), m_crtcId, p.x(), p.y()); | 96 | drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); | ||
113 | } | 97 | } | ||
114 | 98 | | |||
115 | QSize DrmOutput::pixelSize() const | 99 | QSize DrmOutput::pixelSize() const | ||
116 | { | 100 | { | ||
117 | return QSize(m_mode.hdisplay, m_mode.vdisplay); | 101 | return QSize(m_mode.hdisplay, m_mode.vdisplay); | ||
118 | } | 102 | } | ||
119 | 103 | | |||
120 | QRect DrmOutput::geometry() const | 104 | QRect DrmOutput::geometry() const | ||
▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Line(s) | 171 | { | |||
188 | initEdid(connector); | 172 | initEdid(connector); | ||
189 | initDpms(connector); | 173 | initDpms(connector); | ||
190 | initUuid(); | 174 | initUuid(); | ||
191 | if (m_backend->atomicModeSetting()) { | 175 | if (m_backend->atomicModeSetting()) { | ||
192 | if (!initPrimaryPlane()) { | 176 | if (!initPrimaryPlane()) { | ||
193 | return false; | 177 | return false; | ||
194 | } | 178 | } | ||
195 | } | 179 | } | ||
196 | m_savedCrtc.reset(drmModeGetCrtc(m_backend->fd(), m_crtcId)); | 180 | if (!m_crtc->blank()) { | ||
197 | if (!blank()) { | | |||
198 | return false; | 181 | return false; | ||
199 | } | 182 | } | ||
200 | setDpms(DpmsMode::On); | 183 | setDpms(DpmsMode::On); | ||
201 | if (!m_waylandOutput.isNull()) { | 184 | if (!m_waylandOutput.isNull()) { | ||
202 | delete m_waylandOutput.data(); | 185 | delete m_waylandOutput.data(); | ||
203 | m_waylandOutput.clear(); | 186 | m_waylandOutput.clear(); | ||
204 | } | 187 | } | ||
205 | m_waylandOutput = waylandServer()->display()->createOutput(); | 188 | m_waylandOutput = waylandServer()->display()->createOutput(); | ||
▲ Show 20 Lines • Show All 101 Lines • ▼ Show 20 Line(s) | |||||
307 | qCDebug(KWIN_DRM) << "Created OutputDevice"; | 290 | qCDebug(KWIN_DRM) << "Created OutputDevice"; | ||
308 | m_waylandOutputDevice->create(); | 291 | m_waylandOutputDevice->create(); | ||
309 | return true; | 292 | return true; | ||
310 | } | 293 | } | ||
311 | 294 | | |||
312 | void DrmOutput::initUuid() | 295 | void DrmOutput::initUuid() | ||
313 | { | 296 | { | ||
314 | QCryptographicHash hash(QCryptographicHash::Md5); | 297 | QCryptographicHash hash(QCryptographicHash::Md5); | ||
315 | hash.addData(QByteArray::number(m_connector)); | 298 | hash.addData(QByteArray::number(m_conn->id())); | ||
316 | hash.addData(m_edid.eisaId); | 299 | hash.addData(m_edid.eisaId); | ||
317 | hash.addData(m_edid.monitorName); | 300 | hash.addData(m_edid.monitorName); | ||
318 | hash.addData(m_edid.serialNumber); | 301 | hash.addData(m_edid.serialNumber); | ||
319 | m_uuid = hash.result().toHex().left(10); | 302 | m_uuid = hash.result().toHex().left(10); | ||
320 | } | 303 | } | ||
321 | 304 | | |||
322 | bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const | 305 | bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const | ||
323 | { | 306 | { | ||
▲ Show 20 Lines • Show All 184 Lines • ▼ Show 20 Line(s) | 490 | if (p->type() != DrmPlane::TypeIndex::Primary) { | |||
508 | continue; | 491 | continue; | ||
509 | } | 492 | } | ||
510 | if (p->output()) { // Plane already has an output | 493 | if (p->output()) { // Plane already has an output | ||
511 | continue; | 494 | continue; | ||
512 | } | 495 | } | ||
513 | if (m_primaryPlane) { // Output already has a primary plane | 496 | if (m_primaryPlane) { // Output already has a primary plane | ||
514 | continue; | 497 | continue; | ||
515 | } | 498 | } | ||
516 | if (!p->isCrtcSupported(m_crtcId)) { | 499 | if (!p->isCrtcSupported(m_crtc->id())) { | ||
517 | continue; | 500 | continue; | ||
518 | } | 501 | } | ||
519 | p->setOutput(this); | 502 | p->setOutput(this); | ||
520 | m_primaryPlane = p; | 503 | m_primaryPlane = p; | ||
521 | qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtcId; | 504 | qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); | ||
522 | return true; | 505 | return true; | ||
523 | } | 506 | } | ||
524 | qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; | 507 | qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; | ||
525 | return false; | 508 | return false; | ||
526 | } | 509 | } | ||
527 | 510 | | |||
528 | bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) | 511 | bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) | ||
529 | { | 512 | { | ||
530 | for (int i = 0; i < m_backend->planes().size(); ++i) { | 513 | for (int i = 0; i < m_backend->planes().size(); ++i) { | ||
531 | DrmPlane* p = m_backend->planes()[i]; | 514 | DrmPlane* p = m_backend->planes()[i]; | ||
532 | if (!p) { | 515 | if (!p) { | ||
533 | continue; | 516 | continue; | ||
534 | } | 517 | } | ||
535 | if (p->type() != DrmPlane::TypeIndex::Cursor) { | 518 | if (p->type() != DrmPlane::TypeIndex::Cursor) { | ||
536 | continue; | 519 | continue; | ||
537 | } | 520 | } | ||
538 | if (p->output()) { // Plane already has an output | 521 | if (p->output()) { // Plane already has an output | ||
539 | continue; | 522 | continue; | ||
540 | } | 523 | } | ||
541 | if (m_cursorPlane) { // Output already has a cursor plane | 524 | if (m_cursorPlane) { // Output already has a cursor plane | ||
542 | continue; | 525 | continue; | ||
543 | } | 526 | } | ||
544 | if (!p->isCrtcSupported(m_crtcId)) { | 527 | if (!p->isCrtcSupported(m_crtc->id())) { | ||
545 | continue; | 528 | continue; | ||
546 | } | 529 | } | ||
547 | p->setOutput(this); | 530 | p->setOutput(this); | ||
548 | m_cursorPlane = p; | 531 | m_cursorPlane = p; | ||
549 | qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtcId; | 532 | qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); | ||
550 | return true; | 533 | return true; | ||
551 | } | 534 | } | ||
552 | return false; | 535 | return false; | ||
553 | } | 536 | } | ||
554 | 537 | | |||
555 | void DrmOutput::initDpms(drmModeConnector *connector) | 538 | void DrmOutput::initDpms(drmModeConnector *connector) | ||
556 | { | 539 | { | ||
557 | for (int i = 0; i < connector->count_props; ++i) { | 540 | for (int i = 0; i < connector->count_props; ++i) { | ||
Show All 17 Lines | 557 | if (mode == m_dpmsMode) { | |||
575 | qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; | 558 | qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; | ||
576 | return; | 559 | return; | ||
577 | } | 560 | } | ||
578 | 561 | | |||
579 | if (m_backend->atomicModeSetting()) { | 562 | if (m_backend->atomicModeSetting()) { | ||
580 | drmModeAtomicReq *req = drmModeAtomicAlloc(); | 563 | drmModeAtomicReq *req = drmModeAtomicAlloc(); | ||
581 | 564 | | |||
582 | if (atomicReqModesetPopulate(req, mode == DpmsMode::On) == DrmObject::AtomicReturn::Error) { | 565 | if (atomicReqModesetPopulate(req, mode == DpmsMode::On) == DrmObject::AtomicReturn::Error) { | ||
583 | qCWarning(KWIN_DRM) << "Failed to populate atomic request for output" << m_crtcId; | 566 | qCWarning(KWIN_DRM) << "Failed to populate atomic request for output" << m_crtc->id(); | ||
584 | return; | 567 | return; | ||
585 | } | 568 | } | ||
586 | if (drmModeAtomicCommit(m_backend->fd(), req, DRM_MODE_ATOMIC_ALLOW_MODESET, this)) { | 569 | if (drmModeAtomicCommit(m_backend->fd(), req, DRM_MODE_ATOMIC_ALLOW_MODESET, this)) { | ||
587 | qCWarning(KWIN_DRM) << "Failed to commit atomic request for output" << m_crtcId; | 570 | qCWarning(KWIN_DRM) << "Failed to commit atomic request for output" << m_crtc->id(); | ||
588 | } else { | 571 | } else { | ||
589 | qCDebug(KWIN_DRM) << "DPMS set for output" << m_crtcId; | 572 | qCDebug(KWIN_DRM) << "DPMS set for output" << m_crtc->id(); | ||
590 | } | 573 | } | ||
591 | drmModeAtomicFree(req); | 574 | drmModeAtomicFree(req); | ||
592 | } else { | 575 | } else { | ||
593 | if (drmModeConnectorSetProperty(m_backend->fd(), m_connector, m_dpms->prop_id, uint64_t(mode)) < 0) { | 576 | if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(mode)) < 0) { | ||
594 | qCWarning(KWIN_DRM) << "Setting DPMS failed"; | 577 | qCWarning(KWIN_DRM) << "Setting DPMS failed"; | ||
595 | return; | 578 | return; | ||
596 | } | 579 | } | ||
597 | } | 580 | } | ||
598 | 581 | | |||
599 | m_dpmsMode = mode; | 582 | m_dpmsMode = mode; | ||
600 | if (m_waylandOutput) { | 583 | if (m_waylandOutput) { | ||
601 | m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); | 584 | m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); | ||
602 | } | 585 | } | ||
603 | emit dpmsChanged(); | 586 | emit dpmsChanged(); | ||
604 | if (m_dpmsMode != DpmsMode::On) { | 587 | if (m_dpmsMode != DpmsMode::On) { | ||
605 | m_backend->outputWentOff(); | 588 | m_backend->outputWentOff(); | ||
606 | } else { | 589 | } else { | ||
607 | m_backend->checkOutputsAreOn(); | 590 | m_backend->checkOutputsAreOn(); | ||
608 | blank(); | 591 | m_crtc->blank(); | ||
609 | if (Compositor *compositor = Compositor::self()) { | 592 | if (Compositor *compositor = Compositor::self()) { | ||
610 | compositor->addRepaintFull(); | 593 | compositor->addRepaintFull(); | ||
611 | } | 594 | } | ||
612 | } | 595 | } | ||
613 | } | 596 | } | ||
614 | 597 | | |||
615 | QString DrmOutput::name() const | 598 | QString DrmOutput::name() const | ||
616 | { | 599 | { | ||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Line(s) | 675 | if (m_changeset->scaleChanged()) { | |||
693 | qCDebug(KWIN_DRM) << "Setting scale:" << m_changeset->scale(); | 676 | qCDebug(KWIN_DRM) << "Setting scale:" << m_changeset->scale(); | ||
694 | setScale(m_changeset->scale()); | 677 | setScale(m_changeset->scale()); | ||
695 | } | 678 | } | ||
696 | return true; | 679 | return true; | ||
697 | } | 680 | } | ||
698 | 681 | | |||
699 | void DrmOutput::pageFlipped() | 682 | void DrmOutput::pageFlipped() | ||
700 | { | 683 | { | ||
684 | if (!m_crtc) { | ||||
685 | return; | ||||
686 | } | ||||
701 | if (m_backend->atomicModeSetting()){ | 687 | if (m_backend->atomicModeSetting()){ | ||
702 | foreach (DrmPlane *p, m_planesFlipList) { | 688 | foreach (DrmPlane *p, m_planesFlipList) { | ||
703 | pageFlippedBufferRemover(p->current(), p->next()); | 689 | pageFlippedBufferRemover(p->current(), p->next()); | ||
704 | p->setCurrent(p->next()); | 690 | p->setCurrent(p->next()); | ||
705 | p->setNext(nullptr); | 691 | p->setNext(nullptr); | ||
706 | } | 692 | } | ||
707 | m_planesFlipList.clear(); | 693 | m_planesFlipList.clear(); | ||
708 | 694 | | |||
709 | } else { | 695 | } else { | ||
710 | if (!m_nextBuffer) { | 696 | if (!m_crtc->next()) { | ||
711 | // on manual vt switch | 697 | // on manual vt switch | ||
712 | if (m_currentBuffer) { | 698 | if (DrmBuffer *b = m_crtc->current()) { | ||
713 | m_currentBuffer->releaseGbm(); | 699 | b->releaseGbm(); | ||
714 | } | 700 | } | ||
715 | return; | 701 | return; | ||
716 | } | 702 | } | ||
717 | pageFlippedBufferRemover(m_currentBuffer, m_nextBuffer); | 703 | m_crtc->flipBuffer(); | ||
718 | m_currentBuffer = m_nextBuffer; | | |||
719 | m_nextBuffer = nullptr; | | |||
720 | } | 704 | } | ||
721 | cleanupBlackBuffer(); | | |||
722 | } | 705 | } | ||
723 | 706 | | |||
724 | void DrmOutput::pageFlippedBufferRemover(DrmBuffer *oldbuffer, DrmBuffer *newbuffer) | 707 | void DrmOutput::pageFlippedBufferRemover(DrmBuffer *oldbuffer, DrmBuffer *newbuffer) | ||
725 | { | 708 | { | ||
726 | if (oldbuffer && oldbuffer->deleteAfterPageFlip() && oldbuffer != newbuffer) { | 709 | if (oldbuffer && oldbuffer->deleteAfterPageFlip() && oldbuffer != newbuffer) { | ||
727 | delete oldbuffer; | 710 | delete oldbuffer; | ||
728 | } | 711 | } | ||
729 | } | 712 | } | ||
730 | 713 | | |||
731 | void DrmOutput::cleanupBlackBuffer() | | |||
732 | { | | |||
733 | if (m_blackBuffer) { | | |||
734 | delete m_blackBuffer; | | |||
735 | m_blackBuffer = nullptr; | | |||
736 | } | | |||
737 | } | | |||
738 | | ||||
739 | bool DrmOutput::blank() | | |||
740 | { | | |||
741 | if (!m_blackBuffer) { | | |||
742 | m_blackBuffer = m_backend->createBuffer(pixelSize()); | | |||
743 | if (!m_blackBuffer->map()) { | | |||
744 | cleanupBlackBuffer(); | | |||
745 | return false; | | |||
746 | } | | |||
747 | m_blackBuffer->image()->fill(Qt::black); | | |||
748 | } | | |||
749 | // TODO: Do this atomically | | |||
750 | return setModeLegacy(m_blackBuffer); | | |||
751 | } | | |||
752 | | ||||
753 | bool DrmOutput::present(DrmBuffer *buffer) | 714 | bool DrmOutput::present(DrmBuffer *buffer) | ||
754 | { | 715 | { | ||
755 | if (!buffer || buffer->bufferId() == 0) { | 716 | if (!buffer || buffer->bufferId() == 0) { | ||
756 | return false; | 717 | return false; | ||
757 | } | 718 | } | ||
758 | if (m_backend->atomicModeSetting()) { | 719 | if (m_backend->atomicModeSetting()) { | ||
759 | return presentAtomically(buffer); | 720 | return presentAtomically(buffer); | ||
760 | } else { | 721 | } else { | ||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Line(s) | 727 | { | |||
863 | 824 | | |||
864 | drmModeAtomicFree(req); | 825 | drmModeAtomicFree(req); | ||
865 | return true; | 826 | return true; | ||
866 | } | 827 | } | ||
867 | 828 | | |||
868 | 829 | | |||
869 | bool DrmOutput::presentLegacy(DrmBuffer *buffer) | 830 | bool DrmOutput::presentLegacy(DrmBuffer *buffer) | ||
870 | { | 831 | { | ||
871 | if (m_nextBuffer) { | 832 | if (m_crtc->next()) { | ||
872 | return false; | 833 | return false; | ||
873 | } | 834 | } | ||
874 | if (!LogindIntegration::self()->isActiveSession()) { | 835 | if (!LogindIntegration::self()->isActiveSession()) { | ||
875 | m_nextBuffer = buffer; | 836 | m_crtc->setNext(buffer); | ||
876 | return false; | 837 | return false; | ||
877 | } | 838 | } | ||
878 | if (m_dpmsMode != DpmsMode::On) { | 839 | if (m_dpmsMode != DpmsMode::On) { | ||
879 | return false; | 840 | return false; | ||
880 | } | 841 | } | ||
881 | 842 | | |||
882 | // Do we need to set a new mode first? | 843 | // Do we need to set a new mode first? | ||
883 | if (m_lastStride != buffer->stride() || m_lastGbm != buffer->isGbm()){ | 844 | if (m_lastStride != buffer->stride() || m_lastGbm != buffer->isGbm()){ | ||
884 | if (!setModeLegacy(buffer)) | 845 | if (!setModeLegacy(buffer)) | ||
885 | return false; | 846 | return false; | ||
886 | } | 847 | } | ||
887 | int errno_save = 0; | 848 | int errno_save = 0; | ||
888 | const bool ok = drmModePageFlip(m_backend->fd(), m_crtcId, buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; | 849 | const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; | ||
889 | if (ok) { | 850 | if (ok) { | ||
890 | m_nextBuffer = buffer; | 851 | m_crtc->setNext(buffer); | ||
891 | } else { | 852 | } else { | ||
892 | errno_save = errno; | 853 | errno_save = errno; | ||
893 | qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); | 854 | qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); | ||
894 | delete buffer; | 855 | delete buffer; | ||
895 | } | 856 | } | ||
896 | return ok; | 857 | return ok; | ||
897 | } | 858 | } | ||
898 | 859 | | |||
899 | bool DrmOutput::setModeLegacy(DrmBuffer *buffer) | 860 | bool DrmOutput::setModeLegacy(DrmBuffer *buffer) | ||
900 | { | 861 | { | ||
901 | if (drmModeSetCrtc(m_backend->fd(), m_crtcId, buffer->bufferId(), 0, 0, &m_connector, 1, &m_mode) == 0) { | 862 | uint32_t connId = m_conn->id(); | ||
863 | if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { | ||||
902 | m_lastStride = buffer->stride(); | 864 | m_lastStride = buffer->stride(); | ||
903 | m_lastGbm = buffer->isGbm(); | 865 | m_lastGbm = buffer->isGbm(); | ||
904 | return true; | 866 | return true; | ||
905 | } else { | 867 | } else { | ||
906 | qCWarning(KWIN_DRM) << "Mode setting failed"; | 868 | qCWarning(KWIN_DRM) << "Mode setting failed"; | ||
907 | return false; | 869 | return false; | ||
908 | } | 870 | } | ||
909 | } | 871 | } | ||
Show All 35 Lines |