メインコンテンツまでスキップ

KDE Plasma シェル

ShojiWM は KDE の plasmashell を動かせます。KDE のタスクバー、Kickoff、 システムトレイ、カレンダー、通知、デスクトップはそのまま使え、コンポジターだけが KWin から ShojiWM に置き換わります。

┌────────────────────────────────────────────────┐
│ plasmashell: taskbar, Kickoff, tray, calendar, │
│ notifications, desktop, Pager │
├────────────────────────────────────────────────┤
│ ShojiWM (instead of KWin): your config decides │
│ layout, decorations, switchers and effects │
└────────────────────────────────────────────────┘

ShojiWM は plasmashell が必要とする KDE のプロトコルを話します。

プロトコルplasmashell の用途
org_kde_plasma_window_management (v18)タスクバーがウィンドウを一覧・グループ化し、アクティブ化・最小化・クローズします。
org_kde_plasma_shell (v8)Kickoff、ポップアップ、OSD、通知がロールと位置を持ちます。
org_kde_plasma_virtual_desktop_management (v2)Pager がワークスペースを仮想デスクトップとして表示します。
xdg_toplevel_tag_manager_v1受け取って無視します。これがないと Spectacle の範囲選択モードがクラッシュします。

残りは config の役割です。plasmashell を起動し、そのウィンドウをレイアウトから 外し、(必要なら)ワークスペースをデスクトップに対応付けます。以下で順に説明します。

必要なもの​

  • KDE Plasma 6(plasmashell、kglobalacceld、qdbus6)
  • 後述の KWin の代替サービスを使う場合: python-gobject と grim

plasmashell を起動する​

plasmashell は KDE のセッションを前提にしています。KDE の環境変数を付けて サービスとして起動します。Kickoff などのグローバルショートカットには KDE の ショートカットデーモン kglobalacceld も必要です。通常は KWin が kglobalacceld に Wayland バックエンドを提供しますが、ShojiWM では Xwayland 経由の X11 バックエンドで動かします。

COMPOSITOR.process.service('kglobalaccel', {
command: [
'env', '-u', 'WAYLAND_DISPLAY',
'KGLOBALACCELD_PLATFORM=xcb', 'QT_QPA_PLATFORM=xcb', 'XDG_SESSION_TYPE=x11',
'/usr/lib/kglobalacceld',
],
restart: 'on-exit',
});

COMPOSITOR.process.service('plasmashell', {
// Wait up to 3 s for kglobalaccel, so Kickoff keeps its shortcut.
command: [
'sh', '-c',
'for i in 1 2 3 4 5 6; do qdbus6 org.kde.kglobalaccel >/dev/null 2>&1 && break; sleep 0.5; done; exec plasmashell --no-respawn',
],
env: {
XDG_CURRENT_DESKTOP: 'KDE',
XDG_MENU_PREFIX: 'plasma-',
KDE_FULL_SESSION: 'true',
KDE_SESSION_VERSION: '6',
},
restart: 'on-exit',
});
変数理由
XDG_CURRENT_DESKTOP=KDEplasmashell と KDE のライブラリは KDE 上でのみ KDE 向けの挙動を読み込みます。
XDG_MENU_PREFIX=plasma-Kickoff は plasma-applications.menu からアプリ一覧を読みます。これがないとメニューが空になります。
KDE_FULL_SESSION=trueKDE のライブラリが KDE 専用の挙動(パネルの色に合わせたトレイアイコンなど)を使います。
KDE_SESSION_VERSION=6動いている Plasma のバージョンを KDE のライブラリに伝えます。

/usr/lib/kglobalacceld は Arch Linux のパスです。他のディストリビューションでは /usr/libexec にあることがあります。

plasmashell のウィンドウをレイアウトから外す​

plasmashell のパネル、ポップアップ、通知は普通の Wayland ウィンドウです。 window.plasma で区別できます。plasmashell が要求したロールと位置を持ち、 それ以外のウィンドウでは undefined です。

roleウィンドウ
desktop壁紙とデスクトップアイコン
panelパネル(タスクバー)
appletpopupKickoff、カレンダー、トレイのポップアップ
notification、criticalnotification、onscreendisplay通知と OSD
tooltipツールチップ
normal通常はアプリ。タスクバーのグループ化されたウィンドウ一覧も normal ですが、位置を指定します。

一般的な config では、これらのウィンドウにタイトルバーを付けず、ウィンドウ マネージャーに渡さず、plasmashell が指定した位置に通常のウィンドウより上に 描きます。

function isShellWindow(window: WaylandWindow): boolean {
const plasma = window.plasma.peek();
return plasma !== undefined && (plasma.role !== 'normal' || plasma.x !== undefined);
}

COMPOSITOR.window.decoration.configure((window, context) =>
isShellWindow(window) ? { mode: 'client' } : { mode: context.clientPreference ?? 'server' },
);

COMPOSITOR.event.onOpen((window) => {
if (isShellWindow(window)) {
// Popups with a search field close when they lose focus.
const role = window.plasma.peek()?.role;
if (role === 'appletpopup' || role === 'normal') {
window.focus();
}
return; // no tiling, no workspace
}
myWindowManager.onOpen(window);
});

const SHELL_Z_INDEX = { desktop: -2_100_000_000, panel: 2_050_000_000, popup: 2_100_000_000 };

