Added config service
This commit is contained in:
parent
7218885cd7
commit
c9a9091b48
35
app.ts
35
app.ts
@ -2,11 +2,42 @@ import { App } from "astal/gtk3";
|
|||||||
import style from "./style.css";
|
import style from "./style.css";
|
||||||
import Bar from "./widget/Bar";
|
import Bar from "./widget/Bar";
|
||||||
import "@/globals";
|
import "@/globals";
|
||||||
import { CompositorConnection, SwayConnection } from "@services/compositor";
|
import { SwayConnection } from "@services/compositor";
|
||||||
import { OutputService } from "./services/outputs/service";
|
import { OutputService } from "@/services/outputs/service";
|
||||||
|
import { ConfigService } from "@/services/config/service";
|
||||||
|
|
||||||
new SwayConnection();
|
new SwayConnection();
|
||||||
new OutputService();
|
new OutputService();
|
||||||
|
const confSrv = new ConfigService();
|
||||||
|
confSrv.connect("config-reloaded", () => {
|
||||||
|
console.log(confSrv.config);
|
||||||
|
console.log(
|
||||||
|
confSrv.doConfigMath(
|
||||||
|
String(confSrv.config.layouts?.battlestation.primary.position[0] ?? ""),
|
||||||
|
{
|
||||||
|
auxiliary: 50,
|
||||||
|
"left-wing": 250,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
confSrv.formatConfigString("{this.is.a.test.of.nested.parsing}", {
|
||||||
|
this: {
|
||||||
|
is: {
|
||||||
|
a: {
|
||||||
|
test: {
|
||||||
|
of: {
|
||||||
|
nested: {
|
||||||
|
parsing: "This was a successful test of nested parsing!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
css: style,
|
css: style,
|
||||||
|
|||||||
@ -118,6 +118,7 @@ export class CompositorConnection extends GObject.Object {
|
|||||||
* Set an output property without fully reconfiguring the output
|
* Set an output property without fully reconfiguring the output
|
||||||
*/
|
*/
|
||||||
async setOutputProperty<Prop extends string & keyof OutputConfig>(
|
async setOutputProperty<Prop extends string & keyof OutputConfig>(
|
||||||
|
outputName: string,
|
||||||
property: Prop,
|
property: Prop,
|
||||||
value: Exclude<OutputConfig[Prop], undefined>,
|
value: Exclude<OutputConfig[Prop], undefined>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -276,7 +276,10 @@ export class SwayConnection extends CompositorConnection {
|
|||||||
aspect_ratio: picture_aspect_ratio,
|
aspect_ratio: picture_aspect_ratio,
|
||||||
...mode,
|
...mode,
|
||||||
})),
|
})),
|
||||||
mode: {
|
mode:
|
||||||
|
output.current_mode === undefined
|
||||||
|
? null
|
||||||
|
: {
|
||||||
height: output.current_mode.height,
|
height: output.current_mode.height,
|
||||||
width: output.current_mode.width,
|
width: output.current_mode.width,
|
||||||
refresh: output.current_mode.refresh,
|
refresh: output.current_mode.refresh,
|
||||||
|
|||||||
@ -141,6 +141,10 @@ export interface OutputMode {
|
|||||||
* The refresh rate of the mode in millihertz
|
* The refresh rate of the mode in millihertz
|
||||||
*/
|
*/
|
||||||
refresh: number;
|
refresh: number;
|
||||||
|
/**
|
||||||
|
* The aspect ratio of the mode (if it stretches pixels)
|
||||||
|
*/
|
||||||
|
picture_aspect_ratio: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,9 +202,9 @@ export interface OutputResponse {
|
|||||||
*/
|
*/
|
||||||
modes: OutputMode[];
|
modes: OutputMode[];
|
||||||
/**
|
/**
|
||||||
* The current mode of the output.
|
* The current mode of the output. Undefined if the output is disabled.
|
||||||
*/
|
*/
|
||||||
current_mode: OutputMode;
|
current_mode?: OutputMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeType {
|
export enum NodeType {
|
||||||
|
|||||||
@ -171,7 +171,6 @@ export class VSWrapper<WrappedInterface extends Object> extends GObject.Object {
|
|||||||
if (thisValue instanceof VSWrapper && typeof otherValue === "object") {
|
if (thisValue instanceof VSWrapper && typeof otherValue === "object") {
|
||||||
return thisValue.equals(otherValue);
|
return thisValue.equals(otherValue);
|
||||||
}
|
}
|
||||||
console.log(`items differ: ${thisValue} !== ${otherValue}`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +300,7 @@ export interface OutputAdapter {
|
|||||||
readonly model: string;
|
readonly model: string;
|
||||||
readonly serial: string;
|
readonly serial: string;
|
||||||
readonly availableModes: OutputMode[];
|
readonly availableModes: OutputMode[];
|
||||||
readonly mode: OutputMode;
|
readonly mode: OutputMode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@register({
|
@register({
|
||||||
@ -340,7 +339,7 @@ export class VSOutputAdapter
|
|||||||
@property(Object)
|
@property(Object)
|
||||||
declare readonly availableModes: VSOutputMode[];
|
declare readonly availableModes: VSOutputMode[];
|
||||||
@property(VSOutputMode)
|
@property(VSOutputMode)
|
||||||
declare readonly mode: VSOutputMode;
|
declare readonly mode: VSOutputMode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
150
services/config/service.ts
Normal file
150
services/config/service.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import GObject, { register, signal } from "astal/gobject";
|
||||||
|
import { readFileAsync, monitorFile } from "astal/file";
|
||||||
|
import GLib from "gi://GLib";
|
||||||
|
import Gio from "gi://Gio";
|
||||||
|
import yaml from "yaml";
|
||||||
|
import { ConfigFile, Vars } from "./types";
|
||||||
|
|
||||||
|
@register({
|
||||||
|
GTypeName: "ConfigService",
|
||||||
|
})
|
||||||
|
export class ConfigService extends GObject.Object {
|
||||||
|
private get configPath(): string {
|
||||||
|
const envVar = GLib.getenv("VOIDSHELL_CONFIG");
|
||||||
|
if (envVar !== null) {
|
||||||
|
return envVar;
|
||||||
|
}
|
||||||
|
const defaultPath = `${GLib.getenv("XDG_CONFIG_HOME") ?? GLib.getenv("HOME") + "/.config"}/voidshell/config.yml`;
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private fileMonitors: [path: string, mon: Gio.FileMonitor][] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a path to be relative to the config directory, unless it is absolute.
|
||||||
|
*/
|
||||||
|
private normalizePath(path: string) {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
if (path.startsWith("~")) {
|
||||||
|
return `${GLib.getenv("HOME")}/${path.slice(2)}`;
|
||||||
|
}
|
||||||
|
const basedir = this.configPath.split("/").slice(0, -1).join("/");
|
||||||
|
return `${basedir}/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively parse config files
|
||||||
|
*/
|
||||||
|
private async parseConfig(path: string): Promise<ConfigFile> {
|
||||||
|
try {
|
||||||
|
const contents = await readFileAsync(this.normalizePath(path));
|
||||||
|
const parsed = yaml.parse(contents) as ConfigFile;
|
||||||
|
const importedFiles =
|
||||||
|
(await parsed.import?.amap(this.parseConfig.bind(this))) ?? [];
|
||||||
|
|
||||||
|
// Merge config files
|
||||||
|
return importedFiles.reduce((config, imported) => {
|
||||||
|
// Merge output definitions
|
||||||
|
if (config.outputs !== undefined) {
|
||||||
|
Object.entries(imported.outputs ?? {}).forEach(([output, def]) => {
|
||||||
|
config.outputs![output] = { ...config.outputs?.[output], ...def };
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
config.outputs = imported.outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge workspace definitions
|
||||||
|
config.workspaces = { ...config.workspaces, ...imported.workspaces };
|
||||||
|
|
||||||
|
// Merge layout definitions
|
||||||
|
config.layouts = { ...config.layouts, ...imported.layouts };
|
||||||
|
|
||||||
|
// Merge context definitions
|
||||||
|
config.contexts = config.contexts ?? {};
|
||||||
|
Object.entries(imported.contexts ?? {}).forEach(([context, def]) => {
|
||||||
|
if (context in config.contexts!) {
|
||||||
|
config.contexts![context].vars = {
|
||||||
|
...config.contexts![context].vars,
|
||||||
|
...def.vars,
|
||||||
|
};
|
||||||
|
config.contexts![context].groups =
|
||||||
|
def.groups ?? config.contexts![context].groups;
|
||||||
|
} else {
|
||||||
|
config.contexts![context] = def;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Merge variables
|
||||||
|
config.vars = { ...config.vars, ...imported.vars };
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}, parsed);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a string, usually taken from the config, using the variables set in the config and extra variables passed in
|
||||||
|
*/
|
||||||
|
public formatConfigString(value: string, extraArgs?: Vars): string {
|
||||||
|
const args = { ...this.config.vars, ...extraArgs };
|
||||||
|
return value.replace(
|
||||||
|
/{([a-zA-Z_][a-zA-Z0-9_\-\.]*)}/g,
|
||||||
|
(match, identifier: string) => {
|
||||||
|
// @ts-expect-error
|
||||||
|
const replacer = identifier.split(".").reduce((val, id) => {
|
||||||
|
if (val === undefined) return undefined;
|
||||||
|
if (typeof val !== "object") return undefined;
|
||||||
|
if (id in val) return val[id];
|
||||||
|
return undefined;
|
||||||
|
}, args);
|
||||||
|
if (replacer === undefined) return match;
|
||||||
|
if (typeof replacer === "object") return match;
|
||||||
|
return replacer;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass the given string to formatConfigString and then evaluate it as a mathematical expression. Only addition and subtraction are supported.
|
||||||
|
*/
|
||||||
|
public doConfigMath(value: string, extraArgs?: Vars): number {
|
||||||
|
const str = this.formatConfigString(value, extraArgs);
|
||||||
|
let total = 0;
|
||||||
|
const matches = str.match(/[+\-]*(\.\d+|\d+(\.\d+)?)/g) ?? [];
|
||||||
|
while (matches.length) {
|
||||||
|
total += parseFloat(matches.shift() ?? "0");
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get config(): Readonly<ConfigFile> {
|
||||||
|
return this._config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _config: ConfigFile = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully refresh the config file
|
||||||
|
*/
|
||||||
|
async refreshConfig() {
|
||||||
|
this.fileMonitors.forEach(([_, mon]) => {
|
||||||
|
mon.cancel();
|
||||||
|
});
|
||||||
|
this.fileMonitors = [];
|
||||||
|
this._config = await this.parseConfig(this.configPath);
|
||||||
|
this.configReloaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@signal()
|
||||||
|
declare configReloaded: () => void;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.refreshConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
103
services/config/types.ts
Normal file
103
services/config/types.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Generic variable interface for user-defined variables referenced elsewhere
|
||||||
|
* in the configuration
|
||||||
|
*/
|
||||||
|
export interface Vars {
|
||||||
|
[key: string]: string | number | boolean | Vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigFile {
|
||||||
|
vars?: Vars;
|
||||||
|
import?: string[];
|
||||||
|
outputs?: Outputs.Outputs;
|
||||||
|
layouts?: Layouts.Layouts;
|
||||||
|
contexts?: Contexts.Contexts;
|
||||||
|
workspaces?: Workspaces.Workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Outputs {
|
||||||
|
export interface Outputs {
|
||||||
|
[name: string]: Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Criteria {
|
||||||
|
name?: string | string[];
|
||||||
|
make?: string;
|
||||||
|
model?: string;
|
||||||
|
serial?: string | number;
|
||||||
|
hostname?: string | string[];
|
||||||
|
modes: Partial<Mode>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Mode {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
refresh: number;
|
||||||
|
picture_aspect_ratio: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options
|
||||||
|
extends Partial<{
|
||||||
|
resolution: string;
|
||||||
|
refresh: string;
|
||||||
|
"custom-mode": string;
|
||||||
|
scale: number;
|
||||||
|
"calibration-profile": string;
|
||||||
|
"adaptive-sync": boolean;
|
||||||
|
"allow-tearing": boolean;
|
||||||
|
}> {}
|
||||||
|
|
||||||
|
export interface Output {
|
||||||
|
criteria: Criteria;
|
||||||
|
windows: string[] | Record<string, Record<string, string>>;
|
||||||
|
options: Options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Workspaces {
|
||||||
|
export interface Workspaces {
|
||||||
|
[id: string]: Workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Workspace {
|
||||||
|
name: string;
|
||||||
|
application?: string;
|
||||||
|
exec?: string;
|
||||||
|
args?: string[];
|
||||||
|
environ?: Record<string, string>;
|
||||||
|
unit?: string;
|
||||||
|
systemd?: boolean;
|
||||||
|
"log-output"?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Layouts {
|
||||||
|
export interface Layouts {
|
||||||
|
[name: string]: Layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Layout {
|
||||||
|
[screenName: string]: Screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Screen {
|
||||||
|
required?: boolean;
|
||||||
|
score?: number;
|
||||||
|
position: [x: string | number, y: string | number];
|
||||||
|
outputs: string[];
|
||||||
|
options?: Outputs.Options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Contexts {
|
||||||
|
export interface Contexts {
|
||||||
|
[name: string]: Context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Context {
|
||||||
|
vars?: Vars;
|
||||||
|
groups: {
|
||||||
|
[screenName: string]: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -81,12 +81,8 @@ export class DBusObject extends GObject.Object {
|
|||||||
marshall_func_name in this &&
|
marshall_func_name in this &&
|
||||||
typeof this[marshall_func_name] === "function"
|
typeof this[marshall_func_name] === "function"
|
||||||
) {
|
) {
|
||||||
console.log(`Calling ${marshall_func_name} to marshall value`);
|
|
||||||
return this[marshall_func_name]();
|
return this[marshall_func_name]();
|
||||||
}
|
}
|
||||||
console.log(
|
|
||||||
`Did not find ${marshall_func_name}, falling back to default marshalling`,
|
|
||||||
);
|
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case "string":
|
case "string":
|
||||||
return GLib.Variant.new_string(value);
|
return GLib.Variant.new_string(value);
|
||||||
@ -106,9 +102,15 @@ export class DBusObject extends GObject.Object {
|
|||||||
value.map((val) => val.objectPath),
|
value.map((val) => val.objectPath),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new GLib.Variant("av", value);
|
return new GLib.Variant(
|
||||||
|
"av",
|
||||||
|
value.map(this.marshallValue.bind(this)),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return new GLib.Variant("a{sv}", value);
|
console.warn(
|
||||||
|
"No marshall function found for object type, cannot implicitly marshall JS objects. Returning null.",
|
||||||
|
);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
case "undefined":
|
case "undefined":
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import { CompositorConnection } from "@services/compositor";
|
|||||||
export class VSMonitor extends DBusObject {
|
export class VSMonitor extends DBusObject {
|
||||||
static _nextId: number = 1;
|
static _nextId: number = 1;
|
||||||
|
|
||||||
|
private valid: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The simplified name of the monitor, such as HDMI-A-1 or DP-3
|
* The simplified name of the monitor, such as HDMI-A-1 or DP-3
|
||||||
*/
|
*/
|
||||||
@ -45,7 +47,7 @@ export class VSMonitor extends DBusObject {
|
|||||||
@property(String)
|
@property(String)
|
||||||
public readonly serial: string;
|
public readonly serial: string;
|
||||||
|
|
||||||
_mode: OutputMode;
|
_mode: OutputMode | null;
|
||||||
_active: boolean;
|
_active: boolean;
|
||||||
_powered: boolean;
|
_powered: boolean;
|
||||||
_rect: Rect;
|
_rect: Rect;
|
||||||
@ -57,28 +59,30 @@ export class VSMonitor extends DBusObject {
|
|||||||
"dev.ezri.voidshell.Monitor",
|
"dev.ezri.voidshell.Monitor",
|
||||||
`/dev/ezri/VoidShell/Monitor/_${VSMonitor._nextId++}`,
|
`/dev/ezri/VoidShell/Monitor/_${VSMonitor._nextId++}`,
|
||||||
);
|
);
|
||||||
|
console.log("Creating monitor", adapter.name);
|
||||||
this.name = adapter.name;
|
this.name = adapter.name;
|
||||||
this.availableModes = adapter.availableModes;
|
this.availableModes = adapter.availableModes;
|
||||||
this.make = adapter.make;
|
this.make = adapter.make;
|
||||||
this.model = adapter.model;
|
this.model = adapter.model;
|
||||||
this.serial = adapter.serial;
|
this.serial = adapter.serial;
|
||||||
this._mode = adapter.mode.simplify();
|
this._mode = adapter.mode?.simplify() ?? null;
|
||||||
this._active = adapter.active;
|
this._active = adapter.active;
|
||||||
this._powered = adapter.powered;
|
this._powered = adapter.powered;
|
||||||
this._rect = adapter.rect.simplify();
|
this._rect = adapter.rect.simplify();
|
||||||
this._scale = adapter.scale;
|
this._scale = adapter.scale;
|
||||||
this._transform = adapter.transform;
|
this._transform = adapter.transform;
|
||||||
|
|
||||||
const subId = this.connect("destroyed", () => {
|
|
||||||
this.disconnect(subId);
|
|
||||||
this.dbusObj?.unexport();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sync(adapter: VSOutputAdapter) {
|
public sync(adapter: VSOutputAdapter) {
|
||||||
|
if (!this.valid) {
|
||||||
|
console.error(
|
||||||
|
"Cannot sync a destroyed output! You have dangling objects!",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// EDID data shouldn't ever change, so we'll ignore it.
|
// EDID data shouldn't ever change, so we'll ignore it.
|
||||||
const toEmit = new Set<string & keyof this>();
|
const toEmit = new Set<string & keyof this>();
|
||||||
if (!adapter.mode.equals(this._mode)) {
|
if (!adapter.mode?.equals(this._mode)) {
|
||||||
toEmit.add("mode");
|
toEmit.add("mode");
|
||||||
}
|
}
|
||||||
if (this._active !== adapter.active) {
|
if (this._active !== adapter.active) {
|
||||||
@ -99,7 +103,7 @@ export class VSMonitor extends DBusObject {
|
|||||||
if (this._transform !== adapter.transform) {
|
if (this._transform !== adapter.transform) {
|
||||||
toEmit.add("transform");
|
toEmit.add("transform");
|
||||||
}
|
}
|
||||||
this._mode = adapter.mode.simplify();
|
this._mode = adapter.mode?.simplify() ?? null;
|
||||||
this._active = adapter.active;
|
this._active = adapter.active;
|
||||||
this._powered = adapter.powered;
|
this._powered = adapter.powered;
|
||||||
this._rect = adapter.rect.simplify();
|
this._rect = adapter.rect.simplify();
|
||||||
@ -126,10 +130,10 @@ export class VSMonitor extends DBusObject {
|
|||||||
|
|
||||||
protected marshall_mode(): GLib.Variant {
|
protected marshall_mode(): GLib.Variant {
|
||||||
return new GLib.Variant("(iiis)", [
|
return new GLib.Variant("(iiis)", [
|
||||||
this._mode.width,
|
this._mode?.width ?? 0,
|
||||||
this._mode.height,
|
this._mode?.height ?? 0,
|
||||||
this._mode.refresh,
|
this._mode?.refresh ?? 0,
|
||||||
this._mode.aspect_ratio,
|
this._mode?.aspect_ratio ?? "",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +155,10 @@ export class VSMonitor extends DBusObject {
|
|||||||
* will not be immediately reflected.
|
* will not be immediately reflected.
|
||||||
*/
|
*/
|
||||||
@property(VSOutputMode)
|
@property(VSOutputMode)
|
||||||
public get mode(): VSOutputMode {
|
public get mode(): VSOutputMode | null {
|
||||||
|
if (!this._mode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return VSOutputMode.wrap(this._mode);
|
return VSOutputMode.wrap(this._mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,10 +311,16 @@ export class VSMonitor extends DBusObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.dbusObj?.unexport();
|
||||||
|
this.valid = false;
|
||||||
|
this.emit("destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when the monitor is "destroyed" (disconnected). Invalidates the object.
|
* Fired when the monitor is "destroyed" (disconnected). Indicates the object is now invalid.
|
||||||
*/
|
*/
|
||||||
@signal(VSMonitor)
|
@signal()
|
||||||
declare destroyed: () => void;
|
declare destroyed: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1 +1,14 @@
|
|||||||
import GObject, { property, register } from "astal/gobject";
|
import GObject, { property, register } from "astal/gobject";
|
||||||
|
import { VSMonitor } from "./monitor";
|
||||||
|
import { DBusObject } from "@services/dbus";
|
||||||
|
|
||||||
|
@register({
|
||||||
|
GTypeName: "VSOutput",
|
||||||
|
})
|
||||||
|
export class VSOutput extends DBusObject {
|
||||||
|
@property(String)
|
||||||
|
public readonly name: string;
|
||||||
|
|
||||||
|
@property(Object)
|
||||||
|
public readonly compatibleMonitors: VSMonitor[] = [];
|
||||||
|
}
|
||||||
|
|||||||
@ -22,39 +22,47 @@ export class OutputService extends GObject.Object {
|
|||||||
@signal(VSMonitor)
|
@signal(VSMonitor)
|
||||||
declare monitorConnected: (monitor: VSMonitor) => void;
|
declare monitorConnected: (monitor: VSMonitor) => void;
|
||||||
|
|
||||||
constructor() {
|
private onOutputEvent(event: VSOutputEvent) {
|
||||||
super();
|
|
||||||
CompositorConnection.instance.connect(
|
|
||||||
"output-change",
|
|
||||||
(_, event: VSOutputEvent) => {
|
|
||||||
console.log("processing output change event");
|
console.log("processing output change event");
|
||||||
|
const idx = this.#monitors.findIndex(
|
||||||
|
(monitor) => monitor.name === event.output,
|
||||||
|
);
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case EventType.CREATE:
|
case EventType.CREATE:
|
||||||
if (
|
if (idx === -1) {
|
||||||
this.#monitors.find(
|
console.log(`new monitor ${event.output} connected`);
|
||||||
(monitor) => monitor.name === event.output,
|
const newMonitor = new VSMonitor(event.adapter!);
|
||||||
) === null
|
this.#monitors.push(newMonitor);
|
||||||
) {
|
this.monitorConnected(newMonitor);
|
||||||
this.#monitors.push(new VSMonitor(event.adapter!));
|
} else {
|
||||||
break;
|
console.warn(
|
||||||
}
|
`Received create event for monitor ${event.output} we already know about. This is probably a bug!`,
|
||||||
case EventType.CHANGE:
|
|
||||||
const monitor = this.#monitors.find(
|
|
||||||
(monitor) => monitor.name === event.output,
|
|
||||||
);
|
);
|
||||||
monitor?.sync(event.adapter!);
|
}
|
||||||
|
break;
|
||||||
|
case EventType.CHANGE:
|
||||||
|
if (idx === -1) {
|
||||||
|
console.warn(
|
||||||
|
`Received change event for monitor ${event.output} we don't know about. This is probably a bug!`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.#monitors[idx].sync(event.adapter!);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EventType.DESTROY:
|
case EventType.DESTROY:
|
||||||
const idx = this.#monitors.findIndex(
|
if (idx === -1) {
|
||||||
(output) => output.name === event.output,
|
console.warn(
|
||||||
|
`Received destroy event for monitor ${event.output} we don't know about. This is probably a bug!`,
|
||||||
);
|
);
|
||||||
if (idx === -1) break;
|
} else {
|
||||||
this.#monitors[idx].destroyed();
|
this.#monitors[idx].destroy();
|
||||||
this.#monitors.splice(idx, 1);
|
this.#monitors.splice(idx, 1);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
CompositorConnection.instance
|
CompositorConnection.instance
|
||||||
.getOutputs()
|
.getOutputs()
|
||||||
.then((compositorOutputs) => {
|
.then((compositorOutputs) => {
|
||||||
@ -62,6 +70,12 @@ export class OutputService extends GObject.Object {
|
|||||||
this.#monitors.push(new VSMonitor(output)),
|
this.#monitors.push(new VSMonitor(output)),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error)
|
||||||
|
.then(() =>
|
||||||
|
CompositorConnection.instance.connect(
|
||||||
|
"output-change",
|
||||||
|
(_, event: VSOutputEvent) => this.onOutputEvent(event),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,6 @@ import { Tree } from "@/services/sway/workspaces";
|
|||||||
const time = Variable("").poll(1000, "date");
|
const time = Variable("").poll(1000, "date");
|
||||||
|
|
||||||
export default function Bar(monitor: number) {
|
export default function Bar(monitor: number) {
|
||||||
const tree = Tree.instance;
|
|
||||||
const currentContext = bind(tree, "currentContextName");
|
|
||||||
return (
|
return (
|
||||||
<window
|
<window
|
||||||
className="Bar"
|
className="Bar"
|
||||||
@ -21,7 +19,7 @@ export default function Bar(monitor: number) {
|
|||||||
application={App}
|
application={App}
|
||||||
>
|
>
|
||||||
<centerbox>
|
<centerbox>
|
||||||
<label label={currentContext} />
|
<box />
|
||||||
<box>
|
<box>
|
||||||
<SystemName />
|
<SystemName />
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user