Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ RUN apt-get -y install tree
# Install Flutter dependencies
RUN apt-get -y install curl file git unzip xz-utils zip clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
# Install app-specific dependencies
RUN apt-get -y install keybinder-3.0 appindicator3-0.1 libappindicator3-1 libappindicator3-dev
RUN apt-get -y install keybinder-3.0 libayatana-appindicator3-dev

# Install Flutter
RUN git clone https://github.com/flutter/flutter.git -b stable /home/vscode/flutter
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]

env:
# Needed so we don't get errors in CI
XDG_SESSION_TYPE: "x11"
XDG_CURRENT_DESKTOP: "KDE"

steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
Expand All @@ -38,7 +44,7 @@ jobs:
run: flutter gen-l10n

- name: Run lint
run: flutter analyze
run: flutter analyze --no-fatal-infos

- name: Run tests
run: flutter test
41 changes: 41 additions & 0 deletions assets/lib/linux/active_window_kde.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
function print(str) {
console.info('Nyrna KDE Wayland: ' + str);
}

print('Updating active window on DBus');

function windowToJson(window) {
return JSON.stringify({
caption: window.caption,
pid: window.pid,
internalId: window.internalId,
});
}

function updateActiveWindowOnDBus() {
let activeWindow = workspace.activeWindow();

if (!activeWindow) {
print('No active window found');
return;
}

let windowJson = windowToJson(activeWindow);

callDBus(
'codes.merritt.Nyrna',
'/',
'codes.merritt.Nyrna',
'updateActiveWindow',
windowJson,
(result) => {
if (result) {
print('Successfully updated active window on DBus');
} else {
print('Failed to update active window on DBus');
}
}
);
}

updateActiveWindowOnDBus();
94 changes: 94 additions & 0 deletions assets/lib/linux/list_windows_kde.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// https://unix.stackexchange.com/a/706478/379240

function print(str) {
console.info('Nyrna: ' + str);
}

let windows = workspace.windowList();
print('Found ' + windows.length + ' windows');

function updateWindowsOnDBus(windows) {
let windowsList = [];

for (let window of windows) {
windowsList.push({
caption: window.caption,
pid: window.pid,
internalId: window.internalId,
onCurrentDesktop: isWindowOnCurrentDesktop(window),
});
}

callDBus(
'codes.merritt.Nyrna',
'/',
'codes.merritt.Nyrna',
'updateWindows',
JSON.stringify(windowsList),
(result) => {
if (result) {
print('Successfully updated windows on DBus');
} else {
print('Failed to update windows on DBus');
}
}
);
}

function isWindowOnCurrentDesktop(window) {
let windowDesktops = Object.values(window.desktops);
let windowIsOnCurrentDesktop = window.onAllDesktops;

if (!windowIsOnCurrentDesktop) {
for (let windowDesktop of windowDesktops) {
if (windowDesktop.id === workspace.currentDesktop.id) {
windowIsOnCurrentDesktop = true;
break;
} else {
windowIsOnCurrentDesktop = false;
}
}
}

return windowIsOnCurrentDesktop;
}

function updateCurrentDesktopOnDBus() {
print('Current desktop id: ' + workspace.currentDesktop.id);

callDBus(
'codes.merritt.Nyrna',
'/',
'codes.merritt.Nyrna',
'updateCurrentDesktop',
workspace.currentDesktop,
(result) => {
if (result) {
print('Successfully updated current desktop on DBus');
} else {
print('Failed to update current desktop on DBus');
}
}
);
}

updateCurrentDesktopOnDBus();
updateWindowsOnDBus(windows);

workspace.currentDesktopChanged.connect(() => {
print('Current desktop changed');
updateCurrentDesktopOnDBus();
updateWindowsOnDBus(windows);
});

workspace.windowAdded.connect(window => {
print('Window added: ' + window.caption);
windows.push(window);
updateWindowsOnDBus(windows);
});

