Skip to content

Commit

Permalink
feature: providing a minimal launcher written in flutter for ubuntu f…
Browse files Browse the repository at this point in the history
…rame (#191)

## What's new?
- Frame can now optionally support the `launcher`, which is a minimal
dock allowing the user to switch between fullscreen application

## Documentation
Ubuntu Frame's `launcher` daemon provides users with a way to navigate
between multiple applications running on a single frame instance.

### Enablement
To enable the launcher, run:

```sh
snap set ubuntu-frame launcher=true
```

### Behavior
The following specifies the behavior that you can of the launcher
daemon:

- When frame starts, the launcher will appear in the left-hand side of
the screen with a black background
- As window are opened, the launcher will display an icon that
corresponds with the application for that window
- Icons will ONLY be displayed for applications that are packaged as
snaps. The desktop files for these applications are found in the
`/var/lib/snapd/desktop` directory.
- When an icon is clicked, the corresponding window will be brought into
focus
- If a window is closed, then the icon will be removed from the launcher
- If we cannot resolve an icon for a window, then the launcher will
attempt to use the `application-x-executable` icon. If that fails, then
the icon will be missing entirely.
  • Loading branch information
AlanGriffiths authored Jul 26, 2024
2 parents f7b633e + bc5d16f commit 8fb0621
Show file tree
Hide file tree
Showing 39 changed files with 2,740 additions and 4 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ The foundation for many embedded graphical display implementations. `ubuntu-fram

The application you choose (or provide) gets a fullscreen window (or windows) and input from touch, keyboard and mouse without needing to deal with the specific hardware.

## Connections
```sh
snap connect ubuntu-frame:desktop-launch
```

## Configuration

There are three snap configuration options:
There are four snap configuration options:

* `daemon=[true|false]` enables the daemon (defaults to false on classic systems)
* `config=<options for the shell>`
* `display=<options for display layout>`
* `launcher=[true|false]`

The configuration options are described in detail in [the Ubuntu Frame reference](https://mir-server.io/docs/reference).

Expand Down
29 changes: 29 additions & 0 deletions launcher/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock

# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/

# dotenv environment variables file
.env*

# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map

.flutter-plugins
.flutter-plugins-dependencies

linux/flutter/ephemeral
674 changes: 674 additions & 0 deletions launcher/LICENSE

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions launcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ubuntu-frame-launcher
A flutter-based launcher for Ubuntu Frame.

## Install
```sh
flutter pub get
```

## Development
1 . Run `ubuntu-frame`:

```sh
WAYLAND_DISPLAY=wayland-98 ubuntu-frame --add-wayland-extensions zwlr_layer_shell_v1:zwlr_foreign_toplevel_manager_v1
```

2. Run `launcher`:

```sh
cd launcher
WAYLAND_DISPLAY=wayland-98 flutter run
```
29 changes: 29 additions & 0 deletions launcher/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
84 changes: 84 additions & 0 deletions launcher/lib/controllers/application_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright © Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 or 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'dart:async';

import 'package:logging/logging.dart';
import 'package:ubuntu_frame_launcher/models/desktop_file.dart';
import 'package:ubuntu_frame_launcher/models/window_event.dart';
import 'package:ubuntu_frame_launcher/services/window_service.dart';

import '../services/desktop_file_manager.dart';

class ApplicationController {
final WindowService windowService;
final DesktopFileManager desktopFileManager;

final _controller = StreamController<List<DesktopFile>>.broadcast();
late Future<List<DesktopFile>> _installedAppFuture;
final _logger = Logger("ApplicationController");

ApplicationController(this.windowService, this.desktopFileManager) {
_installedAppFuture = _loadInstalledApplications();

windowService.watcher.listen((event) async {
if (event.eventType == WindowEventType.created ||
event.eventType == WindowEventType.removed) {
_controller.add(await getOpenApplications());
}
});
}

Future<List<DesktopFile>> _loadInstalledApplications() async {
final applications = await desktopFileManager.getAllDesktopFiles();
applications.sort((a, b) {
final aName = a.name?.toLowerCase() ?? '';
final bName = b.name?.toLowerCase() ?? '';
return aName.compareTo(bName);
});
return applications;
}

/// Returns a list of applications installed on the system. This list
/// is guaranteed to be sorted alphabetically by name.
Future<List<DesktopFile>> getInstalledApplications() async {
// TODO: Watch directories and dynamically reload if they change
return await _installedAppFuture;
}

void focus(DesktopFile file) {
final handle = windowService.getWindowById(file.id);
if (handle != null) {
handle.activate();
return;
}

_logger.shout("Unable to focus desktop file with id = ${file.id}}");
}

Stream<List<DesktopFile>> getOpenedAppsStream() {
return _controller.stream;
}

Future<List<DesktopFile>> getOpenApplications() async {
final List<DesktopFile> result = [];
for (final handle in windowService.getWindowList()) {
result.add(await desktopFileManager.resolveId(handle.getAppId()));
}

return result;
}
}
38 changes: 38 additions & 0 deletions launcher/lib/controllers/window_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright © Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 or 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'package:ubuntu_frame_launcher/models/desktop_file.dart';
import 'package:ubuntu_frame_launcher/services/window_handle.dart';

import '../services/window_service.dart';

class WindowController {
WindowService service;

WindowController(this.service);

WindowHandle? getWindowById(String appId) {
return service.getWindowById(appId);
}

List<WindowHandle> getWindowList() {
return service.getWindowList();
}

bool isOpen(DesktopFile file) {
return service.getWindowById(file.id) != null;
}
}
87 changes: 87 additions & 0 deletions launcher/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright © Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 or 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'package:flutter/material.dart';
import 'package:ubuntu_frame_launcher/services/wayland_window_watcher.dart';
import 'package:ubuntu_frame_launcher/controllers/window_controller.dart';
import 'package:ubuntu_frame_launcher/services/window_service.dart';
import 'services/desktop_file_manager.dart';
import 'package:get_it/get_it.dart';
import 'views/dock.dart';
import 'controllers/application_controller.dart';
import 'package:logging/logging.dart';

void main() async {
Logger.root.level = Level.ALL; // defaults to Level.INFO
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});

final logger = Logger("main");
logger.info("Ubuntu Frame launcher is starting");

final getIt = GetIt.instance;

// Services
getIt.registerLazySingleton<WindowService>(
() => WindowService(WaylandWindowWatcherService()));
getIt.registerLazySingleton<DesktopFileManager>(() => DesktopFileManager());

// Controllers
getIt.registerLazySingleton<ApplicationController>(() =>
ApplicationController(
getIt.get<WindowService>(), getIt.get<DesktopFileManager>()));
getIt.registerLazySingleton<WindowController>(
() => WindowController(getIt.get<WindowService>()));

runApp(const LauncherApp());
}

class LauncherApp extends StatelessWidget {
const LauncherApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.black),
primaryColor: Colors.black,
useMaterial3: true,
fontFamily: 'Ubuntu',
iconTheme: const IconThemeData(color: Colors.white, size: 20),
textTheme: const TextTheme(
bodyMedium: TextStyle(
color: Colors.white,
),
)),
home: const Launcher(),
);
}
}

class Launcher extends StatelessWidget {
const Launcher({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[Dock()],
));
}
}
Loading

0 comments on commit 8fb0621

Please sign in to comment.