Initial commit

This commit is contained in:
Bruce Leidl 2024-11-12 17:12:37 -05:00
commit 581e404edf
79 changed files with 8842 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/subprojects/blueprint-compiler
/node_modules
/build
/install

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "gi-types"]
path = gi-types
url = https://gitlab.gnome.org/BrainBlasted/gi-typescript-definitions.git

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/subgraph/citadel/Realms">
<file>ui/RealmListItem.ui</file>
<file>ui/RealmRow.ui</file>
<file>ui/RealmsView.ui</file>
<file>ui/RealmInfo.ui</file>
<file>ui/RealmInfoEntry.ui</file>
<file>ui/Window.ui</file>
<file>ui/ConfigureRealm.ui</file>
<file>ui/ConfigureOption.ui</file>
<file>ui/ColorSchemeChooser.ui</file>
<file>ui/ColorSchemeListItem.ui</file>
<file>ui/HelpWindow.ui</file>
<file>css/style.css</file>
<file>dbus-interfaces/com.subgraph.realms.Manager2.xml</file>
<file>dbus-interfaces/com.subgraph.realms.Realm.xml</file>
<file>dbus-interfaces/com.subgraph.realms.RealmFS.xml</file>
</gresource>
</gresources>

View File

@ -0,0 +1,6 @@
[Desktop Entry]
Name=Realms
Type=Application
Exec=com.subgraph.citadel.Realms
Terminal=false
Icon=com.subgraph.citadel.Realms

10
data/css/style.css Normal file
View File

@ -0,0 +1,10 @@
welcome {
border-spacing: 18px;
margin: 36px;
}
welcome.big > label {
font-size: 1.4em;
font-weight: bold;
}

View File

@ -0,0 +1,17 @@
<node>
<interface name="com.subgraph.realms.Manager2">
<method name="CreateRealm">
<arg type="s" name="name" direction="in" />
</method>
<method name="GetCurrent">
<arg type="u" direction="out" />
</method>
<method name="RealmFromCitadelPid">
<arg name="pid" type="u" direction="in" />
<arg type="(us)" direction="out" />
</method>
<method name="GetGlobalConfig">
<arg type="a{ss}" direction="out"/>
</method>
</interface>
</node>

View File

@ -0,0 +1,27 @@
<node>
<interface name="com.subgraph.realms.Realm">
<method name="GetConfig">
<arg type="a{ss}" direction="out"/>
</method>
<method name="SetConfig">
<arg type="a(ss)" direction="in"/>
</method>
<method name="Start"/>
<method name="Stop"/>
<method name="Restart"/>
<method name="SetCurrent"/>
<property name="Name" type="s" access="read"/>
<property name="Description" type="s" access="read"/>
<property name="PidNS" type="t" access="read"/>
<property name="RealmFS" type="u" access="read"/>
<!--
0 = Stopped, 1 = Starting, 2 = Running, 3 = Current, 4 = Stopping,
-->
<property name="RunStatus" type="u" access="read"/>
<property name="IsSystemRealm" type="b" access="read" />
<property name="Timestamp" type="t" access="read" />
</interface>
</node>

View File

@ -0,0 +1,11 @@
<node>
<interface name="com.subgraph.realms.RealmFS">
<property name="Name" type="s" access="read" />
<property name="Path" type="s" access="read" />
<property name="Mountpoint" type="s" access="read" />
<property name="Activated" type="b" access="read" />
<property name="InUse" type="b" access="read" />
<property name="AllocatedSpace" type="t" access="read" />
<property name="FreeSpace" type="t" access="read" />
</interface>
</node>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

30
data/meson.build Normal file
View File

@ -0,0 +1,30 @@
blueprints = custom_target('blueprints',
input: files(
'ui/Window.blp',
'ui/RealmListItem.blp',
'ui/RealmRow.blp',
'ui/RealmsView.blp',
'ui/RealmInfo.blp',
'ui/RealmInfoEntry.blp',
'ui/ConfigureOption.blp',
'ui/ConfigureRealm.blp',
'ui/ColorSchemeChooser.blp',
'ui/ColorSchemeListItem.blp',
'ui/HelpWindow.blp',
),
output: '.',
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
)
gnome.compile_resources(
APP_ID + '.data',
APP_ID + '.data.gresource.xml',
gresource_bundle: true,
install: true,
install_dir: get_option('datadir') / APP_ID,
dependencies: blueprints,
)
install_data('icons/com.subgraph.Realms.png', install_dir: join_paths(get_option('datadir'), 'icons/hicolor/512x512/apps'))

View File

@ -0,0 +1,35 @@
using Gtk 4.0;
using Adw 1;
template $ColorSchemeChooser: Adw.NavigationPage {
title: "Choose Color Scheme";
tag: "realm-colorscheme";
Adw.ToolbarView {
[top]
Adw.HeaderBar {}
content : Paned {
position: 200;
ScrolledWindow {
styles ["sidebar"]
child: ListView colorList {
tab-behavior: item;
styles ["navigation-sidebar"]
factory: Gtk.BuilderListItemFactory {
resource: "/com/subgraph/citadel/Realms/ui/ColorSchemeListItem.ui";
};
};
}
Label previewLabel {
use-markup: true;
xalign: 0;
yalign: 0;
}
};
}
}

View File

@ -0,0 +1,13 @@
using Gtk 4.0;
template Gtk.ListItem {
focusable: false;
child: Gtk.TreeExpander expander {
list-row: bind (template.item) as <TreeListRow>;
child: Label {
hexpand: true;
halign: start;
label: bind expander.item as <$ColorSchemeNode>.text;
};
};
}

View File

@ -0,0 +1,84 @@
using Gtk 4.0;
using Adw 1;
ColorDialog colorDialog {
title: "Choose a window label color";
modal: true;
with-alpha: false;
}
template $ConfigureDialog: Adw.Dialog {
title: "Configure Realm";
content-width: 640;
styles ["preferences"]
child: Adw.NavigationView navigationView {
Adw.NavigationPage {
title: bind template.title;
Adw.ToolbarView {
[top]
Adw.Banner changedBanner {
title: "Realm configuration has changed.";
button-label: "Apply";
button-clicked => $_onApplyClicked();
}
Adw.PreferencesPage {
Adw.PreferencesGroup optionsGroup {
title: "Options";
}
Adw.PreferencesGroup {
title: "Other";
Adw.ComboRow overlayCombo {
title: "Overlay";
model: StringList {
strings [
"Storage",
"TmpFS",
"None",
]
};
}
Adw.ComboRow realmfsCombo {
title: "RealmFS";
model: StringList {
strings []
};
}
Adw.ActionRow {
title: "Color Scheme";
activatable-widget: colorSchemeButton;
[suffix]
Button colorSchemeButton {
can-focus: false;
label: "Default Dark";
}
}
Adw.ActionRow {
title: "Window Label Color";
activatable-widget: labelColorButton;
[suffix]
ColorDialogButton labelColorButton {
can-focus: false;
dialog: colorDialog;
rgba: "#ffff00000000";
}
}
} // Adw.PreferencesGroup ("Other")
} // Adw.PreferencesPage
} // Adw.ToolbarView
} // Adw.NavigationPage
}; // Adw.NavigationView
} // $ConfigureDialog

View File

@ -0,0 +1,23 @@
using Gtk 4.0;
/*
template $ConfigureOption: ListBoxRow {
width-request: 100;
activatable: false;
selectable: false;
child: Box {
margin-bottom: 5;
spacing: 30;
Label name {
hexpand: true;
halign: start;
}
Switch switch {
halign: end;
}
};
}
*/

View File

@ -0,0 +1,75 @@
using Gtk 4.0;
using Adw 1;
ColorDialog colorDialog {
title: "Choose a window label color";
modal: true;
with-alpha: false;
}
template $ConfigureRealm: Adw.NavigationPage {
title: "Realm Config";
tag: "realm-config";
Adw.ToolbarView {
[top]
Adw.HeaderBar {}
[top]
Adw.Banner changedBanner {
title: "Realm configuration has changed.";
button-label: "Apply";
button-clicked => $_onApplyClicked();
}
content: Adw.PreferencesPage {
Adw.PreferencesGroup optionsGroup {
title: "Options";
}
Adw.PreferencesGroup {
title: "Other";
Adw.ComboRow overlayCombo {
title: "Overlay";
model: StringList {
strings [
"Storage",
"TmpFS",
"None",
]
};
}
Adw.ComboRow realmfsCombo {
title: "RealmFS";
model: StringList {
strings []
};
}
Adw.ActionRow {
title: "Color Scheme";
activatable-widget: colorSchemeButton;
[suffix]
Button colorSchemeButton {
can-focus: false;
label: "Default Dark";
}
}
Adw.ActionRow {
title: "Window Label Color";
activatable-widget: labelColorButton;
[suffix]
ColorDialogButton labelColorButton {
can-focus: false;
dialog: colorDialog;
rgba: "#ffff00000000";
}
}
} // Adw.PreferencesGroup ("Other")
}; // Adw.PreferencesPage
} // Adw.ToolbarView
} // $ConfigureRealm

39
data/ui/HelpWindow.blp Normal file
View File

@ -0,0 +1,39 @@
using Gtk 4.0;
ShortcutsWindow helpWindow {
modal: true;
can-focus: false;
ShortcutsSection {
ShortcutsGroup {
title: "General";
ShortcutsShortcut {
accelerator: "Up k";
title: "Previous / Up";
}
ShortcutsShortcut {
accelerator: "Down j";
title: "Next / Down";
}
ShortcutsShortcut {
accelerator: "h question";
title: "Display keyboard help";
}
ShortcutsShortcut {
accelerator: "q";
title: "Quit";
}
}
ShortcutsGroup {
title: "Realm";
ShortcutsShortcut {
accelerator: "c";
title: "Configure selected Realm";
}
}
}
}

25
data/ui/RealmInfo.blp Normal file
View File

@ -0,0 +1,25 @@
using Gtk 4.0;
template $RealmInfo: ScrolledWindow {
Box {
orientation: horizontal;
margin-bottom: 18;
margin-top: 18;
margin-start: 18;
margin-end: 18;
Box column {
orientation: vertical;
spacing: 12;
hexpand: true;
Label columnTitle {
label: "Realm";
halign: start;
styles [ "title-2" ]
}
}
}
}

View File

@ -0,0 +1,19 @@
using Gtk 4.0;
template $RealmInfoEntry: Box {
orientation: vertical;
Label nameLabel {
halign: start;
styles [
"dim-label",
"caption",
]
}
Label valueLabel {
halign: start;
wrap: true;
xalign: 0;
use-markup: true;
}
}

View File

@ -0,0 +1,7 @@
using Gtk 4.0;
template Gtk.ListItem {
child: $RealmRow {
realm: bind template.item;
};
}

15
data/ui/RealmRow.blp Normal file
View File

@ -0,0 +1,15 @@
using Gtk 4.0;
template $RealmRow {
layout-manager: BoxLayout {
orientation: horizontal;
};
Label nameLabel {
xalign: 0;
label: bind template.realm as <$Realm>.name;
styles [
"heading",
"dim-label",
]
}
}

39
data/ui/RealmsView.blp Normal file
View File

@ -0,0 +1,39 @@
using Gtk 4.0;
template $RealmsView {
selected-realm: bind realmsSelection.selected-item;
layout-manager: BinLayout {};
Frame {
width-request: 200;
[label]
Label {
margin-start: 10;
margin-top: 20;
styles ["title-4"]
label: "Realms";
}
ScrolledWindow {
child: ListView {
styles ["navigation-sidebar"]
margin-start: 20;
margin-end: 20;
margin-top: 10;
show-separators: true;
focusable: false;
can-focus: false;
model: SingleSelection realmsSelection {
model: FilterListModel hiddenRealmsFilterModel {
model: SortListModel {
model: $RealmModel{};
sorter: CustomSorter realmSorter {};
};
};
};
factory: BuilderListItemFactory {
resource: "/com/subgraph/citadel/Realms/ui/RealmListItem.ui";
};
};
}
}
}

60
data/ui/Window.blp Normal file
View File

@ -0,0 +1,60 @@
using Gtk 4.0;
using Adw 1;
menu menu {
section {
item {
action: "app.about";
label: "About Realms";
}
}
}
template $RealmsWindow : Adw.ApplicationWindow {
default-width: 1000;
default-height: 640;
/* 'width-request and 'height-request' declare minimum window size */
width-request: 400;
height-request: 300;
title: "Realms";
Adw.NavigationView navView {
Adw.NavigationPage {
title: "Realms";
tag: "main";
Adw.ToolbarView {
[top]
Adw.HeaderBar header {
title-widget: Adw.WindowTitle {
title: "Realms";
};
[end]
MenuButton {
can-focus: false;
primary: true;
menu-model: menu;
icon-name: "open-menu-symbolic";
}
}
content: Paned {
resize-end-child: true;
resize-start-child: false;
shrink-start-child: false;
$RealmsView realmsView {}
$RealmInfo {
realm: bind realmsView.selected-realm;
}
};
}
}
$ConfigureRealm configureRealm {
navigation-view: navView;
colorscheme-chooser: colorChooser;
}
$ColorSchemeChooser colorChooser {}
}
}

1
gi-types Submodule

@ -0,0 +1 @@
Subproject commit dbbaa0527556cd3ce5434c4a5072cd99348eff7a

89
js/Application.js Normal file
View File

@ -0,0 +1,89 @@
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Application_instances, _a, _Application_setupAccelerators, _Application_setupActions, _Application_onQuit, _Application_onRealmConfig, _Application_onShowHelp, _Application_onAbout;
import Adw from 'gi://Adw';
import Gtk from 'gi://Gtk?version=4.0';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import './model/RealmManager.js';
import './model/Realm.js';
import './RealmsView.js';
import './RealmRow.js';
import './RealmInfo.js';
import './RealmModel.js';
import './ConfigureRealm.js';
import { Window } from './Window.js';
export class Application extends Adw.Application {
constructor() {
super({
application_id: 'com.subgraph.citadel.Realms',
flags: Gio.ApplicationFlags.DEFAULT_FLAGS,
});
_Application_instances.add(this);
}
vfunc_activate() {
let { activeWindow } = this;
if (!activeWindow) {
activeWindow = new Window(this);
activeWindow.set_hide_on_close(true);
}
activeWindow.present();
}
vfunc_startup() {
super.vfunc_startup();
__classPrivateFieldGet(this, _Application_instances, "m", _Application_setupActions).call(this);
__classPrivateFieldGet(this, _Application_instances, "m", _Application_setupAccelerators).call(this);
// this.#loadStyleSheet();
const styleManager = Adw.StyleManager.get_default();
styleManager.colorScheme = Adw.ColorScheme.FORCE_DARK;
}
}
_a = Application, _Application_instances = new WeakSet(), _Application_setupAccelerators = function _Application_setupAccelerators() {
this.set_accels_for_action('app.quit', ['q']);
this.set_accels_for_action('app.realmConfig', ['c']);
this.set_accels_for_action('app.showHelp', ['h', 'question']);
}, _Application_setupActions = function _Application_setupActions() {
this.add_action_entries([
// @ts-ignore
{ name: 'quit', activate: __classPrivateFieldGet(this, _Application_instances, "m", _Application_onQuit).bind(this) },
// @ts-ignore
{ name: 'realmConfig', activate: __classPrivateFieldGet(this, _Application_instances, "m", _Application_onRealmConfig).bind(this) },
// @ts-ignore
{ name: 'showHelp', activate: __classPrivateFieldGet(this, _Application_instances, "m", _Application_onShowHelp).bind(this) },
// @ts-ignore
{ name: 'about', activate: __classPrivateFieldGet(this, _Application_instances, "m", _Application_onAbout).bind(this) },
]);
}, _Application_onQuit = function _Application_onQuit() {
let { activeWindow } = this;
if (activeWindow) {
activeWindow.close();
}
}, _Application_onRealmConfig = function _Application_onRealmConfig() {
let { activeWindow } = this;
if (activeWindow) {
let window = activeWindow;
const realm = window.realms_view.selectedRealm;
if (realm) {
window.configureRealm(realm);
}
}
}, _Application_onShowHelp = function _Application_onShowHelp() {
const help = Gtk.Builder.new_from_resource('/com/subgraph/citadel/Realms/ui/HelpWindow.ui').get_object('helpWindow');
help.set_transient_for(this._window);
help.present();
}, _Application_onAbout = function _Application_onAbout() {
const dialog = new Adw.AboutDialog({
application_icon: 'face-smile',
application_name: 'Realms',
developer_name: "Subgraph",
});
dialog.present(this._window);
};
(() => {
GObject.registerClass({
GTypeName: 'RealmsApplication'
}, _a);
})();

176
js/ColorSchemeChooser.js Normal file
View File

@ -0,0 +1,176 @@
var _a;
import Adw from 'gi://Adw';
import GObject from 'gi://GObject';
import GLib from 'gi://GLib';
import Gdk from 'gi://Gdk?version=4.0';
import Gtk from 'gi://Gtk?version=4.0';
import { ColorSchemeModel } from './ColorSchemeModel.js';
class PreviewRenderer {
constructor(theme) {
this.theme = theme;
this.buffer = '';
}
colorAttrib(name, color) {
this.buffer += ` ${name}='${color}'`;
}
colorSpan(fg, bg = null) {
this.buffer += '<span';
if (fg) {
this.colorAttrib("foreground", fg);
}
if (bg) {
this.colorAttrib("background", bg);
}
this.buffer += ">";
}
endSpan() {
this.buffer += '</span>';
}
nl() {
this.buffer += ' \n ';
return this;
}
print(idx, text) {
let s = GLib.markup_escape_text(text, text.length);
let c = this.theme.terminal_palette_color(idx);
this.colorSpan(c);
this.buffer += s;
this.endSpan();
return this;
}
vtype(text) {
return this.print(3, text);
}
konst(text) {
return this.print(1, text);
}
func(text) {
return this.print(4, text);
}
string(text) {
return this.print(2, text);
}
keyword(text) {
return this.print(5, text);
}
comment(text) {
return this.print(8, text);
}
text(text) {
let color = this.theme.terminal_foreground();
this.colorSpan(color);
this.buffer += text;
this.endSpan();
return this;
}
renderPreview() {
let name = this.theme.name;
this.nl()
.comment('/**').nl()
.comment(' * An example of how this color scheme').nl()
.comment(' * might look in a text editor with syntax').nl()
.comment(' * highlighting.').nl()
.comment(' */').nl()
.nl()
.func('#include ').string('<stdio.h>').nl()
.func('#include ').string('<stdlib.h>').nl()
.nl()
.vtype('static char').text(' theme[] = ').string(`"${name}"`).text(';').nl()
.nl()
.vtype('int').text(' main(').vtype('int').text(' argc, ').vtype('char').text(' **argv) {').nl()
.text(' printf(').string('"Hello, ').keyword('%s').text('!').keyword('\\n').string('"').text(', theme);').nl()
.text(' exit(').konst('0').text(');').nl()
.text('}')
.nl();
return this.buffer;
}
}
export class ColorSchemeChooser extends Adw.NavigationPage {
addExpandToggleShortcut(keyval) {
let trigger = Gtk.KeyvalTrigger.new(keyval, Gdk.ModifierType.NO_MODIFIER_MASK);
let action = Gtk.CallbackAction.new((expander) => expander.activate_action('listitem.toggle-expand', null));
Gtk.TreeExpander.add_shortcut(Gtk.Shortcut.new(trigger, action));
}
constructor() {
super();
this.model = new ColorSchemeModel();
this._previewLabel.add_css_class("preview");
this.css_provider = new Gtk.CssProvider();
const display = Gdk.Display.get_default();
if (display) {
Gtk.StyleContext.add_provider_for_display(display, this.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
}
this.addExpandToggleShortcut(Gdk.KEY_space);
this.addExpandToggleShortcut(Gdk.KEY_l);
this._colorList.connect('activate', () => {
this.previewSelectedTheme();
});
this._colorList.single_click_activate = true;
this._colorList.model = this.model.selection;
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', (_, keyval) => {
switch (keyval) {
case Gdk.KEY_j:
case Gdk.KEY_Down:
this.nextScheme();
break;
case Gdk.KEY_k:
case Gdk.KEY_Up:
this.prevScheme();
break;
}
});
this._colorList.add_controller(keyController);
}
nextScheme() {
let pos = this.model.changeSelected(1);
if (pos) {
this._colorList.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, null);
}
}
prevScheme() {
let pos = this.model.changeSelected(-1);
if (pos) {
this._colorList.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, null);
}
}
selectTheme(id) {
const DEFAULT_THEME = '3024';
let row = this.model.searchId(id !== null && id !== void 0 ? id : DEFAULT_THEME);
if (row) {
let pos = row.get_position();
this.model.selectPosition(pos);
this._colorList.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, null);
this.previewSelectedTheme();
}
}
getSelectedTheme() {
return this.model.selectedTheme();
}
previewSelectedTheme() {
let theme = this.model.selectedTheme();
if (theme) {
this.setBackgroundColor(theme);
let renderer = new PreviewRenderer(theme);
// @ts-ignore
this._previewLabel.label = renderer.renderPreview();
}
}
setBackgroundColor(theme) {
let css = `
label.preview {
background-color: ${theme.terminal_background()};
font-family: monospace;
font-size: 14pt;
}`;
this.css_provider.load_from_string(css);
}
}
_a = ColorSchemeChooser;
(() => {
GObject.registerClass({
GTypeName: 'ColorSchemeChooser',
Template: 'resource:///com/subgraph/citadel/Realms/ui/ColorSchemeChooser.ui',
InternalChildren: ['colorList', 'previewLabel'],
}, _a);
})();

147
js/ColorSchemeModel.js Normal file
View File

@ -0,0 +1,147 @@
var _a;
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk?version=4.0';
import { Base16Theme } from './model/Base16Themes.js';
class ModelBuilder {
constructor() {
this.CATEGORIES = [
['atelier', 'Atelier'],
['black-metal', 'Black Metal'],
['brushtrees', 'Brush Trees'],
['classic', 'Classic'],
['default', 'Default'],
['google', 'Google'],
['grayscale', 'Grayscale'],
['gruvbox', 'Gruvbox'],
['harmonic', 'Harmonic'],
['ia', 'iA'],
['material', 'Material'],
['papercolor', 'PaperColor'],
['solarized', 'Solarized'],
['summerfruit', 'Summerfruit'],
['tomorrow', 'Tomorrow'],
['unikitty', 'Unikitty'],
];
this.currentCategory = null;
this.storeModel = new Gio.ListStore(ColorSchemeNode);
}
matchesCategory(theme) {
return (this.CATEGORIES.length > 0 && theme.id.startsWith(this.CATEGORIES[0][0]));
}
appendCurrentCategory() {
if (this.currentCategory) {
this.storeModel.append(this.currentCategory);
this.currentCategory = null;
this.CATEGORIES.shift();
}
}
addToCategory(theme) {
if (!this.currentCategory) {
this.currentCategory = new ColorSchemeNode(this.CATEGORIES[0][1]);
}
this.currentCategory.addChild(new ColorSchemeNode(theme.name, theme));
}
addTheme(theme) {
if (this.matchesCategory(theme)) {
this.addToCategory(theme);
}
else {
if (this.currentCategory) {
this.appendCurrentCategory();
}
this.storeModel.append(new ColorSchemeNode(theme.name, theme));
}
}
static buildTreeModel() {
let builder = new ModelBuilder();
Base16Theme.THEME_LIST.forEach(theme => builder.addTheme(theme));
return Gtk.TreeListModel.new(builder.storeModel, false, false, item => item.child_model);
}
}
export class ColorSchemeNode extends GObject.Object {
constructor(text, theme = null) {
super();
this._text = text;
this.theme = theme;
this.child_model = null;
}
get text() {
return this._text;
}
addChild(child) {
if (this.child_model === null) {
this.child_model = new Gio.ListStore(_a);
}
this.child_model.append(child);
}
matchesId(id) {
return this.theme !== null && this.theme.id === id;
}
searchChildren(id) {
if (this.child_model) {
for (let i = 0; i < this.child_model.n_items; i++) {
let node = this.child_model.get_item(i);
if (node.matchesId(id)) {
return true;
}
}
}
return false;
}
}
_a = ColorSchemeNode;
(() => {
GObject.registerClass({
GTypeName: 'ColorSchemeNode',
Properties: {
'text': GObject.ParamSpec.string('text', '', '', GObject.ParamFlags.READWRITE, ''),
}
}, _a);
})();
export class ColorSchemeModel {
constructor() {
this.treeModel = ModelBuilder.buildTreeModel();
this.selection = Gtk.SingleSelection.new(this.treeModel);
}
selectedTheme() {
// @ts-ignore
let item = this.selection.selected_item.item;
return item === null || item === void 0 ? void 0 : item.theme;
}
changeSelected(offset) {
const n_items = this.selection.n_items;
if (n_items <= 1) {
return;
}
const pos = this.selection.selected;
if (pos == Gtk.INVALID_LIST_POSITION) {
return;
}
const newPos = pos + offset;
if (newPos < 0 || newPos >= n_items) {
return;
}
this.selection.selected = newPos;
return newPos;
}
selectPosition(pos) {
this.selection.selected = pos;
}
searchId(id, from = 0) {
for (let i = from; i < this.treeModel.n_items; i++) {
let row = this.treeModel.get_row(i);
let item = row === null || row === void 0 ? void 0 : row.item;
if (item && item.matchesId(id)) {
return row;
}
if (item.searchChildren(id)) {
row === null || row === void 0 ? void 0 : row.set_expanded(true);
if (from === 0) {
return this.searchId(id, i);
}
}
}
return null;
}
}

151
js/ConfigureRealm.js Normal file
View File

@ -0,0 +1,151 @@
var _a;
import GObject from 'gi://GObject';
import Gdk from 'gi://Gdk?version=4.0';
import Gtk from 'gi://Gtk?version=4.0';
import Adw from 'gi://Adw';
import { BoolOptionData } from './model/RealmConfig.js';
import { Base16Theme } from './model/Base16Themes.js';
import { RealmManager } from './model/RealmManager.js';
import { ColorSchemeChooser } from './ColorSchemeChooser.js';
class OptionState {
constructor(row) {
this.value = null;
this.row = row;
}
initValue(value) {
this.value = value;
this.originalValue = value;
this.row.active = value;
}
/** @param {any} value */
setValue(value) {
this.value = value;
}
hasChanged() {
return this.value !== this.originalValue;
}
}
export class ConfigureRealm extends Adw.NavigationPage {
constructor() {
var _b, _c;
super();
this.realm = null;
this.optionState = new Map();
this.theme = Base16Theme.lookup('default-dark');
this._addOptions();
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', (_, keyval) => {
if (keyval === Gdk.KEY_j) {
this.vfunc_move_focus(Gtk.DirectionType.TAB_FORWARD);
}
else if (keyval === Gdk.KEY_k) {
this.vfunc_move_focus(Gtk.DirectionType.TAB_BACKWARD);
}
});
this.add_controller(keyController);
(_b = this._labelColorButton) === null || _b === void 0 ? void 0 : _b.connect('notify::rgba', () => this._scanChanges());
(_c = this._colorSchemeButton) === null || _c === void 0 ? void 0 : _c.connect('clicked', () => {
var _b;
(_b = this.navigationView) === null || _b === void 0 ? void 0 : _b.push_by_tag('realm-colorscheme');
});
}
get colorschemeChooser() {
return this._colorschemeChooser;
}
set colorschemeChooser(val) {
this._colorschemeChooser = val;
}
set navigationView(val) {
var _b;
this._navigationView = val;
(_b = this._navigationView) === null || _b === void 0 ? void 0 : _b.connect('popped', (_view, page) => {
if (page === this.colorschemeChooser) {
let theme = this.colorschemeChooser.getSelectedTheme();
if (theme && this._colorSchemeButton) {
this.theme = theme;
this._colorSchemeButton.label = this.theme.name;
}
}
});
}
get navigationView() {
return this._navigationView;
}
configure(realm) {
this.realm = realm;
this.set_title(`Configure realm-${realm.name}`);
this._setOptions(realm);
}
_setOptions(realm) {
var _b;
let config = realm.config;
this.optionState.forEach((op, name) => {
let v = config.get_bool(name);
op.initValue(v);
});
let scheme = realm.config.get_colorscheme();
this.theme = Base16Theme.lookup(scheme);
if (this.theme && this._colorSchemeButton) {
this._colorSchemeButton.label = this.theme.name;
(_b = this.colorschemeChooser) === null || _b === void 0 ? void 0 : _b.selectTheme(this.theme.id);
}
let realmfs_list = RealmManager.instance().realmfsList();
// @ts-ignore
let model = this._realmfsCombo.model;
if (model.n_items > 0) {
model.splice(0, model.n_items, []);
}
realmfs_list.forEach(realmfs => model.append(realmfs.name));
let labelColor = realm.getLabelColor();
this._labelColorButton.rgba = labelColor;
this._scanChanges();
}
_addOptions() {
BoolOptionData.allOptions().forEach(option => {
let row = new Adw.SwitchRow({
title: option.description,
});
if (option.tooltip.length > 0) {
row.tooltipMarkup = option.tooltip;
}
let state = new OptionState(row);
this.optionState.set(option.name, state);
row.connect("notify::active", () => {
state.setValue(row.active);
this._scanChanges();
});
this._optionsGroup.add(row);
});
}
_scanChanges() {
var _b;
let changed = false;
this.optionState.forEach(op => {
if (op.hasChanged()) {
changed = true;
}
});
let labelColor = (_b = this.realm) === null || _b === void 0 ? void 0 : _b.getLabelColor();
if (labelColor) {
if (!this._labelColorButton.rgba.equal(labelColor)) {
changed = true;
}
}
this._changedBanner.set_revealed(changed);
}
_onApplyClicked() {
print("clikkk");
}
}
_a = ConfigureRealm;
(() => {
GObject.registerClass({
GTypeName: "ConfigureRealm",
Template: 'resource:///com/subgraph/citadel/Realms/ui/ConfigureRealm.ui',
InternalChildren: ['optionsGroup', 'changedBanner', 'overlayCombo', 'realmfsCombo', 'colorSchemeButton', 'labelColorButton'],
Properties: {
'navigation-view': GObject.ParamSpec.object('navigation-view', '', '', GObject.ParamFlags.READWRITE, Adw.NavigationView),
'colorscheme-chooser': GObject.ParamSpec.object('colorscheme-chooser', '', '', GObject.ParamFlags.READWRITE, ColorSchemeChooser),
}
}, _a);
})();

