Skip to content

Commit 4357141

Browse files
committed
feat: add kde wayland support
1 parent 4b281f0 commit 4357141

37 files changed

+2191
-409
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ RUN apt-get -y install tree
1111
# Install Flutter dependencies
1212
RUN apt-get -y install curl file git unzip xz-utils zip clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
1313
# Install app-specific dependencies
14-
RUN apt-get -y install keybinder-3.0 appindicator3-0.1 libappindicator3-1 libappindicator3-dev
14+
RUN apt-get -y install keybinder-3.0 libayatana-appindicator3-dev
1515

1616
# Install Flutter
1717
RUN git clone https://github.com/flutter/flutter.git -b stable /home/vscode/flutter

.github/workflows/tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ jobs:
1313
strategy:
1414
matrix:
1515
os: [ubuntu-latest, windows-latest]
16+
17+
env:
18+
# Needed so we don't get errors in CI
19+
XDG_SESSION_TYPE: "x11"
20+
XDG_CURRENT_DESKTOP: "KDE"
21+
1622
steps:
1723
- uses: actions/checkout@v3
1824
- uses: subosito/flutter-action@v2
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
function print(str) {
2+
console.info('Nyrna KDE Wayland: ' + str);
3+
}
4+
5+
print('Updating active window on DBus');
6+
7+
function windowToJson(window) {
8+
return JSON.stringify({
9+
caption: window.caption,
10+
pid: window.pid,
11+
internalId: window.internalId,
12+
});
13+
}
14+
15+
function updateActiveWindowOnDBus() {
16+
let activeWindow = workspace.activeWindow();
17+
18+
if (!activeWindow) {
19+
print('No active window found');
20+
return;
21+
}
22+
23+
let windowJson = windowToJson(activeWindow);
24+
25+
callDBus(
26+
'codes.merritt.Nyrna',
27+
'/',
28+
'codes.merritt.Nyrna',
29+
'updateActiveWindow',
30+
windowJson,
31+
(result) => {
32+
if (result) {
33+
print('Successfully updated active window on DBus');
34+
} else {
35+
print('Failed to update active window on DBus');
36+
}
37+
}
38+
);
39+
}
40+
41+
updateActiveWindowOnDBus();
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// https://unix.stackexchange.com/a/706478/379240
2+
3+
function print(str) {
4+
console.info('Nyrna: ' + str);
5+
}
6+
7+
let windows = workspace.windowList();
8+
print('Found ' + windows.length + ' windows');
9+
10+
function updateWindowsOnDBus(windows) {
11+
let windowsList = [];
12+
13+
for (let window of windows) {
14+
windowsList.push({
15+
caption: window.caption,
16+
pid: window.pid,
17+
internalId: window.internalId,
18+
onCurrentDesktop: isWindowOnCurrentDesktop(window),
19+
});
20+
}
21+
22+
callDBus(
23+
'codes.merritt.Nyrna',
24+
'/',
25+
'codes.merritt.Nyrna',
26+
'updateWindows',
27+
JSON.stringify(windowsList),
28+
(result) => {
29+
if (result) {
30+
print('Successfully updated windows on DBus');
31+
} else {
32+
print('Failed to update windows on DBus');
33+
}
34+
}
35+
);
36+
}
37+
38+
function isWindowOnCurrentDesktop(window) {
39+
let windowDesktops = Object.values(window.desktops);
40+
let windowIsOnCurrentDesktop = window.onAllDesktops;
41+
42+
if (!windowIsOnCurrentDesktop) {
43+
for (let windowDesktop of windowDesktops) {
44+
if (windowDesktop.id === workspace.currentDesktop.id) {
45+
windowIsOnCurrentDesktop = true;
46+
break;
47+
} else {
48+
windowIsOnCurrentDesktop = false;
49+
}
50+
}
51+
}
52+
53+
return windowIsOnCurrentDesktop;
54+
}
55+
56+
function updateCurrentDesktopOnDBus() {
57+
print('Current desktop id: ' + workspace.currentDesktop.id);
58+
59+
callDBus(
60+
'codes.merritt.Nyrna',
61+
'/',
62+
'codes.merritt.Nyrna',
63+
'updateCurrentDesktop',
64+
workspace.currentDesktop,
65+
(result) => {
66+
if (result) {
67+
print('Successfully updated current desktop on DBus');
68+
} else {
69+
print('Failed to update current desktop on DBus');
70+
}
71+
}
72+
);
73+
}
74+
75+
updateCurrentDesktopOnDBus();
76+
updateWindowsOnDBus(windows);
77+
78+
workspace.currentDesktopChanged.connect(() => {
79+
print('Current desktop changed');
80+
updateCurrentDesktopOnDBus();
81+
updateWindowsOnDBus(windows);
82+
});
83+
84+
workspace.windowAdded.connect(window => {
85+
print('Window added: ' + window.caption);
86+
windows.push(window);
87+
updateWindowsOnDBus(windows);
88+
});
89+
90+
workspace.windowRemoved.connect(window => {
91+
print('Window removed: ' + window.caption);
92+
windows = windows.filter(w => w.internalId !== window.internalId);
93+
updateWindowsOnDBus(windows);
94+
});

devtools_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
description: This file stores settings for Dart & Flutter DevTools.
2+
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3+
extensions:

lib/active_window/src/active_window.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,31 @@ class ActiveWindow {
8686
log.i('Suspending');
8787

8888
for (int attempt = 0; attempt < _maxRetries; attempt++) {
89-
final window = await _nativePlatform.activeWindow();
89+
final window = _nativePlatform.activeWindow;
90+
if (window == null) {
91+
log.w('No active window found, retrying.');
92+
await _nativePlatform.checkActiveWindow();
93+
await Future.delayed(const Duration(milliseconds: 500));
94+
}
95+
96+
if (window == null && attempt < _maxRetries - 1) {
97+
continue;
98+
} else if (window == null && attempt == _maxRetries - 1) {
99+
log.e('Failed to find active window after $_maxRetries attempts.');
100+
return false;
101+
} else if (window == null) {
102+
log.e('Failed to find active window.');
103+
return false;
104+
}
105+
90106
final String executable = window.process.executable;
91107

92108
if (executable == 'nyrna' || executable == 'nyrna.exe') {
93109
log.w('Active window is Nyrna, hiding and retrying.');
94110
await _appWindow.hide();
95111
await Future.delayed(const Duration(milliseconds: 500));
112+
await _nativePlatform.checkActiveWindow();
113+
await Future.delayed(const Duration(milliseconds: 500));
96114
continue;
97115
}
98116

@@ -141,7 +159,7 @@ class ActiveWindow {
141159
return false;
142160
}
143161

144-
Future<void> _minimize(int windowId) async {
162+
Future<void> _minimize(String windowId) async {
145163
final shouldMinimize = await _getShouldMinimize();
146164
if (!shouldMinimize) return;
147165

@@ -150,7 +168,7 @@ class ActiveWindow {
150168
if (!minimized) log.e('Failed to minimize window.');
151169
}
152170

153-
Future<void> _restore(int windowId) async {
171+
Future<void> _restore(String windowId) async {
154172
final shouldRestore = await _getShouldMinimize();
155173
if (!shouldRestore) return;
156174

lib/app/cubit/app_cubit.dart

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class AppCubit extends Cubit<AppState> {
5858
/// blocking the UI, since none of the data fetched here is critical.
5959
Future<void> _init() async {
6060
await _checkForFirstRun();
61-
await _checkLinuxSessionType();
61+
_checkLinuxSessionType();
6262
await _fetchVersionData();
6363
await _fetchReleaseNotes();
6464
_listenToSystemTrayEvents();
@@ -74,19 +74,19 @@ class AppCubit extends Cubit<AppState> {
7474
}
7575

7676
/// For Linux, checks if the session type is Wayland.
77-
Future<void> _checkLinuxSessionType() async {
77+
void _checkLinuxSessionType() {
7878
if (defaultTargetPlatform != TargetPlatform.linux) return;
7979

80-
final sessionType = await (_nativePlatform as Linux).sessionType();
80+
final sessionType = (_nativePlatform as Linux).sessionType;
8181

8282
final unknownSessionMsg = '''
8383
Unable to determine session type. The XDG_SESSION_TYPE environment variable is set to "$sessionType".
8484
Please note that Wayland is not currently supported.''';
8585

8686
const waylandNotSupportedMsg = '''
87-
Wayland is not currently supported.
87+
Wayland is currently supported only on KDE Plasma.
8888
89-
Only xwayland apps will be detected.
89+
For other desktop environments, only xwayland apps will be detected.
9090
9191
If Wayland support is important to you, consider voting on the issue:
9292
@@ -106,12 +106,19 @@ env QT_QPA_PLATFORM=xcb <app>
106106
107107
Otherwise, [consider signing in using X11 instead](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/).''';
108108

109-
switch (sessionType) {
110-
case 'wayland':
111-
log.w(waylandNotSupportedMsg);
112-
emit(state.copyWith(linuxSessionMessage: waylandNotSupportedMsg));
113-
return;
114-
case 'x11':
109+
emit(state.copyWith(sessionType: sessionType));
110+
111+
log.i('Session type: $sessionType');
112+
113+
switch (sessionType.displayProtocol) {
114+
case DisplayProtocol.wayland:
115+
if (sessionType.environment == DesktopEnvironment.kde) {
116+
log.i('KDE Wayland session detected and is supported, proceeding.');
117+
} else {
118+
log.w(waylandNotSupportedMsg);
119+
emit(state.copyWith(linuxSessionMessage: waylandNotSupportedMsg));
120+
}
121+
case DisplayProtocol.x11:
115122
break;
116123
default:
117124
log.w(unknownSessionMsg);
@@ -202,4 +209,10 @@ Otherwise, [consider signing in using X11 instead](https://docs.fedoraproject.or
202209
return false;
203210
}
204211
}
212+
213+
@override
214+
Future<void> close() async {
215+
await _nativePlatform.dispose();
216+
await super.close();
217+
}
205218
}

lib/app/cubit/app_state.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ class AppState with _$AppState {
77
/// session type is unknown.
88
String? linuxSessionMessage,
99

10+
/// The type of desktop session the user is running.
11+
///
12+
/// Currently only used on Linux.
13+
SessionType? sessionType,
14+
1015
/// True if this is the first run of the app.
1116
required bool firstRun,
1217
required String runningVersion,

lib/apps_list/cubit/apps_list_cubit.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class AppsListCubit extends Cubit<AppsListState> {
226226
_storage,
227227
);
228228

229+
await _nativePlatform.checkActiveWindow();
229230
return await activeWindow.toggle();
230231
}
231232

lib/apps_list/models/interaction_error.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import '../enums.dart';
55
class InteractionError {
66
final InteractionType interactionType;
77
final ProcessStatus statusAfterInteraction;
8-
final int windowId;
8+
final String windowId;
99

1010
const InteractionError({
1111
required this.interactionType,

0 commit comments

Comments
 (0)