Function bodies 110 total
getBestMonitor function · typescript · L16-L28 (13 LOC)ags/app.ts
function getBestMonitor() {
let monitor: Gdk.Monitor | null = null
let width = -1
const monitors = display.get_monitors()
for (let idx = 0; idx < monitors.get_n_items(); idx++) {
const m = monitors.get_item(idx) as Gdk.Monitor;
if (m.get_geometry().width > width) {
width = m.get_geometry().width
monitor = m
}
}
return monitor
}initWidgets function · typescript · L36-L42 (7 LOC)ags/app.ts
function initWidgets(monitor: Gdk.Monitor) {
return [
Bar(monitor, calendarVisible, currentDate),
Calendar(monitor, calendarVisible, currentDate),
Notifications(monitor)
]
}WorkspaceNamingService class · typescript · L4-L94 (91 LOC)ags/service/WorkspaceNaming.ts
class WorkspaceNamingService {
private hypr = Hyprland.get_default()
constructor() {
// Track previous workspace of windows for move detection
const windowWorkspaces = new Map<string, number>()
this.hypr.connect("event", (_, eventName, args) => {
const parts = args.split(",")
switch (eventName) {
case "movewindowv2": {
const address = parts[0]
const destWorkspaceId = parseInt(parts[1])
const sourceWorkspaceId = windowWorkspaces.get(address)
if (sourceWorkspaceId) this.updateWorkspaceName(sourceWorkspaceId)
this.updateWorkspaceName(destWorkspaceId)
windowWorkspaces.set(address, destWorkspaceId)
break
}
case "activewindowv2": {
const client = this.hypr.get_client(parts[0])
const workspace = client?.get_workspace()
if (client && workspace) {
const wsId = workspace.get_id()
this.updateWorkspaceName(wsId)
constructor method · typescript · L7-L48 (42 LOC)ags/service/WorkspaceNaming.ts
constructor() {
// Track previous workspace of windows for move detection
const windowWorkspaces = new Map<string, number>()
this.hypr.connect("event", (_, eventName, args) => {
const parts = args.split(",")
switch (eventName) {
case "movewindowv2": {
const address = parts[0]
const destWorkspaceId = parseInt(parts[1])
const sourceWorkspaceId = windowWorkspaces.get(address)
if (sourceWorkspaceId) this.updateWorkspaceName(sourceWorkspaceId)
this.updateWorkspaceName(destWorkspaceId)
windowWorkspaces.set(address, destWorkspaceId)
break
}
case "activewindowv2": {
const client = this.hypr.get_client(parts[0])
const workspace = client?.get_workspace()
if (client && workspace) {
const wsId = workspace.get_id()
this.updateWorkspaceName(wsId)
windowWorkspaces.set(parts[0], wsId)
}
break
getKittyCwd method · typescript · L50-L59 (10 LOC)ags/service/WorkspaceNaming.ts
private async getKittyCwd(pid: number): Promise<string | null> {
try {
const output = await execAsync(`kitty @ --to unix:@kitty-${pid} ls`)
const data = JSON.parse(output)
const cwd = data[0]?.tabs?.[0]?.windows?.[0]?.cwd
return cwd || null
} catch {
return null
}
}updateWorkspaceName method · typescript · L61-L84 (24 LOC)ags/service/WorkspaceNaming.ts
private async updateWorkspaceName(workspaceId: number) {
const workspace = this.hypr.get_workspace(workspaceId)
if (!workspace) return
// Find kitty client in this workspace
const kittyClient = this.hypr.get_clients().find(
(client) => client.get_workspace()?.get_id() === workspaceId &&
client.get_class() === "kitty"
)
let newName = workspaceId.toString()
if (kittyClient) {
const cwd = await this.getKittyCwd(kittyClient.get_pid())
if (cwd) {
const parts = cwd.split("/")
newName = parts[parts.length - 1] || workspaceId.toString()
}
}
if (workspace.get_name() !== newName) {
exec(`hyprctl dispatch renameworkspace ${workspaceId} ${newName}`)
}
}exec method · typescript · L82-L93 (12 LOC)ags/service/WorkspaceNaming.ts
exec(`hyprctl dispatch renameworkspace ${workspaceId} ${newName}`)
}
}
private async updateAllWorkspaceNames() {
const workspaces = this.hypr.get_workspaces()
.filter((ws) => !(ws.get_id() >= -99 && ws.get_id() <= -2))
for (const workspace of workspaces) {
await this.updateWorkspaceName(workspace.get_id())
}
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
Bar function · typescript · L14-L51 (38 LOC)ags/widget/Bar.ts
export default function Bar(monitor: Gdk.Monitor, calendarVisible: Variable<boolean>,
currentDate: Variable<Date>,
menuVisible: Variable<boolean>,
) {
return Widget.Window(
{
className: "Bar",
visible: true,
gdkmonitor: monitor,
exclusivity: Astal.Exclusivity.EXCLUSIVE,
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT,
application: App
},
Widget.Box({
className: "bar-container",
children: [
Widget.Box({
hexpand: true,
children: [
HyprlandStatus(),
Coach(),
]
}),
Widget.Box({
children: [
LayoutStatus(),
IdleStatus(),
MenuButton(menuVisible),
Volume(),
Battery(),
TimeDate(currentDate, calendarVisible),
Tray()
]
})
]
})
)
}Battery function · typescript · L6-L23 (18 LOC)ags/widget/Battery.tsx
export default function Battery() {
const bat = AstalBattery.get_default()
return Widget.Box({
css_classes: bind(bat, "percentage").as((p) =>
p < 0.20 ? ["battery-widget", "battery-low"] : ["battery-widget"]
),
children: [
Widget.Image({
iconName: bind(bat, "icon-name"),
}),
Widget.Label({
label: bind(bat, "percentage").as((p) => {
return `${Math.round(p * 100)}%`
}),
}),
],
})
}generateCalendarDays function · typescript · L21-L77 (57 LOC)ags/widget/Calendar.tsx
function generateCalendarDays(currentDate: Variable<Date>) {
const firstDay = bind(currentDate).as((date: Date) => {
const year = date.getFullYear()
const month = date.getMonth()
return new Date(year, month, 1).getDay()
})
const daysInMonth = bind(currentDate).as((date: Date) => {
const year = date.getFullYear()
const month = date.getMonth()
return new Date(year, month + 1, 0).getDate()
})
const days = Variable.derive(
[firstDay, daysInMonth],
(firstDay, daysInMonth) => {
const d = Array(firstDay === 0 ? 6 : firstDay - 1).fill(null)
.concat(Array.from({ length: daysInMonth }, (_, i) => i + 1))
// Pad the array to ensure it's divisible by 7
while (d.length % 7 !== 0) {
d.push(null)
}
return d
}
)
const rows = bind(days).as((d: number[]) => {
const today = new Date()
const todayDay = today.getDate()
const viewedMonth = currentDate.get().getMonth()
const viewedYear = currentDmakeWeekdayHeader function · typescript · L79-L94 (16 LOC)ags/widget/Calendar.tsx
function makeWeekdayHeader() {
const weekdays = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
return <box
hexpand={true}
halign={Gtk.Align.FILL}
homogeneous={true}
cssClasses={["calendar-row"]}
>
{weekdays.map((day: string) => (
<label
cssClasses={["weekday-header"]}
label={day}
/>
))}
</box>
}Calendar function · typescript · L96-L140 (45 LOC)ags/widget/Calendar.tsx
export default function Calendar(monitor: Gdk.Monitor, visible: Variable<boolean>, currentDate: Variable<Date>) {
const monthName = bind(currentDate).as((date: Date) => date.toLocaleString('default', { month: 'long', year: 'numeric' }))
const previousMonth = () => {
const current = currentDate.get()
const newDate = new Date(current.getFullYear(), current.getMonth() - 1, 1)
currentDate.set(newDate)
}
const nextMonth = () => {
const current = currentDate.get()
const newDate = new Date(current.getFullYear(), current.getMonth() + 1, 1)
currentDate.set(newDate)
}
return (
<window
gdkmonitor={monitor}
cssClasses={["Calendar"]}
visible={bind(visible)}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT}
application={App}>
<box
cssClasses={["calendar-widget"]}
spacing={5}
valign={Gtk.Align.FILL}
orientation={Gtk.Orientation.VERTICAL}
>
<box cssClasses={["calendar-hsetFocusingState function · typescript · L23-L44 (22 LOC)ags/widget/Coach.ts
function setFocusingState(focusing: boolean, duration: number, numFocuses: number) {
const wasFocusing = isFocusing.get()
if (wasFocusing !== focusing) {
console.log(`Focus state changed: ${wasFocusing} -> ${focusing}`)
}
durationState.set(duration)
numFocusesState.set(numFocuses)
isFocusing.set(focusing)
const durationMinutes = Math.floor(duration / 60)
let durationString = ""
if (durationMinutes == 1) {
durationString = "for 1 minute"
} else if (durationMinutes > 1) {
durationString = `for ${durationMinutes} minutes`
}
let focusingString = "Focusing"
if (!focusing) {
focusingString = "Not focusing"
}
focusingState.set(`${focusingString} ${durationString} [${numFocuses}]`)
changedState.set(new Date())
}updateFocusingState function · typescript · L46-L56 (11 LOC)ags/widget/Coach.ts
function updateFocusingState() {
const now = new Date()
const delta = Math.floor((now.getTime() - changedState.get().getTime()) / 1000)
const duration = durationState.get() + delta
let focusing = true
if (focusingState.get().includes("Not focusing")) {
focusing = false
}
const numFocuses = numFocusesState.get()
setFocusingState(focusing, duration, numFocuses)
}setupHeartbeat function · typescript · L58-L82 (25 LOC)ags/widget/Coach.ts
function setupHeartbeat() {
if (heartbeatSource !== null) {
GLib.source_remove(heartbeatSource)
heartbeatSource = null
}
// Set up a new heartbeat
heartbeatSource = GLib.timeout_add(GLib.PRIORITY_DEFAULT, HEARTBEAT_INTERVAL, () => {
updateFocusingState()
if (connection && connectionState.get() === "connected") {
if (awaitingHeartbeatResponse) {
console.log("Previous heartbeat not answered, reconnecting")
reconnect()
return GLib.SOURCE_CONTINUE
}
console.log("Sending heartbeat get_focusing request")
awaitingHeartbeatResponse = true
sendWebSocketMessage(connection, { type: "get_focusing" })
} else {
console.log("Connection lost, attempting reconnect")
reconnect()
}
return GLib.SOURCE_CONTINUE
})
}Repobility (the analyzer behind this table) · https://repobility.com
reconnect function · typescript · L84-L107 (24 LOC)ags/widget/Coach.ts
function reconnect() {
if (connectionState.get() === "connecting") {
return
}
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
console.error("Maximum reconnection attempts reached")
focusingState.set("Connection failed")
return
}
// Exponential backoff for reconnection attempts
const delay = BASE_RECONNECT_DELAY * Math.pow(1.5, reconnectAttempts)
reconnectAttempts++
console.log(`Attempting to reconnect in ${delay / 1000} seconds (attempt ${reconnectAttempts})`)
connectionState.set("connecting")
GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, () => {
console.log("Reconnecting...")
init()
return GLib.SOURCE_REMOVE // One-time timeout
})
}handleWebSocketConnection function · typescript · L109-L136 (28 LOC)ags/widget/Coach.ts
function handleWebSocketConnection(session: Soup.Session, result: Gio.AsyncResult) {
try {
connection = session.websocket_connect_finish(result)
connectionState.set("connected")
reconnectAttempts = 0
awaitingHeartbeatResponse = false
connection.connect("message", handleWebSocketMessage)
connection.connect("closed", () => {
console.log("WebSocket connection closed")
connectionState.set("disconnected")
connection = null
reconnect()
})
setupHeartbeat()
// sendWebSocketMessage(connection, { type: "health" })
} catch (error) {
console.error("WebSocket connection error:", error)
connectionState.set("disconnected")
connection = null
reconnect()
}
}isFocusInfo function · typescript · L146-L148 (3 LOC)ags/widget/Coach.ts
function isFocusInfo(obj: unknown): obj is FocusInfo {
return typeof obj === "object" && obj !== null && (obj as FocusInfo).type === "focusing"
}handleWebSocketMessage function · typescript · L150-L169 (20 LOC)ags/widget/Coach.ts
function handleWebSocketMessage(_: any, type: Soup.WebsocketDataType, message: any) {
if (type !== Soup.WebsocketDataType.TEXT) return
const data = new TextDecoder().decode(message.get_data())
console.log("Received message:", data)
try {
const parsed = JSON.parse(data)
if (isFocusInfo(parsed)) {
awaitingHeartbeatResponse = false
setFocusingState(parsed.focusing, parsed.since_last_change, parsed.num_focuses)
} else {
console.error("Unknown message type:", parsed.type ?? "missing type", data)
}
} catch (error) {
console.error("Error parsing message:", error)
}
}sendWebSocketMessage function · typescript · L172-L189 (18 LOC)ags/widget/Coach.ts
function sendWebSocketMessage(connection: Soup.WebsocketConnection, messageObj: object) {
if (!connection || connectionState.get() !== "connected") {
console.warn("Cannot send message: WebSocket not connected")
return
}
try {
const data = JSON.stringify(messageObj)
connection.send_message(
Soup.WebsocketDataType.TEXT,
new GLib.Bytes(new TextEncoder().encode(data))
)
} catch (error) {
console.error("Error sending WebSocket message:", error)
connectionState.set("disconnected")
reconnect()
}
}init function · typescript · L191-L223 (33 LOC)ags/widget/Coach.ts
async function init() {
const coachUrl = GLib.getenv("COACH_URL") + "/connect"
console.log("COACH_URL:", coachUrl)
if (!coachUrl) {
console.log("COACH_URL environment variable not set")
focusingState.set("COACH_URL not set")
return
}
try {
connectionState.set("connecting")
const session = new Soup.Session()
const message = new Soup.Message({
method: "GET",
uri: GLib.Uri.parse(coachUrl, GLib.UriFlags.NONE)
})
session.websocket_connect_async(
message,
null, // origin
null,
1,
null, // cancellable
(_, result) => handleWebSocketConnection(session, result)
)
} catch (error) {
console.error("Error setting up WebSocket:", error)
connectionState.set("disconnected")
focusingState.set("Connection error")
reconnect()
}
}toggleFocus function · typescript · L225-L232 (8 LOC)ags/widget/Coach.ts
function toggleFocus() {
if (connection && connectionState.get() === "connected") {
sendWebSocketMessage(connection, { type: "focus" });
console.log("Sent focus toggle message");
} else {
console.warn("Cannot toggle focus: WebSocket not connected");
}
}Coach function · typescript · L234-L251 (18 LOC)ags/widget/Coach.ts
export default function Coach() {
init();
const button = Widget.Button({
// className: "focusing-button",
onClicked: toggleFocus,
child: Widget.Label({
// className: "focusing-label",
label: bind(focusingState),
})
});
return Widget.Box({
css_classes: bind(isFocusing).as(focusing =>
focusing ? ["focusing-widget"] : ["focusing-widget", "not-focusing"]
),
children: [button]
})
}Repobility analyzer · published findings · https://repobility.com
HyprlandStatus function · typescript · L7-L85 (79 LOC)ags/widget/HyprlandStatus.ts
export default function HyprlandStatus() {
const hypr = Hyprland.get_default()
return Widget.Box({
css_classes: ["workspace-widget"],
setup: (self) => {
const updateWorkspaces = () => {
try {
self.children = []
// Get all workspaces, filter and sort
const workspaces = hypr.get_workspaces()
const filtered = workspaces.filter((ws) => !(ws.get_id() >= -99 && ws.get_id() <= -2))
const sorted = filtered.sort((a, b) => a.get_id() - b.get_id())
// Add workspace buttons
self.children = sorted.map((ws) => {
const getLabel = () => {
const id = ws.get_id()
const name = ws.get_name()
return name === id.toString() ? name : `<span alpha="50%">${id}</span> ${name}`
}
const button = Widget.Button({
child: Widget.Label({
label: getLabel(),
use_markup: true,
}),
onClicked: () =IdleStatus function · typescript · L6-L20 (15 LOC)ags/widget/IdleStatus.ts
export default function IdleStatus() {
return Widget.Button({
css_classes: ["idle-status-widget"],
margin: 0,
visible: bind(isIdleRunning).as(running => !running),
child: Widget.Image({
iconName: "face-surprise-symbolic",
css: "font-size: 18px",
margin: 0
}),
onClicked: () => {
GLib.spawn_command_line_async('bash -c "swayidle -w timeout 1500 \'systemctl hibernate\' &"')
},
})
}LayoutStatus function · typescript · L6-L18 (13 LOC)ags/widget/LayoutStatus.ts
export default function LayoutStatus() {
return Widget.Button({
css_classes: ["layout-status-widget"],
margin: 0,
visible: bind(keyboardLayout).as(layout => layout.includes("Russian")),
child: Widget.Image({
file: "/home/dima/dotfiles/ags/assets/ru-tricolor.svg",
}),
onClicked: () => {
GLib.spawn_command_line_async("hyprctl switchxkblayout at-translated-set-2-keyboard next")
},
})
}MenuButton function · typescript · L4-L17 (14 LOC)ags/widget/MenuButton.ts
export default function MenuButton(menuVisible: Variable<boolean>) {
return Widget.Button({
css_classes: ["idle-widget"],
margin: 0,
child: Widget.Image({
iconName: "pan-down-symbolic",
css: "font-size: 18px",
margin: 0
}),
onClicked: () => {
menuVisible.set(!menuVisible.get())
},
})
}checkSwayidle function · typescript · L9-L16 (8 LOC)ags/widget/Menu.tsx
function checkSwayidle(): boolean {
try {
const [success, stdout, stderr] = GLib.spawn_command_line_sync("pgrep swayidle")
return stdout.length > 0
} catch (error) {
return false
}
}checkRecording function · typescript · L18-L25 (8 LOC)ags/widget/Menu.tsx
function checkRecording(): boolean {
try {
const [success, stdout] = GLib.spawn_command_line_sync("pgrep -f 'ffmpeg.*pulse.*records'")
return stdout.length > 0
} catch (error) {
return false
}
}getRecordingDuration function · typescript · L27-L32 (6 LOC)ags/widget/Menu.tsx
function getRecordingDuration(): number {
if (!isRecording.get()) return 0
const startTime = recordingStartTime.get()
if (startTime === 0) return 0
return Math.floor((Date.now() - startTime) / 1000)
}formatDuration function · typescript · L34-L38 (5 LOC)ags/widget/Menu.tsx
function formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}All rows scored by the Repobility analyzer (https://repobility.com)
getCurrentKeyboardLayout function · typescript · L40-L59 (20 LOC)ags/widget/Menu.tsx
function getCurrentKeyboardLayout(): string {
try {
const [success, stdout] = GLib.spawn_command_line_sync("hyprctl devices -j")
if (success) {
const devices = JSON.parse(new TextDecoder().decode(stdout))
// Filter out virtual keyboards (like wtype) and find the main physical keyboard
const realKeyboard = devices.keyboards.find((kb: any) =>
kb.main && !kb.name.includes("virtual")
) || devices.keyboards.find((kb: any) =>
kb.name === "at-translated-set-2-keyboard"
)
if (realKeyboard) {
return realKeyboard.active_keymap || "Unknown"
}
}
} catch (error) {
console.error("Failed to get keyboard layout:", error)
}
return "Unknown"
}getLanguageCode function · typescript · L73-L78 (6 LOC)ags/widget/Menu.tsx
function getLanguageCode(): string {
const layout = keyboardLayout.get().toLowerCase()
if (layout.includes("russian")) return "ru"
if (layout.includes("english")) return "en"
return "auto"
}startRecording function · typescript · L80-L89 (10 LOC)ags/widget/Menu.tsx
function startRecording() {
if (isRecording.get() || isProcessing.get()) return
GLib.spawn_command_line_sync(`mkdir -p ${RECORDS_DIR}`)
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const filename = `${RECORDS_DIR}/record_${timestamp}.wav`
lastRecordedFile.set(filename)
GLib.spawn_command_line_async(`ffmpeg -f pulse -i default -ac 1 -acodec pcm_s16le -ar 16000 ${filename}`)
recordingStartTime.set(Date.now())
}stopRecording function · typescript · L91-L104 (14 LOC)ags/widget/Menu.tsx
function stopRecording() {
if (!isRecording.get() || isProcessing.get()) return
GLib.spawn_command_line_async("pkill -f 'ffmpeg.*pulse.*records'")
const filepath = lastRecordedFile.get()
recordingStartTime.set(0)
menuVisible.set(false)
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
if (filepath) {
transcribeAudio(filepath)
}
return false
})
}cancelRecording function · typescript · L106-L112 (7 LOC)ags/widget/Menu.tsx
function cancelRecording() {
if (!isRecording.get() || isProcessing.get()) return
GLib.spawn_command_line_async("pkill -f 'ffmpeg.*pulse.*records'")
recordingStartTime.set(0)
menuVisible.set(false)
}transcribeAudio function · typescript · L114-L152 (39 LOC)ags/widget/Menu.tsx
async function transcribeAudio(filepath: string): Promise<void> {
isProcessing.set(true)
const langCode = getLanguageCode()
try {
const [success, stdout, stderr] = GLib.spawn_command_line_sync(
`curl -s -X POST https://silence.dimalip.in/speak -F "audio=@${filepath}" -F "file_format=pcm_s16le_16" -F "language_code=${langCode}"`
)
if (success && stdout.length > 0) {
const response = JSON.parse(new TextDecoder().decode(stdout))
if (response.text) {
// Copy to clipboard
const escapedText = response.text.replace(/'/g, "'\\''")
GLib.spawn_command_line_async(`bash -c 'echo -n "${escapedText}" | wl-copy'`)
// Simulate keyboard input
GLib.spawn_command_line_async(`wtype "${response.text.replace(/"/g, '\\"')}"`)
GLib.spawn_command_line_async(
`notify-send -a "Audio Transcription" "ASR Result (typed)" "${response.text.replace(/"/g, '\\"')}"`
)
} else if (response.error) {
GLib.spawMenu function · typescript · L160-L295 (136 LOC)ags/widget/Menu.tsx
export default function Menu(monitor: Gdk.Monitor) {
const win = (
<window
gdkmonitor={monitor}
cssClasses={["Menu"]}
visible={bind(menuVisible)}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT}
keymode={Astal.Keymode.EXCLUSIVE}
application={App}>
<box
cssClasses={["menu-widget"]}
spacing={5}
orientation={Gtk.Orientation.VERTICAL}
>
<button
focusable={false}
css_classes={bind(isIdleRunning).as(running =>
running ? ["idle-toggle-button"] : ["idle-toggle-button", "idle-toggle-button-inactive"]
)}
onClicked={() => {
const running = isIdleRunning.get()
if (running) {
GLib.spawn_command_line_async("pkill swayidle")
} else {
GLib.spawn_command_line_async('bash -c "swayidle -w timeout 1500 \'systemctl hibernate\' &"')
}
}}
>
<box spacing={10}>
NotificationHistory class · typescript · L15-L85 (71 LOC)ags/widget/Notifications.ts
class NotificationHistory implements Subscribable {
private map: Map<number, Gtk.Widget> = new Map()
private subs: Variable<Array<Gtk.Widget>> = Variable([])
private notifiy() {
this.subs.set([...this.map.values()].reverse())
}
constructor() {
const notifd = Notifd.get_default()
// Enforce our own timeout instead of sender's timeout
notifd.ignoreTimeout = true
notifd.connect("notified", (_, id) => {
const notification = notifd.get_notification(id)!
const isPersistent = PERSISTENT_APPS.includes(notification.appName || "")
this.set(id, Notification({
notification,
// Defer dismissal to next event loop iteration to avoid GTK4 crash during event processing
onHoverLost: () => {
timeout(1, () => notification.dismiss())
},
setup: () => {
if (!isPersistent) {
timeout(TIMEOUT_DELAY, () => {
notification.dismiss()
})
}
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
notifiy method · typescript · L19-L21 (3 LOC)ags/widget/Notifications.ts
private notifiy() {
this.subs.set([...this.map.values()].reverse())
}constructor method · typescript · L23-L54 (32 LOC)ags/widget/Notifications.ts
constructor() {
const notifd = Notifd.get_default()
// Enforce our own timeout instead of sender's timeout
notifd.ignoreTimeout = true
notifd.connect("notified", (_, id) => {
const notification = notifd.get_notification(id)!
const isPersistent = PERSISTENT_APPS.includes(notification.appName || "")
this.set(id, Notification({
notification,
// Defer dismissal to next event loop iteration to avoid GTK4 crash during event processing
onHoverLost: () => {
timeout(1, () => notification.dismiss())
},
setup: () => {
if (!isPersistent) {
timeout(TIMEOUT_DELAY, () => {
notification.dismiss()
})
}
}
}))
})
notifd.connect("resolved", (_, id) => {
this.delete(id)
})
}timeout method · typescript · L38-L47 (10 LOC)ags/widget/Notifications.ts
timeout(1, () => notification.dismiss())
},
setup: () => {
if (!isPersistent) {
timeout(TIMEOUT_DELAY, () => {
notification.dismiss()
})
}
}timeout method · typescript · L43-L45 (3 LOC)ags/widget/Notifications.ts
timeout(TIMEOUT_DELAY, () => {
notification.dismiss()
})set method · typescript · L56-L60 (5 LOC)ags/widget/Notifications.ts
private set(key: number, value: Gtk.Widget) {
// Just update the map, GTK will handle widget replacement
this.map.set(key, value)
this.notifiy()
}get method · typescript · L77-L79 (3 LOC)ags/widget/Notifications.ts
get() {
return this.subs.get()
}subscribe method · typescript · L82-L84 (3 LOC)ags/widget/Notifications.ts
subscribe(callback: (list: Array<Gtk.Widget>) => void) {
return this.subs.subscribe(callback)
}Notifications function · typescript · L86-L99 (14 LOC)ags/widget/Notifications.ts
export default function Notifications(monitor: Gdk.Monitor) {
const history = new NotificationHistory()
return Widget.Window({
gdkmonitor: monitor,
visible: bind(history).as(list => list.length > 0),
exclusivity: Astal.Exclusivity.NORMAL,
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT,
}, Widget.Box({
vertical: true,
spacing: 0,
}, bind(history))
)
}Repobility (the analyzer behind this table) · https://repobility.com
Notification function · typescript · L12-L123 (112 LOC)ags/widget/Notification.ts
export default function Notification({ notification, onHoverLost, setup }: NotificationProps): Gtk.Widget {
// Check if appIcon is a file path or icon name
const isFilePath = notification.appIcon?.startsWith("/") || notification.appIcon?.startsWith("~")
const hasValidIcon = notification.appIcon && notification.appIcon !== ""
// Check if image is valid (file path exists and is safe)
const hasValidImage = notification.image &&
notification.image.trim() !== "" &&
(notification.image.startsWith("/") || notification.image.startsWith("~")) &&
!notification.image.includes("..") // Prevent path traversal
// Filter out invalid/empty actions (must have both label and id, and label must not be empty)
const validActions = notification.actions?.filter(action => {
const hasValidLabel = action.label && action.label.trim() !== ""
const hasValidId = action.id && action.id !== ""
return hasValidLabel && hPomodoro function · typescript · L4-L9 (6 LOC)ags/widget/Pomodoro.ts
export default function Pomodoro(pomodoro: Variable<string>) {
return Widget.Label({
className: "pomodoro-widget",
label: bind(pomodoro)
})
}DateWidget function · typescript · L7-L21 (15 LOC)ags/widget/TimeDate.ts
export default function DateWidget(currentDate, calendarVisible) {
const button = Widget.Button({
label: bind(time),
// halign: Gtk.Align.CENTER,
onClicked: () => {
currentDate.set(new Date())
const value = calendarVisible.get()
calendarVisible.set(!value)
}
})
return Widget.Box({
css_classes: ["time-widget"],
children: [button]
})
}page 1 / 3next ›