sway workspace indicator

This commit is contained in:
end-4 2024-01-25 15:53:57 +07:00
parent fb2cb6ed7e
commit c5744aa2fe
4 changed files with 482 additions and 369 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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;
}
}
};

View file

@ -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);
}
});