Make lock screen work

This commit is contained in:
Victor Tran 2023-06-04 18:20:47 +10:00
parent 9bc03dd3bb
commit 7bf5795701
No known key found for this signature in database
17 changed files with 592 additions and 95 deletions

View file

@ -1,2 +1,3 @@
add_subdirectory(locker-common)
add_subdirectory(locker-ui)
add_subdirectory(locker-checker)

View file

@ -12,5 +12,6 @@ cntp_target_name(locker-checker "td-locker-checker")
target_link_libraries(locker-checker Qt::Core crypt)
install(TARGETS locker-checker
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
BUNDLE DESTINATION /Applications)

View file

@ -1,61 +1,78 @@
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDebug>
#include <QFile>
#include <termios.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
QCoreApplication::setSetuidAllowed(true);
QCoreApplication a(argc, argv);
QCommandLineParser parser;
parser.addHelpOption();
parser.addPositionalArgument("username", "Username to check the password of", "<username>");
parser.process(a);
QFile shadow("/etc/shadow");
QStringList arguments = a.arguments();
if (arguments.contains("--help")) {
qDebug() << "tchkpass - Check User Password";
qDebug() << "Usage: checkpassword user password";
qDebug() << "";
qDebug() << "--help Shows this help";
qDebug() << "";
qDebug() << "This command needs to be run as root.";
return 2;
}
shadow.open(QFile::ReadOnly);
if (!shadow.isReadable()) {
qDebug() << "Can't read the shadow file.";
qDebug() << "This command needs to be run as root.";
QTextStream(stderr) << "Can't read the shadow file.\n";
QTextStream(stderr) << "This command needs to be run as root.\n";
return 2;
}
if (parser.positionalArguments().count() < 1) {
QTextStream(stderr) << "Not enough arguments\n";
return 2;
}
// TODO: THIS IS BAD.
// We should not be reading the shadow file directly.
// Consider invoking PAM
QStringList shadowFile(QString(shadow.readAll()).split("\n"));
for (QString user : shadowFile) {
if (user.startsWith(arguments.at(1))) {
QString hashedPassword = user.split(":").at(1);
for (const QString& user : shadowFile) {
auto parts = user.split(":");
if (parts.length() < 2) continue;
if (parts.constFirst() == parser.positionalArguments().at(0)) {
QString hashedPassword = parts.at(1);
QStringList passwordParts = hashedPassword.split("$");
if (passwordParts.count() == 1) {
qDebug() << "No password.";
QTextStream(stdout) << "No password.\n";
return 0;
} else if (passwordParts.count() != 4) {
QTextStream(stdout) << "Unable to parse shadow file\n";
return 2;
} else {
if (arguments.count() < 3) {
qDebug() << "Not enough arguments";
return 1;
}
// QString tryPassword(QCryptographicHash::hash(arguments.at(2).toUtf8(), QCryptographicHash::Sha512));
const char* characters = (crypt(arguments.at(2).toStdString().c_str(), QString("$" + passwordParts.at(1) + "$" + passwordParts.at(2)).toStdString().c_str()));
QTextStream in(stdin, QIODevice::ReadOnly);
// Turn off echo
struct termios term;
tcgetattr(fileno(stdin), &term);
term.c_lflag &= ~ECHO;
tcsetattr(fileno(stdin), 0, &term);
QTextStream(stderr) << "Password: ";
const char* characters = (crypt(in.readLine().toUtf8().data(), QString("$" + passwordParts.at(1) + "$" + passwordParts.at(2)).toStdString().c_str()));
QString encryptedPass(characters);
// QString hashedPass(QCryptographicHash::hash(encryptedPass.toUtf8(), QCryptographicHash::Sha512));
QTextStream(stderr) << "\n";
if (encryptedPass.split("$").at(3) == passwordParts.at(3)) {
qDebug() << "Password correct";
QTextStream(stdout) << "Password correct\n";
return 0;
} else {
qDebug() << "Password incorrect";
QTextStream(stderr) << "Password incorrect\n";
return 1;
}
}
}
}
return 0;
QTextStream(stderr) << "Unable to find user in shadow file\n";
return 2;
}

View file

@ -0,0 +1,21 @@
project(locker VERSION 1.0.0 LANGUAGES CXX)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
find_package(libcontemporary REQUIRED)
cntp_find_pkgconfig(X11 x11)
set(SOURCES
lockergrabs.cpp
)
set(HEADERS
lockergrabs.h
)
add_library(locker-common STATIC ${SOURCES} ${HEADERS})
target_link_libraries(locker-common Qt::Core libcontemporary)
IF(${X11_FOUND})
target_link_libraries(locker-common PkgConfig::X11)
target_compile_definitions(locker-common PRIVATE HAVE_X11)
ENDIF()

View file

@ -0,0 +1,25 @@
#include "lockergrabs.h"
#ifdef HAVE_X11
#include <tx11info.h>
#include <X11/Xlib.h>
#endif
void LockerGrabs::establishGrab() {
#ifdef HAVE_X11
if (tX11Info::isPlatformX11()) {
XGrabKeyboard(tX11Info::display(), tX11Info::appRootWindow(), True, GrabModeAsync, GrabModeAsync, CurrentTime);
XGrabPointer(tX11Info::display(), tX11Info::appRootWindow(), True, None, GrabModeAsync, GrabModeAsync, tX11Info::appRootWindow(), None, CurrentTime);
}
#endif
}
void LockerGrabs::releaseGrab() {
#ifdef HAVE_X11
if (tX11Info::isPlatformX11()) {
XUngrabKeyboard(tX11Info::display(), CurrentTime);
XUngrabPointer(tX11Info::display(), CurrentTime);
}
#endif
}

View file

@ -0,0 +1,9 @@
#ifndef LOCKERGRABS_H
#define LOCKERGRABS_H
namespace LockerGrabs {
void establishGrab();
void releaseGrab();
}; // namespace LockerGrabs
#endif // LOCKERGRABS_H

View file

@ -3,14 +3,19 @@ project(locker VERSION 1.0.0 LANGUAGES CXX)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets DBus Sql PrintSupport)
find_package(libcontemporary REQUIRED)
find_package(libtdesktopenvironment REQUIRED)
cntp_find_pkgconfig(X11 x11)
set(SOURCES
main.cpp
mainwindow.cpp mainwindow.ui
underlineanimation.cpp
lockmanager.cpp
)
set(HEADERS
mainwindow.h
underlineanimation.h
lockmanager.h
)
add_executable(locker-ui ${SOURCES} ${HEADERS})
@ -26,8 +31,8 @@ cntp_init(locker-ui 20)
cntp_translate(locker-ui)
cntp_target_name(locker-ui "td-locker")
target_link_libraries(locker-ui Qt::Widgets Qt::DBus Qt::Sql Qt::PrintSupport libcontemporary libtdesktopenvironment libthedesk)
target_include_directories(locker-ui PUBLIC ../libthedesk/)
target_link_libraries(locker-ui Qt::Widgets Qt::DBus Qt::Sql Qt::PrintSupport libcontemporary libtdesktopenvironment libthedesk locker-common)
target_include_directories(locker-ui PUBLIC ../locker-common/)
install(TARGETS locker-ui
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}