131
js/RealmInfo.js Normal file
View File

@ -0,0 +1,131 @@
var _a;
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import { Realm } from "./model/Realm.js";
import { RealmInfoEntry } from './RealmInfoEntry.js';
const Sections = {
STATUS: 'Status',
OPTIONS: 'Options',
REALMFS: 'RealmFS',
MOUNTPOINT: 'RealmFS Mountpoint',
};
export class RealmInfo extends Gtk.ScrolledWindow {
constructor() {
super();
this._buffer = "";
this._changeId = 0;
this._realm = null;
this._entries = new Map();
this._addEntries({
left: [
Sections.STATUS,
Sections.REALMFS,
Sections.MOUNTPOINT,
Sections.OPTIONS,
],
right: []
});
}
_addEntries(labels) {
let addSectionEntries = (section, entries) => {
entries.forEach(name => {
let entry = new RealmInfoEntry(name);
this._entries.set(name, entry);
section.append(entry);
});
};
addSectionEntries(this._column, labels['left']);
}
get realm() {
return this._realm;
}
set realm(value) {
if (this.realm === value) {
return;
}
if (this._realm) {
this._realm.disconnect(this._changeId);
}
this._realm = value;
if (this._realm) {
this._changeId = this._realm.connect('changed', () => {
this.displayRealm();
});
this.displayRealm();
}
}
setEntry(name, value) {
let entry = this._entries.get(name);
if (entry) {
entry.setValue(value);
}
}
setEntryVisible(name, isVisible = true) {
let entry = this._entries.get(name);
if (entry) {
entry.set_visible(isVisible);
}
}
displayConfig() {
var _b;
let config = (_b = this._realm) === null || _b === void 0 ? void 0 : _b.config;
if (config) {
let enabled_default = config.enabled_bool_option_labels(true);
let enabled = config.enabled_bool_option_labels(false);
let buffer = enabled_default.join(' ');
buffer += '\n';
buffer += enabled.map(s => `<u>${s}</u>`)
.join(' ');
this.setEntry(Sections.OPTIONS, buffer);
}
else {
this.setEntry(Sections.OPTIONS, '');
}
}
displayRealmFS() {
var _b;
let realmfs = (_b = this._realm) === null || _b === void 0 ? void 0 : _b.realmfs;
if (realmfs) {
this.setEntryVisible(Sections.REALMFS);
this.setEntry(Sections.REALMFS, `${realmfs.name}-realmfs.img`);
if (realmfs.activated) {
this.setEntryVisible(Sections.MOUNTPOINT);
this.setEntry(Sections.MOUNTPOINT, realmfs.mountpoint);
}
else {
this.setEntryVisible(Sections.MOUNTPOINT, false);
}
}
else {
this.setEntryVisible(Sections.REALMFS, false);
this.setEntryVisible(Sections.MOUNTPOINT, false);
}
}
displayRealm() {
if (this._realm) {
this._columnTitle.label = `Realm ${this._realm.name}`;
if (this._realm.is_running()) {
this.setEntry("Status", "Running");
}
else {
this.setEntry("Status", "Stopped");
}
this.displayConfig();
this.displayRealmFS();
}
}
}
_a = RealmInfo;
(() => {
GObject.registerClass({
GTypeName: "RealmInfo",
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmInfo.ui',
Properties: {
'realm': GObject.ParamSpec.object('realm', '', '', GObject.ParamFlags.READWRITE, Realm),
},
InternalChildren: [
'column',
'columnTitle',
]
}, _a);
})();

20
js/RealmInfoEntry.js Normal file
View File

@ -0,0 +1,20 @@
var _a;
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
export class RealmInfoEntry extends Gtk.Box {
constructor(name) {
super();
this._nameLabel.label = name;
}
setValue(value) {
this._valueLabel.label = value;
}
}
_a = RealmInfoEntry;
(() => {
GObject.registerClass({
GTypeName: "RealmInfoEntry",
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmInfoEntry.ui',
InternalChildren: ['nameLabel', 'valueLabel']
}, _a);
})();

42
js/RealmModel.js Normal file
View File

@ -0,0 +1,42 @@
var _a;
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import { Realm } from './model/Realm.js';
import { RealmManager } from "./model/RealmManager.js";
export class RealmModel extends GObject.Object {
constructor() {
super();
this._realms = [];
this._realmManager = RealmManager.instance();
this._realmManager.connect('realm-added', (_manager, realm) => {
let pos = this._realms.length;
this._realms.push(realm);
// @ts-ignore
this.items_changed(pos, 0, 1);
});
this._realmManager.connect('realm-removed', (_manager, realm) => {
let pos = this._realms.findIndex(r => r === realm);
if (pos >= 0) {
this._realms.splice(pos, 1);
// @ts-ignore
this.items_changed(pos, 1, 0);
}
});
}
vfunc_get_item_type() {
return Realm;
}
vfunc_get_item(position) {
return this._realms[position] || null;
}
vfunc_get_n_items() {
return this._realms.length;
}
}
_a = RealmModel;
(() => {
GObject.registerClass({
GTypeName: 'RealmModel',
Implements: [Gio.ListModel],
}, _a);
})();

51
js/RealmRow.js Normal file
View File

@ -0,0 +1,51 @@
var _a;
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import { Realm } from './model/Realm.js';
export class RealmRow extends Gtk.Widget {
constructor() {
super(...arguments);
this._notifyId = 0;
}
get realm() {
if (this._realm === undefined) {
this._realm = null;
}
return this._realm;
}
set realm(value) {
if (this.realm === value) {
return;
}
if (this.realm && this._notifyId) {
this.realm.disconnect(this._notifyId);
this._notifyId = 0;
}
this._realm = value;
if (this.realm) {
this._notifyId = this.realm.connect('notify', this.syncRealm.bind(this));
this.syncRealm();
}
this.notify('realm');
}
syncRealm() {
if (this.realm && this.realm.is_running()) {
this._nameLabel.remove_css_class('dim-label');
}
else {
// @ts-ignore
this._nameLabel.add_css_class('dim-label');
}
}
}
_a = RealmRow;
(() => {
GObject.registerClass({
GTypeName: 'RealmRow',
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmRow.ui',
Properties: {
'realm': GObject.ParamSpec.object('realm', '', '', GObject.ParamFlags.READWRITE, Realm),
},
InternalChildren: ['nameLabel'],
}, _a);
})();

91
js/RealmsView.js Normal file
View File

@ -0,0 +1,91 @@
var _a;
import GObject from 'gi://GObject';
import Gdk from 'gi://Gdk?version=4.0';
import Gtk from 'gi://Gtk?version=4.0';
import { Realm } from "./model/Realm.js";
export class RealmsView extends Gtk.Widget {
constructor() {
super();
this._selectedRealm = null;
this._setupHiddenRealmsFilter();
this._setupRealmSorter();
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', (_, keyval) => {
switch (keyval) {
case Gdk.KEY_j:
case Gdk.KEY_Down:
this.nextRealm();
break;
case Gdk.KEY_k:
case Gdk.KEY_Up:
this.prevRealm();
break;
case Gdk.KEY_Escape:
this.activate_action('app.quit', null);
print("escaped");
break;
}
});
this.add_controller(keyController);
}
_changeSelected(offset) {
const n_items = this._realmsSelection.n_items;
if (n_items <= 1) {
return;
}
const pos = this._realmsSelection.selected;
if (pos == Gtk.INVALID_LIST_POSITION) {
return;
}
const newPos = pos + offset;
if (newPos < 0 || newPos >= n_items) {
return;
}
this._realmsSelection.selected = newPos;
}
nextRealm() {
this._changeSelected(1);
}
prevRealm() {
this._changeSelected(-1);
}
get selectedRealm() {
return this._selectedRealm;
}
set selectedRealm(value) {
if (this.selectedRealm === value) {
return;
}
this._selectedRealm = value;
}
_setupHiddenRealmsFilter() {
this._hiddenRealmsFilterModel.filter = Gtk.CustomFilter.new(item => !item.is_system_realm);
}
_setupRealmSorter() {
// 1) Sort 'Current' the lowest (top of list).
// 2) Then sort 'Running' lower than not 'Running'
// 3) Realms in the same run state sorted by lowest timestamp
this._realmSorter.set_sort_func((a, b) => {
if (a.is_current || (a.is_running && !b.is_running)) {
return Gtk.Ordering.SMALLER;
}
else if (b.is_current || (b.is_running && !a.is_running)) {
return Gtk.Ordering.LARGER;
}
else {
return b.timestamp - a.timestamp;
}
});
}
}
_a = RealmsView;
(() => {
GObject.registerClass({
GTypeName: 'RealmsView',
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmsView.ui',
Properties: {
'selected-realm': GObject.ParamSpec.object('selected-realm', '', '', GObject.ParamFlags.READWRITE, Realm),
},
InternalChildren: ['realmsSelection', 'hiddenRealmsFilterModel', 'realmSorter'],
}, _a);
})();

28
js/Window.js Normal file
View File

@ -0,0 +1,28 @@
var _a;
import GObject from 'gi://GObject';
import Adw from 'gi://Adw';
export class Window extends Adw.ApplicationWindow {
constructor(application) {
super({ application: application });
}
configureRealm(realm) {
this._configureRealm.configure(realm);
this._navView.push(this._configureRealm);
}
get realms_view() {
return this._realmsView;
}
vfunc_close_request() {
super.vfunc_close_request();
this.run_dispose();
return true;
}
}
_a = Window;
(() => {
GObject.registerClass({
GTypeName: 'RealmsWindow',
Template: 'resource:///com/subgraph/citadel/Realms/ui/Window.ui',
InternalChildren: ['realmsView', 'configureRealm', 'navView'],
}, _a);
})();

822
js/colors/Base16Theme.js Normal file
View File

