diff --git a/.github/workflows/ddm-archlinux-build.yml b/.github/workflows/ddm-archlinux-build.yml index f9143ed..b45233c 100644 --- a/.github/workflows/ddm-archlinux-build.yml +++ b/.github/workflows/ddm-archlinux-build.yml @@ -21,7 +21,7 @@ jobs: - name: Install base dependencies run: | - pacman -S --noconfirm --noprogressbar base-devel bash gcc-libs glibc libxau pam qt6-base qt6-declarative systemd-libs ttf-font xorg-server xorg-xauth git extra-cmake-modules python-docutils qt6-tools treeland-protocols ninja + pacman -S --noconfirm --noprogressbar base-devel bash gcc-libs glibc libxau pam qt6-base qt6-declarative dtk6core systemd-libs ttf-font xorg-server xorg-xauth git extra-cmake-modules python-docutils qt6-tools treeland-protocols ninja - name: Build and Install treeland-protocols run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 593a085..fdd8216 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,9 @@ find_package(TreelandProtocols 0.5.3 REQUIRED) find_package(Qt6 CONFIG REQUIRED Core DBus Network) qt_standard_project_setup(REQUIRES 6.6) +# DTK (DConfig) +find_package(Dtk6 REQUIRED COMPONENTS Core Tools) + # Uninstall target if ("${ECM_VERSION}" VERSION_LESS "1.7.0") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" @@ -114,13 +117,19 @@ set(DBUS_CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system. set(STATE_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/ddm" CACHE PATH "State directory") set(RUNTIME_DIR "${RUNTIME_DIR_DEFAULT}" CACHE PATH "Runtime data storage directory") +# DConfig schema location of the DDM configuration. +# The application id is a contract shared with dde-daemon and the DSG daemon, and +# schemas are only resolved by it under the standard DSG data directory, so these +# are deliberately plain variables instead of cache options: a -D override would +# install the schema somewhere the runtime never looks. +set(DDM_DCONFIG_APP_ID "org.deepin.dde.ddm") +set(DDM_DCONFIG_DIR "${CMAKE_INSTALL_FULL_DATADIR}/dsg/configs/${DDM_DCONFIG_APP_ID}") +set(DDM_DCONFIG_META_FILE "${DDM_DCONFIG_DIR}/${DDM_DCONFIG_APP_ID}.json") + set(SESSION_COMMAND "${DATA_INSTALL_DIR}/scripts/Xsession" CACHE PATH "Script to execute when starting the X11 desktop session") set(WAYLAND_SESSION_COMMAND "${DATA_INSTALL_DIR}/scripts/wayland-session" CACHE PATH "Script to execute when starting the Wayland desktop session") -set(CONFIG_FILE "${CMAKE_INSTALL_FULL_SYSCONFDIR}/ddm.conf" CACHE PATH "Path of the ddm config file") -set(CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/ddm.conf.d" CACHE PATH "Path of the ddm config directory") set(ACCOUNTSSERVICE_DATA_DIR "/var/lib/AccountsService" CACHE PATH "Path of the accountsservice data directory") -set(SYSTEM_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/lib/ddm/ddm.conf.d" CACHE PATH "Path of the system ddm config directory") set(LOG_FILE "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/ddm.log" CACHE PATH "Path of the ddm log file") set(DBUS_CONFIG_FILENAME "org.freedesktop.DisplayManager.conf" CACHE STRING "Name of the ddm config file") set(COMPONENTS_TRANSLATION_DIR "${DATA_INSTALL_DIR}/translations" CACHE PATH "Components translations directory") diff --git a/REUSE.toml b/REUSE.toml index 536008e..1108613 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -63,7 +63,7 @@ SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "GPL-2.0-or-later" [[annotations]] -path = ["releng/prepare-relnotes", "src/daemon/Auth.cpp", "src/daemon/Auth.h", "src/common/ConfigReader.cpp", "src/common/ConfigReader.h", "src/common/Configuration.cpp", "src/common/Configuration.h", "src/common/MessageHandler.h", "src/common/Messages.h", "src/common/Session.cpp", "src/common/Session.h", "src/common/SignalHandler.cpp", "src/common/SignalHandler.h", "src/common/SocketWriter.cpp", "src/common/SocketWriter.h", "src/common/XAuth.cpp", "src/common/XAuth.h", "src/daemon/DaemonApp.cpp", "src/daemon/DaemonApp.h", "src/daemon/Display.cpp", "src/daemon/Display.h", "src/daemon/DisplayManager.cpp", "src/daemon/DisplayManager.h", "src/daemon/PowerManager.cpp", "src/daemon/PowerManager.h", "src/daemon/SeatManager.cpp", "src/daemon/SeatManager.h", "src/daemon/SocketServer.cpp", "src/daemon/SocketServer.h", "src/daemon/TreelandConnector.cpp", "src/daemon/TreelandConnector.h", "src/daemon/VirtualTerminal.cpp", "src/daemon/VirtualTerminal.h", "src/daemon/Utils.h", "src/daemon/XorgDisplayServer.cpp", "src/daemon/XorgDisplayServer.h", "src/daemon/UserSession.cpp", "src/daemon/UserSession.h", "src/common/LogindDBusTypes.cpp", "src/common/LogindDBusTypes.h"] +path = ["releng/prepare-relnotes", "src/daemon/Auth.cpp", "src/daemon/Auth.h", "src/daemon/Configuration.cpp", "src/daemon/Configuration.h", "src/daemon/MessageHandler.h", "src/common/Messages.h", "src/daemon/Session.cpp", "src/daemon/Session.h", "src/common/SignalHandler.cpp", "src/common/SignalHandler.h", "src/common/SocketWriter.cpp", "src/common/SocketWriter.h", "src/common/XAuth.cpp", "src/common/XAuth.h", "src/daemon/DaemonApp.cpp", "src/daemon/DaemonApp.h", "src/daemon/Display.cpp", "src/daemon/Display.h", "src/daemon/DisplayManager.cpp", "src/daemon/DisplayManager.h", "src/daemon/PowerManager.cpp", "src/daemon/PowerManager.h", "src/daemon/SeatManager.cpp", "src/daemon/SeatManager.h", "src/daemon/SocketServer.cpp", "src/daemon/SocketServer.h", "src/daemon/TreelandConnector.cpp", "src/daemon/TreelandConnector.h", "src/daemon/VirtualTerminal.cpp", "src/daemon/VirtualTerminal.h", "src/daemon/Utils.h", "src/daemon/XorgDisplayServer.cpp", "src/daemon/XorgDisplayServer.h", "src/daemon/UserSession.cpp", "src/daemon/UserSession.h", "src/daemon/LogindDBusTypes.cpp", "src/daemon/LogindDBusTypes.h"] precedence = "aggregate" SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "GPL-2.0-or-later" diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index bef73ba..d1c9a81 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -9,6 +9,19 @@ install(FILES DESTINATION "${DBUS_CONFIG_DIR}" ) +# DConfig schemas (see DDM_DCONFIG_APP_ID) + +configure_file( + "dconfig/org.deepin.dde.ddm.json.in" + "${CMAKE_CURRENT_BINARY_DIR}/dconfig/${DDM_DCONFIG_APP_ID}.json" + @ONLY +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/dconfig/${DDM_DCONFIG_APP_ID}.json" + DESTINATION "${DDM_DCONFIG_DIR}" +) + install(FILES "scripts/Xsession" "scripts/Xsetup" diff --git a/data/dconfig/org.deepin.dde.ddm.json.in b/data/dconfig/org.deepin.dde.ddm.json.in new file mode 100644 index 0000000..8ba8c97 --- /dev/null +++ b/data/dconfig/org.deepin.dde.ddm.json.in @@ -0,0 +1,237 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "haltCommand": { + "value": "@HALT_COMMAND@", + "serial": 0, + "flags": [], + "name": "Halt Command", + "name[zh_CN]": "关机命令", + "description": "Halt command.", + "description[zh_CN]": "关机命令。", + "permissions": "readwrite", + "visibility": "private" + }, + "rebootCommand": { + "value": "@REBOOT_COMMAND@", + "serial": 0, + "flags": [], + "name": "Reboot Command", + "name[zh_CN]": "重启命令", + "description": "Reboot command.", + "description[zh_CN]": "重启命令。", + "permissions": "readwrite", + "visibility": "private" + }, + "namespaces": { + "value": [], + "serial": 0, + "flags": [], + "name": "Namespaces", + "name[zh_CN]": "命名空间", + "description": "List of paths bound to Linux namespaces to enter with setns() before starting the user session.", + "description[zh_CN]": "启动用户会话前通过 setns() 进入的 Linux 命名空间路径列表。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11CursorTheme": { + "value": "", + "serial": 0, + "flags": [], + "name": "X11 Cursor Theme", + "name[zh_CN]": "X11 光标主题", + "description": "Cursor theme used by the X11 display server.", + "description[zh_CN]": "X11 显示服务器使用的光标主题。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11CursorSize": { + "value": "", + "serial": 0, + "flags": [], + "name": "X11 Cursor Size", + "name[zh_CN]": "X11 光标大小", + "description": "Cursor size used by the X11 display server.", + "description[zh_CN]": "X11 显示服务器使用的光标大小。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11ServerPath": { + "value": "/usr/bin/X", + "serial": 0, + "flags": [], + "name": "X11 Server Path", + "name[zh_CN]": "X11 Server 路径", + "description": "Path to the X server binary.", + "description[zh_CN]": "X server 可执行文件路径。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11ServerArguments": { + "value": "-nolisten tcp", + "serial": 0, + "flags": [], + "name": "X11 Server Arguments", + "name[zh_CN]": "X11 Server 参数", + "description": "Arguments passed to the X server invocation.", + "description[zh_CN]": "传递给 X server 的启动参数。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11SessionDirs": { + "value": ["/usr/local/share/xsessions", "/usr/share/xsessions"], + "serial": 0, + "flags": [], + "name": "X11 Session Directories", + "name[zh_CN]": "X11 会话目录", + "description": "List of directories containing available X sessions.", + "description[zh_CN]": "包含可用 X 会话的目录列表。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11SessionCommand": { + "value": "@SESSION_COMMAND@", + "serial": 0, + "flags": [], + "name": "X11 Session Command", + "name[zh_CN]": "X11 会话命令", + "description": "Path to a script to execute when starting the desktop session.", + "description[zh_CN]": "启动桌面会话时执行的脚本路径。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11SessionLogFile": { + "value": ".local/share/ddm/xorg-session.log", + "serial": 0, + "flags": [], + "name": "X11 Session Log File", + "name[zh_CN]": "X11 会话日志文件", + "description": "Path to the user session log file, relative to the home directory.", + "description[zh_CN]": "用户会话日志文件路径,相对于用户主目录。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11DisplayCommand": { + "value": "@DATA_INSTALL_DIR@/scripts/Xsetup", + "serial": 0, + "flags": [], + "name": "X11 Display Command", + "name[zh_CN]": "X11 显示启动命令", + "description": "Path to a script to execute when starting the display server.", + "description[zh_CN]": "启动显示服务器时执行的脚本路径。", + "permissions": "readwrite", + "visibility": "private" + }, + "x11DisplayStopCommand": { + "value": "@DATA_INSTALL_DIR@/scripts/Xstop", + "serial": 0, + "flags": [], + "name": "X11 Display Stop Command", + "name[zh_CN]": "X11 显示停止命令", + "description": "Path to a script to execute when stopping the display server.", + "description[zh_CN]": "停止显示服务器时执行的脚本路径。", + "permissions": "readwrite", + "visibility": "private" + }, + "waylandSessionDirs": { + "value": ["/usr/local/share/wayland-sessions", "/usr/share/wayland-sessions"], + "serial": 0, + "flags": [], + "name": "Wayland Session Directories", + "name[zh_CN]": "Wayland 会话目录", + "description": "List of directories containing available Wayland sessions.", + "description[zh_CN]": "包含可用 Wayland 会话的目录列表。", + "permissions": "readwrite", + "visibility": "private" + }, + "waylandSessionCommand": { + "value": "@WAYLAND_SESSION_COMMAND@", + "serial": 0, + "flags": [], + "name": "Wayland Session Command", + "name[zh_CN]": "Wayland 会话命令", + "description": "Path to a script to execute when starting the desktop session.", + "description[zh_CN]": "启动桌面会话时执行的脚本路径。", + "permissions": "readwrite", + "visibility": "private" + }, + "waylandSessionLogFile": { + "value": ".local/share/ddm/wayland-session.log", + "serial": 0, + "flags": [], + "name": "Wayland Session Log File", + "name[zh_CN]": "Wayland 会话日志文件", + "description": "Path to the user session log file, relative to the home directory.", + "description[zh_CN]": "用户会话日志文件路径,相对于用户主目录。", + "permissions": "readwrite", + "visibility": "private" + }, + "singleSessionCommand": { + "value": "@WAYLAND_SESSION_COMMAND@", + "serial": 0, + "flags": [], + "name": "Single Session Command", + "name[zh_CN]": "单例会话命令", + "description": "Path to a script to execute when starting the Treeland user session.", + "description[zh_CN]": "启动 Treeland 用户会话时执行的脚本路径。", + "permissions": "readwrite", + "visibility": "private" + }, + "usersDefaultPath": { + "value": "/usr/local/bin:/usr/bin:/bin", + "serial": 0, + "flags": [], + "name": "Users Default Path", + "name[zh_CN]": "用户默认 PATH", + "description": "Default $PATH for logged in users.", + "description[zh_CN]": "已登录用户的默认 $PATH。", + "permissions": "readwrite", + "visibility": "private" + }, + "autologinUser": { + "value": "", + "serial": 0, + "flags": [], + "name": "Autologin User", + "name[zh_CN]": "自动登录用户", + "description": "User to log in automatically (empty disables autologin).", + "description[zh_CN]": "自动登录的用户(为空则禁用自动登录)。", + "permissions": "readwrite", + "visibility": "private" + }, + "autologinSession": { + "value": "", + "serial": 0, + "flags": [], + "name": "Autologin Session", + "name[zh_CN]": "自动登录会话", + "description": "Session to log in to automatically, either its name or the absolute path of its desktop file; a name is looked up in the X11 and Wayland session directories. Set together with Autologin User. If empty, the session of the last successful login is used.", + "description[zh_CN]": "自动登录时加载的会话,可为会话名或 desktop 文件绝对路径;会话名在 X11、Wayland 会话目录中查找。与自动登录用户一起配置。为空时使用上次成功登录的会话。", + "permissions": "readwrite", + "visibility": "private" + }, + "lastUser": { + "value": "", + "serial": 0, + "flags": [], + "name": "Last User", + "name[zh_CN]": "上次登录用户", + "description": "User of the last successful login. Maintained by ddm and sent to the greeter.", + "description[zh_CN]": "上次成功登录的用户,由 ddm 维护并下发给 greeter。", + "permissions": "readwrite", + "visibility": "private" + }, + "lastSession": { + "value": "", + "serial": 0, + "flags": [], + "name": "Last Session", + "name[zh_CN]": "上次登录会话", + "description": "Absolute path of the desktop file of the last successful login. Maintained by ddm, sent to the greeter, and used as the autologin session fallback.", + "description[zh_CN]": "上次成功登录所用 desktop 文件的绝对路径,由 ddm 维护并下发给 greeter,同时作为自动登录会话的回退值。", + "permissions": "readwrite", + "visibility": "private" + } + } +} diff --git a/data/man/CMakeLists.txt b/data/man/CMakeLists.txt index 822c093..c5c6deb 100644 --- a/data/man/CMakeLists.txt +++ b/data/man/CMakeLists.txt @@ -19,8 +19,4 @@ function(BUILD_MAN_PAGE _sources _src _dst) endfunction() build_man_page(MAN1_OUTPUT "ddm.rst" "ddm.1") -build_man_page(MAN5_OUTPUT "ddm.conf.rst" "ddm.conf.5") -build_man_page(MAN5_OUTPUT "ddm-state.conf.rst" "ddm-state.conf.5") - install(FILES ${MAN1_OUTPUT} DESTINATION "${CMAKE_INSTALL_FULL_MANDIR}/man1") -install(FILES ${MAN5_OUTPUT} DESTINATION "${CMAKE_INSTALL_FULL_MANDIR}/man5") diff --git a/data/man/ddm-state.conf.rst.in b/data/man/ddm-state.conf.rst.in deleted file mode 100644 index 222828f..0000000 --- a/data/man/ddm-state.conf.rst.in +++ /dev/null @@ -1,46 +0,0 @@ -============ - state.conf -============ - ------------------ -ddm configuration ------------------ - -:Date: Dec 2025 -:Version: ddm @DDM_VERSION@ -:Manual section: 5 -:Manual group: ddm - -SYNOPSIS -======== - -@STATE_DIR@/state.conf - -DESCRIPTION -=========== - -This file carries state configuration for the ddm display manager **ddm**\(1\). -It lives in the home directory of the **dde** user, which is **@STATE_DIR@**. -It is written by ddm and does not need to be edited by hand. - -OPTIONS -======= - -All options are configured in the [Last] section: - -`Session=` - Name of the session file of the last session selected. - This session will be preselected when the login screen shows up. - Default value is empty. - -`User=` - Name of last logged-in user. This username will be - preselected/shown when the login screen shows up. - Default value is empty. - -SEE ALSO -======== - -**ddm**\(1\), **ddm.conf**\(5\) - -The full documentation for ddm is available at https://github.com/linuxdeepin/ddm diff --git a/data/man/ddm.conf.rst.in b/data/man/ddm.conf.rst.in deleted file mode 100644 index 2f5af33..0000000 --- a/data/man/ddm.conf.rst.in +++ /dev/null @@ -1,161 +0,0 @@ -=========== - ddm.conf -=========== - ----------------------------------- -ddm display manager configuration ----------------------------------- - -:Date: Dec 2025 -:Version: ddm @DDM_VERSION@ -:Manual section: 5 -:Manual group: ddm - -SYNOPSIS -======== - -Configuration loads all files in the configuration directories followed by the configuration file in the order listed below with the latter having highest precedence. Changes should be made to the local configurations. - -**@SYSTEM_CONFIG_DIR@** - System configuration directory - -**@CONFIG_DIR@** - Local configuration directory - -**@CONFIG_FILE@** - Local configuration file for compatibility - -DESCRIPTION -=========== - -This file configures various parameters of the ddm display manager **ddm**\(1\). -If this file is not available, default values are used. - -OPTIONS -======= - -[General] section: - -`HaltCommand=` - Halt command. - Default value is "@HALT_COMMAND@". - -`RebootCommand=` - Reboot command. - Default value is "@REBOOT_COMMAND@". - -`Namespaces=` - Comma-separated list of paths bound to Linux namespaces to enter with - setns() before starting the user session. For example, to enter network - namespace `mynet` created with `ip netns add mynet`, the value might be - `/run/netns/mynet`. Default value is empty. (The value is ignored if - the operating system is not Linux.) - -[Theme] section: - -`CursorTheme=` - Name of the cursor theme to be set before starting - an X11 display server. - -`CursorSize=` - Cursor size to be set before starting an X11 display server. - -[Autologin] section: - -`User=` - User to log in automatically. An empty value disables automatic login. - The user must not be the greeter user **dde**. - Default value is empty. - -`Session=` - Session to load for automatic login. This overrides the last used - session. If it is empty, the session remembered in - **ddm-state.conf**\(5\) is used. - Default value is empty. - -[X11] section: - -`ServerPath=` - Path of the X server. - Default value is "/usr/bin/X". - -`ServerArguments=` - Arguments to the X server. - Default value is "-nolisten tcp". - -`SessionDir=` - Comma-separated list of directories containing session files. - Default value is "/usr/local/share/xsessions,/usr/share/xsessions". - -`SessionCommand=` - Path of script to execute when starting the user session. This script - receives the value of the "Exec" setting in the ".desktop" file of the selected - session and runs it. - Default value is "@SESSION_COMMAND@". - -`SessionLogFile=` - Path to the user session log file, relative to the home directory. - Default value is ".local/share/ddm/xorg-session.log". - -`DisplayCommand=` - Path of script to execute when starting the X11 display server. - The script is executed as root by the ddm daemon. - Default value is "@DATA_INSTALL_DIR@/scripts/Xsetup". - -`DisplayStopCommand=` - Path of script to execute when stopping the X11 display server. - The script is executed as root by the ddm daemon. - Default value is "@DATA_INSTALL_DIR@/scripts/Xstop". - -[Wayland] section: - -`SessionDir=` - Comma-separated list of directories containing session files. - Default value is "/usr/local/share/wayland-sessions,/usr/share/wayland-sessions". - -`SessionCommand=` - Path of script to execute when starting the user session. This script - receives the value of the "Exec" setting in the ".desktop" file of the selected - session and run it. - Default value is "@WAYLAND_SESSION_COMMAND@". - -`SessionLogFile=` - Path to the user session log file, relative to the home directory. - Default value is ".local/share/ddm/wayland-session.log". - -[Single] section: - -`SessionCommand=` - Path of script to execute when starting the user session for a - single-mode Treeland session (a session whose desktop file sets - X-DDE-SINGLE-WAYLAND=true). Such a session runs inside the already - running Treeland instance instead of starting its own compositor. - The script receives the value of the "Exec" setting in the ".desktop" - file of the selected session and runs it. - Default value is "@WAYLAND_SESSION_COMMAND@". - -[Users] section: - -`DefaultPath=` - Default path to set after successfully logging in. - This is also where DDM looks for programs. - Default value is "/usr/local/bin:/usr/bin:/bin". - -`RememberLastUser=` - If this flag is true, LastUser value will updated - on every successful login, if false last user value - won't be updated. - Default value is true. - -`RememberLastSession=` - If this flag is true, LastSession value will updated - on every successful login, if false last session value - won't be updated. - Default value is true. - -SEE ALSO -======== - -**ddm**\(1\), **ddm-state.conf**\(5\) - -The full documentation for ddm is available at https://github.com/linuxdeepin/ddm diff --git a/data/man/ddm.rst.in b/data/man/ddm.rst.in index bcaf982..30572e3 100644 --- a/data/man/ddm.rst.in +++ b/data/man/ddm.rst.in @@ -38,28 +38,33 @@ user. OPTIONS ======= ---example-config - Print the complete current configuration to stdout and exit. - --help, -h Show help message and exit. -FILES -===== +CONFIGURATION +============= + +DDM stores its configuration through the DConfig framework of the Deepin +Desktop Environment, under the application id ``org.deepin.dde.ddm``. The +schema installed at **@DDM_DCONFIG_META_FILE@** names and describes every +option; it is the authoritative option list. -**@CONFIG_FILE@** - Local configuration file. +DDM runs as root, so the effective values belong to the root user. Read or +change them as the administrator, for example:: -**@CONFIG_DIR@** - Local configuration directory, has higher precedence than the - configuration file. + dde-dconfig -u 0 list + dde-dconfig -u 0 get -a org.deepin.dde.ddm -r org.deepin.dde.ddm -k autologinUser + dde-dconfig -u 0 set -a org.deepin.dde.ddm -r org.deepin.dde.ddm -k autologinUser -v alice -**@SYSTEM_CONFIG_DIR@** - System configuration directory, has the lowest precedence. +``autologinUser`` enables automatic login. ``autologinSession`` selects the +session to start; when it is empty, DDM uses the session of the last +successful login. -**@STATE_DIR@/state.conf** - State file remembering the last user and session. - See **ddm-state.conf**\(5\). +FILES +===== + +**@DDM_DCONFIG_META_FILE@** + Schema describing the DDM configuration options. **@RUNTIME_DIR@** Runtime directory, holds the X11 authorization file of the display @@ -71,6 +76,4 @@ FILES SEE ALSO ======== -**ddm.conf**\(5\), **ddm-state.conf**\(5\) - The full documentation for ddm is available at https://github.com/linuxdeepin/ddm diff --git a/debian/control b/debian/control index 4f60c2d..47277b3 100644 --- a/debian/control +++ b/debian/control @@ -4,6 +4,8 @@ Maintainer: justforlxz Build-Depends: debhelper-compat (= 13), cmake (>= 3.4~), extra-cmake-modules (>= 1.4.0~), + libdtk6core-bin, + libdtk6core-dev, libpam0g-dev, libsystemd-dev [linux-any], libwayland-dev, @@ -24,6 +26,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, dde-seatd, + dde-dconfig-daemon, treeland, libpam-systemd, Recommends: deepin-keyring-whitebox, diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bfc5960..c799dce 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -81,20 +81,21 @@ against `/run/dde-seatd.sock`. 3. The Treeland greeter connects to that socket and sends `Connect` with the path of its Wayland socket. The daemon connects to it and binds the private `treeland_ddm_v1` protocol. It then sends the host name, the power - capabilities and `ShowGreeter`. + capabilities, the last user and session, the session directories and + `ShowGreeter`. 4. When the greeter sends `Login`, the daemon authenticates the user with - `ddm-helper`. On success it saves the last user and session in - `state.conf` and starts the session. + `ddm-helper`. On success it starts the session and records the user and + session in its own configuration as `lastUser` and `lastSession`. 5. The session type decides what happens next: - **Single-mode Treeland** (`X-DDE-SINGLE-WAYLAND=true`): the daemon asks `dde-seatd` for a grouped VT, opens the logind session and asks Treeland to switch to the user. - **Wayland**: the daemon stops Treeland and runs the session's compositor - through the `Wayland.SessionCommand` script. + through the `waylandSessionCommand` script. - **X11**: the daemon stops Treeland, starts the X server with an authorization file in `RUNTIME_DIR` (`/run/ddm/xauth_XXXXXX`), writes the user session's authorization cookie to `/tmp/xauth_XXXXXX` and runs the - session through the `X11.SessionCommand` script. + session through the `x11SessionCommand` script. 6. `Logout`, `Lock` and `Unlock` messages are forwarded to systemd-logind (`TerminateSession()`, `LockSession()`, `UnlockSession()`). Unlocking also activates the session again, either through Treeland or by switching to the @@ -116,9 +117,13 @@ The greeter talks to the daemon over a local socket named - greeter to daemon: `Connect`, `Login`, `Logout`, `Lock`, `Unlock`, `PowerOff`, `Reboot`, `Suspend`, `Hibernate`, `HybridSleep`, `BackToNormal`. -- daemon to greeter: `HostName`, `Capabilities`, `LoginFailed`, - `InformationMessage`, `UserActivateMessage`, `SwitchToGreeter`, - `UserLoggedIn`, `ShowGreeter`. +- daemon to greeter: `HostName`, `Capabilities`, `LastLogin`, `SessionDirs`, + `LoginFailed`, `InformationMessage`, `UserActivateMessage`, + `SwitchToGreeter`, `UserLoggedIn`, `ShowGreeter`. + +`Login` carries the user name, the password and the absolute path of the +desktop file of the session to log in to; the daemon derives the session type +from the session directories it configures. The opcodes are defined in `src/common/Messages.h`. @@ -140,8 +145,17 @@ daemon socket and to know which user was activated last. ## Configuration -Configuration is loaded, from lowest to highest precedence, from the system -configuration directory (`${CMAKE_INSTALL_PREFIX}/lib/ddm/ddm.conf.d`, which -defaults to `/usr/local/lib/ddm/ddm.conf.d`), `/etc/ddm.conf.d` and -`/etc/ddm.conf`. The last user and session are stored in `state.conf` in the -`dde` user's home directory. See **ddm.conf**(5) and **ddm-state.conf**(5). +The daemon reads its configuration through the DConfig framework of the Deepin +Desktop Environment, under the application id `org.deepin.dde.ddm`. The schema +names and describes every option and is installed together with the daemon, so +there is no separate option list in the documentation. + +The greeter does not read DDM configuration: the daemon passes what it needs +over the greeter socket. + +Which sessions exist, and which of them is offered for a user, is decided by the +greeter: the daemon sends it the directories the sessions are installed in +(`x11SessionDirs` and `waylandSessionDirs`) when it connects, and receives back +the absolute path of the desktop file of the session to log in to. The daemon +also records the last logged in user and session and sends them to the greeter +as `LastLogin`; the greeter uses them to preselect the login. diff --git a/services/ddm-tmpfiles.conf.in b/services/ddm-tmpfiles.conf.in index 1ccdc92..f041b51 100644 --- a/services/ddm-tmpfiles.conf.in +++ b/services/ddm-tmpfiles.conf.in @@ -1,4 +1,4 @@ -# Home dir of the ddm user, also contains state.conf +# Home dir of the ddm user d ${STATE_DIR} 0750 dde dde # This contains X11 auth files passed to Xorg and the greeter d ${RUNTIME_DIR} 0711 root root diff --git a/services/ddm.service.in b/services/ddm.service.in index 2bf14f5..ea254d6 100644 --- a/services/ddm.service.in +++ b/services/ddm.service.in @@ -1,13 +1,14 @@ [Unit] Description=Deepin Display Manager -Documentation=man:ddm(1) man:ddm.conf(5) +Documentation=man:ddm(1) Conflicts=getty@tty${DDM_INITIAL_VT}.service -After=systemd-user-sessions.service getty@tty${DDM_INITIAL_VT}.service plymouth-quit.service systemd-logind.service dde-seatd.service +After=systemd-user-sessions.service getty@tty${DDM_INITIAL_VT}.service plymouth-quit.service systemd-logind.service dde-seatd.service dde-dconfig-daemon.service PartOf=graphical.target StartLimitIntervalSec=30 StartLimitBurst=2 Requires=dde-seatd.service +Wants=dde-dconfig-daemon.service [Service] ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/ddm diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 2b91efc..0792d44 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,28 +1,25 @@ configure_file("Constants.h.in" "Constants.h" IMMEDIATE @ONLY) + +# Keep free of DConfig and D-Bus so that the helper stays a single-threaded +# process. Reading the configuration lives in the daemon only. set(PUBLIC_HEADERS - ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.h - ${CMAKE_SOURCE_DIR}/src/common/Configuration.h - ${CMAKE_SOURCE_DIR}/src/common/MessageHandler.h ${CMAKE_SOURCE_DIR}/src/common/Messages.h - ${CMAKE_SOURCE_DIR}/src/common/Session.h - ${CMAKE_SOURCE_DIR}/src/common/SignalHandler.h ${CMAKE_SOURCE_DIR}/src/common/SocketWriter.h +) + +set(INTERNAL_HEADERS + ${CMAKE_SOURCE_DIR}/src/common/SignalHandler.h ${CMAKE_SOURCE_DIR}/src/common/XAuth.h ${CMAKE_BINARY_DIR}/src/common/Constants.h - ${CMAKE_SOURCE_DIR}/src/common/LogindDBusTypes.h ) set(SRCS - ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp - ${CMAKE_SOURCE_DIR}/src/common/Configuration.cpp - ${CMAKE_SOURCE_DIR}/src/common/Session.cpp ${CMAKE_SOURCE_DIR}/src/common/SignalHandler.cpp ${CMAKE_SOURCE_DIR}/src/common/SocketWriter.cpp ${CMAKE_SOURCE_DIR}/src/common/XAuth.cpp - ${CMAKE_SOURCE_DIR}/src/common/LogindDBusTypes.cpp ) -add_library(common SHARED ${PUBLIC_HEADERS} ${SRCS}) +add_library(common SHARED ${PUBLIC_HEADERS} ${INTERNAL_HEADERS} ${SRCS}) set_target_properties(common PROPERTIES VERSION ${CMAKE_PROJECT_VERSION} @@ -33,7 +30,7 @@ set_target_properties(common PROPERTIES target_link_libraries(common PRIVATE - Qt6::DBus + Qt6::Core Qt6::Network PkgConfig::LibXau ) diff --git a/src/common/ConfigReader.cpp b/src/common/ConfigReader.cpp deleted file mode 100644 index 00baf75..0000000 --- a/src/common/ConfigReader.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/* - * INI Configuration parser classes - * Copyright (C) 2014 Martin Bříza - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "ConfigReader.h" -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -QTextStream &operator>>(QTextStream &str, QStringList &list) { - list.clear(); - - QString line = str.readLine(); - const auto strings = QStringView{line}.split(u','); - for (const QStringView &s : strings) { - QStringView trimmed = s.trimmed(); - if (!trimmed.isEmpty()) - list.append(trimmed.toString()); - } - return str; -} - -QTextStream &operator<<(QTextStream &str, const QStringList &list) { - str << list.join(QLatin1Char(',')); - return str; -} - -QTextStream &operator>>(QTextStream &str, bool &val) { - QString line = str.readLine(); - val = (0 == QStringView(line).trimmed().compare(QLatin1String("true"), Qt::CaseInsensitive)); - return str; -} - -QTextStream &operator<<(QTextStream &str, const bool &val) { - if (val) - str << "true"; - else - str << "false"; - return str; -} - -namespace DDM { - // has to be specialised because QTextStream reads only words into a QString - template <> void ConfigEntry::setValue(const QString &str) { - m_value = str.trimmed(); - } - - - ConfigSection::ConfigSection(ConfigBase *parent, const QString &name) : m_parent(parent), - m_name(name) { - m_parent->m_sections.insert(name, this); - } - - ConfigEntryBase *ConfigSection::entry(const QString &name) { - auto it = m_entries.find(name); - if (it != m_entries.end()) - return it.value(); - return nullptr; - } - - const ConfigEntryBase *ConfigSection::entry(const QString &name) const { - auto it = m_entries.find(name); - if (it != m_entries.end()) - return it.value(); - return nullptr; - } - - const QMap &ConfigSection::entries() const { - return m_entries; - } - - - const QString &ConfigSection::name() const { - return m_name; - } - - void ConfigSection::save(ConfigEntryBase *entry) { - m_parent->save(this, entry); - } - - void ConfigSection::clear() { - for (auto it : m_entries) { - it->setDefault(); - } - } - - QString ConfigSection::toConfigFull() const { - QString final = QStringLiteral("[%1]\n").arg(m_name); - for (const ConfigEntryBase *entry : m_entries) - final.append(entry->toConfigFull()); - return final; - } - - QString ConfigSection::toConfigShort() const { - return QStringLiteral("[%1]").arg(name()); - } - - - - ConfigBase::ConfigBase(const QString &configPath, const QString &configDir, const QString &sysConfigDir) : - m_path(configPath), - m_configDir(configDir), - m_sysConfigDir(sysConfigDir) - { - } - - bool ConfigBase::hasUnused() const { - return m_unusedSections || m_unusedVariables; - } - - QString ConfigBase::toConfigFull() const { - QString ret; - for (ConfigSection *s : m_sections) { - ret.append(s->toConfigFull()); - ret.append(QLatin1Char('\n')); - } - return ret; - } - - void ConfigBase::load() - { - //order of priority from least influence to most influence, is - // * m_sysConfigDir (system settings /usr/lib/ddm/ddm.conf.d/) in alphabetical order - // * m_configDir (user settings in /etc/ddm.conf.d/) in alphabetical order - // * m_path (classic fallback /etc/ddm.conf) - - QStringList files; - QDateTime latestModificationTime = QFileInfo(m_path).lastModified(); - - if (!m_sysConfigDir.isEmpty()) { - //include the configDir in modification time so we also reload on any files added/removed - QDir dir(m_sysConfigDir); - if (dir.exists()) { - latestModificationTime = std::max(latestModificationTime, QFileInfo(m_sysConfigDir).lastModified()); - const auto dirFiles = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware); - for (const QFileInfo &file : dirFiles) { - files << (file.absoluteFilePath()); - latestModificationTime = std::max(latestModificationTime, file.lastModified()); - } - } - } - if (!m_configDir.isEmpty()) { - //include the configDir in modification time so we also reload on any files added/removed - QDir dir(m_configDir); - if (dir.exists()) { - latestModificationTime = std::max(latestModificationTime, QFileInfo(m_configDir).lastModified()); - const auto dirFiles = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware); - for (const QFileInfo &file : dirFiles) { - files << (file.absoluteFilePath()); - latestModificationTime = std::max(latestModificationTime, file.lastModified()); - } - } - } - - files << m_path; - - if (latestModificationTime <= m_fileModificationTime) { - return; - } - m_fileModificationTime = latestModificationTime; - - for (const QString &filepath : std::as_const(files)) { - loadInternal(filepath); - } - } - - - void ConfigBase::loadInternal(const QString &filepath) { - QString currentSection = QStringLiteral(IMPLICIT_SECTION); - - QFile in(filepath); - - if (!in.open(QIODevice::ReadOnly)) - return; - while (!in.atEnd()) { - QString line = QString::fromUtf8(in.readLine()); - QStringView lineRef = QStringView(line).trimmed(); - // get rid of comments first - lineRef = lineRef.left(lineRef.indexOf(QLatin1Char('#'))).trimmed(); - - // In version 0.14.0, these sections were renamed - if (currentSection == QStringLiteral("XDisplay")) - currentSection = QStringLiteral("X11"); - else if (currentSection == QStringLiteral("WaylandDisplay")) - currentSection = QStringLiteral("Wayland"); - - // value assignment - int separatorPosition = lineRef.indexOf(QLatin1Char('=')); - if (separatorPosition >= 0) { - QString name = lineRef.left(separatorPosition).trimmed().toString(); - QStringView value = lineRef.mid(separatorPosition + 1).trimmed(); - - auto sectionIterator = m_sections.constFind(currentSection); - if (sectionIterator != m_sections.constEnd() && sectionIterator.value()->entry(name)) - sectionIterator.value()->entry(name)->setValue(value.toString()); - else - // if we don't have such member in the config, nag about it - m_unusedVariables = true; - } - // section start - else if (lineRef.startsWith(QLatin1Char('[')) && lineRef.endsWith(QLatin1Char(']'))) - currentSection = lineRef.mid(1, lineRef.length() - 2).toString(); - } - } - - void ConfigBase::save(const ConfigSection *section, const ConfigEntryBase *entry) { - // to know if we should overwrite the config or not - bool changed = false; - // stores the order of the loaded sections - // each one could be there only once - if it occurs more times in the config, the occurrences are merged - QVector sectionOrder; - // the actual bytearray data for every section - QHash sectionData; - // map of nondefault entries which should be saved if they are not found in the current config file - QMultiHash remainingEntries; - - - /* - * Initialization of the map of nondefault values to be saved - */ - if (section) { - if (entry && !entry->matchesDefault()) - remainingEntries.insert(section, entry); - else { - for (const ConfigEntryBase *b : std::as_const(section->entries())) - if (!b->matchesDefault()) - remainingEntries.insert(section, b); - } - } - else { - for (const ConfigSection *s : std::as_const(m_sections)) { - for (const ConfigEntryBase *b : std::as_const(s->entries())) - if (!b->matchesDefault()) - remainingEntries.insert(s, b); - } - } - - // initialize the current section - General, usually - const ConfigSection *currentSection = m_sections.value(QStringLiteral(IMPLICIT_SECTION)); - - // stuff to store the pre-section stuff (comments) to the start of the right section, not the end of the previous one - QByteArray junk; - // stores the junk to the temporary storage - auto collectJunk = [&junk](const QString &data) { - junk.append(data.toUtf8()); - }; - - // a short function to assign the current junk and current line to the right section, eventually create a new one - auto writeSectionData = [¤tSection, &junk, §ionOrder, §ionData](const QString &data) { - if (currentSection && !sectionOrder.contains(currentSection)) { - sectionOrder.append(currentSection); - sectionData[currentSection] = QByteArray(); - } - sectionData[currentSection].append(junk); - sectionData[currentSection].append(data.toUtf8()); - junk.clear(); - }; - - // loading and checking phase - QFile file(m_path); - bool ok = file.open(QIODevice::ReadOnly); // first just for reading - Q_ASSERT(ok); - while (!file.atEnd()) { - const QString line = QString::fromUtf8(file.readLine()); - // get rid of comments first - QStringView trimmedLine = QStringView{line}.left(line.indexOf(QLatin1Char('#'))).trimmed(); - QStringView comment; - if (line.indexOf(QLatin1Char('#')) >= 0) - comment = QStringView{line}.mid(line.indexOf(QLatin1Char('#'))).trimmed(); - - // value assignment - int separatorPosition = trimmedLine.indexOf(QLatin1Char('=')); - if (separatorPosition >= 0) { - QString name = trimmedLine.left(separatorPosition).trimmed().toString(); - QStringView value = trimmedLine.mid(separatorPosition + 1).trimmed(); - - if (currentSection && currentSection->entry(name)) { - // this monstrous condition checks the parameters if only one entry/section should be saved - if ((entry && section && section->name() == currentSection->name() && entry->name() == name) || - (!entry && section && section->name() == currentSection->name()) || - value != currentSection->entry(name)->value()) { - changed = true; - writeSectionData(QStringLiteral("%1=%2 %3\n").arg(name).arg(currentSection->entry(name)->value()).arg(comment.toString())); - } - else - writeSectionData(line); - remainingEntries.remove(currentSection, currentSection->entry(name)); - } - else { - if (currentSection) - m_unusedVariables = true; - writeSectionData(QStringLiteral("%1 %2\n").arg(trimmedLine.toString()).arg(QStringLiteral(UNUSED_VARIABLE_COMMENT))); - } - } - - // section start - else if (trimmedLine.startsWith(QLatin1Char('[')) && trimmedLine.endsWith(QLatin1Char(']'))) { - const QString name = trimmedLine.mid(1, trimmedLine.length() - 2).toString(); - auto sectionIterator = m_sections.constFind(name); - if (sectionIterator != m_sections.constEnd()) { - currentSection = sectionIterator.value(); - if (!sectionOrder.contains(currentSection)) - writeSectionData(line); - } - else { - m_unusedSections = true; - currentSection = nullptr; - writeSectionData(line); - } - } - - // other stuff, like comments and whatnot - else { - if (line != QStringLiteral(UNUSED_SECTION_COMMENT)) - collectJunk(line); - } - } - file.close(); - - for (auto it = remainingEntries.begin(); it != remainingEntries.end(); it++) { - changed = true; - currentSection = it.key(); - if (!sectionOrder.contains(currentSection)) - writeSectionData(currentSection->toConfigShort()); - writeSectionData(QStringLiteral("\n")); - writeSectionData(it.value()->toConfigFull()); - } - - // rewrite the whole thing only if there are changes - if (changed) { - bool ok = file.open(QIODevice::WriteOnly | QIODevice::Truncate); - Q_ASSERT(ok); - for (const ConfigSection *s : sectionOrder) - file.write(sectionData.value(s)); - - if (sectionData.contains(nullptr)) { - file.write("\n"); - file.write(UNUSED_SECTION_COMMENT); - file.write(sectionData.value(nullptr).trimmed()); - file.write("\n"); - } - } - } - - void ConfigBase::wipe() { - for (auto it : m_sections) { - it->clear(); - } - } -} diff --git a/src/common/ConfigReader.h b/src/common/ConfigReader.h deleted file mode 100644 index d9ff441..0000000 --- a/src/common/ConfigReader.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * INI Configuration parser classes - * Copyright (C) 2014 Martin Bříza - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef CONFIGREADER_H -#define CONFIGREADER_H - -#include -#include -#include -#include -#include -#include - -#define IMPLICIT_SECTION "General" -#define UNUSED_VARIABLE_COMMENT "# Unused variable" -#define UNUSED_SECTION_COMMENT "### These sections and their variables were not used: ###\n" - -///// convenience macros -// efficient qstring initializer -#define _S(x) QStringLiteral(x) - -// config wrapper -#define Config(name, file, dir, sysDir, ...) \ - class name : public DDM::ConfigBase, public DDM::ConfigSection { \ - public: \ - name() : DDM::ConfigBase(file, dir, sysDir), DDM::ConfigSection(this, QStringLiteral(IMPLICIT_SECTION)) { \ - load(); \ - } \ - void save() { DDM::ConfigBase::save(nullptr, nullptr); } \ - void save(DDM::ConfigEntryBase *) const = delete; \ - QString toConfigFull() const { \ - return DDM::ConfigBase::toConfigFull(); \ - } \ - __VA_ARGS__ \ - } -// entry wrapper -#define Entry(name, type, default, description, ...) \ - DDM::ConfigEntry name { this, QStringLiteral(#name), default, description, __VA_ARGS__ } -// section wrapper -#define Section(name, ...) \ - class name : public DDM::ConfigSection { \ - public: \ - name (DDM::ConfigBase *_parent, const QString &_name) : DDM::ConfigSection(_parent, _name) { } \ - __VA_ARGS__ \ - } name { this, QStringLiteral(#name) }; - -QTextStream &operator>>(QTextStream &str, QStringList &list); -QTextStream &operator<<(QTextStream &str, const QStringList &list); -QTextStream &operator>>(QTextStream &str, bool &val); -QTextStream &operator<<(QTextStream &str, const bool &val); - -namespace DDM { - template class ConfigEntry; - class ConfigSection; - class ConfigBase; - - class ConfigEntryBase { - public: - virtual const QString &name() const = 0; - virtual QString value() const = 0; - virtual void setValue(const QString &str) = 0; - virtual QString toConfigShort() const = 0; - virtual QString toConfigFull() const = 0; - virtual bool matchesDefault() const = 0; - virtual bool isDefault() const = 0; - virtual bool setDefault() = 0; - }; - - class ConfigSection { - public: - ConfigSection(ConfigBase *parent, const QString &name); - ConfigEntryBase *entry(const QString &name); - const ConfigEntryBase *entry(const QString &name) const; - void save(ConfigEntryBase *entry); - void clear(); - const QString &name() const; - QString toConfigShort() const; - QString toConfigFull() const; - const QMap &entries() const; - private: - template friend class ConfigEntryPrivate; - QMap m_entries {}; - - ConfigBase *m_parent { nullptr }; - QString m_name { }; - template friend class ConfigEntry; - }; - - template - class ConfigEntry : public ConfigEntryBase { - public: - ConfigEntry(ConfigSection *parent, const QString &name, const T &value, const QString &description) : ConfigEntryBase(), - m_name(name), - m_description(description), - m_default(value), - m_value(value), - m_isDefault(true), - m_parent(parent) { - m_parent->m_entries[name] = this; - } - - T get() const { - return m_value; - } - - void set(const T val) { - m_value = val; - m_isDefault = false; - } - - bool matchesDefault() const { - return m_value == m_default; - } - - bool isDefault() const { - return m_isDefault; - } - - bool setDefault() { - m_isDefault = true; - if (m_value == m_default) - return false; - m_value = m_default; - return true; - } - - void save() { - m_parent->save(this); - } - - const QString &name() const { - return m_name; - } - - QString value() const { - QString str; - QTextStream out(&str); - out << m_value; - return str; - } - - // specialised for QString - void setValue(const QString &str) { - m_isDefault = false; - QTextStream in(qPrintable(str)); - in >> m_value; - } - - QString toConfigShort() const { - return QStringLiteral("%1=%2").arg(m_name).arg(value()); - } - - QString toConfigFull() const { - QString str; - for (const QString &line : m_description.split(QLatin1Char('\n'))) - str.append(QStringLiteral("# %1\n").arg(line)); - str.append(QStringLiteral("%1=%2\n\n").arg(m_name).arg(value())); - return str; - } - private: - const QString m_name; - const QString m_description; - T m_default; - T m_value; - bool m_isDefault; - ConfigSection *m_parent; - }; - - // Base has to be separate from the Config itself - order of initialization - class ConfigBase { - public: - ConfigBase(const QString &configPath, const QString &configDir=QString(), const QString &sysConfigDir=QString()); - - void load(); - void save(const ConfigSection *section = nullptr, const ConfigEntryBase *entry = nullptr); - void wipe(); - bool hasUnused() const; - QString toConfigFull() const; - protected: - bool m_unusedVariables { false }; - bool m_unusedSections { false }; - - QString m_path {}; - QString m_configDir; - QString m_sysConfigDir; - QMap m_sections; - friend class ConfigSection; - private: - QDateTime dirLatestModifiedTime(const QString &directory); - void loadInternal(const QString &filepath); - QDateTime m_fileModificationTime; - }; -} - - -#endif // CONFIGREADER_H diff --git a/src/common/Configuration.h b/src/common/Configuration.h deleted file mode 100644 index da8c864..0000000 --- a/src/common/Configuration.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * DDM configuration - * Copyright (C) 2014 Martin Bříza - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#ifndef DDM_CONFIGURATION_H -#define DDM_CONFIGURATION_H - -#include -#include -#include -#include -#include - -#include "Constants.h" - -#include "ConfigReader.h" - -namespace DDM { - // Name File Sections and/or Entries (but anything else too, it's a class) - Entries in a Config are assumed to be in the General section - Config(MainConfig, QStringLiteral(CONFIG_FILE), QStringLiteral(CONFIG_DIR), QStringLiteral(SYSTEM_CONFIG_DIR), - - // Name Type Default value Description - Entry(HaltCommand, QString, _S(HALT_COMMAND), _S("Halt command")); - Entry(RebootCommand, QString, _S(REBOOT_COMMAND), _S("Reboot command")); - Entry(Namespaces, QStringList, QStringList(), _S("Comma-separated list of Linux namespaces for user session to enter")); - // Name Entries (but it's a regular class again) - Section(Theme, - Entry(CursorTheme, QString, QString(), _S("Cursor theme used in the greeter")); - Entry(CursorSize, QString, QString(), _S("Cursor size used in the greeter")); - ); - - // TODO: Not absolutely sure if everything belongs here. Xsessions, VT and probably some more seem universal - Section(X11, - Entry(ServerPath, QString, _S("/usr/bin/X"), _S("Path to X server binary")); - Entry(ServerArguments, QString, _S("-nolisten tcp"), _S("Arguments passed to the X server invocation")); - Entry(SessionDir, QStringList, {_S("/usr/local/share/xsessions"), - _S("/usr/share/xsessions")}, _S("Comma-separated list of directories containing available X sessions")); - Entry(SessionCommand, QString, _S(SESSION_COMMAND), _S("Path to a script to execute when starting the desktop session")); - Entry(SessionLogFile, QString, _S(".local/share/ddm/xorg-session.log"), _S("Path to the user session log file")); - Entry(DisplayCommand, QString, _S(DATA_INSTALL_DIR "/scripts/Xsetup"), _S("Path to a script to execute when starting the display server")); - Entry(DisplayStopCommand, QString, _S(DATA_INSTALL_DIR "/scripts/Xstop"), _S("Path to a script to execute when stopping the display server")); - ); - - Section(Wayland, - Entry(SessionDir, QStringList, {_S("/usr/local/share/wayland-sessions"), - _S("/usr/share/wayland-sessions")}, _S("Comma-separated list of directories containing available Wayland sessions")); - Entry(SessionCommand, QString, _S(WAYLAND_SESSION_COMMAND), _S("Path to a script to execute when starting the desktop session")); - Entry(SessionLogFile, QString, _S(".local/share/ddm/wayland-session.log"),_S("Path to the user session log file")); - ); - - Section(Single, - Entry(SessionCommand, QString, _S(WAYLAND_SESSION_COMMAND), _S("Path to a script to execute when starting the desktop session")); - ); - - Section(Users, - Entry(DefaultPath, QString, _S("/usr/local/bin:/usr/bin:/bin"), _S("Default $PATH for logged in users")); - Entry(RememberLastUser, bool, true, _S("Remember the last successfully logged in user")); - Entry(RememberLastSession, bool, true, _S("Remember the session of the last successfully logged in user")); - ); - - Section(Autologin, - Entry(User, QString, QString(), _S("User to log in automatically (empty disables autologin)")); - Entry(Session, QString, QString(), _S("Session to load for automatic login (overrides the last used session)")); - // TODO: InBackground is not implemented yet. - // Entry(InBackground, bool, false, _S("Load the automatic login session in the background without activating it")); - ); - ); - - Config(StateConfig, []()->QString{auto tmp = getpwnam("ddm"); return tmp ? QString::fromLocal8Bit(tmp->pw_dir) : QStringLiteral(STATE_DIR);}().append(QStringLiteral("/state.conf")), QString(), QString(), - Section(Last, - Entry(Session, QString, QString(), _S("Name of the session for the last logged-in user.\n" - "This session will be preselected when the login screen appears.")); - Entry(User, QString, QString(), _S("Name of the last logged-in user.\n" - "This user will be preselected when the login screen appears")); - ); - ); - - extern MainConfig mainConfig; - extern StateConfig stateConfig; -} - -#endif // DDM_CONFIGURATION_H diff --git a/src/common/Constants.h.in b/src/common/Constants.h.in index 098daee..ab91127 100644 --- a/src/common/Constants.h.in +++ b/src/common/Constants.h.in @@ -23,25 +23,23 @@ #define BIN_INSTALL_DIR "@CMAKE_INSTALL_FULL_BINDIR@" #define LIBEXEC_INSTALL_DIR "@CMAKE_INSTALL_FULL_LIBEXECDIR@" #define DATA_INSTALL_DIR "@DATA_INSTALL_DIR@" -#define SYS_CONFIG_DIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@" #define COMPONENTS_TRANSLATION_DIR "@COMPONENTS_TRANSLATION_DIR@" #define RUNTIME_DIR "@RUNTIME_DIR@" #define STATE_DIR "@STATE_DIR@" #define ACCOUNTSSERVICE_DATA_DIR "@ACCOUNTSSERVICE_DATA_DIR@" +#define DDM_DCONFIG_APP_ID "@DDM_DCONFIG_APP_ID@" +#define DDM_DCONFIG_META_FILE "@DDM_DCONFIG_META_FILE@" + #define SESSION_COMMAND "@SESSION_COMMAND@" #define WAYLAND_SESSION_COMMAND "@WAYLAND_SESSION_COMMAND@" -#define CONFIG_FILE "@CONFIG_FILE@" -#define CONFIG_DIR "@CONFIG_DIR@" -#define SYSTEM_CONFIG_DIR "@SYSTEM_CONFIG_DIR@" - #define LOG_FILE "@LOG_FILE@" #define UID_MIN @UID_MIN@ #define UID_MAX @UID_MAX@ -#define HALT_COMMAND "@HALT_COMMAND@" -#define REBOOT_COMMAND "@REBOOT_COMMAND@" +#define HALT_COMMAND "@HALT_COMMAND@" +#define REBOOT_COMMAND "@REBOOT_COMMAND@" #endif // DDM_CONSTANTS_H diff --git a/src/common/Messages.h b/src/common/Messages.h index 61226e9..2b5191d 100644 --- a/src/common/Messages.h +++ b/src/common/Messages.h @@ -52,6 +52,8 @@ namespace DDM { SwitchToGreeter, UserLoggedIn, ShowGreeter, + SessionDirs, + LastLogin, }; enum Capability { diff --git a/src/common/SocketWriter.cpp b/src/common/SocketWriter.cpp index 974bf5e..9e61ba5 100644 --- a/src/common/SocketWriter.cpp +++ b/src/common/SocketWriter.cpp @@ -44,8 +44,8 @@ namespace DDM { return *this; } - SocketWriter &SocketWriter::operator << (const Session &s) { - *output << s; + SocketWriter &SocketWriter::operator << (const QStringList &l) { + *output << l; return *this; } diff --git a/src/common/SocketWriter.h b/src/common/SocketWriter.h index 7ed8545..d771a21 100644 --- a/src/common/SocketWriter.h +++ b/src/common/SocketWriter.h @@ -24,8 +24,6 @@ #include #include -#include "Session.h" - namespace DDM { class SocketWriter { Q_DISABLE_COPY(SocketWriter) @@ -35,7 +33,7 @@ namespace DDM { SocketWriter &operator << (const quint32 &u); SocketWriter &operator << (const QString &s); - SocketWriter &operator << (const Session &s); + SocketWriter &operator << (const QStringList &l); private: QByteArray data; diff --git a/src/common/XAuth.cpp b/src/common/XAuth.cpp index a4ef10c..fb2e94e 100644 --- a/src/common/XAuth.cpp +++ b/src/common/XAuth.cpp @@ -35,7 +35,6 @@ #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX #endif -#include "Configuration.h" #include "Constants.h" #include "XAuth.h" diff --git a/src/daemon/Auth.cpp b/src/daemon/Auth.cpp index 8b99d6e..238ff49 100644 --- a/src/daemon/Auth.cpp +++ b/src/daemon/Auth.cpp @@ -3,6 +3,7 @@ #include "Auth.h" +#include "Configuration.h" #include "Constants.h" #include "DaemonApp.h" #include "DdeSeatdControl.h" @@ -261,6 +262,35 @@ namespace DDM { const QByteArray &cookie) { Q_ASSERT(authenticated); + // Everything the helper needs to run the session comes from the + // configuration here: the helper must not read it, because creating a + // DConfig object starts a thread and opens a D-Bus connection in the + // process that forks inside pam_open_session(). + QString sessionCommand; + QString sessionLogFile; + switch (type) { + case Display::Treeland: + sessionCommand = mainConfig()->singleSessionCommand(); + sessionLogFile = mainConfig()->waylandSessionLogFile(); + break; + case Display::X11: + sessionCommand = mainConfig()->x11SessionCommand(); + sessionLogFile = mainConfig()->x11SessionLogFile(); + break; + case Display::Wayland: + sessionCommand = mainConfig()->waylandSessionCommand(); + sessionLogFile = mainConfig()->waylandSessionLogFile(); + break; + default: + qWarning() << "[Auth] unknown session type" << type; + return {}; + } + + QStringList namespaces; + const auto configuredNamespaces = mainConfig()->namespaces(); + for (const QVariant &ns : configuredNamespaces) + namespaces.append(ns.toString()); + if (!ensureHelper()) { return {}; } @@ -279,7 +309,10 @@ namespace DDM { << display << tty << static_cast(type) - << command; + << command + << sessionCommand + << sessionLogFile + << namespaces; } QByteArray reply; diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 1a84cf2..2726f79 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -17,19 +17,33 @@ add_custom_command( ) configure_file(config.h.in config.h IMMEDIATE @ONLY) + +# Generate the configuration classes from the DConfig schema. They are used by +# the daemon only: the session helper must not read the configuration, because +# creating a DConfig object starts a thread and opens a D-Bus connection. +dtk_add_config_to_cpp(DDM_CONFIG_HEADER + "${CMAKE_BINARY_DIR}/data/dconfig/${DDM_DCONFIG_APP_ID}.json" + OUTPUT_FILE_NAME "DdmConfig.hpp" + CLASS_NAME "DdmConfig" +) + set(DAEMON_SOURCES Auth.cpp + Configuration.cpp DaemonApp.cpp DdeSeatdControl.cpp Display.cpp DisplayManager.cpp + LogindDBusTypes.cpp PowerManager.cpp SeatManager.cpp + Session.cpp SocketServer.cpp TreelandConnector.cpp UserSession.cpp XorgDisplayServer.cpp TreelandDisplayServer.cpp + ${DDM_CONFIG_HEADER} ${TREELAND_DDM_SOURCE} ) @@ -62,6 +76,7 @@ add_executable(ddm ${DAEMON_SOURCES}) target_link_libraries(ddm PRIVATE common + Dtk6::Core PUBLIC Qt6::DBus Qt6::Network diff --git a/src/daemon/Configuration.cpp b/src/daemon/Configuration.cpp new file mode 100644 index 0000000..61304ca --- /dev/null +++ b/src/daemon/Configuration.cpp @@ -0,0 +1,57 @@ +/* + * DDM configuration + * Copyright (C) 2014 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "Configuration.h" + +#include "Constants.h" +#include "DdmConfig.hpp" + +#include + +namespace DDM { + namespace { + DdmConfig *s_mainConfig{ nullptr }; + } + + DdmConfig *mainConfig() { + return s_mainConfig; + } + + Configurations::Configurations(QObject *parent) + : QObject(parent) { + s_mainConfig = DdmConfig::create(QStringLiteral(DDM_DCONFIG_APP_ID), QString(), this); + + connect(s_mainConfig, &DdmConfig::configInitializeSucceed, this, [this]() { + setReady(); + }); + connect(s_mainConfig, &DdmConfig::configInitializeFailed, this, [this]() { + qCritical() << "Failed to initialize the DDM configuration"; + setReady(); + }); + } + + void Configurations::setReady() { + if (m_ready) + return; + + m_ready = true; + Q_EMIT readyChanged(); + } +} diff --git a/src/common/Configuration.cpp b/src/daemon/Configuration.h similarity index 52% rename from src/common/Configuration.cpp rename to src/daemon/Configuration.h index 83563b7..27d1d29 100644 --- a/src/common/Configuration.cpp +++ b/src/daemon/Configuration.h @@ -18,10 +18,37 @@ * */ -#include "Configuration.h" -#include "Constants.h" +#ifndef DDM_CONFIGURATION_H +#define DDM_CONFIGURATION_H + +#include + +// The configuration class is generated from the DConfig schema +#include "DdmConfig.hpp" namespace DDM { - MainConfig mainConfig; - StateConfig stateConfig; + DdmConfig *mainConfig(); + + // Creates the configuration object and reports when the DConfig backend has + // provided its values. Values must not be read before that: the generated + // class is initialized asynchronously. + class Configurations : public QObject { + Q_OBJECT + public: + explicit Configurations(QObject *parent = nullptr); + + bool ready() const { + return m_ready; + } + + Q_SIGNALS: + void readyChanged(); + + private: + void setReady(); + + bool m_ready{ false }; + }; } + +#endif // DDM_CONFIGURATION_H diff --git a/src/daemon/DaemonApp.cpp b/src/daemon/DaemonApp.cpp index f80fdb8..f21f34b 100644 --- a/src/daemon/DaemonApp.cpp +++ b/src/daemon/DaemonApp.cpp @@ -92,9 +92,19 @@ namespace DDM { m_seatManager, &SeatManager::handleVtChanged); if (!m_seatdControl->connectEventSocket()) qWarning() << "Failed to connect dde-seatd event socket during startup"; + + m_configurations = new Configurations(this); + connect(m_configurations, + &Configurations::readyChanged, + this, + &DaemonApp::onConfigurationsReady); + // log message qDebug() << "Starting..."; + } + void DaemonApp::onConfigurationsReady() { + qDebug() << "Configuration initialized, starting seat manager"; m_seatManager->initialize(); } @@ -121,18 +131,11 @@ int main(int argc, char **argv) { if (arguments.contains(QStringLiteral("--help")) || arguments.contains(QStringLiteral("-h"))) { std::cout << "Usage: ddm [options]\n" << "Options: \n" - << " --example-config Print the complete current configuration to stdout" << std::endl; + << " --help, -h Show this message and exit" << std::endl; return EXIT_FAILURE; } - // spit a complete config file on stdout and quit on demand - if (arguments.contains(QStringLiteral("--example-config"))) { - DDM::mainConfig.wipe(); - QTextStream(stdout) << DDM::mainConfig.toConfigFull(); - return EXIT_SUCCESS; - } - // create application DDM::DaemonApp app(argc, argv); diff --git a/src/daemon/DaemonApp.h b/src/daemon/DaemonApp.h index 8cf155f..ca4a532 100644 --- a/src/daemon/DaemonApp.h +++ b/src/daemon/DaemonApp.h @@ -26,7 +26,7 @@ #define daemonApp DaemonApp::instance() namespace DDM { - class Configuration; + class Configurations; class DisplayManager; class PowerManager; class SeatManager; @@ -55,11 +55,15 @@ namespace DDM { public slots: int newSessionId(); + private slots: + void onConfigurationsReady(); + private: static DaemonApp *self; int m_lastSessionId { 0 }; + Configurations *m_configurations { nullptr }; DisplayManager *m_displayManager { nullptr }; PowerManager *m_powerManager { nullptr }; SeatManager *m_seatManager { nullptr }; diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index c41f4e2..1c4cd2d 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -244,7 +244,7 @@ namespace DDM { // Autologin is only tried on the first connection after boot. if (!m_connectedOnce) { m_connectedOnce = true; - if (!hasActiveUser && !mainConfig.Autologin.User.get().isEmpty()) { + if (!hasActiveUser && !mainConfig()->autologinUser().isEmpty()) { if (startAutologin()) return; } @@ -254,6 +254,12 @@ namespace DDM { showGreeter << quint32(DaemonMessages::ShowGreeter); } + void Display::recordLastLogin(const QString &user, const Session *session) { + mainConfig()->setLastUser(user); + if (session) + mainConfig()->setLastSession(session->fileName()); + } + void Display::login(QLocalSocket *socket, const QString &user, const QString &password, const Session &session) { @@ -295,20 +301,14 @@ namespace DDM { return; } - // save last user and last session DaemonApp::instance()->displayManager()->setLastActivatedUser(user); - if (mainConfig.Users.RememberLastUser.get()) - stateConfig.Last.User.set(auth->user); - else - stateConfig.Last.User.setDefault(); - if (mainConfig.Users.RememberLastSession.get()) - stateConfig.Last.Session.set(session.fileName()); - else - stateConfig.Last.Session.setDefault(); - stateConfig.save(); - - if (!startUserSession(auth, session)) + + if (!startUserSession(auth, session)) { Q_EMIT loginFailed(socket, user); + return; + } + + recordLastLogin(user, &session); } bool Display::startUserSession(Auth *auth, const Session &session) { @@ -375,7 +375,7 @@ namespace DDM { env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(sessionId)); - env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("PATH"), mainConfig()->usersDefaultPath()); env.insert(QStringLiteral("DESKTOP_SESSION"), session.desktopSession()); if (!session.desktopNames().isEmpty()) env.insert(QStringLiteral("XDG_CURRENT_DESKTOP"), session.desktopNames()); @@ -447,7 +447,7 @@ namespace DDM { } bool Display::startAutologin() { - const QString user = mainConfig.Autologin.User.get(); + const QString user = mainConfig()->autologinUser(); if (user.isEmpty()) return false; @@ -458,20 +458,27 @@ namespace DDM { qInfo() << "Starting automatic login for user" << user; - // Determine session: autologin-session, or last used session - QString sessionName = mainConfig.Autologin.Session.get(); - if (sessionName.isEmpty()) - sessionName = stateConfig.Last.Session.get(); - if (sessionName.isEmpty()) { + QString sessionValue = mainConfig()->autologinSession(); + if (sessionValue.isEmpty()) { + sessionValue = mainConfig()->lastSession(); + if (!sessionValue.isEmpty()) + qInfo() << "No autologin session configured, using the last session" << sessionValue; + } + if (sessionValue.isEmpty()) { qWarning() << "No session configured for autologin, keeping greeter"; return false; } - Session session(Session::WaylandSession, sessionName); - if (!session.isValid()) - session = Session(Session::X11Session, sessionName); + Session session; + if (QDir::isAbsolutePath(sessionValue)) { + session.setTo(sessionValue); + } else { + session.setTo(Session::WaylandSession, sessionValue); + if (!session.isValid()) + session.setTo(Session::X11Session, sessionValue); + } if (!session.isValid()) { - qCritical() << "Failed to load autologin session" << sessionName; + qCritical() << "Failed to load autologin session" << sessionValue; return false; } @@ -487,23 +494,14 @@ namespace DDM { return false; } - // save last user and last session DaemonApp::instance()->displayManager()->setLastActivatedUser(user); - if (mainConfig.Users.RememberLastUser.get()) - stateConfig.Last.User.set(user); - else - stateConfig.Last.User.setDefault(); - if (mainConfig.Users.RememberLastSession.get()) - stateConfig.Last.Session.set(session.fileName()); - else - stateConfig.Last.Session.setDefault(); - stateConfig.save(); if (!startUserSession(auth, session)) { qWarning() << "Failed to start automatic login session for user" << user; return false; } + recordLastLogin(user, &session); return true; } @@ -549,13 +547,8 @@ namespace DDM { return; } - // Save last user DaemonApp::instance()->displayManager()->setLastActivatedUser(user); - if (mainConfig.Users.RememberLastUser.get()) - stateConfig.Last.User.set(user); - else - stateConfig.Last.User.setDefault(); - stateConfig.save(); + recordLastLogin(user); // Find the auth that started the session, which contains full informations for (auto *auth : std::as_const(auths)) { diff --git a/src/daemon/Display.h b/src/daemon/Display.h index 496c984..fa9d41e 100644 --- a/src/daemon/Display.h +++ b/src/daemon/Display.h @@ -140,6 +140,12 @@ namespace DDM { */ bool startAutologin(); + /** + * Remember the user and optionally the session of a successful + * login. ddm owns this state and hands it to the greeter. + */ + void recordLastLogin(const QString &user, const Session *session = nullptr); + /** Indicates whether the display is started */ bool m_started{ false }; diff --git a/src/common/LogindDBusTypes.cpp b/src/daemon/LogindDBusTypes.cpp similarity index 100% rename from src/common/LogindDBusTypes.cpp rename to src/daemon/LogindDBusTypes.cpp diff --git a/src/common/LogindDBusTypes.h b/src/daemon/LogindDBusTypes.h similarity index 100% rename from src/common/LogindDBusTypes.h rename to src/daemon/LogindDBusTypes.h diff --git a/src/common/MessageHandler.h b/src/daemon/MessageHandler.h similarity index 100% rename from src/common/MessageHandler.h rename to src/daemon/MessageHandler.h diff --git a/src/daemon/PowerManager.cpp b/src/daemon/PowerManager.cpp index 9cacc44..67f4ac6 100644 --- a/src/daemon/PowerManager.cpp +++ b/src/daemon/PowerManager.cpp @@ -88,13 +88,13 @@ const QString UPOWER_OBJECT = QStringLiteral("org.freedesktop.UPower"); } void powerOff() const { - auto command = QProcess::splitCommand(mainConfig.HaltCommand.get()); + auto command = QProcess::splitCommand(mainConfig()->haltCommand()); const QString program = command.takeFirst(); QProcess::execute(program, command); } void reboot() const { - auto command = QProcess::splitCommand(mainConfig.RebootCommand.get()); + auto command = QProcess::splitCommand(mainConfig()->rebootCommand()); const QString program = command.takeFirst(); QProcess::execute(program, command); } diff --git a/src/daemon/SeatManager.cpp b/src/daemon/SeatManager.cpp index cefef96..0898c43 100644 --- a/src/daemon/SeatManager.cpp +++ b/src/daemon/SeatManager.cpp @@ -171,9 +171,6 @@ namespace DDM { } void SeatManager::createSeat(const QString &name) { - //reload config if needed - mainConfig.load(); - // create a new display qDebug() << "Adding new display..."; Display *display = new Display(this, name); diff --git a/src/common/Session.cpp b/src/daemon/Session.cpp similarity index 78% rename from src/common/Session.cpp rename to src/daemon/Session.cpp index 963e3d1..32d370f 100644 --- a/src/common/Session.cpp +++ b/src/daemon/Session.cpp @@ -34,6 +34,21 @@ const QString s_entryExtention = QStringLiteral(".desktop"); namespace DDM { + namespace { + bool isInstalledIn(const QVariantList &dirPaths, const QString &filePath) + { + if (!QFileInfo::exists(filePath)) + return false; + + const QString dirPath = QFileInfo(filePath).absolutePath(); + for (const auto &dir : std::as_const(dirPaths)) { + if (QDir::cleanPath(dirPath) == QDir(dir.toString()).absolutePath()) + return true; + } + return false; + } + } // namespace + // QSettings::IniFormat can't be used to read .desktop files due to different // syntax of values (escape sequences, quoting, automatic QStringList detection). // So implement yet another .desktop file parser. @@ -98,6 +113,12 @@ namespace DDM { setTo(type, fileName); } + Session::Session(const QString &filePath) + : Session() + { + setTo(filePath); + } + bool Session::isValid() const { return m_valid; @@ -191,21 +212,15 @@ namespace DDM { if (!fileName.endsWith(s_entryExtention)) fileName += s_entryExtention; - QFileInfo info(fileName); - m_type = UnknownSession; m_valid = false; m_desktopNames.clear(); - QStringList sessionDirs; - switch (type) { case WaylandSession: - sessionDirs = mainConfig.Wayland.SessionDir.get(); m_xdgSessionType = QStringLiteral("wayland"); break; case X11Session: - sessionDirs = mainConfig.X11.SessionDir.get(); m_xdgSessionType = QStringLiteral("x11"); break; default: @@ -213,20 +228,31 @@ namespace DDM { break; } - QFile file; - for (const auto &path : std::as_const(sessionDirs)) { - m_dir.setPath(path); - m_fileName = m_dir.absoluteFilePath(fileName); - - qDebug() << "Reading from" << m_fileName; + QStringList candidates; + if (QDir::isAbsolutePath(fileName)) { + candidates << fileName; + } else { + const QVariantList sessionDirs = type == WaylandSession + ? mainConfig()->waylandSessionDirs() + : mainConfig()->x11SessionDirs(); + for (const auto &dir : std::as_const(sessionDirs)) + candidates << QDir(dir.toString()).absoluteFilePath(fileName); + } - file.setFileName(m_fileName); - if (file.open(QIODevice::ReadOnly)) + QFile file; + for (const auto &candidate : std::as_const(candidates)) { + file.setFileName(candidate); + if (file.open(QIODevice::ReadOnly)) { + m_fileName = candidate; + m_dir = QFileInfo(candidate).absoluteDir(); break; + } } if (!file.isOpen()) return; + qDebug() << "Reading from" << m_fileName; + QSettings settings(m_fileName, DesktopFileFormat::format()); QStringList locales = { QLocale().name() }; @@ -266,6 +292,37 @@ namespace DDM { m_valid = true; } + void Session::setTo(const QString &filePath) + { + m_type = UnknownSession; + m_valid = false; + m_fileName.clear(); + m_dir = QDir(); + + if (!QDir::isAbsolutePath(filePath)) { + qWarning() << "Not an absolute session path:" << filePath; + return; + } + + if (!QFileInfo::exists(filePath)) { + qWarning() << "Session file does not exist:" << filePath; + return; + } + + if (!mainConfig()) + return; + + if (isInstalledIn(mainConfig()->waylandSessionDirs(), filePath)) + setTo(WaylandSession, filePath); + else if (isInstalledIn(mainConfig()->x11SessionDirs(), filePath)) + setTo(X11Session, filePath); + else + qWarning() << "Session file is outside the configured session directories:" + << filePath + << "wayland:" << mainConfig()->waylandSessionDirs() + << "x11:" << mainConfig()->x11SessionDirs(); + } + Session &Session::operator=(const Session &other) { setTo(other.type(), other.fileName()); diff --git a/src/common/Session.h b/src/daemon/Session.h similarity index 84% rename from src/common/Session.h rename to src/daemon/Session.h index eebb80e..748053e 100644 --- a/src/common/Session.h +++ b/src/daemon/Session.h @@ -20,7 +20,6 @@ #ifndef DDM_SESSION_H #define DDM_SESSION_H -#include #include #include #include @@ -38,6 +37,7 @@ namespace DDM { explicit Session(); Session(Type type, const QString &fileName); + explicit Session(const QString &filePath); bool isValid() const; @@ -68,6 +68,7 @@ namespace DDM { QProcessEnvironment additionalEnv() const; void setTo(Type type, const QString &name); + void setTo(const QString &filePath); Session &operator=(const Session &other); @@ -91,19 +92,6 @@ namespace DDM { friend class SessionModel; }; - - inline QDataStream &operator<<(QDataStream &stream, const Session &session) { - stream << quint32(session.type()) << session.fileName(); - return stream; - } - - inline QDataStream &operator>>(QDataStream &stream, Session &session) { - quint32 type; - QString fileName; - stream >> type >> fileName; - session.setTo(static_cast(type), fileName); - return stream; - } } #endif // DDM_SESSION_H diff --git a/src/daemon/SocketServer.cpp b/src/daemon/SocketServer.cpp index db5e5f9..1660b27 100644 --- a/src/daemon/SocketServer.cpp +++ b/src/daemon/SocketServer.cpp @@ -20,6 +20,7 @@ #include "SocketServer.h" +#include "Configuration.h" #include "DaemonApp.h" #include "Messages.h" #include "PowerManager.h" @@ -29,6 +30,16 @@ #include +namespace { + QStringList sessionDirs(const QList &dirs) { + QStringList paths; + paths.reserve(dirs.size()); + for (const QVariant &dir : dirs) + paths.append(dir.toString()); + return paths; + } +} // namespace + namespace DDM { SocketServer::SocketServer(QObject *parent) : QObject(parent) { } @@ -144,15 +155,24 @@ namespace DDM { // send host name SocketWriter(socket) << quint32(DaemonMessages::HostName) << daemonApp->hostName(); + // send the last successful login before the greeter fills its models + SocketWriter(socket) << quint32(DaemonMessages::LastLogin) + << mainConfig()->lastUser() + << mainConfig()->lastSession(); + + // send session directories + SocketWriter(socket) << quint32(DaemonMessages::SessionDirs) + << sessionDirs(mainConfig()->waylandSessionDirs()) + << sessionDirs(mainConfig()->x11SessionDirs()); + // emit signal emit connected(socket); } break; case GreeterMessages::Login: { - // read username, pasword etc. - QString user, password; - Session session; - input >> user >> password >> session; + // read username, password and the session + QString user, password, sessionPath; + input >> user >> password >> sessionPath; if (!input.commitTransaction()) return; @@ -160,6 +180,7 @@ namespace DDM { qDebug() << "Message received from greeter: Login"; // emit signal + Session session(sessionPath); emit login(socket, user, password, session); } break; diff --git a/src/daemon/UserSession.cpp b/src/daemon/UserSession.cpp index 5db68bf..dd751c9 100644 --- a/src/daemon/UserSession.cpp +++ b/src/daemon/UserSession.cpp @@ -23,7 +23,6 @@ #include #include -#include "Configuration.h" #include "TtyUtils.h" #include "UserSession.h" #include "XAuth.h" @@ -63,15 +62,35 @@ namespace DDM { m_user = user; } + void UserSession::setSessionCommand(const QString &command) + { + m_sessionCommand = command; + } + + void UserSession::setSessionLogFile(const QString &logFile) + { + m_sessionLogFile = logFile; + } + + void UserSession::setNamespaces(const QStringList &namespaces) + { + m_namespaces = namespaces; + } + void UserSession::start(const QString &command, Display::DisplayServerType type, const QByteArray &cookie) { QProcessEnvironment env = processEnvironment(); m_type = type; + if (m_sessionCommand.isEmpty()) { + qCritical() << "Can't start user session: no session command configured"; + return; + } + switch (type) { case Display::Treeland: { - setProgram(mainConfig.Single.SessionCommand.get()); + setProgram(m_sessionCommand); setArguments(QStringList{ command }); qInfo() << "Starting Treeland session:" << program() << command; QProcess::start(); @@ -107,13 +126,13 @@ namespace DDM { setProcessEnvironment(env); qInfo() << "Starting X11 user session:" << command; - setProgram(mainConfig.X11.SessionCommand.get()); + setProgram(m_sessionCommand); setArguments(QStringList{ command }); QProcess::start(); return; } case Display::Wayland: { - setProgram(mainConfig.Wayland.SessionCommand.get()); + setProgram(m_sessionCommand); setArguments(QStringList{ command }); qInfo() << "Starting Wayland user session:" << program() << command; QProcess::start(); @@ -194,8 +213,7 @@ namespace DDM { } // enter Linux namespaces - const auto namespaces = mainConfig.Namespaces.get(); - for (const QString &ns : std::as_const(namespaces)) { + for (const QString &ns : std::as_const(m_namespaces)) { qInfo() << "Entering namespace" << ns; int fd = ::open(qPrintable(ns), O_RDONLY); if (fd < 0) { @@ -306,10 +324,7 @@ namespace DDM { // determine stderr log file based on session type QString sessionLog = QStringLiteral("%1/%2") - .arg(QString::fromLocal8Bit(pw.pw_dir)) - .arg(type == Display::X11 - ? mainConfig.X11.SessionLogFile.get() - : mainConfig.Wayland.SessionLogFile.get()); + .arg(QString::fromLocal8Bit(pw.pw_dir), m_sessionLogFile); // create the path QFileInfo finfo(sessionLog); diff --git a/src/daemon/UserSession.h b/src/daemon/UserSession.h index e5ceffc..6f8119b 100644 --- a/src/daemon/UserSession.h +++ b/src/daemon/UserSession.h @@ -25,6 +25,7 @@ #include #include +#include #include #include "Display.h" @@ -37,7 +38,9 @@ namespace DDM { void setTty(int tty); void setUser(const QString &user); - + void setSessionCommand(const QString &command); + void setSessionLogFile(const QString &logFile); + void setNamespaces(const QStringList &namespaces); void start(const QString &command, Display::DisplayServerType type, const QByteArray &cookie = QByteArray()); @@ -58,6 +61,9 @@ namespace DDM { Display::DisplayServerType m_type{ Display::X11 }; int m_tty{ 0 }; QString m_user; + QString m_sessionCommand; + QString m_sessionLogFile; + QStringList m_namespaces; }; } diff --git a/src/daemon/XorgDisplayServer.cpp b/src/daemon/XorgDisplayServer.cpp index 48f4319..22bea27 100644 --- a/src/daemon/XorgDisplayServer.cpp +++ b/src/daemon/XorgDisplayServer.cpp @@ -75,8 +75,8 @@ namespace DDM { // set process environment QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get()); - QString xcursorSize = mainConfig.Theme.CursorSize.get(); + env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig()->x11CursorTheme()); + QString xcursorSize = mainConfig()->x11CursorSize(); if (!xcursorSize.isEmpty()) env.insert(QStringLiteral("XCURSOR_SIZE"), xcursorSize); process->setProcessEnvironment(env); @@ -90,8 +90,8 @@ namespace DDM { // start display server QStringList args; - process->setProgram(mainConfig.X11.ServerPath.get()); - args << mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), Qt::SkipEmptyParts) + process->setProgram(mainConfig()->x11ServerPath()); + args << mainConfig()->x11ServerArguments().split(QLatin1Char(' '), Qt::SkipEmptyParts) << QStringLiteral("-background") << QStringLiteral("none") << QStringLiteral("-seat") << static_cast(parent())->name << QStringLiteral("vt%1").arg(vt) @@ -196,7 +196,8 @@ namespace DDM { // log message qDebug() << "Display server stopped."; - QStringList displayStopCommand = QProcess::splitCommand(mainConfig.X11.DisplayStopCommand.get()); + QStringList displayStopCommand = + QProcess::splitCommand(mainConfig()->x11DisplayStopCommand()); // create display setup script process QProcess *displayStopScript = new QProcess(); @@ -205,7 +206,7 @@ namespace DDM { QProcessEnvironment env; env.insert(QStringLiteral("DISPLAY"), display); env.insert(QStringLiteral("HOME"), QStringLiteral("/")); - env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("PATH"), mainConfig()->usersDefaultPath()); env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); displayStopScript->setProcessEnvironment(env); @@ -236,11 +237,11 @@ namespace DDM { QProcessEnvironment env; env.insert(QStringLiteral("DISPLAY"), display); env.insert(QStringLiteral("HOME"), QStringLiteral("/")); - env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("PATH"), mainConfig()->usersDefaultPath()); env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath()); env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); - env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get()); - QString xcursorSize = mainConfig.Theme.CursorSize.get(); + env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig()->x11CursorTheme()); + QString xcursorSize = mainConfig()->x11CursorSize(); if (!xcursorSize.isEmpty()) env.insert(QStringLiteral("XCURSOR_SIZE"), xcursorSize); setCursor->setProcessEnvironment(env); @@ -259,8 +260,8 @@ namespace DDM { } // start display setup script - qDebug() << "Running display setup script " << mainConfig.X11.DisplayCommand.get(); - QStringList displayCommand = QProcess::splitCommand(mainConfig.X11.DisplayCommand.get()); + qDebug() << "Running display setup script " << mainConfig()->x11DisplayCommand(); + QStringList displayCommand = QProcess::splitCommand(mainConfig()->x11DisplayCommand()); const QString program = displayCommand.takeFirst(); displayScript->start(program, displayCommand); @@ -270,9 +271,6 @@ namespace DDM { // wait for finished if (!displayScript->waitForFinished(30000)) displayScript->kill(); - - // reload config if needed - mainConfig.load(); } void XorgDisplayServer::changeOwner(const QString &fileName) { diff --git a/src/helper/CMakeLists.txt b/src/helper/CMakeLists.txt index 82a3c98..7c95b18 100644 --- a/src/helper/CMakeLists.txt +++ b/src/helper/CMakeLists.txt @@ -1,9 +1,12 @@ # The session helper. It is a standalone single-threaded QCoreApplication that # runs the full PAM transaction (authenticate + open_session) and starts the # user's desktop session. It deliberately links only against the "common" -# library (Configuration, Session, XAuth, ...) and must NOT pull in daemon-only -# machinery (DBus, display-server management, seatd control), so that it stays -# single-threaded and safe to fork() inside PAM modules. +# library, which provides XAuth, the signal handler and session helpers, and +# must NOT pull in daemon-only machinery (DBus, display-server management, +# seatd control), so that it stays single-threaded and safe to fork() inside +# PAM modules. For the same reason it reads no configuration of its own: +# creating a DConfig object would start a thread and open a DBus connection. +# Everything a session needs is part of the request. set(HELPER_SOURCES HelperApp.cpp ${CMAKE_SOURCE_DIR}/src/daemon/UserSession.cpp diff --git a/src/helper/HelperApp.cpp b/src/helper/HelperApp.cpp index 211d74f..a45943f 100644 --- a/src/helper/HelperApp.cpp +++ b/src/helper/HelperApp.cpp @@ -3,7 +3,6 @@ #include "HelperApp.h" -#include "Configuration.h" #include "SignalHandler.h" #include "TtyUtils.h" #include "UserSession.h" @@ -199,7 +198,11 @@ namespace DDM { int tty = 0; int type = 0; QString command; - in >> envList >> cookie >> display >> tty >> type >> command; + QString sessionCommand; + QString sessionLogFile; + QStringList namespaces; + in >> envList >> cookie >> display >> tty >> type >> command + >> sessionCommand >> sessionLogFile >> namespaces; QProcessEnvironment env; for (const QString &s : envList) { @@ -275,6 +278,9 @@ namespace DDM { m_session = new UserSession(this); m_session->setUser(m_user); m_session->setTty(m_tty); + m_session->setSessionCommand(sessionCommand); + m_session->setSessionLogFile(sessionLogFile); + m_session->setNamespaces(namespaces); m_session->setProcessEnvironment(finalEnv); // Connect to UserSession's forwarded finished signal BEFORE starting diff --git a/src/helper/HelperApp.h b/src/helper/HelperApp.h index 907113e..109f103 100644 --- a/src/helper/HelperApp.h +++ b/src/helper/HelperApp.h @@ -23,8 +23,9 @@ namespace DDM { * long-running QCoreApplication that services two commands: * * - authenticate(user, pamService, secret): full PAM authentication - * - openSession(env, cookie, display, tty, type, command): PAM session + - * start the user's desktop session + * - openSession(env, cookie, display, tty, type, command, sessionCommand, + * sessionLogFile, namespaces): PAM session + start the user's desktop + * session * * Running the whole PAM transaction (authenticate + open_session) here is * what makes ddm immune to the fork()-in-a-multithreaded-Qt-process @@ -32,7 +33,9 @@ namespace DDM { * calls exit() in the grandchild, which would otherwise trigger Qt's * inherited atexit handlers and deadlock on QThread::wait(). Since this * helper is exec()'d and never opens a DBus connection, it is - * single-threaded and safe. + * single-threaded and safe. Everything a session needs is part of the + * request, so the helper reads no configuration of its own: creating a + * DConfig object would start a thread and open a DBus connection again. */ class HelperApp : public QCoreApplication { Q_OBJECT