mirror of
https://github.com/HorlogeSkynet/archey4
synced 2025-05-03 04:00:15 +02:00
[DESKTOP_ENVIRONMENT] Honors environment and adds support for Windows
This patch is actually a complete rework of `WindowManager` entry, implementing a more complete environment-based detection.
This commit is contained in:
parent
a0b51b9758
commit
e4236800a5
@ -10,11 +10,13 @@ and this project (partially) adheres to [Semantic Versioning](https://semver.org
|
||||
- `Model` support for Raspberry Pi 5+
|
||||
- `none` logo style to completely hide distribution logo
|
||||
- AppArmor confinement profile (included in Debian and AUR packages)
|
||||
- `DesktopEnvironment` support for Windows
|
||||
- `WindowManager` support for Windows
|
||||
- `WindowManager` support for some Wayland compositors
|
||||
|
||||
### Changed
|
||||
- `Model` honor `/proc/device-tree/model` when it exists
|
||||
- `DesktopEnvironment` now honors environment (including `XDG_CURRENT_DESKTOP`)
|
||||
|
||||
### Removed
|
||||
- `_distribution` protected attribute from `Output` class
|
||||
|
@ -52,6 +52,9 @@ profile archey4 /usr/{,local/}bin/archey{,4} {
|
||||
# [CPU] entry
|
||||
/{,usr/}bin/lscpu PUx,
|
||||
|
||||
# [Desktop Environment] entry
|
||||
/usr/share/xsessions/*.desktop r,
|
||||
|
||||
# [Disk] entry
|
||||
/{,usr/}bin/df PUx,
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
"""Desktop environment detection class"""
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import platform
|
||||
import typing
|
||||
from contextlib import suppress
|
||||
|
||||
from archey.entry import Entry
|
||||
from archey.processes import Processes
|
||||
|
||||
DE_DICT = {
|
||||
DE_PROCESSES = {
|
||||
"cinnamon": "Cinnamon",
|
||||
"dde-dock": "Deepin",
|
||||
"fur-box-session": "Fur Box",
|
||||
@ -19,11 +22,43 @@ DE_DICT = {
|
||||
"xfce4-session": "Xfce",
|
||||
}
|
||||
|
||||
# From : <https://specifications.freedesktop.org/menu-spec/latest/apb.html>
|
||||
XDG_DESKTOP_NORMALIZATION = {
|
||||
"DDE": "Deepin",
|
||||
"ENLIGHTENMENT": "Enlightenment",
|
||||
"GNOME-CLASSIC": "GNOME Classic",
|
||||
"GNOME-FLASHBACK": "GNOME Flashback",
|
||||
"RAZOR": "Razor-qt",
|
||||
"TDE": "Trinity",
|
||||
"X-CINNAMON": "Cinnamon",
|
||||
}
|
||||
|
||||
# (partly) from : <https://wiki.archlinux.org/title/Xdg-utils#Environment_variables>
|
||||
DE_NORMALIZATION = {
|
||||
"budgie-desktop": "Budgie",
|
||||
"cinnamon": "Cinnamon",
|
||||
"deepin": "Deepin",
|
||||
"enlightenment": "Enlightenment",
|
||||
"gnome": "Gnome",
|
||||
"kde": "KDE",
|
||||
"lumina": "Lumina",
|
||||
"lxde": "LXDE",
|
||||
"lxqt": "LXQt",
|
||||
"mate": "MATE",
|
||||
"muffin": "Cinnamon",
|
||||
"trinity": "Trinity",
|
||||
"xfce session": "Xfce",
|
||||
"xfce": "Xfce",
|
||||
"xfce4": "Xfce",
|
||||
"xfce5": "Xfce",
|
||||
}
|
||||
|
||||
|
||||
class DesktopEnvironment(Entry):
|
||||
"""
|
||||
Just iterate over running processes to find a known-entry.
|
||||
If not, rely on the `XDG_CURRENT_DESKTOP` environment variable.
|
||||
Return static values for macOS and Windows.
|
||||
On Linux, use extensive environment variables processing to find known identifiers.
|
||||
Fallback on running processes to find a known-entry.
|
||||
"""
|
||||
|
||||
_ICON = "\ue23c" # fae_restore
|
||||
@ -32,17 +67,86 @@ class DesktopEnvironment(Entry):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.value = (
|
||||
self._platform_detection() or self._environment_detection() or self._process_detection()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _platform_detection() -> typing.Optional[str]:
|
||||
# macOS' desktop environment is called "Aqua",
|
||||
# and could not be detected from processes list.
|
||||
if platform.system() == "Darwin":
|
||||
self.value = "Aqua"
|
||||
return
|
||||
return "Aqua"
|
||||
|
||||
# Same thing for Windows, based on release version.
|
||||
if platform.system() == "Windows":
|
||||
windows_release = platform.win32_ver()[0]
|
||||
if windows_release in ("Vista", "7"):
|
||||
return "Aero"
|
||||
if windows_release in ("8", "10"):
|
||||
return "Metro"
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _environment_detection() -> (
|
||||
typing.Optional[str]
|
||||
): # pylint: disable=too-many-return-statements
|
||||
"""Implement same algorithm xdg-utils uses"""
|
||||
# Honor XDG_CURRENT_DESKTOP (if set)
|
||||
desktop_identifiers = os.getenv("XDG_CURRENT_DESKTOP", "").split(":")
|
||||
if desktop_identifiers[0]:
|
||||
return XDG_DESKTOP_NORMALIZATION.get(
|
||||
desktop_identifiers[0].upper(), desktop_identifiers[0]
|
||||
)
|
||||
|
||||
# Honor known environment-specific variables
|
||||
if "GNOME_DESKTOP_SESSION_ID" in os.environ:
|
||||
return "GNOME"
|
||||
if "HYPRLAND_CMD" in os.environ:
|
||||
return "Hyprland"
|
||||
if "KDE_FULL_SESSION" in os.environ:
|
||||
return "KDE"
|
||||
if "MATE_DESKTOP_SESSION_ID" in os.environ:
|
||||
return "MATE"
|
||||
if "TDE_FULL_SESSION" in os.environ:
|
||||
return "Trinity"
|
||||
|
||||
# Fallback to (known) "DE"/"DESKTOP_SESSION" legacy environment variables
|
||||
legacy_de = os.getenv("DE", "").lower()
|
||||
if legacy_de in DE_NORMALIZATION:
|
||||
return DE_NORMALIZATION[legacy_de]
|
||||
|
||||
desktop_session = os.getenv("DESKTOP_SESSION")
|
||||
if desktop_session is not None:
|
||||
# If DESKTOP_SESSION corresponds to a session's desktop entry path, parse and honor it
|
||||
with suppress(ValueError, OSError, configparser.Error):
|
||||
desktop_file = os.path.realpath(desktop_session)
|
||||
if (
|
||||
os.path.commonprefix([desktop_file, "/usr/share/xsessions"])
|
||||
== "/usr/share/xsessions"
|
||||
):
|
||||
# Don't expect anything from .desktop files and parse them in a best-effort way
|
||||
config = configparser.ConfigParser(allow_no_value=True, strict=False)
|
||||
with open(desktop_file, encoding="utf-8") as f_desktop_file:
|
||||
config.read_file(f_desktop_file)
|
||||
return (
|
||||
# Honor `DesktopNames` option with `X-LightDM-DesktopName` as a fallback
|
||||
config.get("Desktop Entry", "DesktopNames", fallback=None)
|
||||
or config.get("Desktop Entry", "X-LightDM-DesktopName")
|
||||
).split(";")[0]
|
||||
|
||||
# If not or if file couldn't be read, check whether it corresponds to a known identifier
|
||||
if desktop_session.lower() in DE_NORMALIZATION:
|
||||
return DE_NORMALIZATION[desktop_session.lower()]
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _process_detection() -> typing.Optional[str]:
|
||||
processes = Processes().list
|
||||
for de_id, de_name in DE_DICT.items():
|
||||
for de_id, de_name in DE_PROCESSES.items():
|
||||
if de_id in processes:
|
||||
self.value = de_name
|
||||
break
|
||||
else:
|
||||
# Let's rely on an environment variable if the loop above didn't `break`.
|
||||
self.value = os.getenv("XDG_CURRENT_DESKTOP")
|
||||
return de_name
|
||||
|
||||
return None
|
||||
|
@ -1,16 +1,201 @@
|
||||
"""Test module for Archey's desktop environment detection module"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import mock_open, patch
|
||||
|
||||
from archey.entries.desktop_environment import DesktopEnvironment
|
||||
|
||||
|
||||
@patch("archey.entries.desktop_environment.platform.system", return_value="Linux")
|
||||
class TestDesktopEnvironmentEntry(unittest.TestCase):
|
||||
"""
|
||||
With the help of a fake running processes list, we test the DE matching.
|
||||
"""
|
||||
"""DesktopEnvironment test cases"""
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.platform.system",
|
||||
return_value="Windows",
|
||||
)
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.platform.win32_ver",
|
||||
return_value=("10", "10.0.19042", "SP0", "0", "0", "Workstation"),
|
||||
)
|
||||
def test_platform_detection(self, _, __) -> None:
|
||||
"""_platform_detection simple test"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._platform_detection(), # pylint: disable=protected-access
|
||||
"Metro",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"GNOME-Flashback:GNOME", # XDG_CURRENT_DESKTOP
|
||||
],
|
||||
)
|
||||
def test_environment_detection_1(self, _) -> None:
|
||||
"""_environment_detection XDG_CURRENT_DESKTOP (normalization) test"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
"GNOME Flashback",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"", # XDG_CURRENT_DESKTOP
|
||||
],
|
||||
)
|
||||
@patch.dict(
|
||||
"archey.entries.desktop_environment.os.environ",
|
||||
{
|
||||
"GNOME_DESKTOP_SESSION_ID": "this-is-deprecated",
|
||||
},
|
||||
clear=True,
|
||||
)
|
||||
def test_environment_detection_2(self, _) -> None:
|
||||
"""_environment_detection against environment-specific variables"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
"GNOME",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"", # XDG_CURRENT_DESKTOP
|
||||
"Xfce Session", # DE
|
||||
],
|
||||
)
|
||||
@patch.dict(
|
||||
"archey.entries.desktop_environment.os.environ",
|
||||
{},
|
||||
clear=True,
|
||||
)
|
||||
def test_environment_detection_3(self, _) -> None:
|
||||
"""_environment_detection against legacy `DE` environment variable"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
"Xfce",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"", # XDG_CURRENT_DESKTOP
|
||||
"", # DE
|
||||
"lumina", # SESSION_DESKTOP
|
||||
],
|
||||
)
|
||||
@patch.dict(
|
||||
"archey.entries.desktop_environment.os.environ",
|
||||
{},
|
||||
clear=True,
|
||||
)
|
||||
def test_environment_detection_4(self, _) -> None:
|
||||
"""_environment_detection against legacy `SESSION_DESKTOP` environment variable"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
"Lumina",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"", # XDG_CURRENT_DESKTOP
|
||||
"", # DE
|
||||
"/usr/share/xsessions/retro-home.desktop", # SESSION_DESKTOP
|
||||
],
|
||||
)
|
||||
@patch.dict(
|
||||
"archey.entries.desktop_environment.os.environ",
|
||||
{},
|
||||
clear=True,
|
||||
)
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.open",
|
||||
mock_open(
|
||||
read_data="""\
|
||||
[Desktop Entry]
|
||||
Name=Retro Home
|
||||
Comment=Your home for retro gaming
|
||||
Exec=/usr/local/bin/retro-home
|
||||
TryExec=ludo
|
||||
Type=Application
|
||||
DesktopNames=Retro-Home;Ludo;
|
||||
|
||||
no-value-option
|
||||
"""
|
||||
),
|
||||
)
|
||||
def test_environment_detection_4_desktop_file(self, _) -> None:
|
||||
"""_environment_detection against legacy `SESSION_DESKTOP` pointing to a desktop file"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
"Retro-Home",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"", # XDG_CURRENT_DESKTOP
|
||||
"", # DE
|
||||
"/usr/share/xsessions/emacsdesktop.desktop", # SESSION_DESKTOP
|
||||
],
|
||||
)
|
||||
@patch.dict(
|
||||
"archey.entries.desktop_environment.os.environ",
|
||||
{},
|
||||
clear=True,
|
||||
)
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.open",
|
||||
mock_open(
|
||||
read_data="""\
|
||||
[Desktop Entry]
|
||||
Name=EmacsDesktop
|
||||
Comment=EmacsDesktop
|
||||
Exec=/usr/share/xsessions/emacsdesktop.sh
|
||||
TryExec=emacs
|
||||
Type=Application
|
||||
X-LightDM-DesktopName=EmacsDesktop
|
||||
|
||||
[Desktop Entry]
|
||||
Comment="Just messing with ConfigParser by adding section and option duplicates"
|
||||
"""
|
||||
),
|
||||
)
|
||||
def test_environment_detection_4_desktop_file_fallback(self, _) -> None:
|
||||
"""_environment_detection against legacy `SESSION_DESKTOP` pointing to a desktop file"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
"EmacsDesktop",
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
side_effect=[
|
||||
"", # XDG_CURRENT_DESKTOP
|
||||
"", # DE
|
||||
"/usr/share/xsessions/foo.desktop", # SESSION_DESKTOP
|
||||
],
|
||||
)
|
||||
@patch.dict(
|
||||
"archey.entries.desktop_environment.os.environ",
|
||||
{},
|
||||
clear=True,
|
||||
)
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.open",
|
||||
mock_open(
|
||||
read_data="""\
|
||||
[Desktop Entry]
|
||||
Name=FooDesktop
|
||||
"""
|
||||
),
|
||||
)
|
||||
def test_environment_detection_4_bad_desktop_file(self, _) -> None:
|
||||
"""_environment_detection against legacy `SESSION_DESKTOP` pointing to a desktop file"""
|
||||
self.assertIsNone(
|
||||
DesktopEnvironment._environment_detection(), # pylint: disable=protected-access
|
||||
)
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.Processes.list",
|
||||
@ -20,51 +205,14 @@ class TestDesktopEnvironmentEntry(unittest.TestCase):
|
||||
"like",
|
||||
"cinnamon",
|
||||
"tea",
|
||||
), # Fake running processes list # Match !
|
||||
)
|
||||
def test_match(self, _):
|
||||
"""Simple list matching"""
|
||||
self.assertEqual(DesktopEnvironment().value, "Cinnamon")
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.Processes.list",
|
||||
( # Fake running processes list
|
||||
"do",
|
||||
"you",
|
||||
"like",
|
||||
"unsweetened", # Mismatch...
|
||||
"coffee",
|
||||
),
|
||||
)
|
||||
@patch("archey.entries.desktop_environment.os.getenv", return_value="DESKTOP ENVIRONMENT")
|
||||
def test_mismatch(self, _, __):
|
||||
"""Simple list (mis-)-matching"""
|
||||
self.assertEqual(DesktopEnvironment().value, "DESKTOP ENVIRONMENT")
|
||||
|
||||
@patch("archey.entries.desktop_environment.platform.system")
|
||||
def test_darwin_aqua_deduction(self, _, platform_system_mock):
|
||||
"""Test "Aqua" deduction on Darwin systems"""
|
||||
platform_system_mock.return_value = "Darwin" # Override module-wide mocked value.
|
||||
|
||||
self.assertEqual(DesktopEnvironment().value, "Aqua")
|
||||
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.Processes.list",
|
||||
( # Fake running processes list
|
||||
"do",
|
||||
"you",
|
||||
"like",
|
||||
"unsweetened", # Mismatch...
|
||||
"coffee",
|
||||
),
|
||||
)
|
||||
@patch(
|
||||
"archey.entries.desktop_environment.os.getenv",
|
||||
return_value=None, # The environment variable is empty...
|
||||
)
|
||||
def test_non_detection(self, _, __):
|
||||
"""Simple global non-detection"""
|
||||
self.assertIsNone(DesktopEnvironment().value)
|
||||
def test_process_detection(self) -> None:
|
||||
"""_process_detection simple test"""
|
||||
self.assertEqual(
|
||||
DesktopEnvironment._process_detection(), # pylint: disable=protected-access
|
||||
"Cinnamon",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
x
Reference in New Issue
Block a user