Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
/.pytest_cache/
**/__pycache__/

/shell/build/
/shell/.cache/

**/.DS_Store
/.idea/
66 changes: 53 additions & 13 deletions docs/building.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,29 +156,69 @@ exporting artifacts.
QEMU startup
----------------------

``mosaic-shell.service`` starts ``/usr/bin/mosaic-shell`` on VT1 as the locked,
non-login ``mosaic`` account with ``video`` and ``input`` group membership. It waits
QEMU images boot to ``graphical.target``, which starts ``mosaic-shell.service``.
The service runs ``/usr/bin/mosaic-shell`` on VT1 as the locked,
non-login ``mosaic`` account with ``video``, ``input``, and ``render`` group membership. It waits
for DRM card0, VT1, and systemd user-session initialization, and conflicts with
``getty@tty1.service``. Use the serial console for administration and diagnostics.
``getty@tty1.service``. If VT1 already has a controlling process, startup fails
instead of waiting for that process to release it. Use the serial console for
administration and diagnostics.
systemd creates ``/run/mosaic`` with mode ``0700`` and sets ``XDG_RUNTIME_DIR``
to that directory.
It also creates ``/var/lib/mosaic`` with mode ``0750`` as ``HOME`` and
``/var/cache/mosaic`` with mode ``0700`` as ``XDG_CACHE_HOME``. These directories
are owned by ``mosaic`` and persist across service restarts and boots.
Output goes to journald, and failures restart after two seconds, with at most
five starts per minute.

The product layer installs ``/etc/mosaic/session.env`` with:

.. code-block:: text
QT_QPA_PLATFORM=linuxfb
QT_QPA_FB_DRM=1
QT_QUICK_BACKEND=software
These settings select LinuxFB using DRM dumb buffers and software rendering.
The session includes Qt platform/QML plugins and DejaVu Sans. It uses the pinned
Qt recipe's ``linuxfb``, ``kms``, ``libinput``, and ``udev`` support. Product graphics
policy belongs in ``meta-mosaic-product``, including the service, environment file,
and udev rules under ``recipes-shell/mosaic-session/``. To change the environment
on a running image, edit ``/etc/mosaic/session.env`` and restart the service.
QT_QPA_PLATFORM=eglfs
QT_QPA_EGLFS_INTEGRATION=eglfs_kms
QT_QPA_EGLFS_KMS_CONFIG=/etc/mosaic/eglfs_kms.json
QT_QPA_EGLFS_HIDECURSOR=1
QSG_RHI_BACKEND=opengl
QT_SHADER_CACHE_PATH=/var/cache/mosaic
These settings select EGLFS/KMS and Qt Quick's OpenGL renderer. The installed
``/etc/mosaic/eglfs_kms.json`` selects ``/dev/dri/card0`` and disables the hardware
cursor. The session hides the pointer and stores shader cache files in its
persistent cache directory. Udev rules grant the ``video`` group access to card0
and the ``render`` group access to DRM render nodes.

The QEMU distro override enables ``opengl``. The pinned Qt recipe then enables
``gbm``, ``gles2``, and ``eglfs`` alongside ``kms``, ``libinput``, and ``udev``;
Mesa provides EGL, GLES, GBM, and runtime drivers. The session includes Mesa's
runtime drivers explicitly because they are loaded dynamically, along with Qt
platform/QML plugins and DejaVu Sans. The standard virtio GPU uses Mesa's
software rendering fallback, so host GPU acceleration is not required.
This uses the OpenGL rendering pipeline rather than Qt Quick's software backend.
STM32 graphics configuration is unchanged.

Product graphics policy belongs in ``meta-mosaic-product``, including the service,
environment file, KMS configuration, and udev rules under
``recipes-shell/mosaic-session/``. To change the display setup on a running image,
edit ``/etc/mosaic/session.env`` or ``/etc/mosaic/eglfs_kms.json`` and restart the service.
A missing environment file or failed display/QML startup fails the service.

Lock-screen authentication
--------------------------

The shell package installs ``/etc/pam.d/mosaic-shell``. The authentication backend
selects this PAM service to check the current process user's password and account
status using the system's ``common-auth`` and ``common-account`` stacks, like sudo.
The backend rejects empty passwords and does not open a new login session or
change passwords. ``libpam-runtime`` supplies the common policies, authentication
modules, and password-checking helper. Local edits to the Mosaic policy are
preserved during package upgrades.

Under ``mosaic-shell.service``, the authenticated user is ``mosaic``. The current
boot configuration still locks that account's password and sets
``NoNewPrivileges=yes``. Password provisioning and permission for PAM's
``unix_chkpwd`` helper must be configured before unlocking can work in the image.

Add another machine
-------------------

Expand Down
104 changes: 104 additions & 0 deletions shell/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Generated by CLion for QT
BasedOnStyle: LLVM