@ -0,0 +1,822 @@
export class Base16Theme {
static add(id, name, colors) {
const theme = new Base16Theme(id, name, colors);
Base16Theme.THEMES.push(theme);
}
constructor(id, name, colors) {
this.id = id;
this.name = name;
this.colors = colors;
}
color(idx) {
let hex = this.colors[idx].toString(16).padStart(6, '0');
return `#${hex}`;
}
terminal_background() {
return this.color(0);
}
terminal_foreground() {
return this.color(5);
}
terminal_palette_color(idx) {
return this.color(Base16Theme.TERM_MAP[idx]);
}
}
Base16Theme.CATEGORIES = [
['atelier', 'Atelier'],
['black-metal', 'Black Metal'],
['brushtrees', 'Brush Trees'],
['classic', 'Classic'],
['default', 'Default'],
['google', 'Google'],
['grayscale', 'Grayscale'],
['gruvbox', 'Gruvbox'],
['harmonic', 'Harmonic'],
['ia', 'iA'],
['material', 'Material'],
['papercolor', 'PaperColor'],
['solarized', 'Solarized'],
['summerfruit', 'Summerfruit'],
['tomorrow', 'Tomorrow'],
['unikitty', 'Unikitty'],
];
Base16Theme.TERM_MAP = [
0x00, 0x08, 0x0B, 0x0A, 0x0D, 0x0E, 0x0C, 0x05,
0x03, 0x08, 0x0B, 0x0A, 0x0D, 0x0E, 0x0C, 0x07,
0x09, 0x0F, 0x01, 0x02, 0x04, 0x06,
];
Base16Theme.THEMES = [];
Base16Theme.add("3024", "3024", [
0x090300, 0x3a3432, 0x4a4543, 0x5c5855,
0x807d7c, 0xa5a2a2, 0xd6d5d4, 0xf7f7f7,
0xdb2d20, 0xe8bbd0, 0xfded02, 0x01a252,
0xb5e4f4, 0x01a0e4, 0xa16a94, 0xcdab53,
]);
Base16Theme.add("apathy", 'Apathy', [
0x031A16, 0x0B342D, 0x184E45, 0x2B685E,
0x5F9C92, 0x81B5AC, 0xA7CEC8, 0xD2E7E4,
0x3E9688, 0x3E7996, 0x3E4C96, 0x883E96,
0x963E4C, 0x96883E, 0x4C963E, 0x3E965B,
]);
Base16Theme.add("ashes", "Ashes", [
0x1C2023, 0x393F45, 0x565E65, 0x747C84,
0xADB3BA, 0xC7CCD1, 0xDFE2E5, 0xF3F4F5,
0xC7AE95, 0xC7C795, 0xAEC795, 0x95C7AE,
0x95AEC7, 0xAE95C7, 0xC795AE, 0xC79595,
]);
Base16Theme.add("atelier-cave-light", "Atelier Cave Light", [
0xefecf4, 0xe2dfe7, 0x8b8792, 0x7e7887,
0x655f6d, 0x585260, 0x26232a, 0x19171c,
0xbe4678, 0xaa573c, 0xa06e3b, 0x2a9292,
0x398bc6, 0x576ddb, 0x955ae7, 0xbf40bf,
]);
Base16Theme.add("atelier-cave", "Atelier Cave", [
0x19171c, 0x26232a, 0x585260, 0x655f6d,
0x7e7887, 0x8b8792, 0xe2dfe7, 0xefecf4,
0xbe4678, 0xaa573c, 0xa06e3b, 0x2a9292,
0x398bc6, 0x576ddb, 0x955ae7, 0xbf40bf,
]);
Base16Theme.add("atelier-dune-light", "Atelier Dune Light", [
0xfefbec, 0xe8e4cf, 0xa6a28c, 0x999580,
0x7d7a68, 0x6e6b5e, 0x292824, 0x20201d,
0xd73737, 0xb65611, 0xae9513, 0x60ac39,
0x1fad83, 0x6684e1, 0xb854d4, 0xd43552,
]);
Base16Theme.add("atelier-dune", "Atelier Dune", [
0x20201d, 0x292824, 0x6e6b5e, 0x7d7a68,
0x999580, 0xa6a28c, 0xe8e4cf, 0xfefbec,
0xd73737, 0xb65611, 0xae9513, 0x60ac39,
0x1fad83, 0x6684e1, 0xb854d4, 0xd43552,
]);
Base16Theme.add("atelier-estuary-light", "Atelier Estuary Light", [
0xf4f3ec, 0xe7e6df, 0x929181, 0x878573,
0x6c6b5a, 0x5f5e4e, 0x302f27, 0x22221b,
0xba6236, 0xae7313, 0xa5980d, 0x7d9726,
0x5b9d48, 0x36a166, 0x5f9182, 0x9d6c7c,
]);
Base16Theme.add("atelier-estuary", "Atelier Estuary", [
0x22221b, 0x302f27, 0x5f5e4e, 0x6c6b5a,
0x878573, 0x929181, 0xe7e6df, 0xf4f3ec,
0xba6236, 0xae7313, 0xa5980d, 0x7d9726,
0x5b9d48, 0x36a166, 0x5f9182, 0x9d6c7c,
]);
Base16Theme.add("atelier-forest-light", "Atelier Forest Light", [
0xf1efee, 0xe6e2e0, 0xa8a19f, 0x9c9491,
0x766e6b, 0x68615e, 0x2c2421, 0x1b1918,
0xf22c40, 0xdf5320, 0xc38418, 0x7b9726,
0x3d97b8, 0x407ee7, 0x6666ea, 0xc33ff3,
]);
Base16Theme.add("atelier-forest", "Atelier Forest", [
0x1b1918, 0x2c2421, 0x68615e, 0x766e6b,
0x9c9491, 0xa8a19f, 0xe6e2e0, 0xf1efee,
0xf22c40, 0xdf5320, 0xc38418, 0x7b9726,
0x3d97b8, 0x407ee7, 0x6666ea, 0xc33ff3,
]);
Base16Theme.add("atelier-heath-light", "Atelier Heath Light", [
0xf7f3f7, 0xd8cad8, 0xab9bab, 0x9e8f9e,
0x776977, 0x695d69, 0x292329, 0x1b181b,
0xca402b, 0xa65926, 0xbb8a35, 0x918b3b,
0x159393, 0x516aec, 0x7b59c0, 0xcc33cc,
]);
Base16Theme.add("atelier-heath", "Atelier Heath", [
0x1b181b, 0x292329, 0x695d69, 0x776977,
0x9e8f9e, 0xab9bab, 0xd8cad8, 0xf7f3f7,
0xca402b, 0xa65926, 0xbb8a35, 0x918b3b,
0x159393, 0x516aec, 0x7b59c0, 0xcc33cc,
]);
Base16Theme.add("atelier-lakeside-light", "Atelier Lakeside Light", [
0xebf8ff, 0xc1e4f6, 0x7ea2b4, 0x7195a8,
0x5a7b8c, 0x516d7b, 0x1f292e, 0x161b1d,
0xd22d72, 0x935c25, 0x8a8a0f, 0x568c3b,
0x2d8f6f, 0x257fad, 0x6b6bb8, 0xb72dd2,
]);
Base16Theme.add("atelier-lakeside", "Atelier Lakeside", [
0x161b1d, 0x1f292e, 0x516d7b, 0x5a7b8c,
0x7195a8, 0x7ea2b4, 0xc1e4f6, 0xebf8ff,
0xd22d72, 0x935c25, 0x8a8a0f, 0x568c3b,
0x2d8f6f, 0x257fad, 0x6b6bb8, 0xb72dd2,
]);
Base16Theme.add("atelier-plateau-light", "Atelier Plateau Light", [
0xf4ecec, 0xe7dfdf, 0x8a8585, 0x7e7777,
0x655d5d, 0x585050, 0x292424, 0x1b1818,
0xca4949, 0xb45a3c, 0xa06e3b, 0x4b8b8b,
0x5485b6, 0x7272ca, 0x8464c4, 0xbd5187,
]);
Base16Theme.add("atelier-plateau", "Atelier Plateau", [
0x1b1818, 0x292424, 0x585050, 0x655d5d,
0x7e7777, 0x8a8585, 0xe7dfdf, 0xf4ecec,
0xca4949, 0xb45a3c, 0xa06e3b, 0x4b8b8b,
0x5485b6, 0x7272ca, 0x8464c4, 0xbd5187,
]);
Base16Theme.add("atelier-savanna-light", "Atelier Savanna Light", [
0xecf4ee, 0xdfe7e2, 0x87928a, 0x78877d,
0x5f6d64, 0x526057, 0x232a25, 0x171c19,
0xb16139, 0x9f713c, 0xa07e3b, 0x489963,
0x1c9aa0, 0x478c90, 0x55859b, 0x867469,
]);
Base16Theme.add("atelier-savanna", "Atelier Savanna", [
0x171c19, 0x232a25, 0x526057, 0x5f6d64,
0x78877d, 0x87928a, 0xdfe7e2, 0xecf4ee,
0xb16139, 0x9f713c, 0xa07e3b, 0x489963,
0x1c9aa0, 0x478c90, 0x55859b, 0x867469,
]);
Base16Theme.add("atelier-seaside-light", "Atelier Seaside Light", [
0xf4fbf4, 0xcfe8cf, 0x8ca68c, 0x809980,
0x687d68, 0x5e6e5e, 0x242924, 0x131513,
0xe6193c, 0x87711d, 0x98981b, 0x29a329,
0x1999b3, 0x3d62f5, 0xad2bee, 0xe619c3,
]);
Base16Theme.add("atelier-seaside", "Atelier Seaside", [
0x131513, 0x242924, 0x5e6e5e, 0x687d68,
0x809980, 0x8ca68c, 0xcfe8cf, 0xf4fbf4,
0xe6193c, 0x87711d, 0x98981b, 0x29a329,
0x1999b3, 0x3d62f5, 0xad2bee, 0xe619c3,
]);
Base16Theme.add("atelier-sulphurpool-light", "Atelier Sulphurpool Light", [
0xf5f7ff, 0xdfe2f1, 0x979db4, 0x898ea4,
0x6b7394, 0x5e6687, 0x293256, 0x202746,
0xc94922, 0xc76b29, 0xc08b30, 0xac9739,
0x22a2c9, 0x3d8fd1, 0x6679cc, 0x9c637a,
]);
Base16Theme.add("atelier-sulphurpool", "Atelier Sulphurpool", [
0x202746, 0x293256, 0x5e6687, 0x6b7394,
0x898ea4, 0x979db4, 0xdfe2f1, 0xf5f7ff,
0xc94922, 0xc76b29, 0xc08b30, 0xac9739,
0x22a2c9, 0x3d8fd1, 0x6679cc, 0x9c637a,
]);
Base16Theme.add("atlas", "Atlas", [
0x002635, 0x00384d, 0x517F8D, 0x6C8B91,
0x869696, 0xa1a19a, 0xe6e6dc, 0xfafaf8,
0xff5a67, 0xf08e48, 0xffcc1b, 0x7fc06e,
0x14747e, 0x5dd7b9, 0x9a70a4, 0xc43060,
]);
Base16Theme.add("bespin", "Bespin", [
0x28211c, 0x36312e, 0x5e5d5c, 0x666666,
0x797977, 0x8a8986, 0x9d9b97, 0xbaae9e,
0xcf6a4c, 0xcf7d34, 0xf9ee98, 0x54be0d,
0xafc4db, 0x5ea6ea, 0x9b859d, 0x937121,
]);
Base16Theme.add("black-metal-bathory", "Black Metal (Bathory)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0xe78a53, 0xfbcb97,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-burzum", "Black Metal (Burzum)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x99bbaa, 0xddeecc,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-dark-funeral", "Black Metal (Dark Funeral)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x5f81a5, 0xd0dfee,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-gorgoroth", "Black Metal (Gorgoroth)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x8c7f70, 0x9b8d7f,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-immortal", "Black Metal (Immortal)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x556677, 0x7799bb,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-khold", "Black Metal (Khold)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x974b46, 0xeceee3,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-marduk", "Black Metal (Marduk)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x626b67, 0xa5aaa7,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-mayhem", "Black Metal (Mayhem)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0xeecc6c, 0xf3ecd4,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-nile", "Black Metal (Nile)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x777755, 0xaa9988,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-venom", "Black Metal (Venom)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x79241f, 0xf8f7f2,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal", "Black Metal", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0xa06666, 0xdd9999,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("brewer", "Brewer", [
0x0c0d0e, 0x2e2f30, 0x515253, 0x737475,
0x959697, 0xb7b8b9, 0xdadbdc, 0xfcfdfe,
0xe31a1c, 0xe6550d, 0xdca060, 0x31a354,
0x80b1d3, 0x3182bd, 0x756bb1, 0xb15928,
]);
Base16Theme.add("bright", "Bright", [
0x000000, 0x303030, 0x505050, 0xb0b0b0,
0xd0d0d0, 0xe0e0e0, 0xf5f5f5, 0xffffff,
0xfb0120, 0xfc6d24, 0xfda331, 0xa1c659,
0x76c7b7, 0x6fb3d2, 0xd381c3, 0xbe643c,
]);
Base16Theme.add("brogrammer", "Brogrammer", [
0x1f1f1f, 0xf81118, 0x2dc55e, 0xecba0f,
0x2a84d2, 0x4e5ab7, 0x1081d6, 0xd6dbe5,
0xd6dbe5, 0xde352e, 0x1dd361, 0xf3bd09,
0x1081d6, 0x5350b9, 0x0f7ddb, 0xffffff,
]);
Base16Theme.add("brushtrees-dark", "Brush Trees Dark", [
0x485867, 0x5A6D7A, 0x6D828E, 0x8299A1,
0x98AFB5, 0xB0C5C8, 0xC9DBDC, 0xE3EFEF,
0xb38686, 0xd8bba2, 0xaab386, 0x87b386,
0x86b3b3, 0x868cb3, 0xb386b2, 0xb39f9f,
]);
Base16Theme.add("brushtrees", "Brush Trees", [
0xE3EFEF, 0xC9DBDC, 0xB0C5C8, 0x98AFB5,
0x8299A1, 0x6D828E, 0x5A6D7A, 0x485867,
0xb38686, 0xd8bba2, 0xaab386, 0x87b386,
0x86b3b3, 0x868cb3, 0xb386b2, 0xb39f9f,
]);
Base16Theme.add("chalk", "Chalk", [
0x151515, 0x202020, 0x303030, 0x505050,
0xb0b0b0, 0xd0d0d0, 0xe0e0e0, 0xf5f5f5,
0xfb9fb1, 0xeda987, 0xddb26f, 0xacc267,
0x12cfc0, 0x6fc2ef, 0xe1a3ee, 0xdeaf8f,
]);
Base16Theme.add("circus", "Circus", [
0x191919, 0x202020, 0x303030, 0x5f5a60,
0x505050, 0xa7a7a7, 0x808080, 0xffffff,
0xdc657d, 0x4bb1a7, 0xc3ba63, 0x84b97c,
0x4bb1a7, 0x639ee4, 0xb888e2, 0xb888e2,
]);
Base16Theme.add("classic-dark", "Classic Dark", [
0x151515, 0x202020, 0x303030, 0x505050,
0xB0B0B0, 0xD0D0D0, 0xE0E0E0, 0xF5F5F5,
0xAC4142, 0xD28445, 0xF4BF75, 0x90A959,
0x75B5AA, 0x6A9FB5, 0xAA759F, 0x8F5536,
]);
Base16Theme.add("classic-light", "Classic Light", [
0xF5F5F5, 0xE0E0E0, 0xD0D0D0, 0xB0B0B0,
0x505050, 0x303030, 0x202020, 0x151515,
0xAC4142, 0xD28445, 0xF4BF75, 0x90A959,
0x75B5AA, 0x6A9FB5, 0xAA759F, 0x8F5536,
]);
Base16Theme.add("codeschool", "Codeschool", [
0x232c31, 0x1c3657, 0x2a343a, 0x3f4944,
0x84898c, 0x9ea7a6, 0xa7cfa3, 0xb5d8f6,
0x2a5491, 0x43820d, 0xa03b1e, 0x237986,
0xb02f30, 0x484d79, 0xc59820, 0xc98344,
]);
Base16Theme.add("cupcake", "Cupcake", [
0xfbf1f2, 0xf2f1f4, 0xd8d5dd, 0xbfb9c6,
0xa59daf, 0x8b8198, 0x72677E, 0x585062,
0xD57E85, 0xEBB790, 0xDCB16C, 0xA3B367,
0x69A9A7, 0x7297B9, 0xBB99B4, 0xBAA58C,
]);
Base16Theme.add("cupertino", "Cupertino", [
0xffffff, 0xc0c0c0, 0xc0c0c0, 0x808080,
0x808080, 0x404040, 0x404040, 0x5e5e5e,
0xc41a15, 0xeb8500, 0x826b28, 0x007400,
0x318495, 0x0000ff, 0xa90d91, 0x826b28,
]);
Base16Theme.add("darktooth", "Darktooth", [
0x1D2021, 0x32302F, 0x504945, 0x665C54,
0x928374, 0xA89984, 0xD5C4A1, 0xFDF4C1,
0xFB543F, 0xFE8625, 0xFAC03B, 0x95C085,
0x8BA59B, 0x0D6678, 0x8F4673, 0xA87322,
]);
Base16Theme.add("default-dark", "Default Dark", [
0x181818, 0x282828, 0x383838, 0x585858,
0xb8b8b8, 0xd8d8d8, 0xe8e8e8, 0xf8f8f8,
0xab4642, 0xdc9656, 0xf7ca88, 0xa1b56c,
0x86c1b9, 0x7cafc2, 0xba8baf, 0xa16946,
]);
Base16Theme.add("default-light", "Default Light", [
0xf8f8f8, 0xe8e8e8, 0xd8d8d8, 0xb8b8b8,
0x585858, 0x383838, 0x282828, 0x181818,
0xab4642, 0xdc9656, 0xf7ca88, 0xa1b56c,
0x86c1b9, 0x7cafc2, 0xba8baf, 0xa16946,
]);
Base16Theme.add("dracula", "Dracula", [
0x282936, 0x3a3c4e, 0x4d4f68, 0x626483,
0x62d6e8, 0xe9e9f4, 0xf1f2f8, 0xf7f7fb,
0xea51b2, 0xb45bcf, 0x00f769, 0xebff87,
0xa1efe4, 0x62d6e8, 0xb45bcf, 0x00f769,
]);
Base16Theme.add("eighties", "Eighties", [
0x2d2d2d, 0x393939, 0x515151, 0x747369,
0xa09f93, 0xd3d0c8, 0xe8e6df, 0xf2f0ec,
0xf2777a, 0xf99157, 0xffcc66, 0x99cc99,
0x66cccc, 0x6699cc, 0xcc99cc, 0xd27b53,
]);
Base16Theme.add("embers", "Embers", [
0x16130F, 0x2C2620, 0x433B32, 0x5A5047,
0x8A8075, 0xA39A90, 0xBEB6AE, 0xDBD6D1,
0x826D57, 0x828257, 0x6D8257, 0x57826D,
0x576D82, 0x6D5782, 0x82576D, 0x825757,
]);
Base16Theme.add("flat", "Flat", [
0x2C3E50, 0x34495E, 0x7F8C8D, 0x95A5A6,
0xBDC3C7, 0xe0e0e0, 0xf5f5f5, 0xECF0F1,
0xE74C3C, 0xE67E22, 0xF1C40F, 0x2ECC71,
0x1ABC9C, 0x3498DB, 0x9B59B6, 0xbe643c,
]);
Base16Theme.add("fruit-soda", "Fruit Soda", [
0xf1ecf1, 0xe0dee0, 0xd8d5d5, 0xb5b4b6,
0x979598, 0x515151, 0x474545, 0x2d2c2c,
0xfe3e31, 0xfe6d08, 0xf7e203, 0x47f74c,
0x0f9cfd, 0x2931df, 0x611fce, 0xb16f40,
]);
Base16Theme.add("github", "Github", [
0xffffff, 0xf5f5f5, 0xc8c8fa, 0x969896,
0xe8e8e8, 0x333333, 0xffffff, 0xffffff,
0xed6a43, 0x0086b3, 0x795da3, 0x183691,
0x183691, 0x795da3, 0xa71d5d, 0x333333,
]);
Base16Theme.add("google-dark", "Google Dark", [
0x1d1f21, 0x282a2e, 0x373b41, 0x969896,
0xb4b7b4, 0xc5c8c6, 0xe0e0e0, 0xffffff,
0xCC342B, 0xF96A38, 0xFBA922, 0x198844,
0x3971ED, 0x3971ED, 0xA36AC7, 0x3971ED,
]);
Base16Theme.add("google-light", "Google Light", [
0xffffff, 0xe0e0e0, 0xc5c8c6, 0xb4b7b4,
0x969896, 0x373b41, 0x282a2e, 0x1d1f21,
0xCC342B, 0xF96A38, 0xFBA922, 0x198844,
0x3971ED, 0x3971ED, 0xA36AC7, 0x3971ED,
]);
Base16Theme.add("grayscale-dark", "Grayscale Dark", [
0x101010, 0x252525, 0x464646, 0x525252,
0xababab, 0xb9b9b9, 0xe3e3e3, 0xf7f7f7,
0x7c7c7c, 0x999999, 0xa0a0a0, 0x8e8e8e,
0x868686, 0x686868, 0x747474, 0x5e5e5e,
]);
Base16Theme.add("grayscale-light", "Grayscale Light", [
0xf7f7f7, 0xe3e3e3, 0xb9b9b9, 0xababab,
0x525252, 0x464646, 0x252525, 0x101010,
0x7c7c7c, 0x999999, 0xa0a0a0, 0x8e8e8e,
0x868686, 0x686868, 0x747474, 0x5e5e5e,
]);
Base16Theme.add("greenscreen", "Green Screen", [
0x001100, 0x003300, 0x005500, 0x007700,
0x009900, 0x00bb00, 0x00dd00, 0x00ff00,
0x007700, 0x009900, 0x007700, 0x00bb00,
0x005500, 0x009900, 0x00bb00, 0x005500,
]);
Base16Theme.add("gruvbox-dark-hard", "Gruvbox dark, hard", [
0x1d2021, 0x3c3836, 0x504945, 0x665c54,
0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26,
0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
]);
Base16Theme.add("gruvbox-dark-medium", "Gruvbox dark, medium", [
0x282828, 0x3c3836, 0x504945, 0x665c54,
0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26,
0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
]);
Base16Theme.add("gruvbox-dark-pale", "Gruvbox dark, pale", [
0x262626, 0x3a3a3a, 0x4e4e4e, 0x8a8a8a,
0x949494, 0xdab997, 0xd5c4a1, 0xebdbb2,
0xd75f5f, 0xff8700, 0xffaf00, 0xafaf00,
0x85ad85, 0x83adad, 0xd485ad, 0xd65d0e,
]);
Base16Theme.add("gruvbox-dark-soft", "Gruvbox dark, soft", [
0x32302f, 0x3c3836, 0x504945, 0x665c54,
0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26,
0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
]);
Base16Theme.add("gruvbox-light-hard", "Gruvbox light, hard", [
0xf9f5d7, 0xebdbb2, 0xd5c4a1, 0xbdae93,
0x665c54, 0x504945, 0x3c3836, 0x282828,
0x9d0006, 0xaf3a03, 0xb57614, 0x79740e,
0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
]);
Base16Theme.add("gruvbox-light-medium", "Gruvbox light, medium", [
0xfbf1c7, 0xebdbb2, 0xd5c4a1, 0xbdae93,
0x665c54, 0x504945, 0x3c3836, 0x282828,
0x9d0006, 0xaf3a03, 0xb57614, 0x79740e,
0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
]);
Base16Theme.add("gruvbox-light-soft", "Gruvbox light, soft", [
0xf2e5bc, 0xebdbb2, 0xd5c4a1, 0xbdae93,
0x665c54, 0x504945, 0x3c3836, 0x282828,
0x9d0006, 0xaf3a03, 0xb57614, 0x79740e,
0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
]);
Base16Theme.add("harmonic-dark", "Harmonic16 Dark", [
0x0b1c2c, 0x223b54, 0x405c79, 0x627e99,
0xaabcce, 0xcbd6e2, 0xe5ebf1, 0xf7f9fb,
0xbf8b56, 0xbfbf56, 0x8bbf56, 0x56bf8b,
0x568bbf, 0x8b56bf, 0xbf568b, 0xbf5656,
]);
Base16Theme.add("harmonic-light", "Harmonic16 Light", [
0xf7f9fb, 0xe5ebf1, 0xcbd6e2, 0xaabcce,
0x627e99, 0x405c79, 0x223b54, 0x0b1c2c,
0xbf8b56, 0xbfbf56, 0x8bbf56, 0x56bf8b,
0x568bbf, 0x8b56bf, 0xbf568b, 0xbf5656,
]);
Base16Theme.add("heetch-light", "Heetch Light", [
0xfeffff, 0x392551, 0x7b6d8b, 0x9c92a8,
0xddd6e5, 0x5a496e, 0x470546, 0x190134,
0x27d9d5, 0xbdb6c5, 0x5ba2b6, 0xf80059,
0xc33678, 0x47f9f5, 0xbd0152, 0xdedae2,
]);
Base16Theme.add("heetch", "Heetch Dark", [
0x190134, 0x392551, 0x5A496E, 0x7B6D8B,
0x9C92A8, 0xBDB6C5, 0xDEDAE2, 0xFEFFFF,
0x27D9D5, 0x5BA2B6, 0x8F6C97, 0xC33678,
0xF80059, 0xBD0152, 0x82034C, 0x470546,
]);
Base16Theme.add("helios", "Helios", [
0x1d2021, 0x383c3e, 0x53585b, 0x6f7579,
0xcdcdcd, 0xd5d5d5, 0xdddddd, 0xe5e5e5,
0xd72638, 0xeb8413, 0xf19d1a, 0x88b92d,
0x1ba595, 0x1e8bac, 0xbe4264, 0xc85e0d,
]);
Base16Theme.add("hopscotch", "Hopscotch", [
0x322931, 0x433b42, 0x5c545b, 0x797379,
0x989498, 0xb9b5b8, 0xd5d3d5, 0xffffff,
0xdd464c, 0xfd8b19, 0xfdcc59, 0x8fc13e,
0x149b93, 0x1290bf, 0xc85e7c, 0xb33508,
]);
Base16Theme.add("horizon-dark", "Horizon Dark", [
0x1c1e26, 0x232530, 0x2e303e, 0x676a8d,
0xced1d0, 0xcbced0, 0xdcdfe4, 0xe3e6ee,
0xe93c58, 0xe58d7d, 0xefb993, 0xefaf8e,
0x24a8b4, 0xdf5273, 0xb072d1, 0xe4a382,
]);
Base16Theme.add("ia-dark", "iA Dark", [
0x1a1a1a, 0x222222, 0x1d414d, 0x767676,
0xb8b8b8, 0xcccccc, 0xe8e8e8, 0xf8f8f8,
0xd88568, 0xd86868, 0xb99353, 0x83a471,
0x7c9cae, 0x8eccdd, 0xb98eb2, 0x8b6c37,
]);
Base16Theme.add("ia-light", "iA Light", [
0xf6f6f6, 0xdedede, 0xbde5f2, 0x898989,
0x767676, 0x181818, 0xe8e8e8, 0xf8f8f8,
0x9c5a02, 0xc43e18, 0xc48218, 0x38781c,
0x2d6bb1, 0x48bac2, 0xa94598, 0x8b6c37,
]);
Base16Theme.add("icy", "Icy Dark", [
0x021012, 0x031619, 0x041f23, 0x052e34,
0x064048, 0x095b67, 0x0c7c8c, 0x109cb0,
0x16c1d9, 0xb3ebf2, 0x80deea, 0x4dd0e1,
0x26c6da, 0x00bcd4, 0x00acc1, 0x0097a7,
]);
Base16Theme.add("irblack", "IR Black", [
0x000000, 0x242422, 0x484844, 0x6c6c66,
0x918f88, 0xb5b3aa, 0xd9d7cc, 0xfdfbee,
0xff6c60, 0xe9c062, 0xffffb6, 0xa8ff60,
0xc6c5fe, 0x96cbfe, 0xff73fd, 0xb18a3d,
]);
Base16Theme.add("isotope", "Isotope", [
0x000000, 0x404040, 0x606060, 0x808080,
0xc0c0c0, 0xd0d0d0, 0xe0e0e0, 0xffffff,
0xff0000, 0xff9900, 0xff0099, 0x33ff00,
0x00ffff, 0x0066ff, 0xcc00ff, 0x3300ff,
]);
Base16Theme.add("macintosh", "Macintosh", [
0x000000, 0x404040, 0x404040, 0x808080,
0x808080, 0xc0c0c0, 0xc0c0c0, 0xffffff,
0xdd0907, 0xff6403, 0xfbf305, 0x1fb714,
0x02abea, 0x0000d3, 0x4700a5, 0x90713a,
]);
Base16Theme.add("marrakesh", "Marrakesh", [
0x201602, 0x302e00, 0x5f5b17, 0x6c6823,
0x86813b, 0x948e48, 0xccc37a, 0xfaf0a5,
0xc35359, 0xb36144, 0xa88339, 0x18974e,
0x75a738, 0x477ca1, 0x8868b3, 0xb3588e,
]);
Base16Theme.add("materia", "Materia", [
0x263238, 0x2C393F, 0x37474F, 0x707880,
0xC9CCD3, 0xCDD3DE, 0xD5DBE5, 0xFFFFFF,
0xEC5F67, 0xEA9560, 0xFFCC00, 0x8BD649,
0x80CBC4, 0x89DDFF, 0x82AAFF, 0xEC5F67,
]);
Base16Theme.add("material-darker", "Material Darker", [
0x212121, 0x303030, 0x353535, 0x4A4A4A,
0xB2CCD6, 0xEEFFFF, 0xEEFFFF, 0xFFFFFF,
0xF07178, 0xF78C6C, 0xFFCB6B, 0xC3E88D,
0x89DDFF, 0x82AAFF, 0xC792EA, 0xFF5370,
]);
Base16Theme.add("material-lighter", "Material Lighter", [
0xFAFAFA, 0xE7EAEC, 0xCCEAE7, 0xCCD7DA,
0x8796B0, 0x80CBC4, 0x80CBC4, 0xFFFFFF,
0xFF5370, 0xF76D47, 0xFFB62C, 0x91B859,
0x39ADB5, 0x6182B8, 0x7C4DFF, 0xE53935,
]);
Base16Theme.add("material-palenight", "Material Palenight", [
0x292D3E, 0x444267, 0x32374D, 0x676E95,
0x8796B0, 0x959DCB, 0x959DCB, 0xFFFFFF,
0xF07178, 0xF78C6C, 0xFFCB6B, 0xC3E88D,
0x89DDFF, 0x82AAFF, 0xC792EA, 0xFF5370,
]);
Base16Theme.add("material-vivid", "Material Vivid", [
0x202124, 0x27292c, 0x323639, 0x44464d,
0x676c71, 0x80868b, 0x9e9e9e, 0xffffff,
0xf44336, 0xff9800, 0xffeb3b, 0x00e676,
0x00bcd4, 0x2196f3, 0x673ab7, 0x8d6e63,
]);
Base16Theme.add("material", "Material", [
0x263238, 0x2E3C43, 0x314549, 0x546E7A,
0xB2CCD6, 0xEEFFFF, 0xEEFFFF, 0xFFFFFF,
0xF07178, 0xF78C6C, 0xFFCB6B, 0xC3E88D,
0x89DDFF, 0x82AAFF, 0xC792EA, 0xFF5370,
]);
Base16Theme.add("mellow-purple", "Mellow Purple", [
0x1e0528, 0x1A092D, 0x331354, 0x320f55,
0x873582, 0xffeeff, 0xffeeff, 0xf8c0ff,
0x00d9e9, 0xaa00a3, 0x955ae7, 0x05cb0d,
0xb900b1, 0x550068, 0x8991bb, 0x4d6fff,
]);
Base16Theme.add("mexico-light", "Mexico Light", [
0xf8f8f8, 0xe8e8e8, 0xd8d8d8, 0xb8b8b8,
0x585858, 0x383838, 0x282828, 0x181818,
0xab4642, 0xdc9656, 0xf79a0e, 0x538947,
0x4b8093, 0x7cafc2, 0x96609e, 0xa16946,
]);
Base16Theme.add("mocha", "Mocha", [
0x3B3228, 0x534636, 0x645240, 0x7e705a,
0xb8afad, 0xd0c8c6, 0xe9e1dd, 0xf5eeeb,
0xcb6077, 0xd28b71, 0xf4bc87, 0xbeb55b,
0x7bbda4, 0x8ab3b5, 0xa89bb9, 0xbb9584,
]);
Base16Theme.add("monokai", "Monokai", [
0x272822, 0x383830, 0x49483e, 0x75715e,
0xa59f85, 0xf8f8f2, 0xf5f4f1, 0xf9f8f5,
0xf92672, 0xfd971f, 0xf4bf75, 0xa6e22e,
0xa1efe4, 0x66d9ef, 0xae81ff, 0xcc6633,
]);
Base16Theme.add("nord", "Nord", [
0x2E3440, 0x3B4252, 0x434C5E, 0x4C566A,
0xD8DEE9, 0xE5E9F0, 0xECEFF4, 0x8FBCBB,
0x88C0D0, 0x81A1C1, 0x5E81AC, 0xBF616A,
0xD08770, 0xEBCB8B, 0xA3BE8C, 0xB48EAD,
]);
Base16Theme.add("ocean", "Ocean", [
0x2b303b, 0x343d46, 0x4f5b66, 0x65737e,
0xa7adba, 0xc0c5ce, 0xdfe1e8, 0xeff1f5,
0xbf616a, 0xd08770, 0xebcb8b, 0xa3be8c,
0x96b5b4, 0x8fa1b3, 0xb48ead, 0xab7967,
]);
Base16Theme.add("oceanicnext", "OceanicNext", [
0x1B2B34, 0x343D46, 0x4F5B66, 0x65737E,
0xA7ADBA, 0xC0C5CE, 0xCDD3DE, 0xD8DEE9,
0xEC5F67, 0xF99157, 0xFAC863, 0x99C794,
0x5FB3B3, 0x6699CC, 0xC594C5, 0xAB7967,
]);
Base16Theme.add("one-light", "One Light", [
0xfafafa, 0xf0f0f1, 0xe5e5e6, 0xa0a1a7,
0x696c77, 0x383a42, 0x202227, 0x090a0b,
0xca1243, 0xd75f00, 0xc18401, 0x50a14f,
0x0184bc, 0x4078f2, 0xa626a4, 0x986801,
]);
Base16Theme.add("onedark", "OneDark", [
0x282c34, 0x353b45, 0x3e4451, 0x545862,
0x565c64, 0xabb2bf, 0xb6bdca, 0xc8ccd4,
0xe06c75, 0xd19a66, 0xe5c07b, 0x98c379,
0x56b6c2, 0x61afef, 0xc678dd, 0xbe5046,
]);
Base16Theme.add("outrun-dark", "Outrun Dark", [
0x00002A, 0x20204A, 0x30305A, 0x50507A,
0xB0B0DA, 0xD0D0FA, 0xE0E0FF, 0xF5F5FF,
0xFF4242, 0xFC8D28, 0xF3E877, 0x59F176,
0x0EF0F0, 0x66B0FF, 0xF10596, 0xF003EF,
]);
Base16Theme.add("papercolor-dark", "PaperColor Dark", [
0x1c1c1c, 0xaf005f, 0x5faf00, 0xd7af5f,
0x5fafd7, 0x808080, 0xd7875f, 0xd0d0d0,
0x585858, 0x5faf5f, 0xafd700, 0xaf87d7,
0xffaf00, 0xff5faf, 0x00afaf, 0x5f8787,
]);
Base16Theme.add("papercolor-light", "PaperColor Light", [
0xeeeeee, 0xaf0000, 0x008700, 0x5f8700,
0x0087af, 0x878787, 0x005f87, 0x444444,
0xbcbcbc, 0xd70000, 0xd70087, 0x8700af,
0xd75f00, 0xd75f00, 0x005faf, 0x005f87,
]);
Base16Theme.add("paraiso", "Paraiso", [
0x2f1e2e, 0x41323f, 0x4f424c, 0x776e71,
0x8d8687, 0xa39e9b, 0xb9b6b0, 0xe7e9db,
0xef6155, 0xf99b15, 0xfec418, 0x48b685,
0x5bc4bf, 0x06b6ef, 0x815ba4, 0xe96ba8,
]);
Base16Theme.add("phd", "PhD", [
0x061229, 0x2a3448, 0x4d5666, 0x717885,
0x9a99a3, 0xb8bbc2, 0xdbdde0, 0xffffff,
0xd07346, 0xf0a000, 0xfbd461, 0x99bf52,
0x72b9bf, 0x5299bf, 0x9989cc, 0xb08060,
]);
Base16Theme.add("pico", "Pico", [
0x000000, 0x1d2b53, 0x7e2553, 0x008751,
0xab5236, 0x5f574f, 0xc2c3c7, 0xfff1e8,
0xff004d, 0xffa300, 0xfff024, 0x00e756,
0x29adff, 0x83769c, 0xff77a8, 0xffccaa,
]);
Base16Theme.add("pop", "Pop", [
0x000000, 0x202020, 0x303030, 0x505050,
0xb0b0b0, 0xd0d0d0, 0xe0e0e0, 0xffffff,
0xeb008a, 0xf29333, 0xf8ca12, 0x37b349,
0x00aabb, 0x0e5a94, 0xb31e8d, 0x7a2d00,
]);
Base16Theme.add("porple", "Porple", [
0x292c36, 0x333344, 0x474160, 0x65568a,
0xb8b8b8, 0xd8d8d8, 0xe8e8e8, 0xf8f8f8,
0xf84547, 0xd28e5d, 0xefa16b, 0x95c76f,
0x64878f, 0x8485ce, 0xb74989, 0x986841,
]);
Base16Theme.add("qualia", "Qualia", [
0x101010, 0x454545, 0x454545, 0x454545,
0x808080, 0xc0c0c0, 0xc0c0c0, 0x454545,
0xefa6a2, 0xa3b8ef, 0xe6a3dc, 0x80c990,
0xc8c874, 0x50cacd, 0xe0af85, 0x808080,
]);
Base16Theme.add("railscasts", "Railscasts", [
0x2b2b2b, 0x272935, 0x3a4055, 0x5a647e,
0xd4cfc9, 0xe6e1dc, 0xf4f1ed, 0xf9f7f3,
0xda4939, 0xcc7833, 0xffc66d, 0xa5c261,
0x519f50, 0x6d9cbe, 0xb6b3eb, 0xbc9458,
]);
Base16Theme.add("rebecca", "Rebecca", [
0x292a44, 0x663399, 0x383a62, 0x666699,
0xa0a0c5, 0xf1eff8, 0xccccff, 0x53495d,
0xa0a0c5, 0xefe4a1, 0xae81ff, 0x6dfedf,
0x8eaee0, 0x2de0a7, 0x7aa5ff, 0xff79c6,
]);
Base16Theme.add("seti", "Seti UI", [
0x151718, 0x282a2b, 0x3B758C, 0x41535B,
0x43a5d5, 0xd6d6d6, 0xeeeeee, 0xffffff,
0xcd3f45, 0xdb7b55, 0xe6cd69, 0x9fca56,
0x55dbbe, 0x55b5db, 0xa074c4, 0x8a553f,
]);
Base16Theme.add("shapeshifter", "Shapeshifter", [
0xf9f9f9, 0xe0e0e0, 0xababab, 0x555555,
0x343434, 0x102015, 0x040404, 0x000000,
0xe92f2f, 0xe09448, 0xdddd13, 0x0ed839,
0x23edda, 0x3b48e3, 0xf996e2, 0x69542d,
]);
Base16Theme.add("snazzy", "Snazzy", [
0x282a36, 0x34353e, 0x43454f, 0x78787e,
0xa5a5a9, 0xe2e4e5, 0xeff0eb, 0xf1f1f0,
0xff5c57, 0xff9f43, 0xf3f99d, 0x5af78e,
0x9aedfe, 0x57c7ff, 0xff6ac1, 0xb2643c,
]);
Base16Theme.add("solarflare", "Solar Flare", [
0x18262F, 0x222E38, 0x586875, 0x667581,
0x85939E, 0xA6AFB8, 0xE8E9ED, 0xF5F7FA,
0xEF5253, 0xE66B2B, 0xE4B51C, 0x7CC844,
0x52CBB0, 0x33B5E1, 0xA363D5, 0xD73C9A,
]);
Base16Theme.add("solarized-dark", "Solarized Dark", [
0x002b36, 0x073642, 0x586e75, 0x657b83,
0x839496, 0x93a1a1, 0xeee8d5, 0xfdf6e3,
0xdc322f, 0xcb4b16, 0xb58900, 0x859900,
0x2aa198, 0x268bd2, 0x6c71c4, 0xd33682,
]);
Base16Theme.add("solarized-light", "Solarized Light", [
0xfdf6e3, 0xeee8d5, 0x93a1a1, 0x839496,
0x657b83, 0x586e75, 0x073642, 0x002b36,
0xdc322f, 0xcb4b16, 0xb58900, 0x859900,
0x2aa198, 0x268bd2, 0x6c71c4, 0xd33682,
]);
Base16Theme.add("spacemacs", "Spacemacs", [
0x1f2022, 0x282828, 0x444155, 0x585858,
0xb8b8b8, 0xa3a3a3, 0xe8e8e8, 0xf8f8f8,
0xf2241f, 0xffa500, 0xb1951d, 0x67b11d,
0x2d9574, 0x4f97d7, 0xa31db1, 0xb03060,
]);
Base16Theme.add("summerfruit-dark", "Summerfruit Dark", [
0x151515, 0x202020, 0x303030, 0x505050,
0xB0B0B0, 0xD0D0D0, 0xE0E0E0, 0xFFFFFF,
0xFF0086, 0xFD8900, 0xABA800, 0x00C918,
0x1FAAAA, 0x3777E6, 0xAD00A1, 0xCC6633,
]);
Base16Theme.add("summerfruit-light", "Summerfruit Light", [
0xFFFFFF, 0xE0E0E0, 0xD0D0D0, 0xB0B0B0,
0x000000, 0x101010, 0x151515, 0x202020,
0xFF0086, 0xFD8900, 0xABA800, 0x00C918,
0x1FAAAA, 0x3777E6, 0xAD00A1, 0xCC6633,
]);
Base16Theme.add("synth-midnight-dark", "Synth Midnight", [
0x040404, 0x141414, 0x242424, 0x61507A,
0xBFBBBF, 0xDFDBDF, 0xEFEBEF, 0xFFFBFF,
0xB53B50, 0xE4600E, 0xDAE84D, 0x06EA61,
0x7CEDE9, 0x03AEFF, 0xEA5CE2, 0x9D4D0E,
]);
Base16Theme.add("tomorrow-night-eighties", "Tomorrow Night Eighties", [
0x2d2d2d, 0x393939, 0x515151, 0x999999,
0xb4b7b4, 0xcccccc, 0xe0e0e0, 0xffffff,
0xf2777a, 0xf99157, 0xffcc66, 0x99cc99,
0x66cccc, 0x6699cc, 0xcc99cc, 0xa3685a,
]);
Base16Theme.add("tomorrow-night", "Tomorrow Night", [
0x1d1f21, 0x282a2e, 0x373b41, 0x969896,
0xb4b7b4, 0xc5c8c6, 0xe0e0e0, 0xffffff,
0xcc6666, 0xde935f, 0xf0c674, 0xb5bd68,
0x8abeb7, 0x81a2be, 0xb294bb, 0xa3685a,
]);
Base16Theme.add("tomorrow", "Tomorrow", [
0xffffff, 0xe0e0e0, 0xd6d6d6, 0x8e908c,
0x969896, 0x4d4d4c, 0x282a2e, 0x1d1f21,
0xc82829, 0xf5871f, 0xeab700, 0x718c00,
0x3e999f, 0x4271ae, 0x8959a8, 0xa3685a,
]);
Base16Theme.add("tube", "London Tube", [
0x231f20, 0x1c3f95, 0x5a5758, 0x737171,
0x959ca1, 0xd9d8d8, 0xe7e7e8, 0xffffff,
0xee2e24, 0xf386a1, 0xffd204, 0x00853e,
0x85cebc, 0x009ddc, 0x98005d, 0xb06110,
]);
Base16Theme.add("twilight", "Twilight", [
0x1e1e1e, 0x323537, 0x464b50, 0x5f5a60,
0x838184, 0xa7a7a7, 0xc3c3c3, 0xffffff,
0xcf6a4c, 0xcda869, 0xf9ee98, 0x8f9d6a,
0xafc4db, 0x7587a6, 0x9b859d, 0x9b703f,
]);
Base16Theme.add("unikitty-dark", "Unikitty Dark", [
0x2e2a31, 0x4a464d, 0x666369, 0x838085,
0x9f9da2, 0xbcbabe, 0xd8d7da, 0xf5f4f7,
0xd8137f, 0xd65407, 0xdc8a0e, 0x17ad98,
0x149bda, 0x796af5, 0xbb60ea, 0xc720ca,
]);
Base16Theme.add("unikitty-light", "Unikitty Light", [
0xffffff, 0xe1e1e2, 0xc4c3c5, 0xa7a5a8,
0x89878b, 0x6c696e, 0x4f4b51, 0x322d34,
0xd8137f, 0xd65407, 0xdc8a0e, 0x17ad98,
0x149bda, 0x775dff, 0xaa17e6, 0xe013d0,
]);
Base16Theme.add("woodland", "Woodland", [
0x231e18, 0x302b25, 0x48413a, 0x9d8b70,
0xb4a490, 0xcabcb1, 0xd7c8bc, 0xe4d4c8,
0xd35c5c, 0xca7f32, 0xe0ac16, 0xb7ba53,
0x6eb958, 0x88a4d3, 0xbb90e2, 0xb49368,
]);
Base16Theme.add("xcode-dusk", "XCode Dusk", [
0x282B35, 0x3D4048, 0x53555D, 0x686A71,
0x7E8086, 0x939599, 0xA9AAAE, 0xBEBFC2,
0xB21889, 0x786DC5, 0x438288, 0xDF0002,
0x00A0BE, 0x790EAD, 0xB21889, 0xC77C48,
]);
Base16Theme.add("zenburn", "Zenburn", [
0x383838, 0x404040, 0x606060, 0x6f6f6f,
0x808080, 0xdcdccc, 0xc0c0c0, 0xffffff,
0xdca3a3, 0xdfaf8f, 0xe0cf9f, 0x5f7f5f,
0x93e0e3, 0x7cb8bb, 0xdc8cc3, 0x000000,
]);

View File

@ -0,0 +1,111 @@
var _a;
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk?version=4.0';
import { Base16Theme } from './Base16Theme.js';
export class ColorSchemeModel {
constructor() {
let builder = new TreeModelBuilder();
this.treeModel = builder.buildTreeModel();
this.selection = Gtk.SingleSelection.new(this.treeModel);
}
}
class ColorModelNode extends GObject.Object {
constructor(data) {
super();
this.data = data;
}
get text() {
return this.data.name;
}
child_model() {
if (this.data instanceof ColorThemeCategory) {
return this.data.children;
}
else {
return null;
}
}
isThemeWithId(id) {
return (this.data instanceof Base16Theme) &&
(this.data.id === id);
}
hasChildThemeWithId(id) {
const child_model = this.child_model();
if (child_model) {
for (let i = 0; i < child_model.n_items; i++) {
let node = child_model.get_item(i);
if (node.isThemeWithId(id)) {
return true;
}
}
}
return false;
}
static newScheme(color) {
return new _a(color);
}
static newCategory(category) {
return new _a(category);
}
}
_a = ColorModelNode;
(() => {
GObject.registerClass({
GTypeName: 'ColorModelNode',
Properties: {
'text': GObject.ParamSpec.string('text', '', '', GObject.ParamFlags.READWRITE, ''),
}
}, _a);
})();
class ColorThemeCategory {
constructor(name) {
this.name = name;
this.children = new Gio.ListStore(ColorModelNode);
}
append(color) {
this.children.append(ColorModelNode.newScheme(color));
}
}
class TreeModelBuilder {
constructor() {
this.categories = Base16Theme.CATEGORIES;
this.currentCategory = null;
this.storeModel = new Gio.ListStore(ColorModelNode);
}
buildTreeModel() {
Base16Theme.THEMES.forEach(theme => this.addTheme(theme));
return Gtk.TreeListModel.new(this.storeModel, false, false, item => item.child_model());
}
appendCurrentCategory() {
if (this.currentCategory) {
this.storeModel.append(ColorModelNode.newCategory(this.currentCategory));
this.currentCategory = null;
this.categories.shift();
}
}
matchesNextCategory(theme) {
if (this.categories.length === 0) {
return false;
}
let [id, _name] = this.categories[0];
return theme.id === id;
}
addTheme(theme) {
if (this.matchesNextCategory(theme)) {
this.addToCategory(theme);
return;
}
if (this.currentCategory) {
this.appendCurrentCategory();
}
this.storeModel.append(ColorModelNode.newScheme(theme));
}
addToCategory(theme) {
if (!this.currentCategory) {
let [_id, name] = this.categories[0];
this.currentCategory = new ColorThemeCategory(name);
}
this.currentCategory.append(theme);
}
}

6
js/main.js Normal file
View File

@ -0,0 +1,6 @@
import 'gi://Gdk?version=4.0';
import 'gi://Gtk?version=4.0';
import { Application } from './Application.js';
export function main(argv) {
return new Application().run(argv);
}

809
js/model/Base16Themes.js Normal file
View File

@ -0,0 +1,809 @@
const TERM_MAP = [
0x00, 0x08, 0x0B, 0x0A, 0x0D, 0x0E, 0x0C, 0x05,
0x03, 0x08, 0x0B, 0x0A, 0x0D, 0x0E, 0x0C, 0x07,
0x09, 0x0F, 0x01, 0x02, 0x04, 0x06,
];
export class Base16Theme {
constructor(id, name, colors) {
this.id = id;
this.name = name;
this.colors = colors;
}
static add(id, name, colors) {
const theme = new Base16Theme(id, name, colors);
Base16Theme.THEME_LIST.push(theme);
Base16Theme.THEMES.set(id, theme);
}
static lookup(id) {
return Base16Theme.THEMES.get(id);
}
color(idx) {
let hex = this.colors[idx].toString(16).padStart(6, '0');
return `#${hex}`;
}
terminal_background() {
return this.color(0);
}
terminal_foreground() {
return this.color(5);
}
terminal_palette_color(idx) {
return this.color(TERM_MAP[idx]);
}
}
Base16Theme.THEMES = new Map();
Base16Theme.THEME_LIST = [];
Base16Theme.add("3024", "3024", [
0x090300, 0x3a3432, 0x4a4543, 0x5c5855,
0x807d7c, 0xa5a2a2, 0xd6d5d4, 0xf7f7f7,
0xdb2d20, 0xe8bbd0, 0xfded02, 0x01a252,
0xb5e4f4, 0x01a0e4, 0xa16a94, 0xcdab53,
]);
Base16Theme.add("apathy", 'Apathy', [
0x031A16, 0x0B342D, 0x184E45, 0x2B685E,
0x5F9C92, 0x81B5AC, 0xA7CEC8, 0xD2E7E4,
0x3E9688, 0x3E7996, 0x3E4C96, 0x883E96,
0x963E4C, 0x96883E, 0x4C963E, 0x3E965B,
]);
Base16Theme.add("ashes", "Ashes", [
0x1C2023, 0x393F45, 0x565E65, 0x747C84,
0xADB3BA, 0xC7CCD1, 0xDFE2E5, 0xF3F4F5,
0xC7AE95, 0xC7C795, 0xAEC795, 0x95C7AE,
0x95AEC7, 0xAE95C7, 0xC795AE, 0xC79595,
]);
Base16Theme.add("atelier-cave-light", "Atelier Cave Light", [
0xefecf4, 0xe2dfe7, 0x8b8792, 0x7e7887,
0x655f6d, 0x585260, 0x26232a, 0x19171c,
0xbe4678, 0xaa573c, 0xa06e3b, 0x2a9292,
0x398bc6, 0x576ddb, 0x955ae7, 0xbf40bf,
]);
Base16Theme.add("atelier-cave", "Atelier Cave", [
0x19171c, 0x26232a, 0x585260, 0x655f6d,
0x7e7887, 0x8b8792, 0xe2dfe7, 0xefecf4,
0xbe4678, 0xaa573c, 0xa06e3b, 0x2a9292,
0x398bc6, 0x576ddb, 0x955ae7, 0xbf40bf,
]);
Base16Theme.add("atelier-dune-light", "Atelier Dune Light", [
0xfefbec, 0xe8e4cf, 0xa6a28c, 0x999580,
0x7d7a68, 0x6e6b5e, 0x292824, 0x20201d,
0xd73737, 0xb65611, 0xae9513, 0x60ac39,
0x1fad83, 0x6684e1, 0xb854d4, 0xd43552,
]);
Base16Theme.add("atelier-dune", "Atelier Dune", [
0x20201d, 0x292824, 0x6e6b5e, 0x7d7a68,
0x999580, 0xa6a28c, 0xe8e4cf, 0xfefbec,
0xd73737, 0xb65611, 0xae9513, 0x60ac39,
0x1fad83, 0x6684e1, 0xb854d4, 0xd43552,
]);
Base16Theme.add("atelier-estuary-light", "Atelier Estuary Light", [
0xf4f3ec, 0xe7e6df, 0x929181, 0x878573,
0x6c6b5a, 0x5f5e4e, 0x302f27, 0x22221b,
0xba6236, 0xae7313, 0xa5980d, 0x7d9726,
0x5b9d48, 0x36a166, 0x5f9182, 0x9d6c7c,
]);
Base16Theme.add("atelier-estuary", "Atelier Estuary", [
0x22221b, 0x302f27, 0x5f5e4e, 0x6c6b5a,
0x878573, 0x929181, 0xe7e6df, 0xf4f3ec,
0xba6236, 0xae7313, 0xa5980d, 0x7d9726,
0x5b9d48, 0x36a166, 0x5f9182, 0x9d6c7c,
]);
Base16Theme.add("atelier-forest-light", "Atelier Forest Light", [
0xf1efee, 0xe6e2e0, 0xa8a19f, 0x9c9491,
0x766e6b, 0x68615e, 0x2c2421, 0x1b1918,
0xf22c40, 0xdf5320, 0xc38418, 0x7b9726,
0x3d97b8, 0x407ee7, 0x6666ea, 0xc33ff3,
]);
Base16Theme.add("atelier-forest", "Atelier Forest", [
0x1b1918, 0x2c2421, 0x68615e, 0x766e6b,
0x9c9491, 0xa8a19f, 0xe6e2e0, 0xf1efee,
0xf22c40, 0xdf5320, 0xc38418, 0x7b9726,
0x3d97b8, 0x407ee7, 0x6666ea, 0xc33ff3,
]);
Base16Theme.add("atelier-heath-light", "Atelier Heath Light", [
0xf7f3f7, 0xd8cad8, 0xab9bab, 0x9e8f9e,
0x776977, 0x695d69, 0x292329, 0x1b181b,
0xca402b, 0xa65926, 0xbb8a35, 0x918b3b,
0x159393, 0x516aec, 0x7b59c0, 0xcc33cc,
]);
Base16Theme.add("atelier-heath", "Atelier Heath", [
0x1b181b, 0x292329, 0x695d69, 0x776977,
0x9e8f9e, 0xab9bab, 0xd8cad8, 0xf7f3f7,
0xca402b, 0xa65926, 0xbb8a35, 0x918b3b,
0x159393, 0x516aec, 0x7b59c0, 0xcc33cc,
]);
Base16Theme.add("atelier-lakeside-light", "Atelier Lakeside Light", [
0xebf8ff, 0xc1e4f6, 0x7ea2b4, 0x7195a8,
0x5a7b8c, 0x516d7b, 0x1f292e, 0x161b1d,
0xd22d72, 0x935c25, 0x8a8a0f, 0x568c3b,
0x2d8f6f, 0x257fad, 0x6b6bb8, 0xb72dd2,
]);
Base16Theme.add("atelier-lakeside", "Atelier Lakeside", [
0x161b1d, 0x1f292e, 0x516d7b, 0x5a7b8c,
0x7195a8, 0x7ea2b4, 0xc1e4f6, 0xebf8ff,
0xd22d72, 0x935c25, 0x8a8a0f, 0x568c3b,
0x2d8f6f, 0x257fad, 0x6b6bb8, 0xb72dd2,
]);
Base16Theme.add("atelier-plateau-light", "Atelier Plateau Light", [
0xf4ecec, 0xe7dfdf, 0x8a8585, 0x7e7777,
0x655d5d, 0x585050, 0x292424, 0x1b1818,
0xca4949, 0xb45a3c, 0xa06e3b, 0x4b8b8b,
0x5485b6, 0x7272ca, 0x8464c4, 0xbd5187,
]);
Base16Theme.add("atelier-plateau", "Atelier Plateau", [
0x1b1818, 0x292424, 0x585050, 0x655d5d,
0x7e7777, 0x8a8585, 0xe7dfdf, 0xf4ecec,
0xca4949, 0xb45a3c, 0xa06e3b, 0x4b8b8b,
0x5485b6, 0x7272ca, 0x8464c4, 0xbd5187,
]);
Base16Theme.add("atelier-savanna-light", "Atelier Savanna Light", [
0xecf4ee, 0xdfe7e2, 0x87928a, 0x78877d,
0x5f6d64, 0x526057, 0x232a25, 0x171c19,
0xb16139, 0x9f713c, 0xa07e3b, 0x489963,
0x1c9aa0, 0x478c90, 0x55859b, 0x867469,
]);
Base16Theme.add("atelier-savanna", "Atelier Savanna", [
0x171c19, 0x232a25, 0x526057, 0x5f6d64,
0x78877d, 0x87928a, 0xdfe7e2, 0xecf4ee,
0xb16139, 0x9f713c, 0xa07e3b, 0x489963,
0x1c9aa0, 0x478c90, 0x55859b, 0x867469,
]);
Base16Theme.add("atelier-seaside-light", "Atelier Seaside Light", [
0xf4fbf4, 0xcfe8cf, 0x8ca68c, 0x809980,
0x687d68, 0x5e6e5e, 0x242924, 0x131513,
0xe6193c, 0x87711d, 0x98981b, 0x29a329,
0x1999b3, 0x3d62f5, 0xad2bee, 0xe619c3,
]);
Base16Theme.add("atelier-seaside", "Atelier Seaside", [
0x131513, 0x242924, 0x5e6e5e, 0x687d68,
0x809980, 0x8ca68c, 0xcfe8cf, 0xf4fbf4,
0xe6193c, 0x87711d, 0x98981b, 0x29a329,
0x1999b3, 0x3d62f5, 0xad2bee, 0xe619c3,
]);
Base16Theme.add("atelier-sulphurpool-light", "Atelier Sulphurpool Light", [
0xf5f7ff, 0xdfe2f1, 0x979db4, 0x898ea4,
0x6b7394, 0x5e6687, 0x293256, 0x202746,
0xc94922, 0xc76b29, 0xc08b30, 0xac9739,
0x22a2c9, 0x3d8fd1, 0x6679cc, 0x9c637a,
]);
Base16Theme.add("atelier-sulphurpool", "Atelier Sulphurpool", [
0x202746, 0x293256, 0x5e6687, 0x6b7394,
0x898ea4, 0x979db4, 0xdfe2f1, 0xf5f7ff,
0xc94922, 0xc76b29, 0xc08b30, 0xac9739,
0x22a2c9, 0x3d8fd1, 0x6679cc, 0x9c637a,
]);
Base16Theme.add("atlas", "Atlas", [
0x002635, 0x00384d, 0x517F8D, 0x6C8B91,
0x869696, 0xa1a19a, 0xe6e6dc, 0xfafaf8,
0xff5a67, 0xf08e48, 0xffcc1b, 0x7fc06e,
0x14747e, 0x5dd7b9, 0x9a70a4, 0xc43060,
]);
Base16Theme.add("bespin", "Bespin", [
0x28211c, 0x36312e, 0x5e5d5c, 0x666666,
0x797977, 0x8a8986, 0x9d9b97, 0xbaae9e,
0xcf6a4c, 0xcf7d34, 0xf9ee98, 0x54be0d,
0xafc4db, 0x5ea6ea, 0x9b859d, 0x937121,
]);
Base16Theme.add("black-metal-bathory", "Black Metal (Bathory)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0xe78a53, 0xfbcb97,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-burzum", "Black Metal (Burzum)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x99bbaa, 0xddeecc,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-dark-funeral", "Black Metal (Dark Funeral)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x5f81a5, 0xd0dfee,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-gorgoroth", "Black Metal (Gorgoroth)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x8c7f70, 0x9b8d7f,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-immortal", "Black Metal (Immortal)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x556677, 0x7799bb,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-khold", "Black Metal (Khold)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x974b46, 0xeceee3,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-marduk", "Black Metal (Marduk)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x626b67, 0xa5aaa7,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-mayhem", "Black Metal (Mayhem)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0xeecc6c, 0xf3ecd4,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-nile", "Black Metal (Nile)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x777755, 0xaa9988,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal-venom", "Black Metal (Venom)", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0x79241f, 0xf8f7f2,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("black-metal", "Black Metal", [
0x000000, 0x121212, 0x222222, 0x333333,
0x999999, 0xc1c1c1, 0x999999, 0xc1c1c1,
0x5f8787, 0xaaaaaa, 0xa06666, 0xdd9999,
0xaaaaaa, 0x888888, 0x999999, 0x444444,
]);
Base16Theme.add("brewer", "Brewer", [
0x0c0d0e, 0x2e2f30, 0x515253, 0x737475,
0x959697, 0xb7b8b9, 0xdadbdc, 0xfcfdfe,
0xe31a1c, 0xe6550d, 0xdca060, 0x31a354,
0x80b1d3, 0x3182bd, 0x756bb1, 0xb15928,
]);
Base16Theme.add("bright", "Bright", [
0x000000, 0x303030, 0x505050, 0xb0b0b0,
0xd0d0d0, 0xe0e0e0, 0xf5f5f5, 0xffffff,
0xfb0120, 0xfc6d24, 0xfda331, 0xa1c659,
0x76c7b7, 0x6fb3d2, 0xd381c3, 0xbe643c,
]);
Base16Theme.add("brogrammer", "Brogrammer", [
0x1f1f1f, 0xf81118, 0x2dc55e, 0xecba0f,
0x2a84d2, 0x4e5ab7, 0x1081d6, 0xd6dbe5,
0xd6dbe5, 0xde352e, 0x1dd361, 0xf3bd09,
0x1081d6, 0x5350b9, 0x0f7ddb, 0xffffff,
]);
Base16Theme.add("brushtrees-dark", "Brush Trees Dark", [
0x485867, 0x5A6D7A, 0x6D828E, 0x8299A1,
0x98AFB5, 0xB0C5C8, 0xC9DBDC, 0xE3EFEF,
0xb38686, 0xd8bba2, 0xaab386, 0x87b386,
0x86b3b3, 0x868cb3, 0xb386b2, 0xb39f9f,
]);
Base16Theme.add("brushtrees", "Brush Trees", [
0xE3EFEF, 0xC9DBDC, 0xB0C5C8, 0x98AFB5,
0x8299A1, 0x6D828E, 0x5A6D7A, 0x485867,
0xb38686, 0xd8bba2, 0xaab386, 0x87b386,
0x86b3b3, 0x868cb3, 0xb386b2, 0xb39f9f,
]);
Base16Theme.add("chalk", "Chalk", [
0x151515, 0x202020, 0x303030, 0x505050,
0xb0b0b0, 0xd0d0d0, 0xe0e0e0, 0xf5f5f5,
0xfb9fb1, 0xeda987, 0xddb26f, 0xacc267,
0x12cfc0, 0x6fc2ef, 0xe1a3ee, 0xdeaf8f,
]);
Base16Theme.add("circus", "Circus", [
0x191919, 0x202020, 0x303030, 0x5f5a60,
0x505050, 0xa7a7a7, 0x808080, 0xffffff,
0xdc657d, 0x4bb1a7, 0xc3ba63, 0x84b97c,
0x4bb1a7, 0x639ee4, 0xb888e2, 0xb888e2,
]);
Base16Theme.add("classic-dark", "Classic Dark", [
0x151515, 0x202020, 0x303030, 0x505050,
0xB0B0B0, 0xD0D0D0, 0xE0E0E0, 0xF5F5F5,
0xAC4142, 0xD28445, 0xF4BF75, 0x90A959,
0x75B5AA, 0x6A9FB5, 0xAA759F, 0x8F5536,
]);
Base16Theme.add("classic-light", "Classic Light", [
0xF5F5F5, 0xE0E0E0, 0xD0D0D0, 0xB0B0B0,
0x505050, 0x303030, 0x202020, 0x151515,
0xAC4142, 0xD28445, 0xF4BF75, 0x90A959,
0x75B5AA, 0x6A9FB5, 0xAA759F, 0x8F5536,
]);
Base16Theme.add("codeschool", "Codeschool", [
0x232c31, 0x1c3657, 0x2a343a, 0x3f4944,
0x84898c, 0x9ea7a6, 0xa7cfa3, 0xb5d8f6,
0x2a5491, 0x43820d, 0xa03b1e, 0x237986,
0xb02f30, 0x484d79, 0xc59820, 0xc98344,
]);
Base16Theme.add("cupcake", "Cupcake", [
0xfbf1f2, 0xf2f1f4, 0xd8d5dd, 0xbfb9c6,
0xa59daf, 0x8b8198, 0x72677E, 0x585062,
0xD57E85, 0xEBB790, 0xDCB16C, 0xA3B367,
0x69A9A7, 0x7297B9, 0xBB99B4, 0xBAA58C,
]);
Base16Theme.add("cupertino", "Cupertino", [
0xffffff, 0xc0c0c0, 0xc0c0c0, 0x808080,
0x808080, 0x404040, 0x404040, 0x5e5e5e,
0xc41a15, 0xeb8500, 0x826b28, 0x007400,
0x318495, 0x0000ff, 0xa90d91, 0x826b28,
]);
Base16Theme.add("darktooth", "Darktooth", [
0x1D2021, 0x32302F, 0x504945, 0x665C54,
0x928374, 0xA89984, 0xD5C4A1, 0xFDF4C1,
0xFB543F, 0xFE8625, 0xFAC03B, 0x95C085,
0x8BA59B, 0x0D6678, 0x8F4673, 0xA87322,
]);
Base16Theme.add("default-dark", "Default Dark", [
0x181818, 0x282828, 0x383838, 0x585858,
0xb8b8b8, 0xd8d8d8, 0xe8e8e8, 0xf8f8f8,
0xab4642, 0xdc9656, 0xf7ca88, 0xa1b56c,
0x86c1b9, 0x7cafc2, 0xba8baf, 0xa16946,
]);
Base16Theme.add("default-light", "Default Light", [
0xf8f8f8, 0xe8e8e8, 0xd8d8d8, 0xb8b8b8,
0x585858, 0x383838, 0x282828, 0x181818,
0xab4642, 0xdc9656, 0xf7ca88, 0xa1b56c,
0x86c1b9, 0x7cafc2, 0xba8baf, 0xa16946,
]);
Base16Theme.add("dracula", "Dracula", [
0x282936, 0x3a3c4e, 0x4d4f68, 0x626483,
0x62d6e8, 0xe9e9f4, 0xf1f2f8, 0xf7f7fb,
0xea51b2, 0xb45bcf, 0x00f769, 0xebff87,
0xa1efe4, 0x62d6e8, 0xb45bcf, 0x00f769,
]);
Base16Theme.add("eighties", "Eighties", [
0x2d2d2d, 0x393939, 0x515151, 0x747369,
0xa09f93, 0xd3d0c8, 0xe8e6df, 0xf2f0ec,
0xf2777a, 0xf99157, 0xffcc66, 0x99cc99,
0x66cccc, 0x6699cc, 0xcc99cc, 0xd27b53,
]);
Base16Theme.add("embers", "Embers", [
0x16130F, 0x2C2620, 0x433B32, 0x5A5047,
0x8A8075, 0xA39A90, 0xBEB6AE, 0xDBD6D1,
0x826D57, 0x828257, 0x6D8257, 0x57826D,
0x576D82, 0x6D5782, 0x82576D, 0x825757,
]);
Base16Theme.add("flat", "Flat", [
0x2C3E50, 0x34495E, 0x7F8C8D, 0x95A5A6,
0xBDC3C7, 0xe0e0e0, 0xf5f5f5, 0xECF0F1,
0xE74C3C, 0xE67E22, 0xF1C40F, 0x2ECC71,
0x1ABC9C, 0x3498DB, 0x9B59B6, 0xbe643c,
]);
Base16Theme.add("fruit-soda", "Fruit Soda", [
0xf1ecf1, 0xe0dee0, 0xd8d5d5, 0xb5b4b6,
0x979598, 0x515151, 0x474545, 0x2d2c2c,
0xfe3e31, 0xfe6d08, 0xf7e203, 0x47f74c,
0x0f9cfd, 0x2931df, 0x611fce, 0xb16f40,
]);
Base16Theme.add("github", "Github", [
0xffffff, 0xf5f5f5, 0xc8c8fa, 0x969896,
0xe8e8e8, 0x333333, 0xffffff, 0xffffff,
0xed6a43, 0x0086b3, 0x795da3, 0x183691,
0x183691, 0x795da3, 0xa71d5d, 0x333333,
]);
Base16Theme.add("google-dark", "Google Dark", [
0x1d1f21, 0x282a2e, 0x373b41, 0x969896,
0xb4b7b4, 0xc5c8c6, 0xe0e0e0, 0xffffff,
0xCC342B, 0xF96A38, 0xFBA922, 0x198844,
0x3971ED, 0x3971ED, 0xA36AC7, 0x3971ED,
]);
Base16Theme.add("google-light", "Google Light", [
0xffffff, 0xe0e0e0, 0xc5c8c6, 0xb4b7b4,
0x969896, 0x373b41, 0x282a2e, 0x1d1f21,
0xCC342B, 0xF96A38, 0xFBA922, 0x198844,
0x3971ED, 0x3971ED, 0xA36AC7, 0x3971ED,
]);
Base16Theme.add("grayscale-dark", "Grayscale Dark", [
0x101010, 0x252525, 0x464646, 0x525252,
0xababab, 0xb9b9b9, 0xe3e3e3, 0xf7f7f7,
0x7c7c7c, 0x999999, 0xa0a0a0, 0x8e8e8e,
0x868686, 0x686868, 0x747474, 0x5e5e5e,
]);
Base16Theme.add("grayscale-light", "Grayscale Light", [
0xf7f7f7, 0xe3e3e3, 0xb9b9b9, 0xababab,
0x525252, 0x464646, 0x252525, 0x101010,
0x7c7c7c, 0x999999, 0xa0a0a0, 0x8e8e8e,
0x868686, 0x686868, 0x747474, 0x5e5e5e,
]);
Base16Theme.add("greenscreen", "Green Screen", [
0x001100, 0x003300, 0x005500, 0x007700,
0x009900, 0x00bb00, 0x00dd00, 0x00ff00,
0x007700, 0x009900, 0x007700, 0x00bb00,
0x005500, 0x009900, 0x00bb00, 0x005500,
]);
Base16Theme.add("gruvbox-dark-hard", "Gruvbox dark, hard", [
0x1d2021, 0x3c3836, 0x504945, 0x665c54,
0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26,
0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
]);
Base16Theme.add("gruvbox-dark-medium", "Gruvbox dark, medium", [
0x282828, 0x3c3836, 0x504945, 0x665c54,
0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26,
0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
]);
Base16Theme.add("gruvbox-dark-pale", "Gruvbox dark, pale", [
0x262626, 0x3a3a3a, 0x4e4e4e, 0x8a8a8a,
0x949494, 0xdab997, 0xd5c4a1, 0xebdbb2,
0xd75f5f, 0xff8700, 0xffaf00, 0xafaf00,
0x85ad85, 0x83adad, 0xd485ad, 0xd65d0e,
]);
Base16Theme.add("gruvbox-dark-soft", "Gruvbox dark, soft", [
0x32302f, 0x3c3836, 0x504945, 0x665c54,
0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26,
0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
]);
Base16Theme.add("gruvbox-light-hard", "Gruvbox light, hard", [
0xf9f5d7, 0xebdbb2, 0xd5c4a1, 0xbdae93,
0x665c54, 0x504945, 0x3c3836, 0x282828,
0x9d0006, 0xaf3a03, 0xb57614, 0x79740e,
0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
]);
Base16Theme.add("gruvbox-light-medium", "Gruvbox light, medium", [
0xfbf1c7, 0xebdbb2, 0xd5c4a1, 0xbdae93,
0x665c54, 0x504945, 0x3c3836, 0x282828,
0x9d0006, 0xaf3a03, 0xb57614, 0x79740e,
0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
]);
Base16Theme.add("gruvbox-light-soft", "Gruvbox light, soft", [
0xf2e5bc, 0xebdbb2, 0xd5c4a1, 0xbdae93,
0x665c54, 0x504945, 0x3c3836, 0x282828,
0x9d0006, 0xaf3a03, 0xb57614, 0x79740e,
0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
]);
Base16Theme.add("harmonic-dark", "Harmonic16 Dark", [
0x0b1c2c, 0x223b54, 0x405c79, 0x627e99,
0xaabcce, 0xcbd6e2, 0xe5ebf1, 0xf7f9fb,
0xbf8b56, 0xbfbf56, 0x8bbf56, 0x56bf8b,
0x568bbf, 0x8b56bf, 0xbf568b, 0xbf5656,
]);
Base16Theme.add("harmonic-light", "Harmonic16 Light", [
0xf7f9fb, 0xe5ebf1, 0xcbd6e2, 0xaabcce,
0x627e99, 0x405c79, 0x223b54, 0x0b1c2c,
0xbf8b56, 0xbfbf56, 0x8bbf56, 0x56bf8b,
0x568bbf, 0x8b56bf, 0xbf568b, 0xbf5656,
]);
Base16Theme.add("heetch-light", "Heetch Light", [
0xfeffff, 0x392551, 0x7b6d8b, 0x9c92a8,
0xddd6e5, 0x5a496e, 0x470546, 0x190134,
0x27d9d5, 0xbdb6c5, 0x5ba2b6, 0xf80059,
0xc33678, 0x47f9f5, 0xbd0152, 0xdedae2,
]);
Base16Theme.add("heetch", "Heetch Dark", [
0x190134, 0x392551, 0x5A496E, 0x7B6D8B,
0x9C92A8, 0xBDB6C5, 0xDEDAE2, 0xFEFFFF,
0x27D9D5, 0x5BA2B6, 0x8F6C97, 0xC33678,
0xF80059, 0xBD0152, 0x82034C, 0x470546,
]);
Base16Theme.add("helios", "Helios", [
0x1d2021, 0x383c3e, 0x53585b, 0x6f7579,
0xcdcdcd, 0xd5d5d5, 0xdddddd, 0xe5e5e5,
0xd72638, 0xeb8413, 0xf19d1a, 0x88b92d,
0x1ba595, 0x1e8bac, 0xbe4264, 0xc85e0d,
]);
Base16Theme.add("hopscotch", "Hopscotch", [
0x322931, 0x433b42, 0x5c545b, 0x797379,
0x989498, 0xb9b5b8, 0xd5d3d5, 0xffffff,
0xdd464c, 0xfd8b19, 0xfdcc59, 0x8fc13e,
0x149b93, 0x1290bf, 0xc85e7c, 0xb33508,
]);
Base16Theme.add("horizon-dark", "Horizon Dark", [
0x1c1e26, 0x232530, 0x2e303e, 0x676a8d,
0xced1d0, 0xcbced0, 0xdcdfe4, 0xe3e6ee,
0xe93c58, 0xe58d7d, 0xefb993, 0xefaf8e,
0x24a8b4, 0xdf5273, 0xb072d1, 0xe4a382,
]);
Base16Theme.add("ia-dark", "iA Dark", [
0x1a1a1a, 0x222222, 0x1d414d, 0x767676,
0xb8b8b8, 0xcccccc, 0xe8e8e8, 0xf8f8f8,
0xd88568, 0xd86868, 0xb99353, 0x83a471,
0x7c9cae, 0x8eccdd, 0xb98eb2, 0x8b6c37,
]);
Base16Theme.add("ia-light", "iA Light", [
0xf6f6f6, 0xdedede, 0xbde5f2, 0x898989,
0x767676, 0x181818, 0xe8e8e8, 0xf8f8f8,
0x9c5a02, 0xc43e18, 0xc48218, 0x38781c,
0x2d6bb1, 0x48bac2, 0xa94598, 0x8b6c37,
]);
Base16Theme.add("icy", "Icy Dark", [
0x021012, 0x031619, 0x041f23, 0x052e34,
0x064048, 0x095b67, 0x0c7c8c, 0x109cb0,
0x16c1d9, 0xb3ebf2, 0x80deea, 0x4dd0e1,
0x26c6da, 0x00bcd4, 0x00acc1, 0x0097a7,
]);
Base16Theme.add("irblack", "IR Black", [
0x000000, 0x242422, 0x484844, 0x6c6c66,
0x918f88, 0xb5b3aa, 0xd9d7cc, 0xfdfbee,
0xff6c60, 0xe9c062, 0xffffb6, 0xa8ff60,
0xc6c5fe, 0x96cbfe, 0xff73fd, 0xb18a3d,
]);
Base16Theme.add("isotope", "Isotope", [
0x000000, 0x404040, 0x606060, 0x808080,
0xc0c0c0, 0xd0d0d0, 0xe0e0e0, 0xffffff,
0xff0000, 0xff9900, 0xff0099, 0x33ff00,
0x00ffff, 0x0066ff, 0xcc00ff, 0x3300ff,
]);
Base16Theme.add("macintosh", "Macintosh", [
0x000000, 0x404040, 0x404040, 0x808080,
0x808080, 0xc0c0c0, 0xc0c0c0, 0xffffff,
0xdd0907, 0xff6403, 0xfbf305, 0x1fb714,
0x02abea, 0x0000d3, 0x4700a5, 0x90713a,
]);
Base16Theme.add("marrakesh", "Marrakesh", [
0x201602, 0x302e00, 0x5f5b17, 0x6c6823,
0x86813b, 0x948e48, 0xccc37a, 0xfaf0a5,
0xc35359, 0xb36144, 0xa88339, 0x18974e,
0x75a738, 0x477ca1, 0x8868b3, 0xb3588e,
]);
Base16Theme.add("materia", "Materia", [
0x263238, 0x2C393F, 0x37474F, 0x707880,
0xC9CCD3, 0xCDD3DE, 0xD5DBE5, 0xFFFFFF,
0xEC5F67, 0xEA9560, 0xFFCC00, 0x8BD649,
0x80CBC4, 0x89DDFF, 0x82AAFF, 0xEC5F67,
]);
Base16Theme.add("material-darker", "Material Darker", [
0x212121, 0x303030, 0x353535, 0x4A4A4A,
0xB2CCD6, 0xEEFFFF, 0xEEFFFF, 0xFFFFFF,
0xF07178, 0xF78C6C, 0xFFCB6B, 0xC3E88D,
0x89DDFF, 0x82AAFF, 0xC792EA, 0xFF5370,
]);
Base16Theme.add("material-lighter", "Material Lighter", [
0xFAFAFA, 0xE7EAEC, 0xCCEAE7, 0xCCD7DA,
0x8796B0, 0x80CBC4, 0x80CBC4, 0xFFFFFF,
0xFF5370, 0xF76D47, 0xFFB62C, 0x91B859,
0x39ADB5, 0x6182B8, 0x7C4DFF, 0xE53935,
]);
Base16Theme.add("material-palenight", "Material Palenight", [
0x292D3E, 0x444267, 0x32374D, 0x676E95,
0x8796B0, 0x959DCB, 0x959DCB, 0xFFFFFF,
0xF07178, 0xF78C6C, 0xFFCB6B, 0xC3E88D,
0x89DDFF, 0x82AAFF, 0xC792EA, 0xFF5370,
]);
Base16Theme.add("material-vivid", "Material Vivid", [
0x202124, 0x27292c, 0x323639, 0x44464d,
0x676c71, 0x80868b, 0x9e9e9e, 0xffffff,
0xf44336, 0xff9800, 0xffeb3b, 0x00e676,
0x00bcd4, 0x2196f3, 0x673ab7, 0x8d6e63,
]);
Base16Theme.add("material", "Material", [
0x263238, 0x2E3C43, 0x314549, 0x546E7A,
0xB2CCD6, 0xEEFFFF, 0xEEFFFF, 0xFFFFFF,
0xF07178, 0xF78C6C, 0xFFCB6B, 0xC3E88D,
0x89DDFF, 0x82AAFF, 0xC792EA, 0xFF5370,
]);
Base16Theme.add("mellow-purple", "Mellow Purple", [
0x1e0528, 0x1A092D, 0x331354, 0x320f55,
0x873582, 0xffeeff, 0xffeeff, 0xf8c0ff,
0x00d9e9, 0xaa00a3, 0x955ae7, 0x05cb0d,
0xb900b1, 0x550068, 0x8991bb, 0x4d6fff,
]);
Base16Theme.add("mexico-light", "Mexico Light", [
0xf8f8f8, 0xe8e8e8, 0xd8d8d8, 0xb8b8b8,
0x585858, 0x383838, 0x282828, 0x181818,
0xab4642, 0xdc9656, 0xf79a0e, 0x538947,
0x4b8093, 0x7cafc2, 0x96609e, 0xa16946,
]);
Base16Theme.add("mocha", "Mocha", [
0x3B3228, 0x534636, 0x645240, 0x7e705a,
0xb8afad, 0xd0c8c6, 0xe9e1dd, 0xf5eeeb,
0xcb6077, 0xd28b71, 0xf4bc87, 0xbeb55b,
0x7bbda4, 0x8ab3b5, 0xa89bb9, 0xbb9584,
]);
Base16Theme.add("monokai", "Monokai", [
0x272822, 0x383830, 0x49483e, 0x75715e,
0xa59f85, 0xf8f8f2, 0xf5f4f1, 0xf9f8f5,
0xf92672, 0xfd971f, 0xf4bf75, 0xa6e22e,
0xa1efe4, 0x66d9ef, 0xae81ff, 0xcc6633,
]);
Base16Theme.add("nord", "Nord", [
0x2E3440, 0x3B4252, 0x434C5E, 0x4C566A,
0xD8DEE9, 0xE5E9F0, 0xECEFF4, 0x8FBCBB,
0x88C0D0, 0x81A1C1, 0x5E81AC, 0xBF616A,
0xD08770, 0xEBCB8B, 0xA3BE8C, 0xB48EAD,
]);
Base16Theme.add("ocean", "Ocean", [
0x2b303b, 0x343d46, 0x4f5b66, 0x65737e,
0xa7adba, 0xc0c5ce, 0xdfe1e8, 0xeff1f5,
0xbf616a, 0xd08770, 0xebcb8b, 0xa3be8c,
0x96b5b4, 0x8fa1b3, 0xb48ead, 0xab7967,
]);
Base16Theme.add("oceanicnext", "OceanicNext", [
0x1B2B34, 0x343D46, 0x4F5B66, 0x65737E,
0xA7ADBA, 0xC0C5CE, 0xCDD3DE, 0xD8DEE9,
0xEC5F67, 0xF99157, 0xFAC863, 0x99C794,
0x5FB3B3, 0x6699CC, 0xC594C5, 0xAB7967,
]);
Base16Theme.add("one-light", "One Light", [
0xfafafa, 0xf0f0f1, 0xe5e5e6, 0xa0a1a7,
0x696c77, 0x383a42, 0x202227, 0x090a0b,
0xca1243, 0xd75f00, 0xc18401, 0x50a14f,
0x0184bc, 0x4078f2, 0xa626a4, 0x986801,
]);
Base16Theme.add("onedark", "OneDark", [
0x282c34, 0x353b45, 0x3e4451, 0x545862,
0x565c64, 0xabb2bf, 0xb6bdca, 0xc8ccd4,
0xe06c75, 0xd19a66, 0xe5c07b, 0x98c379,
0x56b6c2, 0x61afef, 0xc678dd, 0xbe5046,
]);
Base16Theme.add("outrun-dark", "Outrun Dark", [
0x00002A, 0x20204A, 0x30305A, 0x50507A,
0xB0B0DA, 0xD0D0FA, 0xE0E0FF, 0xF5F5FF,
0xFF4242, 0xFC8D28, 0xF3E877, 0x59F176,
0x0EF0F0, 0x66B0FF, 0xF10596, 0xF003EF,
]);
Base16Theme.add("papercolor-dark", "PaperColor Dark", [
0x1c1c1c, 0xaf005f, 0x5faf00, 0xd7af5f,
0x5fafd7, 0x808080, 0xd7875f, 0xd0d0d0,
0x585858, 0x5faf5f, 0xafd700, 0xaf87d7,
0xffaf00, 0xff5faf, 0x00afaf, 0x5f8787,
]);
Base16Theme.add("papercolor-light", "PaperColor Light", [
0xeeeeee, 0xaf0000, 0x008700, 0x5f8700,
0x0087af, 0x878787, 0x005f87, 0x444444,
0xbcbcbc, 0xd70000, 0xd70087, 0x8700af,
0xd75f00, 0xd75f00, 0x005faf, 0x005f87,
]);
Base16Theme.add("paraiso", "Paraiso", [
0x2f1e2e, 0x41323f, 0x4f424c, 0x776e71,
0x8d8687, 0xa39e9b, 0xb9b6b0, 0xe7e9db,
0xef6155, 0xf99b15, 0xfec418, 0x48b685,
0x5bc4bf, 0x06b6ef, 0x815ba4, 0xe96ba8,
]);
Base16Theme.add("phd", "PhD", [
0x061229, 0x2a3448, 0x4d5666, 0x717885,
0x9a99a3, 0xb8bbc2, 0xdbdde0, 0xffffff,
0xd07346, 0xf0a000, 0xfbd461, 0x99bf52,
0x72b9bf, 0x5299bf, 0x9989cc, 0xb08060,
]);
Base16Theme.add("pico", "Pico", [
0x000000, 0x1d2b53, 0x7e2553, 0x008751,
0xab5236, 0x5f574f, 0xc2c3c7, 0xfff1e8,
0xff004d, 0xffa300, 0xfff024, 0x00e756,
0x29adff, 0x83769c, 0xff77a8, 0xffccaa,
]);
Base16Theme.add("pop", "Pop", [
0x000000, 0x202020, 0x303030, 0x505050,
0xb0b0b0, 0xd0d0d0, 0xe0e0e0, 0xffffff,
0xeb008a, 0xf29333, 0xf8ca12, 0x37b349,
0x00aabb, 0x0e5a94, 0xb31e8d, 0x7a2d00,
]);
Base16Theme.add("porple", "Porple", [
0x292c36, 0x333344, 0x474160, 0x65568a,
0xb8b8b8, 0xd8d8d8, 0xe8e8e8, 0xf8f8f8,
0xf84547, 0xd28e5d, 0xefa16b, 0x95c76f,
0x64878f, 0x8485ce, 0xb74989, 0x986841,
]);
Base16Theme.add("qualia", "Qualia", [
0x101010, 0x454545, 0x454545, 0x454545,
0x808080, 0xc0c0c0, 0xc0c0c0, 0x454545,
0xefa6a2, 0xa3b8ef, 0xe6a3dc, 0x80c990,
0xc8c874, 0x50cacd, 0xe0af85, 0x808080,
]);
Base16Theme.add("railscasts", "Railscasts", [
0x2b2b2b, 0x272935, 0x3a4055, 0x5a647e,
0xd4cfc9, 0xe6e1dc, 0xf4f1ed, 0xf9f7f3,
0xda4939, 0xcc7833, 0xffc66d, 0xa5c261,
0x519f50, 0x6d9cbe, 0xb6b3eb, 0xbc9458,
]);
Base16Theme.add("rebecca", "Rebecca", [
0x292a44, 0x663399, 0x383a62, 0x666699,
0xa0a0c5, 0xf1eff8, 0xccccff, 0x53495d,
0xa0a0c5, 0xefe4a1, 0xae81ff, 0x6dfedf,
0x8eaee0, 0x2de0a7, 0x7aa5ff, 0xff79c6,
]);
Base16Theme.add("seti", "Seti UI", [
0x151718, 0x282a2b, 0x3B758C, 0x41535B,
0x43a5d5, 0xd6d6d6, 0xeeeeee, 0xffffff,
0xcd3f45, 0xdb7b55, 0xe6cd69, 0x9fca56,
0x55dbbe, 0x55b5db, 0xa074c4, 0x8a553f,
]);
Base16Theme.add("shapeshifter", "Shapeshifter", [
0xf9f9f9, 0xe0e0e0, 0xababab, 0x555555,
0x343434, 0x102015, 0x040404, 0x000000,
0xe92f2f, 0xe09448, 0xdddd13, 0x0ed839,
0x23edda, 0x3b48e3, 0xf996e2, 0x69542d,
]);
Base16Theme.add("snazzy", "Snazzy", [
0x282a36, 0x34353e, 0x43454f, 0x78787e,
0xa5a5a9, 0xe2e4e5, 0xeff0eb, 0xf1f1f0,
0xff5c57, 0xff9f43, 0xf3f99d, 0x5af78e,
0x9aedfe, 0x57c7ff, 0xff6ac1, 0xb2643c,
]);
Base16Theme.add("solarflare", "Solar Flare", [
0x18262F, 0x222E38, 0x586875, 0x667581,
0x85939E, 0xA6AFB8, 0xE8E9ED, 0xF5F7FA,
0xEF5253, 0xE66B2B, 0xE4B51C, 0x7CC844,
0x52CBB0, 0x33B5E1, 0xA363D5, 0xD73C9A,
]);
Base16Theme.add("solarized-dark", "Solarized Dark", [
0x002b36, 0x073642, 0x586e75, 0x657b83,
0x839496, 0x93a1a1, 0xeee8d5, 0xfdf6e3,
0xdc322f, 0xcb4b16, 0xb58900, 0x859900,
0x2aa198, 0x268bd2, 0x6c71c4, 0xd33682,
]);
Base16Theme.add("solarized-light", "Solarized Light", [
0xfdf6e3, 0xeee8d5, 0x93a1a1, 0x839496,
0x657b83, 0x586e75, 0x073642, 0x002b36,
0xdc322f, 0xcb4b16, 0xb58900, 0x859900,
0x2aa198, 0x268bd2, 0x6c71c4, 0xd33682,
]);
Base16Theme.add("spacemacs", "Spacemacs", [
0x1f2022, 0x282828, 0x444155, 0x585858,
0xb8b8b8, 0xa3a3a3, 0xe8e8e8, 0xf8f8f8,
0xf2241f, 0xffa500, 0xb1951d, 0x67b11d,
0x2d9574, 0x4f97d7, 0xa31db1, 0xb03060,
]);
Base16Theme.add("summerfruit-dark", "Summerfruit Dark", [
0x151515, 0x202020, 0x303030, 0x505050,
0xB0B0B0, 0xD0D0D0, 0xE0E0E0, 0xFFFFFF,
0xFF0086, 0xFD8900, 0xABA800, 0x00C918,
0x1FAAAA, 0x3777E6, 0xAD00A1, 0xCC6633,
]);
Base16Theme.add("summerfruit-light", "Summerfruit Light", [
0xFFFFFF, 0xE0E0E0, 0xD0D0D0, 0xB0B0B0,
0x000000, 0x101010, 0x151515, 0x202020,
0xFF0086, 0xFD8900, 0xABA800, 0x00C918,
0x1FAAAA, 0x3777E6, 0xAD00A1, 0xCC6633,
]);
Base16Theme.add("synth-midnight-dark", "Synth Midnight", [
0x040404, 0x141414, 0x242424, 0x61507A,
0xBFBBBF, 0xDFDBDF, 0xEFEBEF, 0xFFFBFF,
0xB53B50, 0xE4600E, 0xDAE84D, 0x06EA61,
0x7CEDE9, 0x03AEFF, 0xEA5CE2, 0x9D4D0E,
]);
Base16Theme.add("tomorrow-night-eighties", "Tomorrow Night Eighties", [
0x2d2d2d, 0x393939, 0x515151, 0x999999,
0xb4b7b4, 0xcccccc, 0xe0e0e0, 0xffffff,
0xf2777a, 0xf99157, 0xffcc66, 0x99cc99,
0x66cccc, 0x6699cc, 0xcc99cc, 0xa3685a,
]);
Base16Theme.add("tomorrow-night", "Tomorrow Night", [
0x1d1f21, 0x282a2e, 0x373b41, 0x969896,
0xb4b7b4, 0xc5c8c6, 0xe0e0e0, 0xffffff,
0xcc6666, 0xde935f, 0xf0c674, 0xb5bd68,
0x8abeb7, 0x81a2be, 0xb294bb, 0xa3685a,
]);
Base16Theme.add("tomorrow", "Tomorrow", [
0xffffff, 0xe0e0e0, 0xd6d6d6, 0x8e908c,
0x969896, 0x4d4d4c, 0x282a2e, 0x1d1f21,
0xc82829, 0xf5871f, 0xeab700, 0x718c00,
0x3e999f, 0x4271ae, 0x8959a8, 0xa3685a,
]);
Base16Theme.add("tube", "London Tube", [
0x231f20, 0x1c3f95, 0x5a5758, 0x737171,
0x959ca1, 0xd9d8d8, 0xe7e7e8, 0xffffff,
0xee2e24, 0xf386a1, 0xffd204, 0x00853e,
0x85cebc, 0x009ddc, 0x98005d, 0xb06110,
]);
Base16Theme.add("twilight", "Twilight", [
0x1e1e1e, 0x323537, 0x464b50, 0x5f5a60,
0x838184, 0xa7a7a7, 0xc3c3c3, 0xffffff,
0xcf6a4c, 0xcda869, 0xf9ee98, 0x8f9d6a,
0xafc4db, 0x7587a6, 0x9b859d, 0x9b703f,
]);
Base16Theme.add("unikitty-dark", "Unikitty Dark", [
0x2e2a31, 0x4a464d, 0x666369, 0x838085,
0x9f9da2, 0xbcbabe, 0xd8d7da, 0xf5f4f7,
0xd8137f, 0xd65407, 0xdc8a0e, 0x17ad98,
0x149bda, 0x796af5, 0xbb60ea, 0xc720ca,
]);
Base16Theme.add("unikitty-light", "Unikitty Light", [
0xffffff, 0xe1e1e2, 0xc4c3c5, 0xa7a5a8,
0x89878b, 0x6c696e, 0x4f4b51, 0x322d34,
0xd8137f, 0xd65407, 0xdc8a0e, 0x17ad98,
0x149bda, 0x775dff, 0xaa17e6, 0xe013d0,
]);
Base16Theme.add("woodland", "Woodland", [
0x231e18, 0x302b25, 0x48413a, 0x9d8b70,
0xb4a490, 0xcabcb1, 0xd7c8bc, 0xe4d4c8,
0xd35c5c, 0xca7f32, 0xe0ac16, 0xb7ba53,
0x6eb958, 0x88a4d3, 0xbb90e2, 0xb49368,
]);
Base16Theme.add("xcode-dusk", "XCode Dusk", [
0x282B35, 0x3D4048, 0x53555D, 0x686A71,
0x7E8086, 0x939599, 0xA9AAAE, 0xBEBFC2,
0xB21889, 0x786DC5, 0x438288, 0xDF0002,
0x00A0BE, 0x790EAD, 0xB21889, 0xC77C48,
]);
Base16Theme.add("zenburn", "Zenburn", [
0x383838, 0x404040, 0x606060, 0x6f6f6f,
0x808080, 0xdcdccc, 0xc0c0c0, 0xffffff,
0xdca3a3, 0xdfaf8f, 0xe0cf9f, 0x5f7f5f,
0x93e0e3, 0x7cb8bb, 0xdc8cc3, 0x000000,
]);

69
js/model/LabelColors.js Normal file
View File

@ -0,0 +1,69 @@
import Gdk from 'gi://Gdk?version=4.0';
import Gio from 'gi://Gio';
const CITADEL_SETTINGS_SCHEMA = 'com.subgraph.citadel';
const LABEL_COLOR_LIST_KEY = 'label-color-list';
const REALM_LABEL_COLORS_KEY = 'realm-label-colors';
const DEFAULT_LABEL_COLOR = new Gdk.RGBA({ red: 153, green: 193, blue: 241, });
export class LabelColorManager {
constructor() {
this._citadelSettings = new Gio.Settings({ schema_id: CITADEL_SETTINGS_SCHEMA });
this._defaultColors = [];
this._realmLabelColors = new Map();
this._loadColors();
}
_loadColors() {
let entries = this._citadelSettings.get_strv(LABEL_COLOR_LIST_KEY);
entries.forEach(entry => {
let c = new Gdk.RGBA();
if (c.parse(entry)) {
this._defaultColors.push(c);
}
});
entries = this._citadelSettings.get_strv(REALM_LABEL_COLORS_KEY);
entries.forEach(entry => {
let parts = entry.split(":");
if (parts.length === 2) {
let c = new Gdk.RGBA();
if (c.parse(parts[1])) {
this._realmLabelColors.set(parts[0], c);
}
}
});
}
updateRealmColor(realm, color) {
this._realmLabelColors.set(realm.name, color);
this._storeRealmColors();
}
lookupRealmColor(realm) {
let c = this._realmLabelColors.get(realm.name);
if (c) {
return c;
}
let newColor = this._allocateColor();
this.updateRealmColor(realm, newColor);
return newColor;
}
_storeRealmColors() {
let entries = [];
this._realmLabelColors.forEach((v, k) => {
entries.push(`${k}:${v.to_string()}`);
});
entries.sort();
this._citadelSettings.set_strv(REALM_LABEL_COLORS_KEY, entries);
}
_allocateColor() {
// 1) No default colors? return a built in color
if (this._defaultColors.length === 0) {
return DEFAULT_LABEL_COLOR;
}
// 2) Find first color on default color list that isn't used already
let usedColors = Array.from(this._realmLabelColors.values());
let defaultColor = this._defaultColors.find(color => !usedColors.some(c => c.equal(color)));
if (defaultColor) {
return defaultColor;
}
// 3) Choose a random element of the default list
let index = Math.floor(Math.random() * this._defaultColors.length);
return this._defaultColors[index];
}
}

183
js/model/ObjectManager.js Normal file
View File

@ -0,0 +1,183 @@
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
const Signals = imports.signals;
Gio._promisify(Gio.DBusProxy.prototype, 'init_async', 'init_finish');
// Specified in the D-Bus specification here:
// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
const ObjectManagerIface = `
<node>
<interface name="org.freedesktop.DBus.ObjectManager">
<method name="GetManagedObjects">
<arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
</method>
<signal name="InterfacesAdded">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="a{sa{sv}}" />
</signal>
<signal name="InterfacesRemoved">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="as" />
</signal>
</interface>
</node>`;
const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface);
;
export class ObjectManager {
constructor(params) {
var _a;
this._connection = params.connection;
this._serviceName = params.name;
this._managerPath = params.objectPath;
this._cancellable = (_a = params.cancellable) !== null && _a !== void 0 ? _a : null;
this._managerProxy = new Gio.DBusProxy({
g_connection: this._connection,
g_interface_name: ObjectManagerInfo.name,
g_interface_info: ObjectManagerInfo,
g_name: this._serviceName,
g_object_path: this._managerPath,
g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
});
this._interfaceInfos = {};
this._objects = {};
this._interfaces = {};
this._onLoaded = params.onLoaded;
if (params.knownInterfaces)
this._registerInterfaces(params.knownInterfaces);
this._initManagerProxy();
}
_completeLoad() {
if (this._onLoaded)
this._onLoaded();
}
async _addInterface(objectPath, interfaceName) {
let info = this._interfaceInfos[interfaceName];
if (!info)
return;
const proxy = new Gio.DBusProxy({
g_connection: this._connection,
g_name: this._serviceName,
g_object_path: objectPath,
g_interface_name: interfaceName,
g_interface_info: info,
g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
});
try {
await proxy.init_async(GLib.PRIORITY_DEFAULT, this._cancellable);
}
catch (e) {
logError(e, `could not initialize proxy for interface ${interfaceName}`);
return;
}
let isNewObject;
if (!this._objects[objectPath]) {
this._objects[objectPath] = {};
isNewObject = true;
}
else {
isNewObject = false;
}
this._objects[objectPath][interfaceName] = proxy;
if (!this._interfaces[interfaceName])
this._interfaces[interfaceName] = [];
this._interfaces[interfaceName].push(proxy);
if (isNewObject)
this.emit('object-added', objectPath);
this.emit('interface-added', interfaceName, proxy);
}
_removeInterface(objectPath, interfaceName) {
if (!this._objects[objectPath])
return;
let proxy = this._objects[objectPath][interfaceName];
if (this._interfaces[interfaceName]) {
let index = this._interfaces[interfaceName].indexOf(proxy);
if (index >= 0)
this._interfaces[interfaceName].splice(index, 1);
if (this._interfaces[interfaceName].length === 0)
delete this._interfaces[interfaceName];
}
this.emit('interface-removed', interfaceName, proxy);
delete this._objects[objectPath][interfaceName];
if (Object.keys(this._objects[objectPath]).length === 0) {
delete this._objects[objectPath];
this.emit('object-removed', objectPath);
}
}
async _initManagerProxy() {
try {
await this._managerProxy.init_async(GLib.PRIORITY_DEFAULT, this._cancellable);
}
catch (e) {
logError(e, `could not initialize object manager for object ${this._serviceName}`);
this._completeLoad();
return;
}
this._managerProxy.connectSignal('InterfacesAdded', (_objectManager, _sender, [objectPath, interfaces]) => {
let interfaceNames = Object.keys(interfaces);
for (let i = 0; i < interfaceNames.length; i++)
this._addInterface(objectPath, interfaceNames[i]);
});
this._managerProxy.connectSignal('InterfacesRemoved', (_objectManager, _sender, [objectPath, interfaceNames]) => {
for (let i = 0; i < interfaceNames.length; i++)
this._removeInterface(objectPath, interfaceNames[i]);
});
if (Object.keys(this._interfaceInfos).length === 0) {
this._completeLoad();
return;
}
this._managerProxy.connect('notify::g-name-owner', () => {
if (this._managerProxy.g_name_owner)
this._onNameAppeared();
else
this._onNameVanished();
});
if (this._managerProxy.g_name_owner)
this._onNameAppeared().catch(logError);
}
async _onNameAppeared() {
try {
const [objects] = await this._managerProxy.GetManagedObjectsAsync();
if (!objects) {
this._completeLoad();
return;
}
const objectPaths = Object.keys(objects);
await Promise.allSettled(objectPaths.flatMap(objectPath => {
const object = objects[objectPath];
const interfaceNames = Object.getOwnPropertyNames(object);
return interfaceNames.map(ifaceName => this._addInterface(objectPath, ifaceName));
}));
}
catch (error) {
logError(error, `could not get remote objects for service ${this._serviceName} path ${this._managerPath}`);
}
finally {
this._completeLoad();
}
}
_onNameVanished() {
let objectPaths = Object.keys(this._objects);
for (let i = 0; i < objectPaths.length; i++) {
let objectPath = objectPaths[i];
let object = this._objects[objectPath];
let interfaceNames = Object.keys(object);
for (let j = 0; j < interfaceNames.length; j++) {
let interfaceName = interfaceNames[j];
if (object[interfaceName])
this._removeInterface(objectPath, interfaceName);
}
}
}
_registerInterfaces(interfaces) {
for (let i = 0; i < interfaces.length; i++) {
let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
this._interfaceInfos[info.name] = info;
}
}
getProxiesForInterface(interfaceName) {
let proxyList = this._interfaces[interfaceName];
if (!proxyList)
return [];
return proxyList;
}
}
Signals.addSignalMethods(ObjectManager.prototype);

73
js/model/OptionData.js Normal file
View File

@ -0,0 +1,73 @@
export class OptionData {
constructor(description, tooltip) {
this.tooltip = tooltip;
this.description = description;
}
static add(name, description, tooltip) {
OptionData.map.set(name, new OptionData(description, tooltip));
}
static description(name) {
var _a;
return (_a = OptionData.map.get(name)) === null || _a === void 0 ? void 0 : _a.description;
}
static tooltip(name) {
var _a;
return (_a = OptionData.map.get(name)) === null || _a === void 0 ? void 0 : _a.tooltip;
}
}
OptionData.map = new Map();
OptionData.add('use-gpu', 'Use GPU in Realm', `If enabled the render node device <tt><b>/dev/dri/renderD128</b></tt> will be mounted into the realm container.
If privileged device <tt><b>/dev/dri/card0</b></tt> is also needed set
additional variable in realm configuration file:
<tt><b>use-gpu-card0 = true</b></tt>
`);
OptionData.add('use-wayland', 'Use Wayland in Realm', `If enabled access to Wayland display will be permitted in realm by adding wayland socket to realm.
<tt><b>/run/user/1000/wayland-0</b></tt>
`);
OptionData.add('use-x11', 'Use X11 in Realm', `If enabled access to X11 server will be added by mounting directory X11 directory into realm.
<tt><b>/tmp/.X11-unix</b></tt>
`);
OptionData.add('use-sound', 'Use sound in Realm', `If enabled allows use of sound inside of realm. The following items will be added:
<tt><b>/dev/snd</b></tt>
<tt><b>/dev/shm</b></tt>
<tt><b>/run/user/1000/pulse</b></tt>
`);
OptionData.add('use-shared-dir', 'Mount /Shared directory in Realm', `If enabled the shared directory will be mounted as <tt><b>/Shared</b></tt> in home directory of realm.
This directory is shared between all realms with this option enabled and is an easy way to move files between realms.
`);
OptionData.add('use-network', 'Realm has network access', `If enabled the realm will have access to the network.`);
OptionData.add('use-kvm', 'Use KVM (/dev/kvm) in Realm', `If enabled device <tt><b>/dev/kvm</b></tt> will be added to realm`);
OptionData.add('use-ephemeral-home', 'Use ephemeral tmpfs mount for home directory', `If enabled the home directory of realm will be set up in ephemeral mode.
The ephemeral home directory is set up with the following steps:
1. Home directory is mounted as tmpfs filesystem
2. Any files in <tt><b>/realms/skel</b></tt> are copied into home directory
3. Any files in <tt><b>/realms/realm-$name/skel</b></tt> are copied into home directory.
4. Any directories listed in config file variable <tt><b>ephemeral_persistent_dirs</b></tt>
are bind mounted from <tt><b>/realms/realm-$name/home</b></tt> into ephemeral
home directory.
`);
OptionData.add('overlay', 'Type of rootfs overlay Realm is configured to use.', `<b><big>Overlay</big></b>
Type of rootfs overlay realm is configured to use.
<b>None</b> Don't use a rootfs overlay
<b>TmpFS</b> Use a rootfs overlay stored on tmpfs
<b>Storage</b> Use a rootfs overlay stored on disk in storage partition
`);
OptionData.add('realmfs', 'Root filesystem image to use for Realm.', `<b><big>RealmFS</big></b>
Root filesystem image to use for realm.`);
OptionData.add('colorscheme', 'Terminal Color Scheme', `<b><big>Terminal Color Scheme</big></b>
Choose a color scheme to use in the terminals in this realm.`);
OptionData.add('window-label-color', 'Window Label Color', `<b><big>Window Label Color</big></b>
Set a color to be used when a label is drawn on the window titlebar indicating which realm the application is running in.`);

94
js/model/Realm.js Normal file
View File

@ -0,0 +1,94 @@
var _a;
import GObject from 'gi://GObject';
import { RealmFS } from './RealmFS.js';
import { RealmConfig } from './RealmConfig.js';
export const RunStatus = {
STOPPED: 0,
STARTING: 1,
RUNNING: 2,
CURRENT: 3,
STOPPING: 4,
};
export class Realm extends GObject.Object {
constructor(proxy, labelColors) {
super();
this.config = new RealmConfig();
this._proxy = proxy;
this._labelColors = labelColors;
this._proxy.connect('g-properties-changed', (_proxy, _changed, _invalidated) => {
this._sync();
});
this._sync();
}
get realmfs() {
return this._realmfs;
}
set realmfs(value) {
if (this.realmfs === value) {
return;
}
this._realmfs = value;
this.emit('changed');
}
_sync() {
this._name = this._proxy.Name;
this._description = this._proxy.Description;
this._pidns = this._proxy.PidNS;
this._run_status = this._proxy.RunStatus;
this._is_system_realm = this._proxy.IsSystemRealm;
this.emit('changed');
}
get name() {
return this._name;
}
get description() {
return this._description;
}
get pidns() {
return this._pidns;
}
get run_status() {
return this._run_status;
}
get is_system_realm() {
return this._is_system_realm;
}
is_running() {
return this.run_status === RunStatus.RUNNING || this.run_status === RunStatus.CURRENT;
}
is_current() {
return this.run_status === RunStatus.CURRENT;
}
getLabelColor() {
return this._labelColors.lookupRealmColor(this);
}
setLabelColor(color) {
this._labelColors.updateRealmColor(this, color);
}
realmfs_index() {
return this._proxy.RealmFS;
}
set_global_config(config) {
this.config.set_global_config(config);
}
async load_config() {
const result = await this._proxy.GetConfigAsync();
this.config.set_config(result[0]);
this.emit('changed');
}
}
_a = Realm;
(() => {
GObject.registerClass({
GTypeName: 'Realm',
Properties: {
'name': GObject.ParamSpec.string('name', '', '', GObject.ParamFlags.READWRITE, ''),
'description': GObject.ParamSpec.string('description', '', '', GObject.ParamFlags.READWRITE, ''),
'pidns': GObject.ParamSpec.uint64('pidns', '', '', GObject.ParamFlags.READWRITE, 0, Number.MAX_SAFE_INTEGER, 0),
'run-status': GObject.ParamSpec.uint('run-status', '', '', GObject.ParamFlags.READWRITE, 0, 4, 0),
'is-system-realm': GObject.ParamSpec.boolean('is-system-realm', '', '', GObject.ParamFlags.READWRITE, false),
'realmfs': GObject.ParamSpec.object('realmfs', '', '', GObject.ParamFlags.READWRITE, RealmFS),
},
Signals: { 'changed': {} },
}, _a);
})();

94
js/model/RealmConfig.js Normal file
View File

@ -0,0 +1,94 @@
import { OptionData } from './OptionData.js';
export class BoolOptionData {
constructor(name) {
var _a, _b;
this.name = name;
this.description = (_a = OptionData.description(name)) !== null && _a !== void 0 ? _a : name;
this.tooltip = (_b = OptionData.tooltip(name)) !== null && _b !== void 0 ? _b : "";
}
static allOptions() {
let options = [];
BoolOptionData.ALL_OPTIONS.forEach(name => {
options.push(new BoolOptionData(name));
});
return options;
}
}
BoolOptionData.ALL_OPTIONS = [
'use-gpu',
'use-wayland',
'use-x11',
'use-sound',
'use-network',
'use-kvm',
'use-shared-dir',
'use-ephemeral-home',
];
export class ConfigOption {
constructor(name, value, defaultValue, description, tooltip) {
this.name = name;
this.value = value;
this.defaultValue = defaultValue;
this.description = description;
this.tooltip = tooltip;
}
}
export class RealmConfig {
constructor() {
this._configVars = {};
this._globalConfig = {};
}
set_config(config) {
this._configVars = config;
}
set_global_config(globalConfig) {
this._globalConfig = globalConfig;
}
get_var(name) {
let value = this._configVars[name];
if (value && value.length > 0) {
return value;
}
value = this._globalConfig[name];
if (value && value.length > 0) {
return value;
}
return null;
}
get_bool(name) {
return this.get_var(name) === 'true';
}
get_colorscheme() {
var _a;
return (_a = this.get_var('terminal-scheme')) !== null && _a !== void 0 ? _a : 'default-dark';
}
get_realmfs() {
var _a;
return (_a = this.get_var('realmfs')) !== null && _a !== void 0 ? _a : 'base';
}
static capitalize(term) {
const acronyms = ["gpu", "kvm"];
if (acronyms.find(s => term === s)) {
return term.toUpperCase();
}
else {
return term.charAt(0).toUpperCase() + term.slice(1);
}
}
static option_label(key) {
return key.substring(4)
.split('-')
.map(RealmConfig.capitalize)
.join('');
}
is_enabled_bool_option(name, value, isDefault) {
const default_val = this._globalConfig[name];
return name.startsWith("use-") && value === "true" && (value === default_val) === isDefault;
}
enabled_bool_option_labels(isDefault) {
return Object.entries(this._configVars)
.filter(([k, v]) => this.is_enabled_bool_option(k, v, isDefault))
.map(([k,]) => RealmConfig.option_label(k))
.sort();
}
}

69
js/model/RealmFS.js Normal file
View File

@ -0,0 +1,69 @@
var _a;
import GObject from 'gi://GObject';
const OBJECT_PREFIX = "/com/subgraph/Realms2/RealmFS";
export class RealmFS extends GObject.Object {
constructor(proxy) {
super();
this._proxy = proxy;
this._proxy.connect('g-properties-changed', (_proxy, _changed, _invalidated) => {
this._sync();
});
this._sync();
}
index() {
let path = this._proxy.get_object_path();
if (path.startsWith(OBJECT_PREFIX)) {
const tail = path.substring(OBJECT_PREFIX.length);
let index = Number.parseInt(tail);
if (!Number.isNaN(index)) {
return index;
}
}
return -1;
}
get name() {
return this._name;
}
get in_use() {
return this._in_use;
}
get activated() {
return this._activated;
}
get mountpoint() {
return this._mountpoint;
}
get path() {
return this._path;
}
get free_space() {
return this._free_space;
}
get allocated_space() {
return this._allocated_space;
}
_sync() {
this._name = this._proxy.Name;
this._in_use = this._proxy.InUse;
this._activated = this._proxy.Activated;
this._mountpoint = this._proxy.Mountpoint;
this._path = this._proxy.Path;
this._free_space = this._proxy.FreeSpace;
this._allocated_space = this._proxy.AllocatedSpace;
}
}
_a = RealmFS;
(() => {
GObject.registerClass({
GTypeName: 'RealmFS',
Properties: {
'name': GObject.ParamSpec.string('name', '', '', GObject.ParamFlags.READWRITE, ''),
'in-use': GObject.ParamSpec.boolean('in-use', '', '', GObject.ParamFlags.READWRITE, false),
'activated': GObject.ParamSpec.boolean('activated', '', '', GObject.ParamFlags.READWRITE, false),
'mountpoint': GObject.ParamSpec.string('mountpoint', '', '', GObject.ParamFlags.READWRITE, ''),
'path': GObject.ParamSpec.string('path', '', '', GObject.ParamFlags.READWRITE, ''),
'free-space': GObject.ParamSpec.uint64('free-space', '', '', GObject.ParamFlags.READWRITE, 0, Number.MAX_SAFE_INTEGER, 0),
'allocated-space': GObject.ParamSpec.uint64('allocated-space', '', '', GObject.ParamFlags.READWRITE, 0, Number.MAX_SAFE_INTEGER, 0),
},
}, _a);
})();

140
js/model/RealmManager.js Normal file
View File

@ -0,0 +1,140 @@
import Gio from 'gi://Gio';
import { ObjectManager } from './ObjectManager.js';
import { loadInterfaceXML } from './Utils.js';
import { Realm } from './Realm.js';
import { RealmFS } from './RealmFS.js';
import { LabelColorManager } from './LabelColors.js';
const Signals = imports.signals;
const RealmIface = loadInterfaceXML("com.subgraph.realms.Realm");
const RealmFSIface = loadInterfaceXML("com.subgraph.realms.RealmFS");
const BUS_NAME = 'com.subgraph.Realms2';
const OBJECT_PATH = '/com/subgraph/Realms2';
const ManagerInterface = loadInterfaceXML("com.subgraph.realms.Manager2");
const ManagerProxy = Gio.DBusProxy.makeProxyWrapper(ManagerInterface);
;
export class RealmManager {
static instance() {
return RealmManager.INSTANCE;
}
constructor() {
this._globalConfig = {};
this._realms = {};
this._realmfs = {};
this._realmfs = {};
this._objectManager = new ObjectManager({
connection: Gio.DBus.system,
name: 'com.subgraph.Realms2',
objectPath: '/com/subgraph/Realms2',
knownInterfaces: [RealmIface, RealmFSIface],
onLoaded: this._onLoaded.bind(this),
});
this._proxy = new ManagerProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH, (_proxy, error) => {
if (error) {
logError(error);
}
else {
this._setGlobalConfig().catch(logError);
}
});
this._labelColors = new LabelColorManager();
}
async _setGlobalConfig() {
let result = await this._proxy.GetGlobalConfigAsync();
this._globalConfig = result[0];
this._assignGlobalConfigToRealms();
}
async _onLoaded() {
let realms = this._objectManager.getProxiesForInterface('com.subgraph.realms.Realm');
for (let i = 0; i < realms.length; i++) {
this._addRealm(realms[i]);
}
let realmfs = this._objectManager.getProxiesForInterface('com.subgraph.realms.RealmFS');
for (let i = 0; i < realmfs.length; i++) {
this._addRealmFS(realmfs[i]);
}
this._objectManager.connect('interface-added', (_objectManager, interfaceName, proxy) => {
if (interfaceName === 'com.subgraph.Realm') {
this._addRealm(proxy);
}
else if (interfaceName === 'com.subgraph.RealmFS') {
this._addRealmFS(proxy);
}
});
this._objectManager.connect('interface-removed', (_objectManager, interfaceName, proxy) => {
if (interfaceName === 'com.subgraph.Realm') {
this._removeRealm(proxy);
}
else if (interfaceName === 'com.subgraph.RealmFS') {
this._removeRealmFS(proxy);
}
});
}
async _addRealm(proxy) {
let objectPath = proxy.get_object_path();
let realm = new Realm(proxy, this._labelColors);
this._realms[objectPath] = realm;
this._findRealmFSForRealm(realm);
if (this._globalConfig) {
realm.set_global_config(this._globalConfig);
}
await realm.load_config();
this.emit('realm-added', realm);
}
_removeRealm(proxy) {
var _a;
let objectPath = proxy.get_object_path();
if (((_a = this._realms[objectPath]) === null || _a === void 0 ? void 0 : _a._proxy) === proxy) {
let realm = this._realms[objectPath];
delete this._realms[objectPath];
this.emit('realm-removed', realm);
}
proxy.disconnectAll();
}
_addRealmFS(proxy) {
let objectPath = proxy.get_object_path();
let realmfs = new RealmFS(proxy);
this._realmfs[objectPath] = realmfs;
this._assignRealmFSToRealms(realmfs);
this.emit('realmfs-added', realmfs);
}
_findRealmFSForRealm(realm) {
var _a;
if (realm.realmfs_index() > 0) {
realm.realmfs = (_a = Object.values(this._realmfs)
.find(realmfs => realmfs.index() === realm.realmfs_index())) !== null && _a !== void 0 ? _a : null;
}
}
realmfsList() {
return Object.values(this._realmfs);
}
_assignRealmFSToRealms(realmfs) {
Object.values(this._realms).forEach(r => {
if (r.realmfs_index() > 0 && r.realmfs_index() === realmfs.index() && r.realmfs != realmfs) {
r.realmfs = realmfs;
}
});
}
_assignGlobalConfigToRealms() {
Object.values(this._realms).forEach(r => r.set_global_config(this._globalConfig));
}
_removeRealmFSFromRealms(realmfs) {
Object.values(this._realms).forEach(r => {
if (r.realmfs === realmfs) {
r.realmfs = null;
}
});
}
_removeRealmFS(proxy) {
var _a;
let objectPath = proxy.get_object_path();
if (((_a = this._realmfs[objectPath]) === null || _a === void 0 ? void 0 : _a._proxy) === proxy) {
let realmfs = this._realmfs[objectPath];
delete this._realmfs[objectPath];
this._removeRealmFSFromRealms(realmfs);
this.emit('realmfs-removed', realmfs);
}
proxy.disconnectAll();
}
}
RealmManager.INSTANCE = new RealmManager();
Signals.addSignalMethods(RealmManager.prototype);

