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 }