update
This commit is contained in:
2
Makefile
2
Makefile
@@ -36,7 +36,7 @@ update:
|
|||||||
echo "Aktualisiert."
|
echo "Aktualisiert."
|
||||||
fi
|
fi
|
||||||
$(MAKE) --no-print-directory sync-config reload
|
$(MAKE) --no-print-directory sync-config reload
|
||||||
echo "Neuer Code wirkt nach dem nächsten Ab- und Anmelden."
|
echo "Neuer Code ist aktiv. Nur extension.js selbst wirkt erst nach dem nächsten Anmelden."
|
||||||
|
|
||||||
# UUID in die aktiven Extensions eintragen, falls sie fehlt
|
# UUID in die aktiven Extensions eintragen, falls sie fehlt
|
||||||
enable:
|
enable:
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ fun NotesScreen(store: Store, modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
val opened = open?.let { store.note(it) }
|
val opened = open?.let { store.note(it) }
|
||||||
if (opened != null) {
|
if (opened != null) {
|
||||||
NoteView(store, opened.id, onClose = { open = null })
|
NoteView(store, opened.id, modifier, onClose = { open = null })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ private fun TreeRow(row: Row, onClick: () -> Unit, onLongClick: () -> Unit) {
|
|||||||
|
|
||||||
// Eine Notiz: formatiert anzeigen, Tipp in den Text bearbeitet. Gespeichert wird alle 5 Sekunden und beim Verlassen.
|
// Eine Notiz: formatiert anzeigen, Tipp in den Text bearbeitet. Gespeichert wird alle 5 Sekunden und beim Verlassen.
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteView(store: Store, id: String, onClose: () -> Unit) {
|
fun NoteView(store: Store, id: String, modifier: Modifier = Modifier, onClose: () -> Unit) {
|
||||||
store.version.intValue
|
store.version.intValue
|
||||||
val note = store.note(id)
|
val note = store.note(id)
|
||||||
var editing by rememberSaveable { mutableStateOf(false) }
|
var editing by rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -211,7 +211,7 @@ fun NoteView(store: Store, id: String, onClose: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxSize()) {
|
Column(modifier.fillMaxSize()) {
|
||||||
Row(Modifier.fillMaxWidth().padding(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth().padding(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
IconButton(onClick = { if (editing) stopEdit() else onClose() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
IconButton(onClick = { if (editing) stopEdit() else onClose() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
||||||
Text(note?.name ?: "", style = MaterialTheme.typography.titleLarge, modifier = Modifier.weight(1f))
|
Text(note?.name ?: "", style = MaterialTheme.typography.titleLarge, modifier = Modifier.weight(1f))
|
||||||
|
|||||||
74
desktop@marha.de/app.js
Normal file
74
desktop@marha.de/app.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import Meta from 'gi://Meta';
|
||||||
|
import St from 'gi://St';
|
||||||
|
|
||||||
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
||||||
|
import {CalendarPanel} from './calendar.js';
|
||||||
|
import {NotesPanel} from './notes.js';
|
||||||
|
import {Store} from './store.js';
|
||||||
|
import {Sync} from './sync.js';
|
||||||
|
import {TodoPanel} from './todos.js';
|
||||||
|
|
||||||
|
// Die eigentliche Extension. extension.js lädt dieses Modul bei jedem Einschalten frisch,
|
||||||
|
// der Zustand liegt auf dem Extension-Objekt (ext), damit Tests und D-Bus ihn dort finden.
|
||||||
|
|
||||||
|
const MARGIN = 24;
|
||||||
|
|
||||||
|
export function enable(ext) {
|
||||||
|
ext._store = new Store();
|
||||||
|
|
||||||
|
// Nur die Panels sind reaktiv. Der Rest lässt Klicks zum Desktop durch.
|
||||||
|
ext._root = new St.BoxLayout({style_class: 'desktop-root', reactive: false});
|
||||||
|
ext._notes = new NotesPanel(ext._store);
|
||||||
|
ext._root.add_child(ext._notes);
|
||||||
|
// Rechte Spalte füllt den Rest, der Abstand dazwischen entspricht dem Außenrand.
|
||||||
|
const right = new St.BoxLayout({vertical: true, style_class: 'desktop-right', reactive: false, x_expand: true});
|
||||||
|
right.add_child(new CalendarPanel(ext._store));
|
||||||
|
right.add_child(new TodoPanel(ext._store));
|
||||||
|
ext._root.add_child(right);
|
||||||
|
|
||||||
|
global.window_group.add_child(ext._root);
|
||||||
|
ext._restackId = global.display.connect('restacked', () => restack(ext));
|
||||||
|
ext._mapId = global.window_manager.connect('map', () => restack(ext));
|
||||||
|
ext._workareaId = global.display.connect('workareas-changed', () => layout(ext));
|
||||||
|
layout(ext);
|
||||||
|
restack(ext);
|
||||||
|
|
||||||
|
ext._sync = new Sync(ext._store);
|
||||||
|
ext._sync.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disable(ext) {
|
||||||
|
ext._sync.stop();
|
||||||
|
ext._sync = null;
|
||||||
|
global.display.disconnect(ext._restackId);
|
||||||
|
global.window_manager.disconnect(ext._mapId);
|
||||||
|
global.display.disconnect(ext._workareaId);
|
||||||
|
ext._root.destroy();
|
||||||
|
ext._root = null;
|
||||||
|
ext._notes = null;
|
||||||
|
ext._store = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Füllt den Arbeitsbereich des Hauptmonitors (ohne Topbar und Dock). Notizen nehmen die halbe Bildschirmbreite.
|
||||||
|
function layout(ext) {
|
||||||
|
const area = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
|
ext._root.set_position(area.x + MARGIN, area.y + MARGIN);
|
||||||
|
ext._root.set_size(area.width - 2 * MARGIN, area.height - 2 * MARGIN);
|
||||||
|
ext._notes.set_width(Math.round(Main.layoutManager.primaryMonitor.width / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liegt direkt über Desktop-Fenstern (z.B. Desktop Icons), sonst direkt über dem Wallpaper.
|
||||||
|
// Mutter sortiert die Fensterebene bei jedem Restack neu, daher hier nachziehen.
|
||||||
|
function restack(ext) {
|
||||||
|
let below = Main.layoutManager._backgroundGroup;
|
||||||
|
for (const actor of global.window_group.get_children()) {
|
||||||
|
if (!(actor instanceof Meta.WindowActor))
|
||||||
|
continue;
|
||||||
|
const window = actor.meta_window;
|
||||||
|
const isDing = window.customJS_ding || window.title?.startsWith('Desktop Icons');
|
||||||
|
if (isDing || window.window_type === Meta.WindowType.DESKTOP)
|
||||||
|
below = actor;
|
||||||
|
}
|
||||||
|
global.window_group.set_child_above_sibling(ext._root, below);
|
||||||
|
}
|
||||||
@@ -1,74 +1,67 @@
|
|||||||
import Meta from 'gi://Meta';
|
import Gio from 'gi://Gio';
|
||||||
import St from 'gi://St';
|
import GLib from 'gi://GLib';
|
||||||
|
|
||||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
||||||
|
|
||||||
import {CalendarPanel} from './calendar.js';
|
|
||||||
import {NotesPanel} from './notes.js';
|
|
||||||
import {Store} from './store.js';
|
|
||||||
import {Sync} from './sync.js';
|
|
||||||
import {TodoPanel} from './todos.js';
|
|
||||||
|
|
||||||
const MARGIN = 24;
|
|
||||||
|
|
||||||
|
// GNOME lädt Extension-Code nur einmal pro Sitzung. Deshalb ist diese Datei nur ein Lader:
|
||||||
|
// Beim Einschalten kopiert er die Module in einen frischen Ordner und importiert sie von dort.
|
||||||
|
// Neuer Ordner heißt neue Adresse, also lädt GJS wirklich den aktuellen Code. Nach einer
|
||||||
|
// Änderung reicht so make update (kopiert, schaltet aus und ein), ohne Ab- und Anmelden.
|
||||||
|
// Nur diese Datei selbst wirkt erst nach dem nächsten Anmelden.
|
||||||
export default class DesktopExtension extends Extension {
|
export default class DesktopExtension extends Extension {
|
||||||
|
_generation = 0;
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
this._store = new Store();
|
const generation = ++this._generation;
|
||||||
|
const dir = copyModules(this.dir);
|
||||||
// Nur die Panels sind reaktiv. Der Rest lässt Klicks zum Desktop durch.
|
import(`${dir.get_uri()}/app.js`)
|
||||||
this._root = new St.BoxLayout({style_class: 'desktop-root', reactive: false});
|
.then(app => {
|
||||||
this._notes = new NotesPanel(this._store);
|
// Inzwischen aus- oder erneut eingeschaltet: dieser Stand ist überholt
|
||||||
this._root.add_child(this._notes);
|
if (this._generation !== generation)
|
||||||
// Rechte Spalte füllt den Rest, der Abstand dazwischen entspricht dem Außenrand.
|
return;
|
||||||
const right = new St.BoxLayout({vertical: true, style_class: 'desktop-right', reactive: false, x_expand: true});
|
this._app = app;
|
||||||
right.add_child(new CalendarPanel(this._store));
|
app.enable(this);
|
||||||
right.add_child(new TodoPanel(this._store));
|
})
|
||||||
this._root.add_child(right);
|
.catch(e => console.error(`desktop@marha.de: Laden fehlgeschlagen: ${e.message}\n${e.stack}`));
|
||||||
|
|
||||||
global.window_group.add_child(this._root);
|
|
||||||
this._restackId = global.display.connect('restacked', () => this._restack());
|
|
||||||
this._mapId = global.window_manager.connect('map', () => this._restack());
|
|
||||||
this._workareaId = global.display.connect('workareas-changed', () => this._layout());
|
|
||||||
this._layout();
|
|
||||||
this._restack();
|
|
||||||
|
|
||||||
this._sync = new Sync(this._store);
|
|
||||||
this._sync.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
this._sync.stop();
|
this._generation++;
|
||||||
this._sync = null;
|
this._app?.disable(this);
|
||||||
global.display.disconnect(this._restackId);
|
this._app = null;
|
||||||
global.window_manager.disconnect(this._mapId);
|
|
||||||
global.display.disconnect(this._workareaId);
|
|
||||||
this._root.destroy();
|
|
||||||
this._root = null;
|
|
||||||
this._notes = null;
|
|
||||||
this._store = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Füllt den Arbeitsbereich des Hauptmonitors (ohne Topbar und Dock). Notizen nehmen die halbe Bildschirmbreite.
|
|
||||||
_layout() {
|
|
||||||
const area = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
|
||||||
this._root.set_position(area.x + MARGIN, area.y + MARGIN);
|
|
||||||
this._root.set_size(area.width - 2 * MARGIN, area.height - 2 * MARGIN);
|
|
||||||
this._notes.set_width(Math.round(Main.layoutManager.primaryMonitor.width / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Liegt direkt über Desktop-Fenstern (z.B. Desktop Icons), sonst direkt über dem Wallpaper.
|
|
||||||
// Mutter sortiert die Fensterebene bei jedem Restack neu, daher hier nachziehen.
|
|
||||||
_restack() {
|
|
||||||
let below = Main.layoutManager._backgroundGroup;
|
|
||||||
for (const actor of global.window_group.get_children()) {
|
|
||||||
if (!(actor instanceof Meta.WindowActor))
|
|
||||||
continue;
|
|
||||||
const window = actor.meta_window;
|
|
||||||
const isDing = window.customJS_ding || window.title?.startsWith('Desktop Icons');
|
|
||||||
if (isDing || window.window_type === Meta.WindowType.DESKTOP)
|
|
||||||
below = actor;
|
|
||||||
}
|
|
||||||
global.window_group.set_child_above_sibling(this._root, below);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alle Module außer dieser Datei nach ~/.cache/desktop-app/live-<Zeit> kopieren. Ältere Kopien
|
||||||
|
// sind schon geladen und werden gelöscht.
|
||||||
|
function copyModules(source) {
|
||||||
|
const base = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_cache_dir(), 'desktop-app']));
|
||||||
|
GLib.mkdir_with_parents(base.get_path(), 0o700);
|
||||||
|
for (const name of listNames(base)) {
|
||||||
|
if (name.startsWith('live-'))
|
||||||
|
removeDir(base.get_child(name));
|
||||||
|
}
|
||||||
|
const dest = base.get_child(`live-${GLib.get_real_time()}`);
|
||||||
|
dest.make_directory(null);
|
||||||
|
for (const name of listNames(source)) {
|
||||||
|
if (name.endsWith('.js') && name !== 'extension.js')
|
||||||
|
source.get_child(name).copy(dest.get_child(name), Gio.FileCopyFlags.OVERWRITE, null, null);
|
||||||
|
}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listNames(dir) {
|
||||||
|
const names = [];
|
||||||
|
const children = dir.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, null);
|
||||||
|
let info;
|
||||||
|
while ((info = children.next_file(null)))
|
||||||
|
names.push(info.get_name());
|
||||||
|
children.close(null);
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDir(dir) {
|
||||||
|
for (const name of listNames(dir))
|
||||||
|
dir.get_child(name).delete(null);
|
||||||
|
dir.delete(null);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import GObject from 'gi://GObject';
|
||||||
import Pango from 'gi://Pango';
|
import Pango from 'gi://Pango';
|
||||||
import St from 'gi://St';
|
import St from 'gi://St';
|
||||||
|
|
||||||
@@ -50,8 +51,19 @@ export function scrollBox(styleClass) {
|
|||||||
|
|
||||||
// Mehrzeiliges Eingabefeld in einem Scrollbereich. Der Scrollbereich ist der sichtbare Rahmen,
|
// Mehrzeiliges Eingabefeld in einem Scrollbereich. Der Scrollbereich ist der sichtbare Rahmen,
|
||||||
// ein Klick in die freie Fläche darunter setzt den Cursor ans Ende. Der Cursor bleibt beim Tippen sichtbar.
|
// ein Klick in die freie Fläche darunter setzt den Cursor ans Ende. Der Cursor bleibt beim Tippen sichtbar.
|
||||||
|
// Ein mehrzeiliges Feld ist so hoch wie sein Text. Themes wie Yaru geben StEntry eine min-height.
|
||||||
|
// Die ersetzt die Mindesthöhe, der Scrollbereich richtet sich aber genau danach: er hielte das
|
||||||
|
// Feld für schrumpfbar und ließe nichts zu scrollen.
|
||||||
|
const MultilineEntry = GObject.registerClass(
|
||||||
|
class MultilineEntry extends St.Entry {
|
||||||
|
vfunc_get_preferred_height(forWidth) {
|
||||||
|
const [, natural] = super.vfunc_get_preferred_height(forWidth);
|
||||||
|
return [natural, natural];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export function multilineEntry(text, styleClass, scrollClass, hint = '') {
|
export function multilineEntry(text, styleClass, scrollClass, hint = '') {
|
||||||
const entry = new St.Entry({can_focus: true, text, hint_text: hint, style_class: `${styleClass} multiline`, x_expand: true});
|
const entry = new MultilineEntry({can_focus: true, text, hint_text: hint, style_class: `${styleClass} multiline`, x_expand: true});
|
||||||
const clutterText = entry.clutter_text;
|
const clutterText = entry.clutter_text;
|
||||||
clutterText.single_line_mode = false;
|
clutterText.single_line_mode = false;
|
||||||
clutterText.line_wrap = true;
|
clutterText.line_wrap = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user