14
js/model/Utils.js Normal file
View File

@ -0,0 +1,14 @@
import Gio from 'gi://Gio';
export function loadInterfaceXML(iface) {
let uri = `resource:///com/subgraph/citadel/Realms/dbus-interfaces/${iface}.xml`;
let f = Gio.File.new_for_uri(uri);
try {
let [_ok, bytes] = f.load_contents(null);
// @ts-ignore
return new TextDecoder().decode(bytes);
}
catch (e) {
log(`Failed to load D-Bus interface ${iface}`);
}
return null;
}

16
meson.build Normal file
View File

@ -0,0 +1,16 @@
project(
'Realms',
version: '0.0.1',
license: ['GLP-Stallmans-Razor'],
meson_version: '>= 0.59.0'
)
APP_ID = 'com.subgraph.citadel.Realms'
gnome = import('gnome')
#tsc = find_program('tsc', required: true)
subdir('data')
subdir('src')

27
package-lock.json generated Normal file
View File

@ -0,0 +1,27 @@
{
"name": "Realms",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Realms",
"version": "1.0.0",
"dependencies": {
"typescript": "^5.3.3"
}
},
"node_modules/typescript": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}

12
package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "Realms",
"version": "1.0.0",
"main": "src/main.ts",
"dependencies": {
"typescript": "^5.3.3"
},
"scripts": {
"build": "tsc --strict",
"typecheck": "tsc --strict --noEmit"
}
}