workspace.windowRemoved.connect(window => {
print('Window removed: ' + window.caption);
windows = windows.filter(w => w.internalId !== window.internalId);
updateWindowsOnDBus(windows);
});
3 changes: 3 additions & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
24 changes: 21 additions & 3 deletions lib/active_window/src/active_window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,31 @@ class ActiveWindow {
log.i('Suspending');

for (int attempt = 0; attempt < _maxRetries; attempt++) {
final window = await _nativePlatform.activeWindow();
final window = _nativePlatform.activeWindow;
if (window == null) {
log.w('No active window found, retrying.');
await _nativePlatform.checkActiveWindow();
await Future.delayed(const Duration(milliseconds: 500));
}

if (window == null && attempt < _maxRetries - 1) {
continue;
} else if (window == null && attempt == _maxRetries - 1) {
log.e('Failed to find active window after $_maxRetries attempts.');
return false;
} else if (window == null) {
log.e('Failed to find active window.');
return false;
}

final String executable = window.process.executable;

if (executable == 'nyrna' || executable == 'nyrna.exe') {
log.w('Active window is Nyrna, hiding and retrying.');
await _appWindow.hide();
await Future.delayed(const Duration(milliseconds: 500));
await _nativePlatform.checkActiveWindow();
await Future.delayed(const Duration(milliseconds: 500));
continue;
}

Expand Down Expand Up @@ -141,7 +159,7 @@ class ActiveWindow {
return false;
}

Future<void> _minimize(int windowId) async {
Future<void> _minimize(String windowId) async {
final shouldMinimize = await _getShouldMinimize();
if (!shouldMinimize) return;

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

Future<void> _restore(int windowId) async {
Future<void> _restore(String windowId) async {
final shouldRestore = await _getShouldMinimize();
if (!shouldRestore) return;

Expand Down
35 changes: 24 additions & 11 deletions lib/app/cubit/app_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class AppCubit extends Cubit<AppState> {
/// blocking the UI, since none of the data fetched here is critical.
Future<void> _init() async {
await _checkForFirstRun();
await _checkLinuxSessionType();
_checkLinuxSessionType();
await _fetchVersionData();
await _fetchReleaseNotes();
_listenToSystemTrayEvents();
Expand All @@ -74,19 +74,19 @@ class AppCubit extends Cubit<AppState> {
}

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

final sessionType = await (_nativePlatform as Linux).sessionType();
final sessionType = (_nativePlatform as Linux).sessionType;

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

const waylandNotSupportedMsg = '''
Wayland is not currently supported.
Wayland is currently supported only on KDE Plasma.

Only xwayland apps will be detected.
For other desktop environments, only xwayland apps will be detected.

If Wayland support is important to you, consider voting on the issue:

Expand All @@ -106,12 +106,19 @@ env QT_QPA_PLATFORM=xcb <app>

Otherwise, [consider signing in using X11 instead](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/).''';

switch (sessionType) {
case 'wayland':
log.w(waylandNotSupportedMsg);
emit(state.copyWith(linuxSessionMessage: waylandNotSupportedMsg));
return;
case 'x11':
emit(state.copyWith(sessionType: sessionType));

log.i('Session type: $sessionType');

switch (sessionType.displayProtocol) {
case DisplayProtocol.wayland:
if (sessionType.environment == DesktopEnvironment.kde) {
log.i('KDE Wayland session detected and is supported, proceeding.');
} else {
log.w(waylandNotSupportedMsg);
emit(state.copyWith(linuxSessionMessage: waylandNotSupportedMsg));
}
case DisplayProtocol.x11:
break;
default:
log.w(unknownSessionMsg);
Expand Down Expand Up @@ -202,4 +209,10 @@ Otherwise, [consider signing in using X11 instead](https://docs.fedoraproject.or
return false;
}
}

@override
Future<void> close() async {
await _nativePlatform.dispose();
await super.close();
}
}
5 changes: 5 additions & 0 deletions lib/app/cubit/app_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ class AppState with _$AppState {
/// session type is unknown.
String? linuxSessionMessage,

/// The type of desktop session the user is running.
///
/// Currently only used on Linux.
SessionType? sessionType,

/// True if this is the first run of the app.
required bool firstRun,
required String runningVersion,
Expand Down
1 change: 1 addition & 0 deletions lib/apps_list/cubit/apps_list_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ class AppsListCubit extends Cubit<AppsListState> {
_storage,
);

await _nativePlatform.checkActiveWindow();
return await activeWindow.toggle();
}

Expand Down
2 changes: 1 addition & 1 deletion lib/apps_list/models/interaction_error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import '../enums.dart';
class InteractionError {
final InteractionType interactionType;
final ProcessStatus statusAfterInteraction;
final int windowId;
final String windowId;

const InteractionError({
required this.interactionType,
Expand Down
4 changes: 1 addition & 3 deletions lib/loading/cubit/loading_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ part 'loading_cubit.freezed.dart';
class LoadingCubit extends Cubit<LoadingState> {
final NativePlatform nativePlatform;

LoadingCubit()
: nativePlatform = NativePlatform(),
super(const LoadingInitial()) {
LoadingCubit(this.nativePlatform) : super(const LoadingInitial()) {
checkDependencies();
}

Expand Down
Loading
Loading