COMPOSITOR.window.composition = (window) => {
const plasma = window.plasma();
if (plasma && isShellWindow(window)) {
const rect = computed(() => ({
x: plasma.x ?? window.position.x,
y: plasma.y ?? window.position.y,
width: window.position.width,
height: window.position.height,
}));
const zIndex =
plasma.role === 'desktop' ? SHELL_Z_INDEX.desktop
: plasma.role === 'panel' ? SHELL_Z_INDEX.panel
: SHELL_Z_INDEX.popup;
return (
<ManagedWindow rect={rect} zIndex={zIndex}>
<ClientWindow />
</ManagedWindow>
);
}
return myComposition(window);
};

他のウィンドウイベント(onClose、onFocus、移動やリサイズの要求)も同じように 振り分け、ウィンドウマネージャーがシェルのウィンドウを扱わないようにします。

KWin は plasmashell のポップアップの下に影を描きます(org_kde_kwin_shadow)。 ShojiWM はこのプロトコルを話さないので、必要なら WindowBorder の boxShadow で 影を描いてください。

ワークスペースを Pager に表示する​

COMPOSITOR.workspace.configure は省略可能な virtualDesktops を受け取ります。 各デスクトップの id はワークスペースの id です。Pager のクリックは workspace.event.onActivate に届きます。Pager やタスクバーがウィンドウを別の デスクトップへ移すときは、代わりに workspace.event.onMoveWindow にウィンドウの id とデスクトップの id が届きます。以降のモデルで virtualDesktops を省略すると、 デスクトップは削除されます。

COMPOSITOR.workspace.configure(() => ({
groups: myWorkspaceGroups(),
virtualDesktops: {
desktops: [
{ id: 'DP-1:1', name: 'Desktop 1', active: true, windowIds: ['0x1', '0x2'] },
{ id: 'DP-1:2', name: 'Desktop 2', windowIds: [] },
],
rows: 1,
// Really minimized windows. A window on another desktop is hidden too,
// but must not look minimized in the taskbar.
minimizedWindowIds: [],
},
}));

COMPOSITOR.workspace.event.onActivate((event) => {
activateWorkspace(event.workspaceId);
});

COMPOSITOR.workspace.event.onMoveWindow((event) => {
moveWindowToWorkspace(event.windowId, event.workspaceId);
});

KDE の Pager はデスクトップが1つしかないと自分を隠します。空でもいくつかの デスクトップを一覧に残してください。

タスクバーへの最小化​

タスクバーは各ウィンドウのエントリの位置をコンポジターに伝えます。ShojiWM は これを window.minimizedGeometry(グローバル論理 px)として公開するので、最小化 アニメーションでウィンドウをタスクバーのアイコンへ飛ばせます。

Super キーで Kickoff を開く​

plasmashell の D-Bus 呼び出し activateLauncherMenu は KWin の下でしか動きません。 代わりに kglobalaccel で Kickoff 自身のグローバルショートカットを呼びます。 下の例は Super の短押しで開く タップバインドです。

const KICKOFF_ID = 'panels().forEach(function (p) { p.widgets("org.kde.plasma.kickoff").forEach(function (w) { print(w.id + "\\n"); }); });';

function toggleKickoff() {
COMPOSITOR.process.spawn({
command: [
'sh', '-c',
'id=$(qdbus6 org.kde.plasmashell /PlasmaShell evaluateScript "$1" | head -n 1) && [ -n "$id" ] && qdbus6 org.kde.kglobalaccel /component/plasmashell org.kde.kglobalaccel.Component.invokeShortcut "activate widget $id"',
'toggle-launcher', KICKOFF_ID,
],
});
}

// A quick Super tap.
COMPOSITOR.key.bind('launcher-tap', 'Super', toggleKickoff, {on: 'release', within: 300});

KWin の D-Bus 代替サービス(任意)​

KDE のいくつかの機能は D-Bus で KWin を呼びます。そのバス名の持ち主がいないと、 代替動作になるか失敗します。 examples/kde-plasmashell にある2つの小さな Python サービスがこれらの名前を持ち、IPC ソケット (createIpcServer)経由で config とやり取りします。

サービスバス名動くようになるものconfig が応答するもの
window-view-dbus.pyorg.kde.KWin.Effect.WindowView1グループ化されたタスクバーアイコンのクリックで、テキスト一覧ではなくウィンドウを並べて表示します。{ windowIds } 付きの windowView.activate
screenshot-dbus.pyorg.kde.KWin.ScreenShot2Spectacle(Print Screen)。grim で撮影します。workspaces.get と windows.rect

screenshot-dbus.py があると、セッションバス上のどのプログラムも確認なしで スクリーンショットを撮れます。KWin は許可されたアプリにしか ScreenShot2 を 使わせませんが、この代替サービスは何も確認しません。サンドボックス外のプログラムは もともと grim で画面を撮れますし、Flatpak アプリは通常この名前に届きませんが、 Spectacle を使う場合だけ起動してください。

for (const name of ['window-view-dbus', 'screenshot-dbus']) {
COMPOSITOR.process.service(name, {
command: ['python3', `${configDir}/helpers/${name}.py`, defaultSocketPath()],
restart: 'on-exit',
});
}

制限​

  • KWin のエフェクトと KWin スクリプトは動きません。エフェクトはすべて config が描きます。
  • KDE のセッションマネージャー(ksmserver)は動かないので、Plasma の「ログアウト」 ボタンは何もしません。代わりに shoji_wm --quit をキーに割り当ててください。
  • Spectacle の「カーソル下のウィンドウ」モードはアクティブなウィンドウを撮影し、 KWin 経由の画面録画は動きません。
  • ウィンドウの撮影は画面上のウィンドウの矩形を撮るので、ウィンドウに重なっている ものも写ります。
  • Plasma 6 でのみテストしています。