From 60096ced7cd1fa3c25b30b4e626f89381b619959 Mon Sep 17 00:00:00 2001 From: Jas Singh Date: Sun, 25 Aug 2024 18:11:15 -0700 Subject: [PATCH] Added the ability to hide unoccupied workspaces. (#189) * Consolidated workspace logic * Added the ability to hide unoccupied workspaces. --- modules/bar/workspaces/helpers.ts | 162 ++++++++++++++ modules/bar/workspaces/index.ts | 254 ++++++++++------------ options.ts | 1 + widget/settings/pages/config/bar/index.ts | 1 + 4 files changed, 273 insertions(+), 145 deletions(-) create mode 100644 modules/bar/workspaces/helpers.ts diff --git a/modules/bar/workspaces/helpers.ts b/modules/bar/workspaces/helpers.ts new file mode 100644 index 0000000..b294b5b --- /dev/null +++ b/modules/bar/workspaces/helpers.ts @@ -0,0 +1,162 @@ +const hyprland = await Service.import("hyprland"); + +import { WorkspaceMap, WorkspaceRule } from "lib/types/workspace"; +import options from "options"; +import { Variable } from "types/variable"; + +const { + workspaces, + reverse_scroll, +} = options.bar.workspaces; + + +export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, monitor: number): boolean => { + if (!wsRules || !Object.keys(wsRules).length) { + return true; + } + + const monitorMap = {}; + hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name)); + + const currentMonitorName = monitorMap[monitor]; + const monitorWSRules = wsRules[currentMonitorName]; + + if (monitorWSRules === undefined) { + return true; + } + return monitorWSRules.includes(curWs); +}; + +export const getWorkspaceRules = (): WorkspaceMap => { + try { + const rules = Utils.exec("hyprctl workspacerules -j"); + + const workspaceRules = {}; + + JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => { + if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) { + workspaceRules[rule.monitor].push(index + 1); + } else { + workspaceRules[rule.monitor] = [index + 1]; + } + }); + + return workspaceRules; + } catch (err) { + console.error(err); + return {}; + } +}; + +export const getCurrentMonitorWorkspaces = (monitor: number): number[] => { + if (hyprland.monitors.length === 1) { + return Array.from({ length: workspaces.value }, (_, i) => i + 1); + } + + const monitorWorkspaces = getWorkspaceRules(); + const monitorMap = {}; + hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name)); + + const currentMonitorName = monitorMap[monitor]; + + return monitorWorkspaces[currentMonitorName]; +} + +export const goToNextWS = (currentMonitorWorkspaces: Variable, activeWorkspaces: boolean): void => { + if (activeWorkspaces === true) { + const activeWses = hyprland.workspaces.filter((ws) => hyprland.active.monitor.id === ws.monitorID); + + let nextIndex = hyprland.active.workspace.id + 1; + if (nextIndex > activeWses[activeWses.length - 1].id) { + + nextIndex = activeWses[0].id; + } + + hyprland.messageAsync(`dispatch workspace ${nextIndex}`) + } else if (currentMonitorWorkspaces.value === undefined) { + let nextIndex = hyprland.active.workspace.id + 1; + if (nextIndex > workspaces.value) { + nextIndex = 0; + } + + hyprland.messageAsync(`dispatch workspace ${nextIndex}`) + } else { + const curWorkspace = hyprland.active.workspace.id; + const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace); + let nextIndex = indexOfWs + 1; + if (nextIndex >= currentMonitorWorkspaces.value.length) { + nextIndex = 0; + } + + hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[nextIndex]}`) + } +} + +export const goToPrevWS = (currentMonitorWorkspaces: Variable, activeWorkspaces: boolean): void => { + if (activeWorkspaces === true) { + const activeWses = hyprland.workspaces.filter((ws) => hyprland.active.monitor.id === ws.monitorID); + + let prevIndex = hyprland.active.workspace.id - 1; + if (prevIndex < activeWses[0].id) { + + prevIndex = activeWses[activeWses.length - 1].id; + } + + hyprland.messageAsync(`dispatch workspace ${prevIndex}`) + } else if (currentMonitorWorkspaces.value === undefined) { + let prevIndex = hyprland.active.workspace.id - 1; + + if (prevIndex <= 0) { + prevIndex = workspaces.value; + } + + hyprland.messageAsync(`dispatch workspace ${prevIndex}`) + } else { + const curWorkspace = hyprland.active.workspace.id; + const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace); + let prevIndex = indexOfWs - 1; + if (prevIndex < 0) { + prevIndex = currentMonitorWorkspaces.value.length - 1; + } + + hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[prevIndex]}`) + } +} + +export function throttle void>(func: T, limit: number): T { + let inThrottle: boolean; + return function (this: ThisParameterType, ...args: Parameters) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => { + inThrottle = false; + }, limit); + } + } as T; +} + +type ThrottledScrollHandlers = { + throttledScrollUp: () => void; + throttledScrollDown: () => void; +}; + +export const createThrottledScrollHandlers = (scrollSpeed: number, currentMonitorWorkspaces: Variable, activeWorkspaces: boolean = false): ThrottledScrollHandlers => { + const throttledScrollUp = throttle(() => { + if (reverse_scroll.value === true) { + goToPrevWS(currentMonitorWorkspaces, activeWorkspaces); + } else { + goToNextWS(currentMonitorWorkspaces, activeWorkspaces); + } + }, 200 / scrollSpeed); + + const throttledScrollDown = throttle(() => { + if (reverse_scroll.value === true) { + goToNextWS(currentMonitorWorkspaces, activeWorkspaces); + } else { + goToPrevWS(currentMonitorWorkspaces, activeWorkspaces); + } + }, 200 / scrollSpeed); + + return { throttledScrollUp, throttledScrollDown }; +} diff --git a/modules/bar/workspaces/index.ts b/modules/bar/workspaces/index.ts index 44ce11f..c076b3d 100644 --- a/modules/bar/workspaces/index.ts +++ b/modules/bar/workspaces/index.ts @@ -1,12 +1,11 @@ const hyprland = await Service.import("hyprland"); -import { WorkspaceRule, WorkspaceMap } from "lib/types/workspace"; import options from "options"; +import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces, getWorkspaceRules, getWorkspacesForMonitor } from "./helpers"; const { workspaces, monitorSpecific, workspaceMask, - reverse_scroll, scroll_speed, spacing } = options.bar.workspaces; @@ -15,145 +14,47 @@ function range(length: number, start = 1) { return Array.from({ length }, (_, i) => i + start); } -const Workspaces = (monitor = -1, ws = 8) => { - const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap): boolean => { - if (!wsRules || !Object.keys(wsRules).length) { - return true; - } - - const monitorMap = {}; - hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name)); - - const currentMonitorName = monitorMap[monitor]; - const monitorWSRules = wsRules[currentMonitorName]; - - if (monitorWSRules === undefined) { - return true; - } - return monitorWSRules.includes(curWs); - }; - - const getWorkspaceRules = (): WorkspaceMap => { - try { - const rules = Utils.exec("hyprctl workspacerules -j"); - - const workspaceRules = {}; - - JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => { - if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) { - workspaceRules[rule.monitor].push(index + 1); - } else { - workspaceRules[rule.monitor] = [index + 1]; - } - }); - - return workspaceRules; - } catch (err) { - console.error(err); - return {}; - } - }; - - const getCurrentMonitorWorkspaces = (): number[] => { - if (hyprland.monitors.length === 1) { - return Array.from({ length: workspaces.value }, (_, i) => i + 1); - } - - const monitorWorkspaces = getWorkspaceRules(); - const monitorMap = {}; - hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name)); - - const currentMonitorName = monitorMap[monitor]; - - return monitorWorkspaces[currentMonitorName]; - } - const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces()); +const Workspaces = (monitor = -1) => { + const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor)); workspaces.connect("changed", () => { - currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces() + currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor) }) - const goToNextWS = (): void => { - if (currentMonitorWorkspaces.value === undefined) { - let nextIndex = hyprland.active.workspace.id + 1; - if (nextIndex > workspaces.value) { - nextIndex = 0; - } - hyprland.messageAsync(`dispatch workspace ${nextIndex}`) - - } else { - const curWorkspace = hyprland.active.workspace.id; - const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace); - let nextIndex = indexOfWs + 1; - if (nextIndex >= currentMonitorWorkspaces.value.length) { - nextIndex = 0; - } - - hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[nextIndex]}`) + const renderClassnames = (showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string, i: number) => { + if (showIcons) { + return `workspace-icon txt-icon bar`; } - } + if (showNumbered) { + const numActiveInd = hyprland.active.workspace.id === i + ? numberedActiveIndicator + : ""; - const goToPrevWS = (): void => { - if (currentMonitorWorkspaces.value === undefined) { - let prevIndex = hyprland.active.workspace.id - 1; - - if (prevIndex <= 0) { - prevIndex = workspaces.value; - } - hyprland.messageAsync(`dispatch workspace ${prevIndex}`) - } else { - const curWorkspace = hyprland.active.workspace.id; - const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace); - let prevIndex = indexOfWs - 1; - if (prevIndex < 0) { - prevIndex = currentMonitorWorkspaces.value.length - 1; - } - - hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[prevIndex]}`) + return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; } + return "default"; } - function throttle void>(func: T, limit: number): T { - let inThrottle: boolean; - return function (this: ThisParameterType, ...args: Parameters) { - if (!inThrottle) { - func.apply(this, args); - inThrottle = true; - setTimeout(() => { - inThrottle = false; - }, limit); + const renderLabel = (showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, i: number, index: number) => { + if (showIcons) { + if (hyprland.active.workspace.id === i) { + return active; } - } as T; + if ((hyprland.getWorkspace(i)?.windows || 0) > 0) { + return occupied; + } + if ( + monitor !== -1 + ) { + return available; + } + } + return workspaceMask + ? `${index + 1}` + : `${i}`; } - - type ThrottledScrollHandlers = { - throttledScrollUp: () => void; - throttledScrollDown: () => void; - }; - - const createThrottledScrollHandlers = (scrollSpeed: number): ThrottledScrollHandlers => { - const throttledScrollUp = throttle(() => { - if (reverse_scroll.value === true) { - goToPrevWS(); - } else { - goToNextWS(); - } - }, 200 / scrollSpeed); - - const throttledScrollDown = throttle(() => { - if (reverse_scroll.value === true) { - goToNextWS(); - } else { - goToPrevWS(); - } - }, 200 / scrollSpeed); - - return { throttledScrollUp, throttledScrollDown }; - } - - return { - component: Widget.Box({ - class_name: "workspaces", + const defaultWses = () => { + return Widget.Box({ children: Utils.merge( [workspaces.bind(), monitorSpecific.bind()], (workspaces, monitorSpecific) => { @@ -163,7 +64,7 @@ const Workspaces = (monitor = -1, ws = 8) => { return true; } const workspaceRules = getWorkspaceRules(); - return getWorkspacesForMonitor(i, workspaceRules); + return getWorkspacesForMonitor(i, workspaceRules, monitor); }) .map((i, index) => { return Widget.Button({ @@ -187,6 +88,11 @@ const Workspaces = (monitor = -1, ws = 8) => { hyprland.active.workspace.bind("id") ], (show_icons, show_numbered, numbered_active_indicator) => { + if (index === 0) { + console.log('in'); + + } + if (show_icons) { return `workspace-icon txt-icon bar`; } @@ -244,25 +150,82 @@ const Workspaces = (monitor = -1, ws = 8) => { }); }); }, - ), - setup: (box) => { - if (ws === 0) { - box.hook(hyprland.active.workspace, () => - box.children.map((btn) => { - btn.visible = hyprland.workspaces.some( - (ws) => ws.id === btn.attribute, - ); - }), - ); - } - }, + ) + }) + } + const occupiedWses = () => { + return Widget.Box({ + children: Utils.merge( + [ + monitorSpecific.bind("value"), + hyprland.bind("workspaces"), + workspaceMask.bind("value"), + options.bar.workspaces.show_icons.bind("value"), + options.bar.workspaces.icons.available.bind("value"), + options.bar.workspaces.icons.active.bind("value"), + options.bar.workspaces.icons.occupied.bind("value"), + options.bar.workspaces.show_numbered.bind("value"), + options.bar.workspaces.numbered_active_indicator.bind("value"), + spacing.bind("value"), + hyprland.active.workspace.bind("id"), + ], + (monitorSpecific, wkSpaces, workspaceMask, showIcons, available, active, occupied, showNumbered, numberedActiveIndicator, spacing, activeId) => { + const activeWorkspaces = wkSpaces.map(w => w.id); + return activeWorkspaces + .filter((i) => { + if (monitorSpecific === false) { + return true; + } + + const isOnMonitor = hyprland.workspaces.find(w => w.id === i)?.monitorID === monitor; + return isOnMonitor; + }) + .map((i, index) => { + return Widget.Button({ + class_name: "workspace-button", + on_primary_click: () => { + hyprland.messageAsync(`dispatch workspace ${i}`) + + }, + child: Widget.Label({ + attribute: i, + vpack: "center", + css: `margin: 0rem ${0.375 * spacing}rem;`, + class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i), + label: renderLabel(showIcons, available, active, occupied, workspaceMask, i, index), + setup: (self) => { + if (index === 0) { + console.log('in'); + + } + self.toggleClassName( + "active", + activeId === i, + ); + self.toggleClassName( + "occupied", + (hyprland.getWorkspace(i)?.windows || 0) > 0, + ); + }, + }) + }); + }); + }, + ) + }) + } + + return { + component: Widget.Box({ + class_name: "workspaces", + child: options.bar.workspaces.hideUnoccupied.bind("value").as(hideUnoccupied => hideUnoccupied ? occupiedWses() : defaultWses()), }), isVisible: true, boxClass: "workspaces", props: { setup: (self: any) => { - self.hook(scroll_speed, () => { - const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(scroll_speed.value); + Utils.merge([scroll_speed.bind("value"), options.bar.workspaces.hideUnoccupied.bind("value")], (scroll_speed, hideUnoccupied) => { + const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(scroll_speed, currentMonitorWorkspaces, hideUnoccupied) self.on_scroll_up = throttledScrollUp; self.on_scroll_down = throttledScrollDown; }); @@ -270,4 +233,5 @@ const Workspaces = (monitor = -1, ws = 8) => { } }; }; + export { Workspaces }; diff --git a/options.ts b/options.ts index 6450023..71f6f6d 100644 --- a/options.ts +++ b/options.ts @@ -718,6 +718,7 @@ const options = mkOptions(OPTIONS, { workspaces: opt(10), spacing: opt(1), monitorSpecific: opt(true), + hideUnoccupied: opt(false), workspaceMask: opt(false), reverse_scroll: opt(false), scroll_speed: opt(5), diff --git a/widget/settings/pages/config/bar/index.ts b/widget/settings/pages/config/bar/index.ts index b19b49b..1b8bfe6 100644 --- a/widget/settings/pages/config/bar/index.ts +++ b/widget/settings/pages/config/bar/index.ts @@ -50,6 +50,7 @@ export const BarSettings = () => { Option({ opt: options.bar.workspaces.spacing, title: 'Spacing', subtitle: 'Spacing between workspace icons', type: 'float' }), Option({ opt: options.bar.workspaces.workspaces, title: 'Total Workspaces', type: 'number' }), Option({ opt: options.bar.workspaces.monitorSpecific, title: 'Monitor Specific', subtitle: 'Only workspaces applicable to the monitor will be displayed', type: 'boolean' }), + Option({ opt: options.bar.workspaces.hideUnoccupied, title: 'Hide Unoccupied', subtitle: 'Only show workspaces that are occupied or active', type: 'boolean' }), Option({ opt: options.bar.workspaces.workspaceMask, title: 'Mask Workspace Numbers On Monitors',