120
src/Application.ts Normal file
View File

@ -0,0 +1,120 @@
import Adw from 'gi://Adw';
import Gtk from 'gi://Gtk?version=4.0';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import './model/RealmManager.js';
import './model/Realm.js';
import './RealmsView.js';
import './RealmRow.js';
import './RealmInfo.js';
import './RealmModel.js';
import './ConfigureRealm.js';
import { Window } from './Window.js';
export class Application extends Adw.Application {
static {
GObject.registerClass({
GTypeName: 'RealmsApplication'
}, this);
}
_window?: Window;
constructor() {
super({
application_id: 'com.subgraph.citadel.Realms',
flags: Gio.ApplicationFlags.DEFAULT_FLAGS,
})
}
vfunc_activate() {
let {activeWindow} = this;
if (!activeWindow) {
activeWindow = new Window(this);
activeWindow.set_hide_on_close(true);
}
activeWindow.present();
}
vfunc_startup() {
super.vfunc_startup();
this.#setupActions();
this.#setupAccelerators();
// this.#loadStyleSheet();
const styleManager = Adw.StyleManager.get_default();
styleManager.colorScheme = Adw.ColorScheme.FORCE_DARK;
}
/*
#loadStyleSheet() {
const provider = new Gtk.CssProvider();
provider.load_from_resource('/com/subgraph/citadel/Realms/css/style.css');
const display = Gdk.Display.get_default();
if (display) {
Gtk.StyleContext.add_provider_for_display(
display,
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
}
}
*/
#setupAccelerators() {
this.set_accels_for_action('app.quit', ['q']);
this.set_accels_for_action('app.realmConfig', ['c']);
this.set_accels_for_action('app.showHelp', ['h','question']);
}
#setupActions() {
this.add_action_entries([
// @ts-ignore
{ name: 'quit', activate: this.#onQuit.bind(this) },
// @ts-ignore
{ name: 'realmConfig', activate: this.#onRealmConfig.bind(this) },
// @ts-ignore
{ name: 'showHelp', activate: this.#onShowHelp.bind(this) },
// @ts-ignore
{ name: 'about', activate: this.#onAbout.bind(this) },
]);
}
#onQuit() {
let {activeWindow} = this;
if (activeWindow) {
activeWindow.close();
}
}
#onRealmConfig() {
let {activeWindow} = this;
if (activeWindow) {
let window = activeWindow as Window;
const realm = window.realms_view.selectedRealm;
if (realm) {
window.configureRealm(realm);
}
}
}
#onShowHelp() {
const help: Gtk.ShortcutsWindow = Gtk.Builder.new_from_resource('/com/subgraph/citadel/Realms/ui/HelpWindow.ui').get_object('helpWindow');
help.set_transient_for(this._window);
help.present();
}
#onAbout() {
const dialog = new Adw.AboutDialog({
application_icon: 'face-smile',
application_name: 'Realms',
developer_name: "Subgraph",
})
dialog.present(this._window);
}
}