View file

@ -0,0 +1,61 @@
#include "lockmanager.h"
#include "mainwindow.h"
#include <QTimer>
#include <Screens/screendaemon.h>
#include <Screens/systemscreen.h>
#include <Wm/desktopwm.h>
#include <lockergrabs.h>
struct LockManagerPrivate {
QList<MainWindow*> openWindows;
QTimer* grabTimer;
};
LockManager* LockManager::instance() {
static auto manager = new LockManager();
return manager;
}
void LockManager::openLockWindows() {
for (auto window : d->openWindows) {
window->close();
window->deleteLater();
}
d->openWindows.clear();
for (auto screen : ScreenDaemon::instance()->screens()) {
auto* w = new MainWindow();
w->setWindowFlags(Qt::WindowStaysOnTopHint);
w->show();
w->setGeometry(screen->geometry());
w->showFullScreen();
d->openWindows.append(w);
}
}
void LockManager::establishGrab() {
LockerGrabs::establishGrab();
}
void LockManager::raiseAll() {
for (auto w : d->openWindows) {
w->raise();
}
}
LockManager::LockManager(QObject* parent) :
QObject{parent} {
d = new LockManagerPrivate();
connect(ScreenDaemon::instance(), &ScreenDaemon::screensUpdated, this, &LockManager::openLockWindows);
openLockWindows();
d->grabTimer = new QTimer();
d->grabTimer->setInterval(5000);
connect(d->grabTimer, &QTimer::timeout, this, &LockManager::establishGrab);
connect(d->grabTimer, &QTimer::timeout, this, &LockManager::raiseAll);
d->grabTimer->start();
establishGrab();
}