AccessModifierOffset: -8
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: DontAlign
AlignOperands: Align
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: Never
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- forever # avoids { wrapped to next line
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeCategories:
- Regex: "^<Q.*"
Priority: 200
IncludeIsMainRegex: "(Test)?$"
IndentCaseLabels: false
IndentWidth: 8
IndentWrappedFunctionNames: false
InsertBraces: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
# Do not add QT_BEGIN_NAMESPACE/QT_END_NAMESPACE as this will indent lines in between.
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 88
PenaltyBreakBeforeFirstCallParameter: 300
PenaltyBreakComment: 500
PenaltyBreakFirstLessLess: 400
PenaltyBreakString: 600
PenaltyExcessCharacter: 50
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Right
ReflowComments: false
SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++17
TabWidth: 8
UseTab: Never
25 changes: 22 additions & 3 deletions shell/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Qt6 6.5 REQUIRED COMPONENTS Quick)
find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h)
find_library(PAM_LIBRARY NAMES pam)
if(NOT PAM_INCLUDE_DIR OR NOT PAM_LIBRARY)
message(FATAL_ERROR "PAM development files not found. Install libpam0g-dev on Ubuntu, or provide PAM in the target sysroot.")
endif()

find_package(Qt6 6.5 REQUIRED COMPONENTS Quick QuickControls2 Concurrent)
qt_standard_project_setup(REQUIRES 6.5)

qt_add_executable(app src/main.cpp)
Expand All @@ -17,8 +23,21 @@ set_source_files_properties(qml/Main.qml PROPERTIES QT_RESOURCE_ALIAS Main.qml)

qt_add_qml_module(app
URI Mosaic
QML_FILES qml/Main.qml
QML_FILES
qml/Main.qml
qml/Shell.qml
qml/LockScreen.qml
qml/HomeScreen.qml
SOURCES
src/session/sessionController.h
src/session/sessionController.cpp

src/auth/AuthenticationBackend.h
src/auth/PamAuthenticationBackend.h
src/auth/PamAuthenticationBackend.cpp
)

target_link_libraries(app PRIVATE Qt6::Quick)
target_include_directories(app PRIVATE src/session src/auth ${PAM_INCLUDE_DIR})

target_link_libraries(app PRIVATE Qt6::Quick Qt6::QuickControls2 Qt6::Concurrent ${PAM_LIBRARY})
target_compile_options(app PRIVATE -Wall -Wextra)
12 changes: 12 additions & 0 deletions shell/qml/HomeScreen.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import QtQuick

Item {
anchors.fill: parent

Text {
text: "HOME"
color: "black"
font.pixelSize: 44
anchors.centerIn: parent
}
}
88 changes: 88 additions & 0 deletions shell/qml/LockScreen.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Controls

Rectangle {
id: root
color: "white"

property string errorMessage: ""
signal unlockRequested(string password)

function resetPasswordInput() {
passwordField.clear()

if (root.visible)
passwordField.forceActiveFocus()
}

function submitPassword() {
const password = passwordField.text

if (password.length === 0)
return

passwordField.clear()
root.unlockRequested(password)

Qt.callLater(root.resetPasswordInput)
}

Component.onCompleted: Qt.callLater(root.resetPasswordInput)
onVisibleChanged: Qt.callLater(root.resetPasswordInput)

Column {
anchors.centerIn: parent
width: Math.max(0, Math.min(320, root.width - 40))
spacing: 16

Label {
id: clockLabel
property date currentTime: new Date()

text: Qt.formatTime(currentTime, "hh:mm")
color: "Black"
font.pixelSize: 64
anchors.horizontalCenter: parent.horizontalCenter

Timer {
interval: 1000
repeat: true
running: root.visible
triggeredOnStart: true
onTriggered: clockLabel.currentTime = new Date()
}
}

TextField {
id: passwordField
width: parent.width
height: width / 3


horizontalAlignment: TextInput.AlignHCenter
placeholderText: qsTr("****")

font.pixelSize: 34
color: "black"
background: Rectangle {
color: "white"
border.width: 2
border.color: "gainsboro"
}

echoMode: TextInput.Password
inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText

onAccepted: root.submitPassword()
}

Label {
width: parent.width
text: root.errorMessage
visible: text.length > 0
color: "red"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}
7 changes: 2 additions & 5 deletions shell/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ Window {
title: qsTr("Mosaic")
color: "white"

Text {
text: "Mosaic"
color: "black"
font.pixelSize: 48
anchors.centerIn: parent
Shell {
anchors.fill: parent
}
}
23 changes: 23 additions & 0 deletions shell/qml/Shell.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import QtQuick
import Mosaic

Item {
HomeScreen {
anchors.fill: parent
visible: !SessionController.locked
enabled: !SessionController.locked
}

LockScreen {
anchors.fill: parent
z: 1

visible: SessionController.locked
enabled: SessionController.locked
errorMessage: SessionController.error_msg

onUnlockRequested: (password) => {
SessionController.requestUnlock(password)
}
}
}
22 changes: 22 additions & 0 deletions shell/src/auth/AuthenticationBackend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <QObject>
#include <QString>

class AuthenticationBackend : public QObject
{
Q_OBJECT

public:
enum class Result { SUCCESS, REJECTED, BACKENDFAILURE };
Q_ENUM(Result)

explicit AuthenticationBackend(QObject *parent = nullptr)
: QObject(parent)
{}

virtual void authenticate(quint64 request_id, QString password) = 0;

signals:
void authenticationFinished(quint64 request_id, Result result);
};
Loading
Loading