225
src/ColorSchemeChooser.ts Normal file
View File

@ -0,0 +1,225 @@
import Adw from 'gi://Adw';
import GObject from 'gi://GObject';
import GLib from 'gi://GLib';
import Gdk from 'gi://Gdk?version=4.0';
import Gtk from 'gi://Gtk?version=4.0';
import {Base16Theme} from './model/Base16Themes.js';
import {ColorSchemeModel} from './ColorSchemeModel.js';
class PreviewRenderer {
private theme: Base16Theme;
private buffer: string;
constructor(theme: Base16Theme) {
this.theme = theme;
this.buffer = '';
}
colorAttrib(name: string, color: string) {
this.buffer += ` ${name}='${color}'`;
}
colorSpan(fg: string, bg = null) {
this.buffer += '<span';
if (fg) {
this.colorAttrib("foreground", fg);
}
if (bg) {
this.colorAttrib("background", bg);
}
this.buffer += ">";
}
endSpan() {
this.buffer += '</span>';
}
nl() {
this.buffer += ' \n ';
return this;
}
print(idx: number, text: string) {
let s = GLib.markup_escape_text(text, text.length);
let c = this.theme.terminal_palette_color(idx);
this.colorSpan(c);
this.buffer += s;
this.endSpan();
return this;
}
vtype(text: string) {
return this.print(3, text);
}
konst(text: string) {
return this.print(1, text);
}
func(text: string) {
return this.print(4, text);
}
string(text: string) {
return this.print(2, text);
}
keyword(text: string) {
return this.print(5, text);
}
comment(text: string) {
return this.print(8, text);
}
text(text: string) {
let color = this.theme.terminal_foreground();
this.colorSpan(color);
this.buffer += text;
this.endSpan();
return this;
}
renderPreview() {
let name = this.theme.name;
this.nl()
.comment('/**').nl()
.comment(' * An example of how this color scheme').nl()
.comment(' * might look in a text editor with syntax').nl()
.comment(' * highlighting.').nl()
.comment(' */').nl()
.nl()
.func('#include ').string('<stdio.h>').nl()
.func('#include ').string('<stdlib.h>').nl()
.nl()
.vtype('static char').text(' theme[] = ').string(`"${name}"`).text(';').nl()
.nl()
.vtype('int').text(' main(').vtype('int').text(' argc, ').vtype('char').text(' **argv) {').nl()
.text(' printf(').string('"Hello, ').keyword('%s').text('!').keyword('\\n').string('"').text(', theme);').nl()
.text(' exit(').konst('0').text(');').nl()
.text('}')
.nl();
return this.buffer;
}
}
export class ColorSchemeChooser extends Adw.NavigationPage{
private _colorList!: Gtk.ListView;
private _previewLabel!: Gtk.Label;
css_provider: Gtk.CssProvider;
model: ColorSchemeModel;
static {
GObject.registerClass({
GTypeName: 'ColorSchemeChooser',
Template: 'resource:///com/subgraph/citadel/Realms/ui/ColorSchemeChooser.ui',
InternalChildren: ['colorList', 'previewLabel'],
}, this);
}
addExpandToggleShortcut(keyval: number) {
let trigger = Gtk.KeyvalTrigger.new(keyval, Gdk.ModifierType.NO_MODIFIER_MASK);
let action = Gtk.CallbackAction.new((expander) => expander.activate_action('listitem.toggle-expand', null));
Gtk.TreeExpander.add_shortcut(Gtk.Shortcut.new(trigger, action));
}
constructor() {
super();
this.model = new ColorSchemeModel();
this._previewLabel.add_css_class("preview");
this.css_provider = new Gtk.CssProvider();
const display = Gdk.Display.get_default();
if (display) {
Gtk.StyleContext.add_provider_for_display(
display,
this.css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
}
this.addExpandToggleShortcut(Gdk.KEY_space);
this.addExpandToggleShortcut(Gdk.KEY_l);
this._colorList.connect('activate', () => {
this.previewSelectedTheme();
})
this._colorList.single_click_activate = true;
this._colorList.model = this.model.selection;
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', (_, keyval) => {
switch (keyval) {
case Gdk.KEY_j:
case Gdk.KEY_Down:
this.nextScheme();
break;
case Gdk.KEY_k:
case Gdk.KEY_Up:
this.prevScheme();
break;
}
});
this._colorList.add_controller(keyController);
}
nextScheme() {
let pos = this.model.changeSelected(1);
if (pos) {
this._colorList.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, null);
}
}
prevScheme() {
let pos = this.model.changeSelected(-1);
if(pos) {
this._colorList.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, null);
}
}
selectTheme(id?: string) {
const DEFAULT_THEME = '3024';
let row = this.model.searchId(id ?? DEFAULT_THEME);
if (row) {
let pos = row.get_position();
this.model.selectPosition(pos);
this._colorList.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, null);
this.previewSelectedTheme();
}
}
getSelectedTheme() {
return this.model.selectedTheme();
}
previewSelectedTheme() {
let theme = this.model.selectedTheme();
if (theme) {
this.setBackgroundColor(theme);
let renderer = new PreviewRenderer(theme);
// @ts-ignore
this._previewLabel.label = renderer.renderPreview();
}
}
setBackgroundColor(theme: Base16Theme) {
let css = `
label.preview {
background-color: ${theme.terminal_background()};
font-family: monospace;
font-size: 14pt;
}`;
this.css_provider.load_from_string(css);
}
}

176
src/ColorSchemeModel.ts Normal file
View File