View file

@ -0,0 +1,24 @@
#ifndef LOCKMANAGER_H
#define LOCKMANAGER_H
#include <QObject>
struct LockManagerPrivate;
class LockManager : public QObject {
Q_OBJECT
public:
static LockManager* instance();
void openLockWindows();
void establishGrab();
void raiseAll();
signals:
private:
explicit LockManager(QObject* parent = nullptr);
LockManagerPrivate* d;
};
#endif // LOCKMANAGER_H

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#include "mainwindow.h"
#include "lockmanager.h"
#include <tapplication.h>
@ -37,8 +37,7 @@ int main(int argc, char* argv[]) {
a.registerCrashTrap();
MainWindow w;
w.show();
LockManager::instance();
int retval = a.exec();
return retval;

View file

@ -4,6 +4,9 @@
#include <QCoroProcess>
#include <QProcess>
#include <SystemSlide/systemslide.h>
#include <Wm/desktopwm.h>
#include <lockmanager.h>
#include <terrorflash.h>
struct MainWindowPrivate {
QString lockCheckProcess;
@ -29,25 +32,20 @@ MainWindow::MainWindow(QWidget* parent) :
d->lockCheckProcess = "/usr/lib/td-locker-checker"; // TODO: Don't hardcode
}
connect(d->slide, &SystemSlide::deactivated, this, [this]() -> QCoro::Task<> {
ui->usernameLabel->setText(DesktopWm::userDisplayName());
ui->capsLockOn->setVisible(false);
connect(d->slide, &SystemSlide::deactivated, this, [this] {
if (this->checkPassword("")) {
QApplication::exit(0);
return;
}
// tPropertyAnimation* opacity = new tPropertyAnimation(passwordFrameOpacity, "opacity");
// opacity->setStartValue(0.0);
// opacity->setEndValue(1.0);
// opacity->setDuration(500);
// opacity->setEasingCurve(QEasingCurve::OutCubic);
// connect(opacity, SIGNAL(finished()), opacity, SLOT(deleteLater()));
// opacity->start();
ui->password->setFocus();
// QTimer::singleShot(100, this, [ = ] {
// ui->MouseUnderline->startAnimation();
// ui->PasswordUnderline->startAnimation();
// });
// ui->password->setFocus();
QTimer::singleShot(100, this, [this] {
ui->PasswordUnderline->startAnimation();
});
});
}
@ -68,6 +66,33 @@ bool MainWindow::checkPassword(QString password) {
QProcess checker;
checker.start(d->lockCheckProcess, {name});
checker.write(password.toUtf8());
checker.write("\n");
checker.closeWriteChannel();
checker.waitForFinished();
return checker.exitCode() == 0;
}
void MainWindow::on_unlockButton_clicked() {
this->setEnabled(false);
if (checkPassword(ui->password->text())) {
QApplication::exit(0);
return;
}
this->setEnabled(true);
ui->password->setText("");
ui->password->setFocus();
tErrorFlash::flashError(ui->password);
}
void MainWindow::on_password_returnPressed() {
ui->unlockButton->click();
}
void MainWindow::changeEvent(QEvent* event) {
if (event->type() == QEvent::ActivationChange) {
LockManager::instance()->raiseAll();
}
}

View file

@ -18,11 +18,19 @@ class MainWindow : public QMainWindow {
private slots:
void on_titleLabel_backButtonClicked();
void on_unlockButton_clicked();
void on_password_returnPressed();
private:
Ui::MainWindow* ui;
MainWindowPrivate* d;
bool checkPassword(QString password);
// QWidget interface
protected:
void changeEvent(QEvent* event);
};
#endif // MAINWINDOW_H

View file

@ -38,17 +38,215 @@
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>9</number>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>580</height>
</size>
<property name="topMargin">
<number>9</number>
</property>
</spacer>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QFrame" name="lockFrame">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="usernameLabel">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string notr="true">Username</string>
</property>
<property name="buddy">
<cstring></cstring>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string>Please enter your password</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_14">
<property name="spacing">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="password">
<property name="font">
<font>
<pointsize>30</pointsize>
</font>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="capsLockOn">
<property name="toolTip">
<string>Caps Lock is on</string>
</property>
<property name="text">
<string notr="true">Caps Lock is on</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="unlockButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-next"/>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="UnderlineAnimation" name="PasswordUnderline" native="true"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
@ -62,6 +260,12 @@
<slot>backButtonClicked()</slot>
</slots>
</customwidget>
<customwidget>
<class>UnderlineAnimation</class>
<extends>QWidget</extends>
<header>underlineanimation.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View file

@ -5,23 +5,35 @@
<name>MainWindow</name>
<message>
<location filename="../mainwindow.ui" line="14"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/locker/locker-ui/locker-ui_autogen/include/ui_mainwindow.h" line="59"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/locker/locker-ui/locker-ui_autogen/include/ui_mainwindow.h" line="188"/>
<source>MainWindow</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="36"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/locker/locker-ui/locker-ui_autogen/include/ui_mainwindow.h" line="60"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/locker/locker-ui/locker-ui_autogen/include/ui_mainwindow.h" line="189"/>
<source>Unlock Session</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="22"/>
<location filename="../mainwindow.ui" line="137"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/locker/locker-ui/locker-ui_autogen/include/ui_mainwindow.h" line="190"/>
<source>Please enter your password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="197"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/locker/locker-ui/locker-ui_autogen/include/ui_mainwindow.h" line="192"/>
<source>Caps Lock is on</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="25"/>
<source>Unlock</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="22"/>
<location filename="../mainwindow.cpp" line="25"/>
<source>Resume your session, continuing where you left off</source>
<translation type="unfinished"></translation>
</message>

View file

@ -0,0 +1,45 @@
#include "underlineanimation.h"
UnderlineAnimation::UnderlineAnimation(QWidget *parent) : QWidget(parent)
{
anim1 = new tVariantAnimation();
anim1->setStartValue(0);
anim1->setEndValue(this->width() / 2);
anim1->setDuration(500);
anim1->setEasingCurve(QEasingCurve::OutCubic);
connect(anim1, &tVariantAnimation::valueChanged, [=](QVariant value) {
this->repaint();
});
QColor col = this->palette().color(QPalette::WindowText).toRgb();
anim2 = new tVariantAnimation();
anim2->setStartValue(col);
col.setAlpha(0);
anim2->setEndValue(col);
anim2->setDuration(500);
anim2->setEasingCurve(QEasingCurve::OutCubic);
}
QSize UnderlineAnimation::sizeHint() const {
return QSize(20, 1);
}
void UnderlineAnimation::startAnimation() {
anim1->setEndValue(this->width() / 2);
anim1->start();
anim2->setCurrentTime(0);
//QTimer::singleShot(500, [=] {
anim2->start();
//});
}
void UnderlineAnimation::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setPen(Qt::transparent);
p.setBrush(anim2->currentValue().value<QColor>());
int mid = this->width() / 2;
p.drawRect(mid - anim1->currentValue().toInt(), 0, anim1->currentValue().toInt() * 2, this->height());
}

View file

@ -0,0 +1,29 @@
#ifndef UNDERLINEANIMATION_H
#define UNDERLINEANIMATION_H
#include <QWidget>
#include <QPaintEvent>
#include <tvariantanimation.h>
#include <QPainter>
#include <QTimer>
class UnderlineAnimation : public QWidget
{
Q_OBJECT
public:
explicit UnderlineAnimation(QWidget *parent = nullptr);
QSize sizeHint() const;
signals:
public slots:
void startAnimation();
private:
void paintEvent(QPaintEvent* event);
tVariantAnimation *anim1, *anim2;
};
#endif // UNDERLINEANIMATION_H

View file

@ -1,55 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl">
<context>
<context>
<name>PenButton</name>
<message>
<translation>Supergum</translation>
<location filename="../penbutton.cpp" line="54" />
<source>Erase-o</source>
<source>Erase-o</source>
<translation type="vanished">Supergum</translation>
</message>
<message>
<translation>2000</translation>
<location filename="../penbutton.cpp" line="55" />
<source>matic 2000</source>
<source>matic 2000</source>
<translation type="vanished">2000</translation>
</message>
</context>
<context>
<message>
<location filename="../penbutton.cpp" line="54"/>
<source>Erase-o</source>
<comment>This and the next translation span two lines. Be spiffy with these!</comment>
<translation type="unfinished">Supergum</translation>
</message>
<message>
<location filename="../penbutton.cpp" line="55"/>
<source>matic 2000</source>
<comment>This and the previous translation span two lines. Be spiffy with these!</comment>
<translation type="unfinished">2000</translation>
</message>
</context>
<context>
<name>ScreenshotWindow</name>
<message>
<translation>SCHERMAFBEELDING</translation>
<location filename="../screenshotwindow.ui" line="55" />
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="192" />
<source>SCREENSHOT</source>
<location filename="../screenshotwindow.ui" line="55"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="192"/>
<source>SCREENSHOT</source>
<translation>SCHERMAFBEELDING</translation>
</message>
<message>
<translation>Bijsnijden</translation>
<location filename="../screenshotwindow.ui" line="130" />
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="193" />
<source>Crop</source>
<location filename="../screenshotwindow.ui" line="130"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="193"/>
<source>Crop</source>
<translation>Bijsnijden</translation>
</message>
<message>
<translation>Redigeren</translation>
<location filename="../screenshotwindow.ui" line="150" />
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="194" />
<source>Redact</source>
<location filename="../screenshotwindow.ui" line="150"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="194"/>
<source>Redact</source>
<translation>Redigeren</translation>
</message>
<message>
<translation>Opmaak Herstellen</translation>
<location filename="../screenshotwindow.ui" line="205" />
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="195" />
<source>Reset Markup</source>
<location filename="../screenshotwindow.ui" line="205"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="195"/>
<source>Reset Markup</source>
<translation>Opmaak Herstellen</translation>
</message>
<message>
<translation>Verwijderen</translation>
<location filename="../screenshotwindow.ui" line="216" />
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="196" />
<source>Discard</source>
<location filename="../screenshotwindow.ui" line="216"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="196"/>
<source>Discard</source>
<translation>Verwijderen</translation>
</message>
<message>
<translation>Kopiëren</translation>
<location filename="../screenshotwindow.ui" line="227" />
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="197" />
<source>Copy</source>
<location filename="../screenshotwindow.ui" line="227"/>
<location filename="../../../../build-thedesk-Chroot_6-Debug/plugins/ScreenshotPlugin/plugin-screenshot_autogen/include/ui_screenshotwindow.h" line="197"/>
<source>Copy</source>
<translation>Kopiëren</translation>
</message>
</context>
</TS>
</context>
</TS>