dank-qml-common
August 7, 2026 · View on GitHub
Common QML assets for DMS, Dank Calendar, and the rest of the Dank Linux Suite.
The library lives in DankCommon/ and is consumed through Quickshell's qs. namespace:
import qs.DankCommon.Widgets
import qs.DankCommon.Common
import qs.DankCommon.Modals.FileBrowser
import qs.DankCommon.Session
DankCommon/Session/ holds the components shared between the DMS lock screen and dms-greeter: the power menu (LockPowerMenu) and the on-screen keyboard (Keyboard, KeyboardController, CustomButtonKeyboard). DankCommon/Common/LayoutCodes.js (keyboard layout name → short code) is imported by relative path.
Consuming from an app
Add this repo as a git submodule at the app repo root, then symlink it into the quickshell config root:
git submodule add https://github.com/AvengeMedia/dank-qml-common.git dank-qml-common
ln -s ../dank-qml-common/DankCommon quickshell/DankCommon
Anything that copies the quickshell tree for packaging must dereference the symlink (cp -rL) - go:embed and most packaging flows reject symlinks.
Standalone development
The repo root is a runnable Quickshell config with stub singletons and a widget gallery:
qs -c /path/to/dank-qml-common
For qmlls completion, create an empty .qmlls.ini at the repo root once (touch .qmlls.ini, gitignored) - quickshell replaces it with a generated config on the next launch, and every file in the repo gets language-server support. The stubs in Common/ and Services/ double as the executable contract below - if a shared widget needs a new singleton property, add it to the stub in the same change.
The contract
Shared code never imports app singletons by path - it imports qs.Common and qs.Services, which resolve to the consuming app at runtime. Every consuming app must provide these singletons with at least the properties the library reads:
qs.Common → Theme
Colors: primary, primaryText, primaryContainer, primaryHover, primaryHoverLight, primaryPressed, primarySelected, secondary, surface, surfaceText, surfaceTextHover, surfaceTextMedium, surfaceTextSecondary, surfaceVariant, surfaceVariantText, surfaceVariantAlpha, surfaceHover, surfacePressed, surfaceContainer, surfaceContainerHigh, surfaceTint, surfaceLight, background, outline, outlineButton, outlineMedium, outlineStrong, outlineHeavy, error, errorHover, errorSelected, warning, shadowStrong, buttonBg, buttonText, buttonHover, buttonPressed, floatingSurface, nestedSurface, floatingWindowSurface, floatingWindowNestedSurface, floatingWindowFieldColor, floatingWindowFieldBorderColor, floatingWindowFieldFocusedBorderColor, popupFieldColor, popupFieldBorderColor, popupFieldFocusedBorderColor, widgetBaseHoverColor, onPrimary, onSurface, onSurface_12, onSurface_38.
Metrics: spacingXXS..spacingXL, fontSizeSmall..fontSizeXLarge, iconSizeSmall/iconSize/iconSizeLarge, cornerRadius.
Typography: fontFamily, monoFontFamily, defaultFontFamily, defaultMonoFontFamily, fontWeight. The library bundles and registers its own fonts (Inter, FiraCode Nerd Font, Material Symbols - DankCommon/assets/fonts/) through the Fonts singleton in qs.DankCommon.Common; apps typically bind defaultFontFamily: Fonts.sans and defaultMonoFontFamily: Fonts.mono rather than shipping font files of their own.
Animation: shorterDuration, shortDuration, mediumDuration, standardEasing, emphasizedEasing, currentAnimationSpeed, expressiveCurves, expressiveDurations.
Misc: isLightMode, popupTransparency, floatingWindowTransparency, blurLayersActive, elevationEnabled, elevationLevel2 ({blurPx, offsetX, offsetY, spreadPx, alpha}), currentAnimationBaseDuration, withAlpha(color, alpha) - which must tolerate an undefined color and return transparent - and blendAlpha(color, alpha) with the same tolerance.
Optional (used by ElevationShadow when present, static fallbacks otherwise): elevationLightDirection, elevationOffsetXFor(), elevationOffsetYFor(), elevationShadowColor(), elevationAmbient().
qs.Common → SettingsData
Enums AnimationSpeed, TextRenderType, TextRenderQuality; properties animationSpeed, enableRippleEffects, popoutElevationEnabled, textRenderType, textRenderQuality.
Power menu (Session components): powerActionConfirm, powerActionHoldDuration, powerMenuActions, powerMenuDefaultAction, powerMenuGridLayout.
qs.Common → Anims, Paths, CacheData, I18n
- Anims:
durShort,standard,emphasized(bezier arrays) - Paths:
xdgCache,imagecache(urls),strip(url),stringify(url),resolveIconPath(iconName)(return""when the app has no icon-theme resolution),trashPath(path, callback)(callback receives a success bool),copyPathToClipboard(path); the app must createimagecache. The stub defaults usegio trashandQuickshell.clipboardText- apps route these through their own trash and clipboard machinery so the library itself imposes no runtime dependency - CacheData:
fileBrowserSettings(var),wallpaperLastPath,profileLastPath,saveCache() - I18n:
tr(term, context),isRtl
qs.Services → Log
scoped(module) returning {debug, info, warn, error}.
qs.Services → SessionService
Used by LockPowerMenu: hibernateSupported plus logout(), suspend(), hibernate(), reboot(), poweroff(). Apps where an action makes no sense (logout in a greeter) provide it as a no-op.
Translations
Widget strings are owned here, not by the consuming apps. translations/extract_translations.py scrapes I18n.tr() from DankCommon/ into translations/en.json; the DMS POEditor project is the source of truth for translating those terms, and its sync writes the per-locale exports into DankCommon/translations/poexports/. Because that directory lives inside DankCommon/, translations ship to every consumer with the submodule pointer like any other file.
Consuming apps keep their own POEditor projects app-only (their extractors must not descend into DankCommon/) and merge both sources at runtime in their I18n singleton - app terms win on collision.
Making changes
The submodule is a real worktree; edit it in place inside whichever app you are working on and the running app picks changes up live. Land the library PR first, then bump the pointer in the app (make update-common keeps the submodule and nix flake input in lockstep; app CI re-syncs flake.lock automatically if they drift). If a change reads a new app-singleton property, add it to the root stubs and the contract above in the same PR; the gallery won't run without it. Other consumers upgrade whenever they bump the pointer - no lockstep.
Notes
Common/Proc.qmlexposesdmsBin(DMS_EXECUTABLEenv override) as a DMS convenience; it is inert elsewhere.- Log stays app-owned so each app keeps its own env-var prefix (
DMS_LOG_LEVEL,DANKCAL_LOG_LEVEL, ...).