@ -0,0 +1,176 @@
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk?version=4.0';
import {Base16Theme} from './model/Base16Themes.js';
class ModelBuilder {
CATEGORIES = [
['atelier', 'Atelier'],
['black-metal', 'Black Metal'],
['brushtrees', 'Brush Trees'],
['classic', 'Classic'],
['default', 'Default'],
['google', 'Google'],
['grayscale', 'Grayscale'],
['gruvbox', 'Gruvbox'],
['harmonic', 'Harmonic'],
['ia', 'iA'],
['material', 'Material'],
['papercolor', 'PaperColor'],
['solarized', 'Solarized'],
['summerfruit', 'Summerfruit'],
['tomorrow', 'Tomorrow'],
['unikitty', 'Unikitty'],
];
currentCategory: null | ColorSchemeNode = null;
storeModel = new Gio.ListStore(ColorSchemeNode);
matchesCategory(theme: Base16Theme): boolean {
return (this.CATEGORIES.length > 0 && theme.id.startsWith(this.CATEGORIES[0][0]))
}
appendCurrentCategory() {
if (this.currentCategory) {
this.storeModel.append(this.currentCategory);
this.currentCategory = null;
this.CATEGORIES.shift();
}
}
addToCategory(theme: Base16Theme) {
if (!this.currentCategory) {
this.currentCategory = new ColorSchemeNode(this.CATEGORIES[0][1]);
}
this.currentCategory.addChild(new ColorSchemeNode(theme.name, theme));
}
addTheme(theme: Base16Theme) {
if (this.matchesCategory(theme)) {
this.addToCategory(theme);
} else {
if (this.currentCategory) {
this.appendCurrentCategory();
}
this.storeModel.append(new ColorSchemeNode(theme.name, theme));
}
}
static buildTreeModel(): Gtk.TreeListModel {
let builder = new ModelBuilder();
Base16Theme.THEME_LIST.forEach(theme => builder.addTheme(theme));
return Gtk.TreeListModel.new(
builder.storeModel,
false,
false,
item => (item as ColorSchemeNode).child_model,
);
}
}
export class ColorSchemeNode extends GObject.Object {
private _text: string;
theme: Base16Theme | null;
child_model: Gio.ListStore | null;
static {
GObject.registerClass({
GTypeName: 'ColorSchemeNode',
Properties: {
'text': GObject.ParamSpec.string('text', '', '', GObject.ParamFlags.READWRITE, ''),
}
}, this);
}
constructor(text: string, theme: Base16Theme | null = null) {
super();
this._text = text;
this.theme = theme;
this.child_model = null;
}
get text(): string {
return this._text;
}
addChild(child: ColorSchemeNode) {
if(this.child_model === null) {
this.child_model = new Gio.ListStore(ColorSchemeNode);
}
this.child_model.append(child);
}
matchesId(id: string): boolean {
return this.theme !== null && this.theme.id === id;
}
searchChildren(id: string): boolean {
if (this.child_model) {
for(let i = 0; i < this.child_model.n_items; i++) {
let node = this.child_model.get_item(i) as ColorSchemeNode;
if (node.matchesId(id)) {
return true;
}
}
}
return false;
}
}
export class ColorSchemeModel {
selection: Gtk.SingleSelection;
treeModel: Gtk.TreeListModel;
constructor() {
this.treeModel = ModelBuilder.buildTreeModel();
this.selection = Gtk.SingleSelection.new(this.treeModel);
}
selectedTheme() {
// @ts-ignore
let item = this.selection.selected_item.item;
return item?.theme;
}
changeSelected(offset: number) {
const n_items = this.selection.n_items;
if (n_items <= 1) {
return;
}
const pos = this.selection.selected;
if (pos == Gtk.INVALID_LIST_POSITION) {
return;
}
const newPos = pos + offset;
if (newPos < 0 || newPos >= n_items) {
return;
}
this.selection.selected = newPos;
return newPos;
}
selectPosition(pos: number) {
this.selection.selected = pos;
}
searchId(id: string, from = 0): Gtk.TreeListRow | null {
for(let i = from; i < this.treeModel.n_items; i++) {
let row = this.treeModel.get_row(i);
let item = row?.item as ColorSchemeNode;
if (item && item.matchesId(id)) {
return row;
}
if (item.searchChildren(id)) {
row?.set_expanded(true);
if(from === 0) {
return this.searchId(id, i);
}
}
}
return null;
}
}

189
src/ConfigureRealm.ts Normal file
View File

@ -0,0 +1,189 @@
import GObject from 'gi://GObject'
import Gdk from 'gi://Gdk?version=4.0'
import Gtk from 'gi://Gtk?version=4.0'
import Adw from 'gi://Adw';
import {Realm} from './model/Realm.js';
import {BoolOptionData} from './model/RealmConfig.js';
import {Base16Theme} from './model/Base16Themes.js';
import { RealmManager } from './model/RealmManager.js';
import {ColorSchemeChooser} from './ColorSchemeChooser.js';
class OptionState {
value: any = null;
originalValue: any;
row: Adw.SwitchRow;
constructor(row: Adw.SwitchRow) {
this.row = row;
}
initValue(value: any) {
this.value = value;
this.originalValue = value;
this.row.active = value;
}
/** @param {any} value */
setValue(value: any) {
this.value = value;
}
hasChanged() {
return this.value !== this.originalValue;
}
}
export class ConfigureRealm extends Adw.NavigationPage {
static {
GObject.registerClass({
GTypeName: "ConfigureRealm",
Template: 'resource:///com/subgraph/citadel/Realms/ui/ConfigureRealm.ui',
InternalChildren: ['optionsGroup', 'changedBanner', 'overlayCombo', 'realmfsCombo', 'colorSchemeButton', 'labelColorButton'],
Properties: {
'navigation-view': GObject.ParamSpec.object('navigation-view', '', '', GObject.ParamFlags.READWRITE, Adw.NavigationView),
'colorscheme-chooser': GObject.ParamSpec.object('colorscheme-chooser', '', '', GObject.ParamFlags.READWRITE, ColorSchemeChooser),
}
}, this);
}
realm: Realm | null = null;
private _colorSchemeButton!: Gtk.Button ;
private _labelColorButton!: Gtk.ColorDialogButton;
private _optionsGroup!: Adw.PreferencesGroup;
private _changedBanner!: Adw.Banner;
private _navigationView!: Adw.NavigationView;
private _colorschemeChooser!: ColorSchemeChooser;
optionState: Map<string, OptionState> = new Map();
theme = Base16Theme.lookup('default-dark');
constructor() {
super();
this._addOptions();
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', (_, keyval) => {
if (keyval === Gdk.KEY_j) {
this.vfunc_move_focus(Gtk.DirectionType.TAB_FORWARD);
} else if (keyval === Gdk.KEY_k) {
this.vfunc_move_focus(Gtk.DirectionType.TAB_BACKWARD);
}
});
this.add_controller(keyController);
this._labelColorButton?.connect('notify::rgba', () => this._scanChanges());
this._colorSchemeButton?.connect('clicked', () => {
this.navigationView?.push_by_tag('realm-colorscheme');
});
}
get colorschemeChooser() {
return this._colorschemeChooser;
}
set colorschemeChooser(val) {
this._colorschemeChooser = val;
}
set navigationView(val) {
this._navigationView = val;
this._navigationView?.connect('popped', (_view, page) => {
if (page === this.colorschemeChooser) {
let theme = this.colorschemeChooser.getSelectedTheme();
if (theme && this._colorSchemeButton) {
this.theme = theme;
this._colorSchemeButton.label = this.theme.name;
}
}
});
}
get navigationView() {
return this._navigationView;
}
configure(realm: Realm) {
this.realm = realm;
this.set_title(`Configure realm-${realm.name}`);
this._setOptions(realm);
}
_setOptions(realm: Realm) {
let config = realm.config;
this.optionState.forEach((op,name) => {
let v = config.get_bool(name);
op.initValue(v);
})
let scheme = realm.config.get_colorscheme();
this.theme = Base16Theme.lookup(scheme);
if (this.theme && this._colorSchemeButton) {
this._colorSchemeButton.label = this.theme.name;
this.colorschemeChooser?.selectTheme(this.theme.id);
}
let realmfs_list = RealmManager.instance().realmfsList();
// @ts-ignore
let model = this._realmfsCombo.model;
if(model.n_items > 0) {
model.splice(0, model.n_items, []);
}
realmfs_list.forEach(realmfs => model.append(realmfs.name));
let labelColor = realm.getLabelColor();
this._labelColorButton.rgba = labelColor;
this._scanChanges();
}
_addOptions() {
BoolOptionData.allOptions().forEach(option => {
let row = new Adw.SwitchRow({
title: option.description,
});
if (option.tooltip.length > 0) {
row.tooltipMarkup = option.tooltip;
}
let state = new OptionState(row);
this.optionState.set(option.name, state);
row.connect("notify::active", () => {
state.setValue(row.active);
this._scanChanges();
});
this._optionsGroup.add(row);
})
}
_scanChanges() {
let changed = false;
this.optionState.forEach(op => {
if (op.hasChanged()) {
changed = true;
}
});
let labelColor = this.realm?.getLabelColor();
if (labelColor) {
if (!this._labelColorButton.rgba.equal(labelColor)) {
changed = true;
}
}
this._changedBanner.set_revealed(changed);
}
_onApplyClicked() {
print("clikkk");
}
}

147
src/RealmInfo.ts Normal file
View File

@ -0,0 +1,147 @@
import GObject from 'gi://GObject'
import Gtk from 'gi://Gtk?version=4.0';
import {Realm} from "./model/Realm.js";
import {RealmInfoEntry} from './RealmInfoEntry.js';
const Sections = {
STATUS: 'Status',
OPTIONS: 'Options',
REALMFS: 'RealmFS',
MOUNTPOINT: 'RealmFS Mountpoint',
};
export class RealmInfo extends Gtk.ScrolledWindow {
private _column!: Gtk.Box;
private _columnTitle!: Gtk.Label;
static {
GObject.registerClass({
GTypeName: "RealmInfo",
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmInfo.ui',
Properties: {
'realm': GObject.ParamSpec.object('realm', '', '', GObject.ParamFlags.READWRITE, Realm),
},
InternalChildren: [
'column',
'columnTitle',
]
}, this);
}
_buffer = "";
_changeId: number = 0;
_realm: Realm | null = null;
_entries: Map<string, RealmInfoEntry> = new Map();
constructor() {
super();
this._addEntries({
left: [
Sections.STATUS,
Sections.REALMFS,
Sections.MOUNTPOINT,
Sections.OPTIONS,
],
right: []
});
}
_addEntries(labels: { left: string[]; right: string[]; }) {
let addSectionEntries = (section: Gtk.Box, entries: string[]) => {
entries.forEach(name => {
let entry = new RealmInfoEntry(name);
this._entries.set(name, entry);
section.append(entry);
})
};
addSectionEntries(this._column, labels['left']);
}
get realm(): Realm | null {
return this._realm;
}
set realm(value) {
if (this.realm === value) {
return;
}
if (this._realm) {
this._realm.disconnect(this._changeId);
}
this._realm = value;
if (this._realm) {
this._changeId = this._realm.connect('changed', () => {
this.displayRealm();
});
this.displayRealm();
}
}
setEntry(name: string, value: string) {
let entry = this._entries.get(name);
if (entry) {
entry.setValue(value);
}
}
setEntryVisible(name: string, isVisible: boolean = true) {
let entry = this._entries.get(name);
if (entry) {
entry.set_visible(isVisible);
}
}
displayConfig() {
let config = this._realm?.config;
if (config) {
let enabled_default = config.enabled_bool_option_labels(true);
let enabled = config.enabled_bool_option_labels(false);
let buffer = enabled_default.join(' ');
buffer += '\n';
buffer += enabled.map(s => `<u>${s}</u>`)
.join(' ');
this.setEntry(Sections.OPTIONS, buffer);
} else {
this.setEntry(Sections.OPTIONS, '');
}
}
displayRealmFS() {
let realmfs = this._realm?.realmfs;
if (realmfs) {
this.setEntryVisible(Sections.REALMFS);
this.setEntry(Sections.REALMFS, `${realmfs.name}-realmfs.img`);
if(realmfs.activated) {
this.setEntryVisible(Sections.MOUNTPOINT);
this.setEntry(Sections.MOUNTPOINT, realmfs.mountpoint);
} else {
this.setEntryVisible(Sections.MOUNTPOINT, false);
}
} else {
this.setEntryVisible(Sections.REALMFS, false);
this.setEntryVisible(Sections.MOUNTPOINT, false);
}
}
displayRealm() {
if (this._realm) {
this._columnTitle.label = `Realm ${this._realm.name}`;
if (this._realm.is_running()) {
this.setEntry("Status", "Running");
} else {
this.setEntry("Status", "Stopped");
}
this.displayConfig();
this.displayRealmFS();
}
}
}

24
src/RealmInfoEntry.ts Normal file
View File

@ -0,0 +1,24 @@
import GObject from 'gi://GObject'
import Gtk from 'gi://Gtk?version=4.0';
export class RealmInfoEntry extends Gtk.Box {
private _nameLabel!: Gtk.Label;
private _valueLabel!: Gtk.Label;
static {
GObject.registerClass({
GTypeName: "RealmInfoEntry",
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmInfoEntry.ui',
InternalChildren: ['nameLabel', 'valueLabel']
}, this);
}
constructor(name: string) {
super();
this._nameLabel.label = name;
}
setValue(value: string) {
this._valueLabel.label = value;
}
}

49
src/RealmModel.ts Normal file
View File

@ -0,0 +1,49 @@
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import { Realm } from './model/Realm.js';
import {RealmManager} from "./model/RealmManager.js";
export class RealmModel extends GObject.Object {
static {
GObject.registerClass({
GTypeName: 'RealmModel',
Implements: [Gio.ListModel],
}, this);
}
_realms: Realm[] = [];
_realmManager: RealmManager;
constructor() {
super();
this._realmManager = RealmManager.instance();
this._realmManager.connect('realm-added', (_manager, realm: Realm) => {
let pos = this._realms.length;
this._realms.push(realm);
// @ts-ignore
this.items_changed(pos, 0, 1);
});
this._realmManager.connect('realm-removed', (_manager, realm) => {
let pos = this._realms.findIndex(r => r === realm);
if (pos >= 0) {
this._realms.splice(pos, 1);
// @ts-ignore
this.items_changed(pos, 1, 0);
}
});
}
vfunc_get_item_type() {
return Realm;
}
vfunc_get_item(position: number) {
return this._realms[position] || null;
}
vfunc_get_n_items() {
return this._realms.length;
}
}

59
src/RealmRow.ts Normal file
View File

@ -0,0 +1,59 @@
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import { Realm } from './model/Realm.js';
export class RealmRow extends Gtk.Widget {
private _nameLabel!: Gtk.Label;
static {
GObject.registerClass({
GTypeName: 'RealmRow',
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmRow.ui',
Properties: {
'realm': GObject.ParamSpec.object('realm', '','',GObject.ParamFlags.READWRITE, Realm),
},
InternalChildren: ['nameLabel'],
}, this);
}
_realm!: Realm | null;
_notifyId = 0;
get realm() {
if (this._realm === undefined) {
this._realm = null;
}
return this._realm;
}
set realm(value) {
if(this.realm === value) {
return;
}
if(this.realm && this._notifyId) {
this.realm.disconnect(this._notifyId);
this._notifyId = 0;
}
this._realm = value;
if(this.realm) {
this._notifyId = this.realm.connect('notify', this.syncRealm.bind(this));
this.syncRealm();
}
this.notify('realm');
}
syncRealm() {
if (this.realm && this.realm.is_running()) {
this._nameLabel.remove_css_class('dim-label');
} else {
// @ts-ignore
this._nameLabel.add_css_class('dim-label');
}
}
}

106
src/RealmsView.ts Normal file
View File

@ -0,0 +1,106 @@
import GObject from 'gi://GObject';
import Gdk from 'gi://Gdk?version=4.0';
import Gtk from 'gi://Gtk?version=4.0';
import {Realm} from "./model/Realm.js";
export class RealmsView extends Gtk.Widget {
private _realmsSelection!: Gtk.SingleSelection;
private _hiddenRealmsFilterModel!: Gtk.FilterListModel;
private _realmSorter!: Gtk.CustomSorter;
static {
GObject.registerClass({
GTypeName: 'RealmsView',
Template: 'resource:///com/subgraph/citadel/Realms/ui/RealmsView.ui',
Properties: {
'selected-realm': GObject.ParamSpec.object('selected-realm', '', '', GObject.ParamFlags.READWRITE, Realm),
},
InternalChildren: ['realmsSelection', 'hiddenRealmsFilterModel', 'realmSorter'],
}, this);
}
_selectedRealm = null;
constructor() {
super();
this._setupHiddenRealmsFilter();
this._setupRealmSorter();
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', (_, keyval) => {
switch (keyval) {
case Gdk.KEY_j:
case Gdk.KEY_Down:
this.nextRealm();
break;
case Gdk.KEY_k:
case Gdk.KEY_Up:
this.prevRealm();
break;
case Gdk.KEY_Escape:
this.activate_action('app.quit', null);
print ("escaped");
break;
}
});
this.add_controller(keyController);
}
_changeSelected(offset: number) {
const n_items = this._realmsSelection.n_items;
if (n_items <= 1) {
return;
}
const pos = this._realmsSelection.selected;
if (pos == Gtk.INVALID_LIST_POSITION) {
return;
}
const newPos = pos + offset;
if (newPos < 0 || newPos >= n_items) {
return;
}
this._realmsSelection.selected = newPos;
}
nextRealm() {
this._changeSelected(1);
}
prevRealm() {
this._changeSelected(-1);
}
get selectedRealm() {
return this._selectedRealm;
}
set selectedRealm(value) {
if (this.selectedRealm === value) {
return;
}
this._selectedRealm = value;
}
_setupHiddenRealmsFilter() {
this._hiddenRealmsFilterModel.filter = Gtk.CustomFilter.new(item => !(item as Realm).is_system_realm);
}
_setupRealmSorter() {
// 1) Sort 'Current' the lowest (top of list).
// 2) Then sort 'Running' lower than not 'Running'
// 3) Realms in the same run state sorted by lowest timestamp
this._realmSorter.set_sort_func((a,b) => {
if (a.is_current || (a.is_running && !b.is_running)) {
return Gtk.Ordering.SMALLER;
} else if (b.is_current || (b.is_running && !a.is_running)) {
return Gtk.Ordering.LARGER;
} else {
return b.timestamp - a.timestamp;
}
});
}
}

41
src/Window.ts Normal file
View File

@ -0,0 +1,41 @@
import GObject from 'gi://GObject';
import Adw from 'gi://Adw';
import { Realm } from './model/Realm.js';
import {ConfigureRealm} from './ConfigureRealm.js'
import { RealmsView } from './RealmsView.js';
export class Window extends Adw.ApplicationWindow {
private _realmsView!: RealmsView;
private _configureRealm!: ConfigureRealm;
private _navView!: Adw.NavigationView;
static {
GObject.registerClass({
GTypeName: 'RealmsWindow',
Template: 'resource:///com/subgraph/citadel/Realms/ui/Window.ui',
InternalChildren: ['realmsView', 'configureRealm', 'navView'],
}, this);
}
constructor(application: Adw.Application) {
super({ application: application });
}
configureRealm(realm: Realm) {
this._configureRealm.configure(realm);
this._navView.push(this._configureRealm);
}
get realms_view() {
return this._realmsView;
}
vfunc_close_request() {
super.vfunc_close_request();
this.run_dispose();
return true;
}
}

1097
src/colors/Base16Theme.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,147 @@
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk?version=4.0';
import { Base16Theme } from './Base16Theme.js';
export class ColorSchemeModel {
selection: Gtk.SingleSelection;
treeModel: Gtk.TreeListModel;
constructor() {
let builder = new TreeModelBuilder();
this.treeModel = builder.buildTreeModel();
this.selection = Gtk.SingleSelection.new(this.treeModel);
}
}
class ColorModelNode extends GObject.Object {
static {
GObject.registerClass({
GTypeName: 'ColorModelNode',
Properties: {
'text': GObject.ParamSpec.string('text', '', '', GObject.ParamFlags.READWRITE, ''),
}
}, this);
}
data: Base16Theme | ColorThemeCategory;
constructor(data: Base16Theme | ColorThemeCategory) {
super();
this.data = data;
}
get text(): string {
return this.data.name;
}
child_model() {
if (this.data instanceof ColorThemeCategory) {
return this.data.children;
} else {
return null;
}
}
isThemeWithId(id: string): boolean {
return (this.data instanceof Base16Theme) &&
(this.data.id === id);
}
hasChildThemeWithId(id: string): boolean {
const child_model = this.child_model();
if (child_model) {
for (let i = 0; i < child_model.n_items; i++) {
let node = child_model.get_item(i) as ColorModelNode;
if (node.isThemeWithId(id)) {
return true;
}
}
}
return false;
}
static newScheme(color: Base16Theme) {
return new ColorModelNode(color);
}
static newCategory(category: ColorThemeCategory) {
return new ColorModelNode(category);
}
}
class ColorThemeCategory {
name: string;
children: Gio.ListStore;
constructor(name: string) {
this.name = name;
this.children = new Gio.ListStore(ColorModelNode);
}
append(color: Base16Theme) {
this.children.append(ColorModelNode.newScheme(color));
}
}
class TreeModelBuilder {
categories: [string,string][];
currentCategory: null | ColorThemeCategory;
storeModel: Gio.ListStore;
constructor() {
this.categories = Base16Theme.CATEGORIES;
this.currentCategory = null;
this.storeModel = new Gio.ListStore(ColorModelNode);
}
buildTreeModel() {
Base16Theme.THEMES.forEach(theme => this.addTheme(theme));
return Gtk.TreeListModel.new(
this.storeModel,
false,
false,
item => (item as ColorModelNode).child_model(),
);
}
appendCurrentCategory() {
if (this.currentCategory) {
this.storeModel.append(ColorModelNode.newCategory(this.currentCategory));
this.currentCategory = null;
this.categories.shift();
}
}
matchesNextCategory(theme: Base16Theme): boolean {
if (this.categories.length === 0) {
return false;
}
let [id, _name] = this.categories[0];
return theme.id === id;
}
addTheme(theme: Base16Theme) {
if (this.matchesNextCategory(theme)) {
this.addToCategory(theme);
return;
}
if (this.currentCategory) {
this.appendCurrentCategory();
}
this.storeModel.append(ColorModelNode.newScheme(theme));
}
addToCategory(theme: Base16Theme) {
if (!this.currentCategory) {
let [_id, name] = this.categories[0];
this.currentCategory = new ColorThemeCategory(name);
}
this.currentCategory.append(theme);
}
}

View File

@ -0,0 +1,27 @@
#!@GJS@ -m
import GLib from 'gi://GLib'
// Initialize the package
imports.package.init({
name: '@PACKAGE_NAME@',
version: '@PACKAGE_VERSION@',
prefix: '@PREFIX@',
libdir: '@LIBDIR@',
});
// Import the main module and run the main function
const loop = new GLib.MainLoop(null, false);
// @ts-ignore
import('resource:///com/subgraph/citadel/Realms/js/main.js')
.then((main) => {
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
loop.quit();
imports.package.run(main);
return GLib.SOURCE_REMOVE;
});
})
.catch(logError);
loop.run();

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/subgraph/citadel/Realms/js">
<file>main.js</file>
<file>model/Base16Themes.js</file>
<file>model/LabelColors.js</file>
<file>model/ObjectManager.js</file>
<file>model/RealmManager.js</file>
<file>model/Realm.js</file>
<file>model/RealmConfig.js</file>
<file>model/OptionData.js</file>
<file>model/RealmFS.js</file>
<file>model/Utils.js</file>
<file>Application.js</file>
<file>ConfigureRealm.js</file>
<file>ColorSchemeChooser.js</file>
<file>ColorSchemeModel.js</file>
<file>RealmRow.js</file>
<file>RealmsView.js</file>
<file>RealmInfo.js</file>
<file>RealmInfoEntry.js</file>
<file>RealmModel.js</file>
<file>Window.js</file>
</gresource>
</gresources>

8
src/main.ts Normal file
View File

@ -0,0 +1,8 @@
import 'gi://Gdk?version=4.0'
import 'gi://Gtk?version=4.0'
import { Application } from './Application.js'
export function main(argv: string[]) {
return new Application().run(argv);
}

24
src/meson.build Normal file
View File

@ -0,0 +1,24 @@
configure_file(
input: APP_ID + '.js',
output: APP_ID,
configuration: {
'GJS': find_program('gjs').full_path(),
'PACKAGE_NAME': APP_ID,
'PACKAGE_VERSION': meson.project_version(),
'PREFIX': get_option('prefix'),
'LIBDIR': get_option('prefix') / get_option('libdir')
},
install_dir: get_option('bindir'),
install_mode: 'rwxr-xr-x'
)
app_resource = gnome.compile_resources(
APP_ID + '.src',
APP_ID + '.src.gresource.xml',
gresource_bundle: true,
source_dir: '../js',
install: true,
install_dir: get_option('datadir') / APP_ID,
)

1081
src/model/Base16Themes.ts Normal file

File diff suppressed because it is too large Load Diff

89
src/model/LabelColors.ts Normal file
View File

@ -0,0 +1,89 @@
import Gdk from 'gi://Gdk?version=4.0';
import Gio from 'gi://Gio';
import {Realm} from './Realm.js'
const CITADEL_SETTINGS_SCHEMA = 'com.subgraph.citadel';
const LABEL_COLOR_LIST_KEY = 'label-color-list';
const REALM_LABEL_COLORS_KEY = 'realm-label-colors';
const DEFAULT_LABEL_COLOR = new Gdk.RGBA({ red: 153, green: 193, blue: 241, });
export class LabelColorManager {
private _citadelSettings: Gio.Settings;
private _defaultColors: Gdk.RGBA[];
private _realmLabelColors: Map<string, Gdk.RGBA>;
constructor() {
this._citadelSettings = new Gio.Settings({ schema_id: CITADEL_SETTINGS_SCHEMA });
this._defaultColors = [];
this._realmLabelColors = new Map();
this._loadColors();
}
_loadColors() {
let entries = this._citadelSettings.get_strv(LABEL_COLOR_LIST_KEY);
entries.forEach(entry => {
let c = new Gdk.RGBA();
if (c.parse(entry)) {
this._defaultColors.push(c);
}
});
entries = this._citadelSettings.get_strv(REALM_LABEL_COLORS_KEY);
entries.forEach(entry => {
let parts = entry.split(":");
if (parts.length === 2) {
let c = new Gdk.RGBA();
if (c.parse(parts[1])) {
this._realmLabelColors.set(parts[0], c);
}
}
})
}
updateRealmColor(realm: Realm, color: Gdk.RGBA) {
this._realmLabelColors.set(realm.name, color);
this._storeRealmColors();
}
lookupRealmColor(realm: Realm): Gdk.RGBA {
let c = this._realmLabelColors.get(realm.name);
if (c) {
return c;
}
let newColor = this._allocateColor();
this.updateRealmColor(realm, newColor);
return newColor;
}
_storeRealmColors() {
let entries: string[] = [];
this._realmLabelColors.forEach((v,k) => {
entries.push(`${k}:${v.to_string()}`);
});
entries.sort();
this._citadelSettings.set_strv(REALM_LABEL_COLORS_KEY, entries);
}
_allocateColor() {
// 1) No default colors? return a built in color
if (this._defaultColors.length === 0) {
return DEFAULT_LABEL_COLOR;
}
// 2) Find first color on default color list that isn't used already
let usedColors = Array.from(this._realmLabelColors.values());
let defaultColor = this._defaultColors.find(color => !usedColors.some(c => c.equal(color)));
if (defaultColor) {
return defaultColor;
}
// 3) Choose a random element of the default list
let index = Math.floor(Math.random() * this._defaultColors.length);
return this._defaultColors[index];
}
}

239
src/model/ObjectManager.ts Normal file
View File

