/ modules / programs / quickshell / services / HyprlandIpc.qml
HyprlandIpc.qml
  1  pragma Singleton
  2  
  3  import Quickshell
  4  import Quickshell.Io
  5  import QtQuick
  6  import ".."
  7  
  8  Singleton {
  9  	property string submap: ""
 10  	property string activeMonitor: ""
 11  	property var activeScreen: Quickshell.screens[0]
 12  	property QtObject activeWindow: QtObject {
 13  		property string address: "0"
 14  		property string title: ""
 15  		property string klass: ""
 16  	}
 17  	property list<var> windows: ({}) // { address: { workspace, klass, title, initialClass, initialTitle } }
 18  	property list<var> workspaceInfosArray: Array.from({ length: 9 }, (_, i) => {
 19  		return { id: i + 1, name: String(i + 1), focused: false, exists: false }
 20  	})
 21  	property var workspaceInfos: workspaceInfosArray.reduce((p, c) => { p[c.name] = c; return p }, {})
 22  	property QtObject activeWorkspace: QtObject {
 23  		property int id: 1
 24  		property string name: "1"
 25  	}
 26  	property QtObject activeKeyboardLayout: QtObject {
 27  		property string keyboard: "(unknown)"
 28  		property string layout: "(unknown)"
 29  	}
 30  
 31  	property string windowToFocus: ""
 32  	property string workspaceToFocus: ""
 33  	property string workspaceToFocusOnCurrentMonitor: ""
 34  	property string windowWithUpdatedTitle: ""
 35  
 36  	signal configReloaded()
 37  	signal windowOpened(address: string, workspace: string, klass: string, title: string)
 38  	signal windowClosed(address: string)
 39  	signal windowFocused(klass: string, title: string)
 40  	signal monitorFocused(name: string, workspaceName: string)
 41  	signal keyboardLayoutChanged(keyboard: string, layout: string)
 42  
 43  	Socket {
 44  		connected: true
 45  		onConnectedChanged: connected = true
 46  		path: `/tmp/hypr/${Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")}/.socket2.sock`
 47  
 48  		parser: SplitParser {
 49  			splitMarker: ""
 50  			onRead: message => {
 51  				if (Config.debug) {
 52  					console.log("HyprlandIpc [stdin]: " + message)
 53  				}
 54  				const lines = message.split(/\n(?=.+>>|$)/)
 55  				for (const line of lines) {
 56  					if (!line) continue
 57  					const [, type, body] = line.match(/(.+)>>([\s\S]+)/)
 58  					if (body === undefined) {
 59  						console.log("HyprlandIpc: error: malformed message: " + message)
 60  						continue
 61  					}
 62  					const args = body.split(",")
 63  					switch (type) {
 64  						case "configreloaded": {
 65  							configReloaded()
 66  							break
 67  						}
 68  						case "submap": {
 69  							[submap] = args
 70  							if (submap === "quickshell:workspaces_overview:toggle") {
 71  								ShellIpc.workspacesOverview = !ShellIpc.workspacesOverview
 72  							}
 73  							break
 74  						}
 75  						case "openwindow": {
 76  							const [address, workspace, klass, title] = args
 77  							windowOpened(address, workspace, klass, title)
 78  							const info = windows[address] ?? {}
 79  							info.address = address
 80  							info.workspace = workspace
 81  							info.klass = klass
 82  							info.title = title
 83  							info.initialClass = klass
 84  							info.initialTitle = title
 85  							windows[address] = info
 86  							break
 87  						}
 88  						case "closewindow": {
 89  							const [address] = args
 90  							delete windows[address]
 91  							windowClosed(address)
 92  							break
 93  						}
 94  						case "windowtitle": {
 95  							const [address] = args
 96  							windowWithUpdatedTitle = address
 97  							break
 98  						}
 99  						case "activewindow": {
100  							const [klass, title] = args
101  							activeWindow.klass = klass
102  							activeWindow.title = title
103  							windowFocused(klass, title)
104  							if (windowWithUpdatedTitle) {
105  								const info = windows[windowWithUpdatedTitle]
106  								if (info) {
107  									info.klass = klass
108  									info.title = title
109  									windows[windowWithUpdatedTitle] = info
110  								}
111  							}
112  							break
113  						}
114  						case "activewindowv2": {
115  							const [address] = args
116  							activeWindow.address = address
117  							windowWithUpdatedTitle = ""
118  							break
119  						}
120  						case "activelayout": {
121  							const [keyboard, layout] = args
122  							activeKeyboardLayout.keyboard = keyboard
123  							activeKeyboardLayout.layout = layout
124  							keyboardLayoutChanged(id, layout)
125  							break
126  						}
127  						case "focusedmon": {
128  							const [monitor, workspace] = args
129  							activeMonitor = monitor
130  							activeScreen = Quickshell.screens.find(screen => screen.name === monitor)
131  							monitorFocused(monitor, workspace)
132  							for (const key in workspaceInfos) {
133  								const info = workspaceInfos[key]
134  								info.focused = info.name === workspace
135  								setWorkspaceInfo(info)
136  							}
137  							break
138  						}
139  						// TODO: handle `movewindow`
140  						case "createworkspacev2": {
141  							const [idString, name] = args
142  							const id = Number(idString)
143  							if (id >= 0) {
144  								for (const key in workspaceInfos) {
145  									const info = workspaceInfos[key]
146  									if (!info) continue
147  									info.focused = false
148  									setWorkspaceInfo(info)
149  								}
150  							}
151  							const info = workspaceInfos[name] ?? {}
152  							info.id = id
153  							info.name = name
154  							info.focused = true
155  							info.exists = true
156  							setWorkspaceInfo(info)
157  							break
158  						}
159  						case "destroyworkspace": {
160  							const [name] = args
161  							const info = workspaceInfos[name]
162  							if (!info) break
163  							info.focused = false
164  							info.exists = false
165  							setWorkspaceInfo(info)
166  							break
167  						}
168  					}
169  				}
170  			}
171  		}
172  	}
173  
174  	Process {
175  		running: true
176  		command: ["hyprctl", "activewindow", "-j"]
177  		stdout: SplitParser {
178  			splitMarker: ""
179  			onRead: json => {
180  				const data = JSON.parse(json)
181  				activeWindow.address = data.address.slice(2)
182  				activeWindow.title = data.title
183  				activeWindow.klass = data.class
184  				activeWorkspace.id = data.workspace.id
185  				activeWorkspace.name = data.workspace.name
186  			}
187  		}
188  	}
189  
190  	Process {
191  		running: true
192  		command: ["hyprctl", "workspaces", "-j"]
193  		stdout: SplitParser {
194  			splitMarker: ""
195  			onRead: json => {
196  				const data = JSON.parse(json)
197  				for (const datum of data) {
198  					const info = workspaceInfos[datum.name]
199  					if (!info) continue
200  					info.exists = true
201  					setWorkspaceInfo(info)
202  				}
203  			}
204  		}
205  	}
206  
207  	Process {
208  		running: true
209  		command: ["hyprctl", "clients", "-j"]
210  		stdout: SplitParser {
211  			splitMarker: ""
212  			onRead: json => {
213  				const data = JSON.parse(json)
214  				for (const datum of data) {
215  					const address = datum.address.slice(2)
216  					const info = windows[address] ?? {}
217  					info.address = address
218  					info.klass = datum.class
219  					info.title = datum.title
220  					info.initialClass = datum.initialClass
221  					info.initialTitle = datum.initialTitle
222  					windows[datum.address] = info
223  				}
224  			}
225  		}
226  	}
227  
228  	Process {
229  		running: true
230  		command: ["hyprctl", "activeworkspace", "-j"]
231  		stdout: SplitParser {
232  			splitMarker: ""
233  			onRead: json => {
234  				const data = JSON.parse(json)
235  				const info = workspaceInfos[data.name]
236  				if (!info) return
237  				info.focused = true
238  				setWorkspaceInfo(info)
239  			}
240  		}
241  	}
242  
243  	function setWorkspaceInfo(info) {
244  		workspaceInfos[info.name] = info
245  		if (info.id >= 1 && info.id <= 9) workspaceInfosArray[info.id - 1] = info
246  	}
247  
248  	Process {
249  		id: focusWindowProcess
250  		command: ["hyprctl", "dispatch", "focuswindow", windowToFocus]
251  	}
252  
253  	function focusWindow(id) {
254  		windowToFocus = String(id)
255  		focusWindowProcess.running = true
256  	}
257  
258  	Process {
259  		id: focusWorkspaceProcess
260  		command: ["hyprctl", "dispatch", "workspace", workspaceToFocus]
261  	}
262  
263  	function focusWorkspace(address) {
264  		workspaceToFocus = String(address)
265  		focusWorkspaceProcess.running = true
266  	}
267  
268  	Process {
269  		id: focusWorkspaceOnCurrentMonitorProcess
270  		command: ["hyprctl", "dispatch", "workspace", workspaceToFocusOnCurrentMonitor]
271  	}
272  
273  	function focusWorkspaceOnCurrentMonitor(id) {
274  		workspaceToFocusOnCurrentMonitor = String(id)
275  		focusWorkspaceOnCurrentMonitorProcess.running = true
276  	}
277  }