diff --git a/.config/ags/config.js b/.config/ags/config.js index 3e6d1bdf..d44c12ec 100644 --- a/.config/ags/config.js +++ b/.config/ags/config.js @@ -64,6 +64,7 @@ export default { }; // Stuff that don't need to be toggled. And they're async so ugh... +// Bar().catch(print); forMonitors(Bar); forMonitors(BarCornerTopleft); forMonitors(BarCornerTopright); \ No newline at end of file diff --git a/.config/ags/services/sway.js b/.config/ags/services/sway.js index d32e7ef8..388372e9 100644 --- a/.config/ags/services/sway.js +++ b/.config/ags/services/sway.js @@ -1,309 +1,303 @@ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import Service from "resource:///com/github/Aylur/ags/service.js"; + +const SIS = GLib.getenv('SWAYSOCK'); + +export const PAYLOAD_TYPE = { + MESSAGE_RUN_COMMAND: 0, + MESSAGE_GET_WORKSPACES: 1, + MESSAGE_SUBSCRIBE: 2, + MESSAGE_GET_OUTPUTS: 3, + MESSAGE_GET_TREE: 4, + MESSAGE_GET_MARKS: 5, + MESSAGE_GET_BAR_CONFIG: 6, + MESSAGE_GET_VERSION: 7, + MESSAGE_GET_BINDING_NODES: 8, + MESSAGE_GET_CONFIG: 9, + MESSAGE_SEND_TICK: 10, + MESSAGE_SYNC: 11, + MESSAGE_GET_BINDING_STATE: 12, + MESSAGE_GET_INPUTS: 100, + MESSAGE_GET_SEATS: 101, + EVENT_WORKSPACE: 0x80000000, + EVENT_MODE: 0x80000002, + EVENT_WINDOW: 0x80000003, + EVENT_BARCONFIG_UPDATE: 0x80000004, + EVENT_BINDING: 0x80000005, + EVENT_SHUTDOWN: 0x80000006, + EVENT_TICK: 0x80000007, + EVENT_BAR_STATE_UPDATE: 0x80000014, + EVENT_INPUT: 0x80000015, +} + +const Client_Event = { + change: undefined, + container: undefined, +} + +const Workspace_Event = { + change: undefined, + current: undefined, + old: undefined, +} + +const Geometry = { + x: undefined, + y: undefined, + width: undefined, + height: undefined, +} + +//NOTE: not all properties are listed here +export const Node = { + id: undefined, + name: undefined, + type: undefined, + border: undefined, + current_border_width: undefined, + layout: undefined, + orientation: undefined, + percent: undefined, + rect: undefined, + window_rect: undefined, + deco_rect: undefined, + geometry: undefined, + urgent: undefined, + sticky: undefined, + marks: undefined, + focused: undefined, + active: undefined, + focus: undefined, + nodes: undefined, + floating_nodes: undefined, + representation: undefined, + fullscreen_mode: undefined, + app_id: undefined, + pid: undefined, + visible: undefined, + shell: undefined, + output: undefined, + inhibit_idle: undefined, + idle_inhibitors: { + application: undefined, + user: undefined, + }, + window: undefined, + window_properties: { + title: undefined, + class: undefined, + instance: undefined, + window_role: undefined, + window_type: undefined, + transient_for: undefined, } -}; -var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { - if (!ar) ar = Array.prototype.slice.call(from, 0, i); - ar[i] = from[i]; - } +} + +export class SwayActiveClient extends Service { + static { + Service.register(this, {}, { + 'id': ['int'], + 'name': ['string'], + 'class': ['string'], + }); } - return to.concat(ar || Array.prototype.slice.call(from)); -}; -var _a, _b, _c, _d; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.sway = exports.Sway = exports.SwayActives = exports.SwayActiveID = exports.SwayActiveClient = void 0; -var _1 = require("gi://GLib"); -var _2 = require("gi://Gio"); -var service_js_1 = require("../service.js"); -var SIS = _1.default.getenv('SWAYSOCK'); -var SwayActiveClient = /** @class */ (function (_super) { - __extends(SwayActiveClient, _super); - function SwayActiveClient() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this._id = 0; - _this._name = ''; - _this._class = ''; - return _this; - } - Object.defineProperty(SwayActiveClient.prototype, "id", { - get: function () { return this._id; }, - enumerable: false, - configurable: true - }); - Object.defineProperty(SwayActiveClient.prototype, "name", { - get: function () { return this._name; }, - enumerable: false, - configurable: true - }); - Object.defineProperty(SwayActiveClient.prototype, "class", { - get: function () { return this._class; }, - enumerable: false, - configurable: true - }); - SwayActiveClient.prototype.updateProperty = function (prop, value) { - _super.prototype.updateProperty.call(this, prop, value); + + _id = 0; + _name = ''; + _class = ''; + + get id() { return this._id; } + get name() { return this._name; } + get class() { return this._class; } + + updateProperty(prop, value) { + if (!['id', 'name', 'class'].includes(prop)) return; + super.updateProperty(prop, value); this.emit('changed'); - }; - return SwayActiveClient; -}(service_js_1.default)); -exports.SwayActiveClient = SwayActiveClient; -_a = SwayActiveClient; -(function () { - service_js_1.default.register(_a, {}, { - 'id': ['int'], - 'name': ['string'], - 'class': ['string'], - }); -})(); -var SwayActiveID = /** @class */ (function (_super) { - __extends(SwayActiveID, _super); - function SwayActiveID() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this._id = 1; - _this._name = ''; - return _this; } - Object.defineProperty(SwayActiveID.prototype, "id", { - get: function () { return this._id; }, - enumerable: false, - configurable: true - }); - Object.defineProperty(SwayActiveID.prototype, "name", { - get: function () { return this._name; }, - enumerable: false, - configurable: true - }); - SwayActiveID.prototype.update = function (id, name) { - _super.prototype.updateProperty.call(this, 'id', id); - _super.prototype.updateProperty.call(this, 'name', name); +} + +export class SwayActiveID extends Service { + static { + Service.register(this, {}, { + 'id': ['int'], + 'name': ['string'], + }); + } + + _id = 0; + _name = ''; + + get id() { return this._id; } + get name() { return this._name; } + + update(id, name) { + super.updateProperty('id', id); + super.updateProperty('name', name); this.emit('changed'); - }; - return SwayActiveID; -}(service_js_1.default)); -exports.SwayActiveID = SwayActiveID; -_b = SwayActiveID; -(function () { - service_js_1.default.register(_b, {}, { - 'id': ['int'], - 'name': ['string'], - }); -})(); -var SwayActives = /** @class */ (function (_super) { - __extends(SwayActives, _super); - function SwayActives() { - var _this = _super.call(this) || this; - _this._client = new SwayActiveClient; - _this._monitor = new SwayActiveID; - _this._workspace = new SwayActiveID; - ['client', 'workspace', 'monitor'].forEach(function (obj) { - _this["_".concat(obj)].connect('changed', function () { - _this.notify(obj); - _this.emit('changed'); + } +} + +export class SwayActives extends Service { + static { + Service.register(this, {}, { + 'client': ['jsobject'], + 'monitor': ['jsobject'], + 'workspace': ['jsobject'], + }); + } + + _client = new SwayActiveClient; + _monitor = new SwayActiveID; + _workspace = new SwayActiveID; + + constructor() { + super(); + + (['client', 'workspace', 'monitor']).forEach(obj => { + this[`_${obj}`].connect('changed', () => { + this.notify(obj); + this.emit('changed'); }); }); - return _this; } - Object.defineProperty(SwayActives.prototype, "client", { - get: function () { return this._client; }, - enumerable: false, - configurable: true - }); - Object.defineProperty(SwayActives.prototype, "monitor", { - get: function () { return this._monitor; }, - enumerable: false, - configurable: true - }); - Object.defineProperty(SwayActives.prototype, "workspace", { - get: function () { return this._workspace; }, - enumerable: false, - configurable: true - }); - return SwayActives; -}(service_js_1.default)); -exports.SwayActives = SwayActives; -_c = SwayActives; -(function () { - service_js_1.default.register(_c, {}, { - 'client': ['jsobject'], - 'monitor': ['jsobject'], - 'workspace': ['jsobject'], - }); -})(); -var Sway = /** @class */ (function (_super) { - __extends(Sway, _super); - function Sway() { - var _this = this; + + get client() { return this._client; } + get monitor() { return this._monitor; } + get workspace() { return this._workspace; } +} + +export class Sway extends Service { + static { + Service.register(this, {}, { + 'active': ['jsobject'], + 'monitors': ['jsobject'], + 'workspaces': ['jsobject'], + 'clients': ['jsobject'], + }); + } + + _decoder = new TextDecoder(); + _encoder = new TextEncoder(); + _socket; + + _active; + _monitors; + _workspaces; + _clients; + + get active() { return this._active; } + get monitors() { return Array.from(this._monitors.values()); } + get workspaces() { return Array.from(this._workspaces.values()); } + get clients() { return Array.from(this._clients.values()); } + + getMonitor(id) { return this._monitors.get(id); } + getWorkspace(name) { return this._workspaces.get(name); } + getClient(id) { return this._clients.get(id); } + + msg(payload) { this._send(PAYLOAD_TYPE.MESSAGE_RUN_COMMAND, payload); } + + constructor() { if (!SIS) console.error('Sway is not running'); - _this = _super.call(this) || this; - _this._decoder = new TextDecoder(); - _this._encoder = new TextEncoder(); - _this._active = new SwayActives(); - _this._monitors = new Map(); - _this._workspaces = new Map(); - _this._clients = new Map(); - var socket = new _2.default.SocketClient().connect(new _2.default.UnixSocketAddress({ - path: "".concat(SIS), + super(); + + this._active = new SwayActives(); + this._monitors = new Map(); + this._workspaces = new Map(); + this._clients = new Map(); + + this._socket = new Gio.SocketClient().connect(new Gio.UnixSocketAddress({ + path: `${SIS}`, }), null); - _this._watchSocket(socket.get_input_stream()); - _this._output_stream = socket.get_output_stream(); - _this.send(4 /* PAYLOAD_TYPE.MESSAGE_GET_TREE */, ''); - _this.send(2 /* PAYLOAD_TYPE.MESSAGE_SUBSCRIBE */, JSON.stringify(['window', 'workspace'])); - _this._active.connect('changed', function () { return _this.emit('changed'); }); - ['monitor', 'workspace', 'client'].forEach(function (active) { - return _this._active.connect("notify::".concat(active), function () { return _this.notify('active'); }); - }); - return _this; + + this._watchSocket(this._socket.get_input_stream()); + this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, ''); + this._send(PAYLOAD_TYPE.MESSAGE_SUBSCRIBE, JSON.stringify(['window', 'workspace'])); + + this._active.connect('changed', () => this.emit('changed')); + ['monitor', 'workspace', 'client'].forEach(active => + this._active.connect(`notify::${active}`, () => this.notify('active'))); } - Object.defineProperty(Sway.prototype, "active", { - get: function () { return this._active; }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Sway.prototype, "monitors", { - get: function () { return Array.from(this._monitors.values()); }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Sway.prototype, "workspaces", { - get: function () { return Array.from(this._workspaces.values()); }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Sway.prototype, "clients", { - get: function () { return Array.from(this._clients.values()); }, - enumerable: false, - configurable: true - }); - Sway.prototype.getMonitor = function (id) { return this._monitors.get(id); }; - Sway.prototype.getWorkspace = function (name) { return this._workspaces.get(name); }; - Sway.prototype.getClient = function (id) { return this._clients.get(id); }; - Sway.prototype.send = function (payloadType, payload) { - var pb = this._encoder.encode(payload); - var type = new Uint32Array([payloadType]); - var pl = new Uint32Array([pb.length]); - var magic_string = this._encoder.encode('i3-ipc'); - var data = new Uint8Array(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], magic_string, true), (new Uint8Array(pl.buffer)), true), (new Uint8Array(type.buffer)), true), pb, true)); - this._output_stream.write(data, null); - }; - Sway.prototype._watchSocket = function (stream) { - var _this = this; - stream.read_bytes_async(14, _1.default.PRIORITY_DEFAULT, null, function (_, resultHeader) { - var data = stream.read_bytes_finish(resultHeader).get_data(); + + _send(payloadType, payload) { + const pb = this._encoder.encode(payload); + const type = new Uint32Array([payloadType]); + const pl = new Uint32Array([pb.length]); + const magic_string = this._encoder.encode('i3-ipc'); + const data = new Uint8Array([ + ...magic_string, + ...(new Uint8Array(pl.buffer)), + ...(new Uint8Array(type.buffer)), + ...pb]); + this._socket.get_output_stream().write(data, null); + } + + _watchSocket(stream) { + stream.read_bytes_async(14, GLib.PRIORITY_DEFAULT, null, (_, resultHeader) => { + const data = stream.read_bytes_finish(resultHeader).get_data(); if (!data) return; - var payloadLength = new Uint32Array(data.slice(6, 10).buffer)[0]; - var payloadType = new Uint32Array(data.slice(10, 14).buffer)[0]; - stream.read_bytes_async(payloadLength, _1.default.PRIORITY_DEFAULT, null, function (_, resultPayload) { - var data = stream.read_bytes_finish(resultPayload).get_data(); - if (!data) - return; - _this._onEvent(payloadType, JSON.parse(_this._decoder.decode(data))); - _this._watchSocket(stream); - }); + const payloadLength = new Uint32Array(data.slice(6, 10).buffer)[0]; + const payloadType = new Uint32Array(data.slice(10, 14).buffer)[0]; + stream.read_bytes_async( + payloadLength, + GLib.PRIORITY_DEFAULT, + null, + (_, resultPayload) => { + const data = stream.read_bytes_finish(resultPayload).get_data(); + if (!data) + return; + this._onEvent(payloadType, JSON.parse(this._decoder.decode(data))); + this._watchSocket(stream); + }); }); - }; - Sway.prototype._onEvent = function (event_type, event) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_e) { - if (!event) - return [2 /*return*/]; - try { - switch (event_type) { - case 2147483648 /* PAYLOAD_TYPE.EVENT_WORKSPACE */: - this._handleWorkspaceEvent(event); - break; - case 2147483651 /* PAYLOAD_TYPE.EVENT_WINDOW */: - this._handleWindowEvent(event); - break; - case 4 /* PAYLOAD_TYPE.MESSAGE_GET_TREE */: - this._handleTreeMessage(event); - break; - default: - break; - } - } - catch (error) { - logError(error); - } - this.emit('changed'); - return [2 /*return*/]; - }); - }); - }; - Sway.prototype._handleWorkspaceEvent = function (workspaceEvent) { - var workspace = workspaceEvent.current; + } + + async _onEvent(event_type, event) { + if (!event) + return; + try { + switch (event_type) { + case PAYLOAD_TYPE.EVENT_WORKSPACE: + this._handleWorkspaceEvent(event); + break; + case PAYLOAD_TYPE.EVENT_WINDOW: + this._handleWindowEvent(event); + break; + case PAYLOAD_TYPE.MESSAGE_GET_TREE: + this._handleTreeMessage(event); + break; + default: + break; + } + } catch (error) { + logError(error); + } + this.emit('changed'); + } + + _handleWorkspaceEvent(workspaceEvent) { + const workspace = workspaceEvent.current; switch (workspaceEvent.change) { case 'init': this._workspaces.set(workspace.name, workspace); - this.notify('workspaces'); break; case 'empty': this._workspaces.delete(workspace.name); - this.notify('workspaces'); break; case 'focus': this._active.workspace.update(workspace.id, workspace.name); this._active.monitor.update(1, workspace.output); + this._workspaces.set(workspace.name, workspace); this._workspaces.set(workspaceEvent.old.name, workspaceEvent.old); - this.notify('workspaces'); break; case 'rename': if (this._active.workspace.id === workspace.id) this._active.workspace.updateProperty('name', workspace.name); this._workspaces.set(workspace.name, workspace); - this.notify('workspaces'); break; case 'reload': break; @@ -311,34 +305,36 @@ var Sway = /** @class */ (function (_super) { case 'urgent': default: this._workspaces.set(workspace.name, workspace); - this.notify('workspaces'); } - }; - Sway.prototype._handleWindowEvent = function (clientEvent) { - var _e; - var client = clientEvent.container; - var id = client.id; + this.notify('workspaces'); + } + + _handleWindowEvent(clientEvent) { + const client = clientEvent.container; + const id = client.id; switch (clientEvent.change) { case 'new': - this._clients.set(id, client); - this.notify('clients'); - break; case 'close': - this._clients.delete(id); - this.notify('clients'); + case 'floating': + case 'move': + // Refresh tree since client events don't contain the relevant information + // to be able to modify `workspace.nodes` or `workspace.floating_nodes`. + // There has to be a better way than this though :/ + this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, ''); break; case 'focus': if (this._active.client.id === id) return; // eslint-disable-next-line no-case-declarations - var current_active = this._clients.get(this._active.client.id); + const current_active = this._clients.get(this._active.client.id); if (current_active) current_active.focused = false; this._active.client.updateProperty('id', id); this._active.client.updateProperty('name', client.name); this._active.client.updateProperty('class', client.shell === 'xwayland' - ? ((_e = client.window_properties) === null || _e === void 0 ? void 0 : _e.class) || '' - : client.app_id); + ? client.window_properties?.class || '' + : client.app_id, + ); break; case 'title': if (client.focused) @@ -347,43 +343,39 @@ var Sway = /** @class */ (function (_super) { this.notify('clients'); break; case 'fullscreen_mode': - case 'move': - case 'floating': case 'urgent': case 'mark': default: this._clients.set(id, client); this.notify('clients'); } - }; - Sway.prototype._handleTreeMessage = function (node) { - var _this = this; - var _e; + } + + _handleTreeMessage(node) { switch (node.type) { case 'root': this._workspaces.clear(); this._clients.clear(); this._monitors.clear(); - node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); - ['workspaces', 'clients', 'monitors'].forEach(function (t) { - _this.notify(t); - }); + node.nodes.map(n => this._handleTreeMessage(n)); break; case 'output': this._monitors.set(node.id, node); if (node.active) - this._active.monitor.updateProperty('name', node.name); - node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); + this._active.monitor.update(node.id, node.name); + node.nodes.map(n => this._handleTreeMessage(n)); this.notify('monitors'); break; case 'workspace': this._workspaces.set(node.name, node); // I think I'm missing something. There has to be a better way. // eslint-disable-next-line no-case-declarations - var hasFocusedChild_1 = function (n) { return n.nodes.some(function (c) { return c.focused || hasFocusedChild_1(c); }); }; - if (hasFocusedChild_1(node)) + const hasFocusedChild = + (n) => n.nodes.some(c => c.focused || hasFocusedChild(c)); + if (node.focused || hasFocusedChild(node)) this._active.workspace.update(node.id, node.name); - node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); + + node.nodes.map(n => this._handleTreeMessage(n)); this.notify('workspaces'); break; case 'con': @@ -393,25 +385,16 @@ var Sway = /** @class */ (function (_super) { this._active.client.updateProperty('id', node.id); this._active.client.updateProperty('name', node.name); this._active.client.updateProperty('class', node.shell === 'xwayland' - ? ((_e = node.window_properties) === null || _e === void 0 ? void 0 : _e.class) || '' - : node.app_id); + ? node.window_properties?.class || '' + : node.app_id, + ); } - node.nodes.map(function (n) { return _this._handleTreeMessage(n); }); + node.nodes.map(n => this._handleTreeMessage(n)); this.notify('clients'); break; } - }; - return Sway; -}(service_js_1.default)); -exports.Sway = Sway; -_d = Sway; -(function () { - service_js_1.default.register(_d, {}, { - 'active': ['jsobject'], - 'monitors': ['jsobject'], - 'workspaces': ['jsobject'], - 'clients': ['jsobject'], - }); -})(); -exports.sway = new Sway; -exports.default = exports.sway; + } +} + +export const sway = new Sway; +export default sway; \ No newline at end of file diff --git a/.config/ags/widgets/bar/main.js b/.config/ags/widgets/bar/main.js index 934bb475..8ca1d8fb 100644 --- a/.config/ags/widgets/bar/main.js +++ b/.config/ags/widgets/bar/main.js @@ -11,8 +11,11 @@ const OptionalWorkspaces = async () => { try { return (await import('./workspaces_hyprland.js')).default(); } catch { - // return (await import('./workspaces_sway.js')).default(); - return null; + try { + return (await import('./workspaces_sway.js')).default(); + } catch { + return null; + } } }; diff --git a/.config/ags/widgets/bar/workspaces_sway.js b/.config/ags/widgets/bar/workspaces_sway.js index 70084866..53612eb9 100644 --- a/.config/ags/widgets/bar/workspaces_sway.js +++ b/.config/ags/widgets/bar/workspaces_sway.js @@ -1,58 +1,184 @@ +const { GLib, Gdk, Gtk } = imports.gi; +const Lang = imports.lang; +const Cairo = imports.cairo; +const Pango = imports.gi.Pango; +const PangoCairo = imports.gi.PangoCairo; import Widget from "resource:///com/github/Aylur/ags/widget.js"; import Sway from "../../services/sway.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import options from "../../options.js"; -import { range } from "../../utils.js"; +const { execAsync, exec } = Utils; +const { Box, DrawingArea, EventBox } = Widget; +const NUM_OF_WORKSPACES = 10; +const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props +const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props +const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props -const dispatch = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`); +const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print); +const switchToRelativeWorkspace = (self, num) => + execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print); -const Workspaces = () => { - const ws = options.workspaces.value || 20; - return Widget.Box({ - children: range(ws).map((i) => - Widget.Button({ - setup: (btn) => (btn.id = i), - on_clicked: () => dispatch(i), - child: Widget.Label({ - label: `${i}`, - class_name: "indicator", - vpack: "center", - }), - setup: (self) => self.hook(Sway, (btn) => { - btn.toggleClassName("active", Sway.active.workspace.name == i); - btn.toggleClassName( - "occupied", - Sway.getWorkspace(`${i}`)?.nodes.length > 0, - ); - }), +const WorkspaceContents = (count = 10) => { + return DrawingArea({ + css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`, + attribute: { + initialized: false, + workspaceMask: 0, + updateMask: (self) => { + if (self.attribute.initialized) return; // We only need this to run once + const workspaces = Sway.workspaces; + let workspaceMask = 0; + // console.log('----------------') + for (let i = 0; i < workspaces.length; i++) { + const ws = workspaces[i]; + // console.log(ws.name, ',', ws.num); + if (!Number(ws.name)) return; + const id = Number(ws.name); + if (id <= 0) continue; // Ignore scratchpads + if (id > count) return; // Not rendered + if (workspaces[i].windows > 0) { + workspaceMask |= (1 << id); + } + } + self.attribute.workspaceMask = workspaceMask; + self.attribute.initialized = true; + }, + toggleMask: (self, occupied, name) => { + if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name)); + else self.attribute.workspaceMask &= ~(1 << parseInt(name)); + }, + }, + setup: (area) => area + .hook(Sway.active.workspace, (area) => { + area.setCss(`font-size: ${Sway.active.workspace.name}px;`) }) - ), - setup: (self) => self.hook(Sway.active.workspace, - (box) => box.children.map((btn) => { - btn.visible = Sway.workspaces.some( - (ws) => ws.name == btn.id, - ); - }) - ), - }); -}; + .hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces') + // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added') + // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed') + .on('draw', Lang.bind(area, (area, cr) => { + const allocation = area.get_allocation(); + const { width, height } = allocation; -export default () => Widget.EventBox({ - class_name: "workspaces panel-button", - child: Widget.Box({ - // its nested like this to keep it consistent with other PanelButton widgets - child: Widget.EventBox({ - on_scroll_up: () => dispatch("next"), - on_scroll_down: () => dispatch("prev"), - class_name: "eventbox", - // binds: [["child", options.workspaces, "value", Workspaces]], - setup: (self) => self - .hook(options.workspaces, (self) => Selection.child = Workspaces(), "value") - , - }), + const workspaceStyleContext = dummyWs.get_style_context(); + const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL); + const workspaceRadius = workspaceDiameter / 2; + const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3; + const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL); + const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL); + + const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context(); + const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL); + + const activeWorkspaceStyleContext = dummyActiveWs.get_style_context(); + const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL); + area.set_size_request(workspaceDiameter * count, -1); + const widgetStyleContext = area.get_style_context(); + const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL); + + const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs); + const activeWsCenterY = height / 2; + + // Font + const layout = PangoCairo.create_layout(cr); + const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`); + layout.set_font_description(fontDesc); + cr.setAntialias(Cairo.Antialias.BEST); + // Get kinda min radius for number indicators + layout.set_text("0".repeat(count.toString().length), -1); + const [layoutWidth, layoutHeight] = layout.get_pixel_size(); + const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius + const indicatorGap = workspaceRadius - indicatorRadius; + + // Draw workspace numbers + for (let i = 1; i <= count; i++) { + if (area.attribute.workspaceMask & (1 << i)) { + // Draw bg highlight + cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha); + const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i); + const wsCenterY = height / 2; + if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left + cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI); + cr.fill(); + } + else { + cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2) + cr.fill(); + } + if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right + cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI); + cr.fill(); + } + else { + cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2) + cr.fill(); + } + + // Set color for text + cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha); + } + else + cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha); + layout.set_text(`${i}`, -1); + const [layoutWidth, layoutHeight] = layout.get_pixel_size(); + const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2); + const y = (height - layoutHeight) / 2; + cr.moveTo(x, y); + // cr.showText(text); + PangoCairo.show_layout(cr, layout); + cr.stroke(); + } + + // Draw active ws + // base + cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha); + cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI); + cr.fill(); + // inner decor + cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha); + cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI); + cr.fill(); + })) + , + }) +} + +export default () => EventBox({ + onScrollUp: (self) => switchToRelativeWorkspace(self, -1), + onScrollDown: (self) => switchToRelativeWorkspace(self, +1), + onMiddleClickRelease: () => App.toggleWindow('overview'), + onSecondaryClickRelease: () => App.toggleWindow('osk'), + attribute: { clicked: false }, + child: Box({ + homogeneous: true, + className: 'bar-group-margin', + children: [Box({ + className: 'bar-group bar-group-standalone bar-group-pad', + css: 'min-width: 2px;', + children: [ + WorkspaceContents(10), + ] + })] }), setup: (self) => { - console.log('[LOG] Sway workspace module loaded') + self.add_events(Gdk.EventMask.POINTER_MOTION_MASK); + self.on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + const [_, cursorX, cursorY] = event.get_coords(); + const widgetWidth = self.get_allocation().width; + const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth); + switchToWorkspace(wsId); + }) + self.on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + const [_, cursorX, cursorY] = event.get_coords(); + const widgetWidth = self.get_allocation().width; + const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth); + switchToWorkspace(wsId); + }) + self.on('button-release-event', (self) => self.attribute.clicked = false); } });