@ -0,0 +1,239 @@
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
const Signals = imports.signals;
import type {SignalMethods} from 'gjs';
Gio._promisify(Gio.DBusProxy.prototype, 'init_async', 'init_finish');
// Specified in the D-Bus specification here:
// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
const ObjectManagerIface = `
<node>
<interface name="org.freedesktop.DBus.ObjectManager">
<method name="GetManagedObjects">
<arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
</method>
<signal name="InterfacesAdded">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="a{sa{sv}}" />
</signal>
<signal name="InterfacesRemoved">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="as" />
</signal>
</interface>
</node>`;
const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface);
export interface ObjectManager extends SignalMethods {};
export class ObjectManager {
private _connection: Gio.DBusConnection;
private _serviceName: string;
private _managerPath: string;
private _cancellable: any;
private _interfaceInfos: { [s: string]: Gio.DBusInterfaceInfo };
private _objects: { [s: string]: { [s: string]: Gio.DBusProxy }};
private _onLoaded: any;
private _managerProxy: Gio.DBusProxy;
private _interfaces: { [s: string]: Gio.DBusProxy[] };
constructor(params: { connection: Gio.DBusConnection; name: string; objectPath: string; knownInterfaces: any; onLoaded: any; cancellable?: any; }) {
this._connection = params.connection;
this._serviceName = params.name;
this._managerPath = params.objectPath;
this._cancellable = params.cancellable ?? null;
this._managerProxy = new Gio.DBusProxy({
g_connection: this._connection,
g_interface_name: ObjectManagerInfo.name,
g_interface_info: ObjectManagerInfo,
g_name: this._serviceName,
g_object_path: this._managerPath,
g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
});
this._interfaceInfos = {};
this._objects = {};
this._interfaces = {};
this._onLoaded = params.onLoaded;
if (params.knownInterfaces)
this._registerInterfaces(params.knownInterfaces);
this._initManagerProxy();
}
_completeLoad() {
if (this._onLoaded)
this._onLoaded();
}
async _addInterface(objectPath: string, interfaceName: string) {
let info = this._interfaceInfos[interfaceName];
if (!info)
return;
const proxy = new Gio.DBusProxy({
g_connection: this._connection,
g_name: this._serviceName,
g_object_path: objectPath,
g_interface_name: interfaceName,
g_interface_info: info,
g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
});
try {
await proxy.init_async(GLib.PRIORITY_DEFAULT, this._cancellable);
} catch (e: any) {
logError(e, `could not initialize proxy for interface ${interfaceName}`);
return;
}
let isNewObject;
if (!this._objects[objectPath]) {
this._objects[objectPath] = {};
isNewObject = true;
} else {
isNewObject = false;
}
this._objects[objectPath][interfaceName] = proxy;
if (!this._interfaces[interfaceName])
this._interfaces[interfaceName] = [];
this._interfaces[interfaceName].push(proxy);
if (isNewObject)
this.emit('object-added', objectPath);
this.emit('interface-added', interfaceName, proxy);
}
_removeInterface(objectPath: string, interfaceName: string) {
if (!this._objects[objectPath])
return;
let proxy = this._objects[objectPath][interfaceName];
if (this._interfaces[interfaceName]) {
let index = this._interfaces[interfaceName].indexOf(proxy);
if (index >= 0)
this._interfaces[interfaceName].splice(index, 1);
if (this._interfaces[interfaceName].length === 0)
delete this._interfaces[interfaceName];
}
this.emit('interface-removed', interfaceName, proxy);
delete this._objects[objectPath][interfaceName];
if (Object.keys(this._objects[objectPath]).length === 0) {
delete this._objects[objectPath];
this.emit('object-removed', objectPath);
}
}
async _initManagerProxy() {
try {
await this._managerProxy.init_async(
GLib.PRIORITY_DEFAULT, this._cancellable);
} catch (e: any) {
logError(e, `could not initialize object manager for object ${this._serviceName}`);
this._completeLoad();
return;
}
this._managerProxy.connectSignal('InterfacesAdded',
(_objectManager: any, _sender: any, [objectPath, interfaces]: any) => {
let interfaceNames = Object.keys(interfaces);
for (let i = 0; i < interfaceNames.length; i++)
this._addInterface(objectPath, interfaceNames[i]);
});
this._managerProxy.connectSignal('InterfacesRemoved',
(_objectManager: any, _sender: any, [objectPath, interfaceNames]: any) => {
for (let i = 0; i < interfaceNames.length; i++)
this._removeInterface(objectPath, interfaceNames[i]);
});
if (Object.keys(this._interfaceInfos).length === 0) {
this._completeLoad();
return;
}
this._managerProxy.connect('notify::g-name-owner', () => {
if (this._managerProxy.g_name_owner)
this._onNameAppeared();
else
this._onNameVanished();
});
if (this._managerProxy.g_name_owner)
this._onNameAppeared().catch(logError);
}
async _onNameAppeared() {
try {
const [objects] = await this._managerProxy.GetManagedObjectsAsync();
if (!objects) {
this._completeLoad();
return;
}
const objectPaths = Object.keys(objects);
await Promise.allSettled(objectPaths.flatMap(objectPath => {
const object = objects[objectPath];
const interfaceNames = Object.getOwnPropertyNames(object);
return interfaceNames.map(
ifaceName => this._addInterface(objectPath, ifaceName));
}));
} catch (error: any) {
logError(error, `could not get remote objects for service ${this._serviceName} path ${this._managerPath}`);
} finally {
this._completeLoad();
}
}
_onNameVanished() {
let objectPaths = Object.keys(this._objects);
for (let i = 0; i < objectPaths.length; i++) {
let objectPath = objectPaths[i];
let object = this._objects[objectPath];
let interfaceNames = Object.keys(object);
for (let j = 0; j < interfaceNames.length; j++) {
let interfaceName = interfaceNames[j];
if (object[interfaceName])
this._removeInterface(objectPath, interfaceName);
}
}
}
_registerInterfaces(interfaces: string | any[]) {
for (let i = 0; i < interfaces.length; i++) {
let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
this._interfaceInfos[info.name] = info;
}
}
getProxiesForInterface(interfaceName: string) {
let proxyList = this._interfaces[interfaceName];
if (!proxyList)
return [];
return proxyList;
}
}
Signals.addSignalMethods(ObjectManager.prototype);

109
src/model/OptionData.ts Normal file
View File

@ -0,0 +1,109 @@
export class OptionData {
tooltip;
description;
static map: Map<string, OptionData> = new Map();
constructor(description: string, tooltip: string) {
this.tooltip = tooltip;
this.description = description;
}
static add(name: string, description: string, tooltip: string) {
OptionData.map.set(name, new OptionData(description, tooltip));
}
static description(name: string) {
return OptionData.map.get(name)?.description;
}
static tooltip(name: string) {
return OptionData.map.get(name)?.tooltip;
}
}
OptionData.add('use-gpu','Use GPU in Realm',
`If enabled the render node device <tt><b>/dev/dri/renderD128</b></tt> will be mounted into the realm container.
If privileged device <tt><b>/dev/dri/card0</b></tt> is also needed set
additional variable in realm configuration file:
<tt><b>use-gpu-card0 = true</b></tt>
`);
OptionData.add('use-wayland', 'Use Wayland in Realm',
`If enabled access to Wayland display will be permitted in realm by adding wayland socket to realm.
<tt><b>/run/user/1000/wayland-0</b></tt>
`);
OptionData.add('use-x11', 'Use X11 in Realm',
`If enabled access to X11 server will be added by mounting directory X11 directory into realm.
<tt><b>/tmp/.X11-unix</b></tt>
`);
OptionData.add('use-sound', 'Use sound in Realm',
`If enabled allows use of sound inside of realm. The following items will be added:
<tt><b>/dev/snd</b></tt>
<tt><b>/dev/shm</b></tt>
<tt><b>/run/user/1000/pulse</b></tt>
`);
OptionData.add('use-shared-dir', 'Mount /Shared directory in Realm',
`If enabled the shared directory will be mounted as <tt><b>/Shared</b></tt> in home directory of realm.
This directory is shared between all realms with this option enabled and is an easy way to move files between realms.
`);
OptionData.add('use-network', 'Realm has network access',
`If enabled the realm will have access to the network.`);
OptionData.add('use-kvm', 'Use KVM (/dev/kvm) in Realm',
`If enabled device <tt><b>/dev/kvm</b></tt> will be added to realm`);
OptionData.add('use-ephemeral-home', 'Use ephemeral tmpfs mount for home directory',
`If enabled the home directory of realm will be set up in ephemeral mode.
The ephemeral home directory is set up with the following steps:
1. Home directory is mounted as tmpfs filesystem
2. Any files in <tt><b>/realms/skel</b></tt> are copied into home directory
3. Any files in <tt><b>/realms/realm-$name/skel</b></tt> are copied into home directory.
4. Any directories listed in config file variable <tt><b>ephemeral_persistent_dirs</b></tt>
are bind mounted from <tt><b>/realms/realm-$name/home</b></tt> into ephemeral
home directory.
`);
OptionData.add('overlay', 'Type of rootfs overlay Realm is configured to use.',
`<b><big>Overlay</big></b>
Type of rootfs overlay realm is configured to use.
<b>None</b> Don't use a rootfs overlay
<b>TmpFS</b> Use a rootfs overlay stored on tmpfs
<b>Storage</b> Use a rootfs overlay stored on disk in storage partition
`);
OptionData.add('realmfs', 'Root filesystem image to use for Realm.',
`<b><big>RealmFS</big></b>
Root filesystem image to use for realm.`);
OptionData.add('colorscheme', 'Terminal Color Scheme',
`<b><big>Terminal Color Scheme</big></b>
Choose a color scheme to use in the terminals in this realm.`);
OptionData.add('window-label-color', 'Window Label Color',
`<b><big>Window Label Color</big></b>
Set a color to be used when a label is drawn on the window titlebar indicating which realm the application is running in.`);

129
src/model/Realm.ts Normal file
View File

@ -0,0 +1,129 @@
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import {RealmFS} from './RealmFS.js';
import {RealmConfig} from './RealmConfig.js';
import {LabelColorManager} from './LabelColors.js';
import { RGBA } from 'gi-types/gdk4.js';
export const RunStatus = {
STOPPED: 0,
STARTING: 1,
RUNNING: 2,
CURRENT: 3,
STOPPING: 4,
};
export class Realm extends GObject.Object {
private _name!: string;
private _description!: string;
private _pidns!: number;
private _run_status!: number;
private _is_system_realm!: boolean;
private _realmfs!: RealmFS | null;
private _labelColors: LabelColorManager;
static {
GObject.registerClass({
GTypeName: 'Realm',
Properties: {
'name': GObject.ParamSpec.string('name', '', '', GObject.ParamFlags.READWRITE, ''),
'description': GObject.ParamSpec.string('description', '', '', GObject.ParamFlags.READWRITE, ''),
'pidns': GObject.ParamSpec.uint64('pidns', '', '', GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER, 0),
'run-status': GObject.ParamSpec.uint('run-status', '', '', GObject.ParamFlags.READWRITE,
0, 4, 0),
'is-system-realm': GObject.ParamSpec.boolean('is-system-realm', '', '', GObject.ParamFlags.READWRITE, false),
'realmfs': GObject.ParamSpec.object('realmfs', '', '', GObject.ParamFlags.READWRITE, RealmFS),
},
Signals: { 'changed': {}},
}, this);
}
_proxy: Gio.DBusProxy;
config = new RealmConfig();
constructor(proxy: Gio.DBusProxy, labelColors: LabelColorManager) {
super();
this._proxy = proxy;
this._labelColors = labelColors;
this._proxy.connect('g-properties-changed', (_proxy, _changed, _invalidated) => {
this._sync();
});
this._sync();
}
get realmfs(): RealmFS | null {
return this._realmfs;
}
set realmfs(value: RealmFS | null) {
if (this.realmfs === value) {
return;
}
this._realmfs = value;
this.emit('changed');
}
_sync() {
this._name = this._proxy.Name;
this._description = this._proxy.Description;
this._pidns = this._proxy.PidNS;
this._run_status = this._proxy.RunStatus;
this._is_system_realm = this._proxy.IsSystemRealm;
this.emit('changed');
}
get name() {
return this._name;
}
get description() {
return this._description;
}
get pidns() {
return this._pidns;
}
get run_status() {
return this._run_status;
}
get is_system_realm() {
return this._is_system_realm;
}
is_running() {
return this.run_status === RunStatus.RUNNING || this.run_status === RunStatus.CURRENT;
}
is_current() {
return this.run_status === RunStatus.CURRENT
}
getLabelColor() {
return this._labelColors.lookupRealmColor(this);
}
setLabelColor(color: RGBA) {
this._labelColors.updateRealmColor(this, color);
}
realmfs_index() {
return this._proxy.RealmFS
}
set_global_config(config: { [s: string]: string; }) {
this.config.set_global_config(config);
}
async load_config() {
const result = await this._proxy.GetConfigAsync();
this.config.set_config(result[0]);
this.emit('changed');
}
}

116
src/model/RealmConfig.ts Normal file
View File

@ -0,0 +1,116 @@
import {OptionData} from './OptionData.js';
export class BoolOptionData {
static ALL_OPTIONS = [
'use-gpu',
'use-wayland',
'use-x11',
'use-sound',
'use-network',
'use-kvm',
'use-shared-dir',
'use-ephemeral-home',
]
name;
description;
tooltip;
constructor(name: string) {
this.name = name;
this.description = OptionData.description(name) ?? name;
this.tooltip = OptionData.tooltip(name) ?? "";
}
static allOptions(): BoolOptionData[] {
let options: BoolOptionData[] = [];
BoolOptionData.ALL_OPTIONS.forEach(name => {
options.push(new BoolOptionData(name));
})
return options;
}
}
export class ConfigOption {
name;
value;
defaultValue;
description;
tooltip;
constructor(name: string, value: string, defaultValue: string, description: string, tooltip: string) {
this.name = name;
this.value = value;
this.defaultValue = defaultValue;
this.description = description;
this.tooltip = tooltip;
}
}
export class RealmConfig {
_configVars: { [s: string]: string; } = {};
_globalConfig: { [s: string]: string; } = {};
set_config(config: { [s: string]: string; }) {
this._configVars = config;
}
set_global_config(globalConfig: { [s: string]: string; }) {
this._globalConfig = globalConfig;
}
get_var(name: string) {
let value = this._configVars[name];
if (value && value.length > 0) {
return value;
}
value = this._globalConfig[name];
if (value && value.length > 0) {
return value;
}
return null;
}
get_bool(name: string): boolean {
return this.get_var(name) === 'true';
}
get_colorscheme() {
return this.get_var('terminal-scheme') ?? 'default-dark';
}
get_realmfs() {
return this.get_var('realmfs') ?? 'base';
}
static capitalize(term: string) {
const acronyms = ["gpu", "kvm"];
if (acronyms.find(s => term === s)) {
return term.toUpperCase();
} else {
return term.charAt(0).toUpperCase() + term.slice(1);
}
}
static option_label(key: string) {
return key.substring(4)
.split('-')
.map(RealmConfig.capitalize)
.join('');
}
is_enabled_bool_option(name: string, value: string, isDefault: boolean) {
const default_val = this._globalConfig[name];
return name.startsWith("use-") && value === "true" && (value === default_val) === isDefault;
}
enabled_bool_option_labels(isDefault: boolean) {
return Object.entries(this._configVars)
.filter(([k,v]) => this.is_enabled_bool_option(k, v, isDefault))
.map(([k,]) => RealmConfig.option_label(k))
.sort();
}
}

93
src/model/RealmFS.ts Normal file
View File

@ -0,0 +1,93 @@
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
const OBJECT_PREFIX = "/com/subgraph/Realms2/RealmFS";
export class RealmFS extends GObject.Object {
private _name!: string;
private _in_use!: boolean;
private _activated!: boolean;
private _mountpoint!: string;
private _path!: string;
private _free_space!: number;
private _allocated_space!: number;
static {
GObject.registerClass({
GTypeName: 'RealmFS',
Properties: {
'name': GObject.ParamSpec.string('name', '', '', GObject.ParamFlags.READWRITE, ''),
'in-use': GObject.ParamSpec.boolean('in-use', '', '', GObject.ParamFlags.READWRITE, false),
'activated': GObject.ParamSpec.boolean('activated', '', '', GObject.ParamFlags.READWRITE, false),
'mountpoint': GObject.ParamSpec.string('mountpoint', '', '', GObject.ParamFlags.READWRITE, ''),
'path': GObject.ParamSpec.string('path', '', '', GObject.ParamFlags.READWRITE, ''),
'free-space': GObject.ParamSpec.uint64('free-space', '', '', GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER, 0),
'allocated-space': GObject.ParamSpec.uint64('allocated-space', '', '', GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER, 0),
},
}, this);
}
_proxy: Gio.DBusProxy;
constructor(proxy: Gio.DBusProxy) {
super();
this._proxy = proxy;
this._proxy.connect('g-properties-changed', (_proxy, _changed, _invalidated) => {
this._sync();
});
this._sync();
}
index() {
let path = this._proxy.get_object_path();
if (path.startsWith(OBJECT_PREFIX)) {
const tail = path.substring(OBJECT_PREFIX.length);
let index = Number.parseInt(tail);
if (!Number.isNaN(index)) {
return index;
}
}
return -1;
}
get name() {
return this._name;
}
get in_use() {
return this._in_use;
}
get activated() {
return this._activated;
}
get mountpoint() {
return this._mountpoint;
}
get path() {
return this._path;
}
get free_space() {
return this._free_space;
}
get allocated_space() {
return this._allocated_space;
}
_sync() {
this._name = this._proxy.Name;
this._in_use = this._proxy.InUse;
this._activated = this._proxy.Activated;
this._mountpoint = this._proxy.Mountpoint;
this._path = this._proxy.Path;
this._free_space = this._proxy.FreeSpace;
this._allocated_space = this._proxy.AllocatedSpace;
}
}

171
src/model/RealmManager.ts Normal file
View File

@ -0,0 +1,171 @@
import Gio from 'gi://Gio';
import {ObjectManager} from './ObjectManager.js';
import {loadInterfaceXML} from './Utils.js'
import {Realm} from './Realm.js';
import {RealmFS} from './RealmFS.js';
import {LabelColorManager} from './LabelColors.js';
import type {SignalMethods} from 'gjs';
const Signals = imports.signals;
const RealmIface = loadInterfaceXML("com.subgraph.realms.Realm");
const RealmFSIface = loadInterfaceXML("com.subgraph.realms.RealmFS");
const BUS_NAME = 'com.subgraph.Realms2';
const OBJECT_PATH = '/com/subgraph/Realms2';
const ManagerInterface = loadInterfaceXML("com.subgraph.realms.Manager2");
const ManagerProxy = Gio.DBusProxy.makeProxyWrapper(ManagerInterface);
export interface RealmManager extends SignalMethods {};
export class RealmManager {
_globalConfig: { [s: string]: string; } = {};
_realms: { [s: string]: Realm; } = {};
_realmfs: { [s: string]: RealmFS; } = {};
static INSTANCE = new RealmManager();
private _objectManager: ObjectManager;
private _labelColors: LabelColorManager;
private _proxy: Gio.DBusProxy;
static instance(): RealmManager {
return RealmManager.INSTANCE;
}
constructor() {
this._realmfs = {};
this._objectManager = new ObjectManager({
connection: Gio.DBus.system,
name: 'com.subgraph.Realms2',
objectPath: '/com/subgraph/Realms2',
knownInterfaces: [RealmIface, RealmFSIface],
onLoaded: this._onLoaded.bind(this),
});
this._proxy = new ManagerProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
(_proxy: any, error: any) => {
if(error) {
logError(error);
} else {
this._setGlobalConfig().catch(logError);
}
});
this._labelColors = new LabelColorManager();
}
async _setGlobalConfig() {
let result = await this._proxy.GetGlobalConfigAsync();
this._globalConfig = result[0];
this._assignGlobalConfigToRealms();
}
async _onLoaded() {
let realms = this._objectManager.getProxiesForInterface('com.subgraph.realms.Realm');
for (let i = 0; i < realms.length; i++) {
this._addRealm(realms[i]);
}
let realmfs = this._objectManager.getProxiesForInterface('com.subgraph.realms.RealmFS');
for (let i = 0; i < realmfs.length; i++) {
this._addRealmFS(realmfs[i]);
}
this._objectManager.connect('interface-added', (_objectManager, interfaceName, proxy) => {
if (interfaceName === 'com.subgraph.Realm') {
this._addRealm(proxy);
} else if (interfaceName === 'com.subgraph.RealmFS') {
this._addRealmFS(proxy);
}
});
this._objectManager.connect('interface-removed', (_objectManager, interfaceName, proxy) => {
if (interfaceName === 'com.subgraph.Realm') {
this._removeRealm(proxy);
} else if (interfaceName === 'com.subgraph.RealmFS') {
this._removeRealmFS(proxy);
}
});
}
async _addRealm(proxy: Gio.DBusProxy) {
let objectPath = proxy.get_object_path();
let realm = new Realm(proxy, this._labelColors);
this._realms[objectPath] = realm;
this._findRealmFSForRealm(realm);
if (this._globalConfig) {
realm.set_global_config(this._globalConfig);
}
await realm.load_config();
this.emit('realm-added', realm);
}
_removeRealm(proxy: Gio.DBusProxy) {
let objectPath = proxy.get_object_path();
if (this._realms[objectPath]?._proxy === proxy) {
let realm = this._realms[objectPath];
delete this._realms[objectPath];
this.emit('realm-removed', realm);
}
proxy.disconnectAll();
}
_addRealmFS(proxy: Gio.DBusProxy) {
let objectPath = proxy.get_object_path();
let realmfs = new RealmFS(proxy);
this._realmfs[objectPath] = realmfs;
this._assignRealmFSToRealms(realmfs);
this.emit('realmfs-added', realmfs);
}
_findRealmFSForRealm(realm: Realm) {
if (realm.realmfs_index() > 0) {
realm.realmfs = Object.values(this._realmfs)
.find(realmfs => realmfs.index() === realm.realmfs_index()) ?? null;
}
}
realmfsList() {
return Object.values(this._realmfs);
}
_assignRealmFSToRealms(realmfs: RealmFS) {
Object.values(this._realms).forEach(r => {
if (r.realmfs_index() > 0 && r.realmfs_index() === realmfs.index() && r.realmfs != realmfs) {
r.realmfs = realmfs;
}
}) ;
}
_assignGlobalConfigToRealms() {
Object.values(this._realms).forEach(r => r.set_global_config(this._globalConfig));
}
_removeRealmFSFromRealms(realmfs: RealmFS) {
Object.values(this._realms).forEach(r => {
if (r.realmfs === realmfs) {
r.realmfs = null;
}
});
}
_removeRealmFS(proxy: Gio.DBusProxy) {
let objectPath = proxy.get_object_path();
if (this._realmfs[objectPath]?._proxy === proxy) {
let realmfs = this._realmfs[objectPath];
delete this._realmfs[objectPath];
this._removeRealmFSFromRealms(realmfs);
this.emit('realmfs-removed', realmfs);
}
proxy.disconnectAll();
}
}
Signals.addSignalMethods(RealmManager.prototype);

17
src/model/Utils.ts Normal file
View File

@ -0,0 +1,17 @@
import Gio from 'gi://Gio';
export function loadInterfaceXML(iface: string): string | null {
let uri = `resource:///com/subgraph/citadel/Realms/dbus-interfaces/${iface}.xml`;
let f = Gio.File.new_for_uri(uri);
try {
let [_ok, bytes] = f.load_contents(null);
// @ts-ignore
return new TextDecoder().decode(bytes);
} catch (e) {
log(`Failed to load D-Bus interface ${iface}`);
}
return null;
}

View File

@ -0,0 +1,9 @@
[wrap-git]
directory = blueprint-compiler
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
revision = main
depth = 1
[provide]
program_names = blueprint-compiler

40
tsconfig.json Normal file
View File

@ -0,0 +1,40 @@
{
"compilerOptions": {
"strictNullChecks": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"noUnusedParameters": true,
"experimentalDecorators": true,
"strict": true,
"paths": {
"*": [
"*",
"types/*",
"gi-types/*"
]
},
"target": "ES2018",
"baseUrl": ".",
"moduleResolution": "node",
"module": "ES2020",
"outDir": "js",
"lib": [
"es2020"
],
"skipLibCheck": true,
"typeRoots": [
"types",
"gi-types",
],
},
"include": [
"gi-types/gi.d.ts",
"types/ambient.d.ts",
"types/gjs.d.ts",
"src/**/*",
],
"exclude": [
"node_modules/@types/**",
],
}

32
types/ambient.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
declare function _(id: string): string;
declare function print(args: string): void;
declare function log(obj: object, others?: object[]): void;
declare function log(msg: string, subsitutions?: any[]): void;
declare const pkg: {
version: string;
name: string;
};
declare module console {
export function error(obj: object, others?: object[]): void;
export function error(msg: string, subsitutions?: any[]): void;
}
declare class TextDecoder {
constructor(format: string);
decode(buffer: ArrayBuffer): string;
}
declare class TextEncoder {
constructor();
encode(str: string): Uint8Array;
}
declare interface String {
format(...replacements: string[]): string;
format(...replacements: number[]): string;
}
declare interface Number {
toFixed(digits: number): number;
}

35
types/gettext.d.ts vendored Normal file
View File

@ -0,0 +1,35 @@
export namespace Gettext {
export enum LocaleCategory {
CTYPE = 0,
NUMERIC = 1,
TIME = 2,
COLLATE = 3,
MONETARY = 4,
MESSAGES = 5,
ALL = 6,
}
export function setlocale(category: LocaleCategory, locale: string | null): string | null;
export function textdomain(domainName: string): void;
export function bindtextdomain(domainName: string, dirName: string): void;
export function gettext(msgid: string): string;
export function dgettext(domainName: string | null, msgid: string): string;
export function dcgettext(domainName: string | null, msgid: string, category: LocaleCategory): string;
export function ngettext(msgid1: string, msgid2: string, n: number): string;
export function dngettext(domainName: string | null, msgid1: string, msgid2: string, n: number): string;
export function pgettext(context: string | null, msgid: string): string;
export function dpgettext(domainName: string | null, context: string | null, msgid: string): string;
export class GettextObject {
gettext(msgid: string): string;
ngettext(msgid1: string, msgid2: string, n: number): string;
pgettext(context: string | null, msgid: string): string;
}
export function domain(domainName: string | null): GettextObject;
}
export default Gettext;

183
types/gjs.d.ts vendored Normal file
View File

@ -0,0 +1,183 @@
/**
* Type Definitions for Gjs (https://gjs.guide/)
*
* These type definitions are automatically generated, do not edit them by hand.
* If you found a bug fix it in ts-for-gir itself or create a bug report on https://github.com/gjsify/ts-for-gir
*/
/*
import type GObject from '@girs/gobject-2.0';
import type GLib from '@girs/glib-2.0';
import gettext from './gettext.js';
import system from './system.js';
import cairo from './cairo.js';
*/
/**
* You can use the `Signals.addSignalMethods` method to apply the `Signals` convenience methods to an `Object`.
* Generally, this is called on an object prototype, but may also be called on an object instance.
* You can use this Interface for this object or prototype to make the methods in typescript known
* @example
* ```ts
* const Signals = imports.signals;
*
* // Define an interface with the same name of your class to make the methods known
* interface Events extends Signals.Methods {}
*
* class Events {}
* Signals.addSignalMethods(Events.prototype);
*
* const events = new Events();
*
* // Typescript will not complain here
* events.emit("test-signal", "test argument");
* ```
*/
export interface SignalMethods {
/**
* Connects a callback to a signal for an object. Pass the returned ID to
* `disconnect()` to remove the handler.
*
* If `callback` returns `true`, emission will stop and no other handlers will be
* invoked.
*
* > Warning: Unlike GObject signals, `this` within a signal callback will always
* > refer to the global object (ie. `globalThis`).
*
* @param sigName A signal name
* @param callback A callback function
* @returns A handler ID
*/
connect(sigName: string, callback: (self: any, ...args: any[]) => void): number;
/**
* Emits a signal for an object. Emission stops if a signal handler returns `true`.
*
* Unlike GObject signals, it is not necessary to declare signals or define their
* signature. Simply call `emit()` with whatever signal name you wish, with
* whatever arguments you wish.
* @param sigName A signal name
* @param args Any number of arguments, of any type
*/
emit(sigName: string, ...args: any[]): void;
/**
* Disconnects a handler for a signal.
* @param id The ID of the handler to be disconnected
*/
disconnect(id: number): void;
/**
* Disconnects all signal handlers for an object.
*/
disconnectAll(): void;
/**
* Checks if a handler ID is connected.
* @param id The ID of the handler to be disconnected
* @returns `true` if connected, or `false` if not
*/
signalHandlerIsConnected(id: number): boolean;
}
declare namespace signals {
interface Methods {
/**
* Connects a callback to a signal for an object. Pass the returned ID to
* `disconnect()` to remove the handler.
*
* If `callback` returns `true`, emission will stop and no other handlers will be
* invoked.
*
* > Warning: Unlike GObject signals, `this` within a signal callback will always
* > refer to the global object (ie. `globalThis`).
*
* @param sigName A signal name
* @param callback A callback function
* @returns A handler ID
*/
connect(sigName: string, callback: (self: any, ...args: any[]) => void): number;
/**
* Emits a signal for an object. Emission stops if a signal handler returns `true`.
*
* Unlike GObject signals, it is not necessary to declare signals or define their
* signature. Simply call `emit()` with whatever signal name you wish, with
* whatever arguments you wish.
* @param sigName A signal name
* @param args Any number of arguments, of any type
*/
emit(sigName: string, ...args: any[]): void;
/**
* Disconnects a handler for a signal.
* @param id The ID of the handler to be disconnected
*/
disconnect(id: number): void;
/**
* Disconnects all signal handlers for an object.
*/
disconnectAll(): void;
/**
* Checks if a handler ID is connected.
* @param id The ID of the handler to be disconnected
* @returns `true` if connected, or `false` if not
*/
signalHandlerIsConnected(id: number): boolean;
}
export function addSignalMethods<T = any>(proto: T): proto is T & Methods;
}
declare global {
interface GjsGiImports {
// Will be extended by the import of more gir types
versions: {
[namespace: string]: string;
};
}
interface GjsImports {
gi: GjsGiImports;
signals: typeof signals;
searchPath: string[];
}
/**
* Run `pkg.initGettext()` before using this.
* Currently not implemented.
*/
const N_: undefined | ((x: string) => string);
function print(...args: any[]): void;
function printerr(...args: any[]): void;
function log(obj: object, others?: object[]): void;
function log(msg: string, substitutions?: any[]): void;
function logError(exception: object, message?: any): void;
function logError(message?: any): void;
interface BooleanConstructor {
$gtype: GObject.GType<boolean>;
}
interface NumberConstructor {
$gtype: GObject.GType<number>;
}
interface StringConstructor {
$gtype: GObject.GType<string>;
}
interface StringConstructor {
$gtype: GObject.GType<string>;
}
interface ObjectConstructor {
$gtype: GObject.GType<Object>;
}
const imports: GjsImports;
const ARGV: string[];
}
declare const _imports: GjsImports;
export default _imports;
export { _imports as imports };

6
types/gjs.js Normal file
View File

@ -0,0 +1,6 @@
const imports = globalThis.imports || {};
export { imports }
export default imports;