├── .github └── FUNDING.yml ├── .gitignore ├── .idea ├── .gitignore ├── misc.xml ├── modules.xml ├── vcs.xml └── waffy-rs.iml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── res ├── default_config.json5 └── default_style.css └── src ├── config.rs ├── desktop_entry.rs ├── grid.rs ├── main.rs ├── resource.rs └── style.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: wvffle 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/wvffle'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/waffy-rs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anyhow" 5 | version = "1.0.26" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" 8 | 9 | [[package]] 10 | name = "arrayref" 11 | version = "0.3.5" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 14 | 15 | [[package]] 16 | name = "arrayvec" 17 | version = "0.5.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 20 | 21 | [[package]] 22 | name = "atk" 23 | version = "0.8.0" 24 | source = "git+https://github.com/gtk-rs/atk#b56689dea48ac579b53041d89544ff18a72ec08b" 25 | dependencies = [ 26 | "atk-sys", 27 | "bitflags", 28 | "glib", 29 | "glib-sys", 30 | "gobject-sys", 31 | "libc", 32 | ] 33 | 34 | [[package]] 35 | name = "atk-sys" 36 | version = "0.9.1" 37 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 38 | dependencies = [ 39 | "glib-sys", 40 | "gobject-sys", 41 | "libc", 42 | "pkg-config", 43 | ] 44 | 45 | [[package]] 46 | name = "backtrace" 47 | version = "0.3.40" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" 50 | dependencies = [ 51 | "backtrace-sys", 52 | "cfg-if", 53 | "libc", 54 | "rustc-demangle", 55 | ] 56 | 57 | [[package]] 58 | name = "backtrace-sys" 59 | version = "0.1.32" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 62 | dependencies = [ 63 | "cc", 64 | "libc", 65 | ] 66 | 67 | [[package]] 68 | name = "base64" 69 | version = "0.10.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 72 | dependencies = [ 73 | "byteorder", 74 | ] 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 81 | 82 | [[package]] 83 | name = "blake2b_simd" 84 | version = "0.5.10" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 87 | dependencies = [ 88 | "arrayref", 89 | "arrayvec", 90 | "constant_time_eq", 91 | ] 92 | 93 | [[package]] 94 | name = "block-buffer" 95 | version = "0.7.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 98 | dependencies = [ 99 | "block-padding", 100 | "byte-tools", 101 | "byteorder", 102 | "generic-array", 103 | ] 104 | 105 | [[package]] 106 | name = "block-padding" 107 | version = "0.1.5" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 110 | dependencies = [ 111 | "byte-tools", 112 | ] 113 | 114 | [[package]] 115 | name = "byte-tools" 116 | version = "0.3.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 119 | 120 | [[package]] 121 | name = "byteorder" 122 | version = "1.3.2" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 125 | 126 | [[package]] 127 | name = "cairo-rs" 128 | version = "0.8.0" 129 | source = "git+https://github.com/gtk-rs/cairo#b555469778becf3ae547dbf08f2ddc8ffd1c0617" 130 | dependencies = [ 131 | "bitflags", 132 | "cairo-sys-rs", 133 | "glib", 134 | "glib-sys", 135 | "gobject-sys", 136 | "libc", 137 | ] 138 | 139 | [[package]] 140 | name = "cairo-sys-rs" 141 | version = "0.9.2" 142 | source = "git+https://github.com/gtk-rs/cairo#b555469778becf3ae547dbf08f2ddc8ffd1c0617" 143 | dependencies = [ 144 | "glib-sys", 145 | "libc", 146 | "pkg-config", 147 | ] 148 | 149 | [[package]] 150 | name = "cc" 151 | version = "1.0.50" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 154 | 155 | [[package]] 156 | name = "cfg-if" 157 | version = "0.1.10" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 160 | 161 | [[package]] 162 | name = "cloudabi" 163 | version = "0.0.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 166 | dependencies = [ 167 | "bitflags", 168 | ] 169 | 170 | [[package]] 171 | name = "constant_time_eq" 172 | version = "0.1.4" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" 175 | 176 | [[package]] 177 | name = "crossbeam-utils" 178 | version = "0.6.6" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 181 | dependencies = [ 182 | "cfg-if", 183 | "lazy_static", 184 | ] 185 | 186 | [[package]] 187 | name = "digest" 188 | version = "0.8.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 191 | dependencies = [ 192 | "generic-array", 193 | ] 194 | 195 | [[package]] 196 | name = "dirs" 197 | version = "2.0.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 200 | dependencies = [ 201 | "cfg-if", 202 | "dirs-sys", 203 | ] 204 | 205 | [[package]] 206 | name = "dirs-sys" 207 | version = "0.3.4" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 210 | dependencies = [ 211 | "cfg-if", 212 | "libc", 213 | "redox_users", 214 | "winapi", 215 | ] 216 | 217 | [[package]] 218 | name = "either" 219 | version = "1.5.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 222 | 223 | [[package]] 224 | name = "failure" 225 | version = "0.1.6" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 228 | dependencies = [ 229 | "backtrace", 230 | "failure_derive", 231 | ] 232 | 233 | [[package]] 234 | name = "failure_derive" 235 | version = "0.1.6" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 238 | dependencies = [ 239 | "proc-macro2", 240 | "quote", 241 | "syn", 242 | "synstructure", 243 | ] 244 | 245 | [[package]] 246 | name = "fake-simd" 247 | version = "0.1.2" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 250 | 251 | [[package]] 252 | name = "fuchsia-cprng" 253 | version = "0.1.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 256 | 257 | [[package]] 258 | name = "futures-channel" 259 | version = "0.3.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" 262 | dependencies = [ 263 | "futures-core", 264 | ] 265 | 266 | [[package]] 267 | name = "futures-core" 268 | version = "0.3.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" 271 | 272 | [[package]] 273 | name = "futures-executor" 274 | version = "0.3.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" 277 | dependencies = [ 278 | "futures-core", 279 | "futures-task", 280 | "futures-util", 281 | ] 282 | 283 | [[package]] 284 | name = "futures-io" 285 | version = "0.3.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" 288 | 289 | [[package]] 290 | name = "futures-macro" 291 | version = "0.3.1" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" 294 | dependencies = [ 295 | "proc-macro-hack", 296 | "proc-macro2", 297 | "quote", 298 | "syn", 299 | ] 300 | 301 | [[package]] 302 | name = "futures-task" 303 | version = "0.3.1" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" 306 | 307 | [[package]] 308 | name = "futures-util" 309 | version = "0.3.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" 312 | dependencies = [ 313 | "futures-core", 314 | "futures-macro", 315 | "futures-task", 316 | "pin-utils", 317 | "proc-macro-hack", 318 | "proc-macro-nested", 319 | "slab", 320 | ] 321 | 322 | [[package]] 323 | name = "gdk" 324 | version = "0.12.0" 325 | source = "git+https://github.com/gtk-rs/gdk#63d16649441dba756936c993761d6917c077e10c" 326 | dependencies = [ 327 | "bitflags", 328 | "cairo-rs", 329 | "cairo-sys-rs", 330 | "gdk-pixbuf", 331 | "gdk-sys", 332 | "gio", 333 | "gio-sys", 334 | "glib", 335 | "glib-sys", 336 | "gobject-sys", 337 | "libc", 338 | "pango", 339 | ] 340 | 341 | [[package]] 342 | name = "gdk-pixbuf" 343 | version = "0.8.0" 344 | source = "git+https://github.com/gtk-rs/gdk-pixbuf#060b57509cc106904ce1d9ff142bbfcde224a463" 345 | dependencies = [ 346 | "gdk-pixbuf-sys", 347 | "gio", 348 | "gio-sys", 349 | "glib", 350 | "glib-sys", 351 | "gobject-sys", 352 | "libc", 353 | ] 354 | 355 | [[package]] 356 | name = "gdk-pixbuf-sys" 357 | version = "0.9.1" 358 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 359 | dependencies = [ 360 | "gio-sys", 361 | "glib-sys", 362 | "gobject-sys", 363 | "libc", 364 | "pkg-config", 365 | ] 366 | 367 | [[package]] 368 | name = "gdk-sys" 369 | version = "0.9.1" 370 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 371 | dependencies = [ 372 | "cairo-sys-rs", 373 | "gdk-pixbuf-sys", 374 | "gio-sys", 375 | "glib-sys", 376 | "gobject-sys", 377 | "libc", 378 | "pango-sys", 379 | "pkg-config", 380 | ] 381 | 382 | [[package]] 383 | name = "generic-array" 384 | version = "0.12.3" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 387 | dependencies = [ 388 | "typenum", 389 | ] 390 | 391 | [[package]] 392 | name = "gio" 393 | version = "0.8.0" 394 | source = "git+https://github.com/gtk-rs/gio#06558414d906c77c540d8b3caf44a8409f9730f8" 395 | dependencies = [ 396 | "bitflags", 397 | "futures-channel", 398 | "futures-core", 399 | "futures-io", 400 | "futures-util", 401 | "gio-sys", 402 | "glib", 403 | "glib-sys", 404 | "gobject-sys", 405 | "libc", 406 | "once_cell", 407 | ] 408 | 409 | [[package]] 410 | name = "gio-sys" 411 | version = "0.9.1" 412 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 413 | dependencies = [ 414 | "glib-sys", 415 | "gobject-sys", 416 | "libc", 417 | "pkg-config", 418 | ] 419 | 420 | [[package]] 421 | name = "glib" 422 | version = "0.9.1" 423 | source = "git+https://github.com/gtk-rs/glib#cb01dd6886337d29270c682aeb5814c59e123db8" 424 | dependencies = [ 425 | "bitflags", 426 | "futures-channel", 427 | "futures-core", 428 | "futures-executor", 429 | "futures-task", 430 | "futures-util", 431 | "glib-macros", 432 | "glib-sys", 433 | "gobject-sys", 434 | "libc", 435 | "once_cell", 436 | ] 437 | 438 | [[package]] 439 | name = "glib-macros" 440 | version = "0.9.0" 441 | source = "git+https://github.com/gtk-rs/glib#cb01dd6886337d29270c682aeb5814c59e123db8" 442 | dependencies = [ 443 | "anyhow", 444 | "heck", 445 | "itertools", 446 | "proc-macro-error", 447 | "proc-macro2", 448 | "quote", 449 | "syn", 450 | ] 451 | 452 | [[package]] 453 | name = "glib-sys" 454 | version = "0.9.1" 455 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 456 | dependencies = [ 457 | "libc", 458 | "pkg-config", 459 | ] 460 | 461 | [[package]] 462 | name = "gobject-sys" 463 | version = "0.9.1" 464 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 465 | dependencies = [ 466 | "glib-sys", 467 | "libc", 468 | "pkg-config", 469 | ] 470 | 471 | [[package]] 472 | name = "gtk" 473 | version = "0.8.0" 474 | source = "git+https://github.com/gtk-rs/gtk#f3201e9561f9b4ecaa724bdba4459ad7edc68533" 475 | dependencies = [ 476 | "atk", 477 | "bitflags", 478 | "cairo-rs", 479 | "cairo-sys-rs", 480 | "cc", 481 | "gdk", 482 | "gdk-pixbuf", 483 | "gdk-pixbuf-sys", 484 | "gdk-sys", 485 | "gio", 486 | "gio-sys", 487 | "glib", 488 | "glib-sys", 489 | "gobject-sys", 490 | "gtk-sys", 491 | "libc", 492 | "once_cell", 493 | "pango", 494 | "pango-sys", 495 | ] 496 | 497 | [[package]] 498 | name = "gtk-layer-shell-rs" 499 | version = "0.1.0" 500 | source = "git+https://github.com/subgraph/gtk-layer-shell-rs#6cca35d8d7957cdfaca5431855877ef2d5bef8e3" 501 | dependencies = [ 502 | "bitflags", 503 | "gdk", 504 | "gio", 505 | "glib", 506 | "glib-sys", 507 | "gtk", 508 | "gtk-layer-shell-sys", 509 | "gtk-sys", 510 | "libc", 511 | ] 512 | 513 | [[package]] 514 | name = "gtk-layer-shell-sys" 515 | version = "0.0.1" 516 | source = "git+https://github.com/subgraph/gtk-layer-shell-rs#6cca35d8d7957cdfaca5431855877ef2d5bef8e3" 517 | dependencies = [ 518 | "cairo-sys-rs", 519 | "gdk-pixbuf-sys", 520 | "gdk-sys", 521 | "gio-sys", 522 | "glib-sys", 523 | "gobject-sys", 524 | "gtk-sys", 525 | "libc", 526 | "pkg-config", 527 | ] 528 | 529 | [[package]] 530 | name = "gtk-sys" 531 | version = "0.9.2" 532 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 533 | dependencies = [ 534 | "atk-sys", 535 | "cairo-sys-rs", 536 | "gdk-pixbuf-sys", 537 | "gdk-sys", 538 | "gio-sys", 539 | "glib-sys", 540 | "gobject-sys", 541 | "libc", 542 | "pango-sys", 543 | "pkg-config", 544 | ] 545 | 546 | [[package]] 547 | name = "heck" 548 | version = "0.3.1" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 551 | dependencies = [ 552 | "unicode-segmentation", 553 | ] 554 | 555 | [[package]] 556 | name = "itertools" 557 | version = "0.8.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" 560 | dependencies = [ 561 | "either", 562 | ] 563 | 564 | [[package]] 565 | name = "json5" 566 | version = "0.2.5" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "85fb48cdfbe18a1ef5ce0a0edc30b8b8f61422f7073f709dd09311c2b3d2bba6" 569 | dependencies = [ 570 | "pest", 571 | "pest_derive", 572 | "serde", 573 | ] 574 | 575 | [[package]] 576 | name = "lazy_static" 577 | version = "1.4.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 580 | 581 | [[package]] 582 | name = "libc" 583 | version = "0.2.66" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 586 | 587 | [[package]] 588 | name = "maplit" 589 | version = "1.0.2" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 592 | 593 | [[package]] 594 | name = "once_cell" 595 | version = "1.2.0" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed" 598 | 599 | [[package]] 600 | name = "opaque-debug" 601 | version = "0.2.3" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 604 | 605 | [[package]] 606 | name = "pango" 607 | version = "0.8.0" 608 | source = "git+https://github.com/gtk-rs/pango#377c7d1e4a9a58749be9cb81df71b84ac65a05e1" 609 | dependencies = [ 610 | "bitflags", 611 | "glib", 612 | "glib-sys", 613 | "gobject-sys", 614 | "libc", 615 | "once_cell", 616 | "pango-sys", 617 | ] 618 | 619 | [[package]] 620 | name = "pango-sys" 621 | version = "0.9.1" 622 | source = "git+https://github.com/gtk-rs/sys#bac293d7ce7547bd238a7f5e3e130c4f66c8df3d" 623 | dependencies = [ 624 | "glib-sys", 625 | "gobject-sys", 626 | "libc", 627 | "pkg-config", 628 | ] 629 | 630 | [[package]] 631 | name = "pest" 632 | version = "2.1.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a" 635 | dependencies = [ 636 | "ucd-trie", 637 | ] 638 | 639 | [[package]] 640 | name = "pest_derive" 641 | version = "2.1.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 644 | dependencies = [ 645 | "pest", 646 | "pest_generator", 647 | ] 648 | 649 | [[package]] 650 | name = "pest_generator" 651 | version = "2.1.1" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0" 654 | dependencies = [ 655 | "pest", 656 | "pest_meta", 657 | "proc-macro2", 658 | "quote", 659 | "syn", 660 | ] 661 | 662 | [[package]] 663 | name = "pest_meta" 664 | version = "2.1.2" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "df43fd99896fd72c485fe47542c7b500e4ac1e8700bf995544d1317a60ded547" 667 | dependencies = [ 668 | "maplit", 669 | "pest", 670 | "sha-1", 671 | ] 672 | 673 | [[package]] 674 | name = "pin-utils" 675 | version = "0.1.0-alpha.4" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" 678 | 679 | [[package]] 680 | name = "pkg-config" 681 | version = "0.3.17" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 684 | 685 | [[package]] 686 | name = "proc-macro-error" 687 | version = "0.4.4" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "53c98547ceaea14eeb26fcadf51dc70d01a2479a7839170eae133721105e4428" 690 | dependencies = [ 691 | "proc-macro-error-attr", 692 | "proc-macro2", 693 | "quote", 694 | "rustversion", 695 | "syn", 696 | ] 697 | 698 | [[package]] 699 | name = "proc-macro-error-attr" 700 | version = "0.4.3" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "c2bf5d493cf5d3e296beccfd61794e445e830dfc8070a9c248ad3ee071392c6c" 703 | dependencies = [ 704 | "proc-macro2", 705 | "quote", 706 | "rustversion", 707 | "syn", 708 | "syn-mid", 709 | ] 710 | 711 | [[package]] 712 | name = "proc-macro-hack" 713 | version = "0.5.11" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" 716 | dependencies = [ 717 | "proc-macro2", 718 | "quote", 719 | "syn", 720 | ] 721 | 722 | [[package]] 723 | name = "proc-macro-nested" 724 | version = "0.1.3" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" 727 | 728 | [[package]] 729 | name = "proc-macro2" 730 | version = "1.0.7" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" 733 | dependencies = [ 734 | "unicode-xid", 735 | ] 736 | 737 | [[package]] 738 | name = "quote" 739 | version = "1.0.2" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 742 | dependencies = [ 743 | "proc-macro2", 744 | ] 745 | 746 | [[package]] 747 | name = "rand_core" 748 | version = "0.3.1" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 751 | dependencies = [ 752 | "rand_core 0.4.2", 753 | ] 754 | 755 | [[package]] 756 | name = "rand_core" 757 | version = "0.4.2" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 760 | 761 | [[package]] 762 | name = "rand_os" 763 | version = "0.1.3" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 766 | dependencies = [ 767 | "cloudabi", 768 | "fuchsia-cprng", 769 | "libc", 770 | "rand_core 0.4.2", 771 | "rdrand", 772 | "winapi", 773 | ] 774 | 775 | [[package]] 776 | name = "rdrand" 777 | version = "0.4.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 780 | dependencies = [ 781 | "rand_core 0.3.1", 782 | ] 783 | 784 | [[package]] 785 | name = "redox_syscall" 786 | version = "0.1.56" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 789 | 790 | [[package]] 791 | name = "redox_users" 792 | version = "0.3.1" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" 795 | dependencies = [ 796 | "failure", 797 | "rand_os", 798 | "redox_syscall", 799 | "rust-argon2", 800 | ] 801 | 802 | [[package]] 803 | name = "rust-argon2" 804 | version = "0.5.1" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" 807 | dependencies = [ 808 | "base64", 809 | "blake2b_simd", 810 | "crossbeam-utils", 811 | ] 812 | 813 | [[package]] 814 | name = "rust-embed" 815 | version = "5.2.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "b18893bdbdb0fa5bce588f5d7ab4afbd0678fc879d31535912bf39b7fbc062d6" 818 | dependencies = [ 819 | "rust-embed-impl", 820 | "rust-embed-utils", 821 | "walkdir", 822 | ] 823 | 824 | [[package]] 825 | name = "rust-embed-impl" 826 | version = "5.2.0" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "50633968284cfc373661345fc6382e62b738079f045738023ebc5e445cf44357" 829 | dependencies = [ 830 | "quote", 831 | "rust-embed-utils", 832 | "syn", 833 | "walkdir", 834 | ] 835 | 836 | [[package]] 837 | name = "rust-embed-utils" 838 | version = "5.0.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "97655158074ccb2d2cfb1ccb4c956ef0f4054e43a2c1e71146d4991e6961e105" 841 | dependencies = [ 842 | "walkdir", 843 | ] 844 | 845 | [[package]] 846 | name = "rustc-demangle" 847 | version = "0.1.16" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 850 | 851 | [[package]] 852 | name = "rustversion" 853 | version = "1.0.1" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986" 856 | dependencies = [ 857 | "proc-macro2", 858 | "quote", 859 | "syn", 860 | ] 861 | 862 | [[package]] 863 | name = "same-file" 864 | version = "1.0.5" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" 867 | dependencies = [ 868 | "winapi-util", 869 | ] 870 | 871 | [[package]] 872 | name = "serde" 873 | version = "1.0.104" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 876 | dependencies = [ 877 | "serde_derive", 878 | ] 879 | 880 | [[package]] 881 | name = "serde_derive" 882 | version = "1.0.104" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" 885 | dependencies = [ 886 | "proc-macro2", 887 | "quote", 888 | "syn", 889 | ] 890 | 891 | [[package]] 892 | name = "sha-1" 893 | version = "0.8.2" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 896 | dependencies = [ 897 | "block-buffer", 898 | "digest", 899 | "fake-simd", 900 | "opaque-debug", 901 | ] 902 | 903 | [[package]] 904 | name = "slab" 905 | version = "0.4.2" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 908 | 909 | [[package]] 910 | name = "sublime_fuzzy" 911 | version = "0.6.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "bdac3d983d073c19487ba1f5e16eda43e9c6e50aa895d87110d0febe389b66b9" 914 | 915 | [[package]] 916 | name = "syn" 917 | version = "1.0.13" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" 920 | dependencies = [ 921 | "proc-macro2", 922 | "quote", 923 | "unicode-xid", 924 | ] 925 | 926 | [[package]] 927 | name = "syn-mid" 928 | version = "0.4.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" 931 | dependencies = [ 932 | "proc-macro2", 933 | "quote", 934 | "syn", 935 | ] 936 | 937 | [[package]] 938 | name = "synstructure" 939 | version = "0.12.3" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 942 | dependencies = [ 943 | "proc-macro2", 944 | "quote", 945 | "syn", 946 | "unicode-xid", 947 | ] 948 | 949 | [[package]] 950 | name = "typenum" 951 | version = "1.11.2" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" 954 | 955 | [[package]] 956 | name = "ucd-trie" 957 | version = "0.1.2" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" 960 | 961 | [[package]] 962 | name = "unicode-segmentation" 963 | version = "1.6.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 966 | 967 | [[package]] 968 | name = "unicode-xid" 969 | version = "0.2.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 972 | 973 | [[package]] 974 | name = "waffy" 975 | version = "0.1.0" 976 | dependencies = [ 977 | "dirs", 978 | "gdk", 979 | "gdk-pixbuf", 980 | "gio", 981 | "gtk", 982 | "gtk-layer-shell-rs", 983 | "json5", 984 | "once_cell", 985 | "pango", 986 | "rust-embed", 987 | "serde", 988 | "sublime_fuzzy", 989 | ] 990 | 991 | [[package]] 992 | name = "walkdir" 993 | version = "2.2.9" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" 996 | dependencies = [ 997 | "same-file", 998 | "winapi", 999 | "winapi-util", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "winapi" 1004 | version = "0.3.8" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1007 | dependencies = [ 1008 | "winapi-i686-pc-windows-gnu", 1009 | "winapi-x86_64-pc-windows-gnu", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "winapi-i686-pc-windows-gnu" 1014 | version = "0.4.0" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1017 | 1018 | [[package]] 1019 | name = "winapi-util" 1020 | version = "0.1.2" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 1023 | dependencies = [ 1024 | "winapi", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "winapi-x86_64-pc-windows-gnu" 1029 | version = "0.4.0" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1032 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "waffy" 3 | version = "0.1.0" 4 | authors = ["Kasper Seweryn "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | dirs = "2.0.2" 9 | json5 = "0.2.5" 10 | rust-embed = "5.2.0" 11 | serde = { version = "1.0.104", features = ["derive"] } 12 | once_cell = "1.2.0" 13 | sublime_fuzzy = "0.6.0" 14 | 15 | # Github deps 16 | gtk-layer-shell-rs = { git = "https://github.com/subgraph/gtk-layer-shell-rs" } 17 | gtk = { git = "https://github.com/gtk-rs/gtk.git" } 18 | gdk = { git = "https://github.com/gtk-rs/gdk.git" } 19 | gdk-pixbuf = { git = "https://github.com/gtk-rs/gdk-pixbuf.git" } 20 | gio = { git = "https://github.com/gtk-rs/gio.git" } 21 | pango = { git = "https://github.com/gtk-rs/pango.git" } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kasper Seweryn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waffy 2 | Wayland compatible, touch friendly application launcher 3 | 4 | ![waffy-screenshot](https://i.imgur.com/iyEQVqo.png) 5 | 6 | ## Features 7 | - wlroots dedicated launcher 8 | - Touch support 9 | - Read all application locations 10 | - Respect hidden apps 11 | - Fuzzy find apps 12 | 13 | ## Requirements 14 | - [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell) - A library to write GTK applications that use Layer Shell 15 | 16 | ## Building 17 | ### Build with git and cmake 18 | ```shell script 19 | git clone https://github.com/wvffle/waffy.git 20 | cd waffy 21 | cargo build --release 22 | ``` 23 | -------------------------------------------------------------------------------- /res/default_config.json5: -------------------------------------------------------------------------------- 1 | // ___ ___ // 2 | // /'___\ /'___\ // 3 | // __ __ __ __ /\ \__//\ \__/ __ __ // 4 | // /\ \/\ \/\ \ /'__`\ \ \ ,__\ \ ,__\/\ \/\ \ // 5 | // \ \ \_/ \_/ \/\ \L\.\_\ \ \_/\ \ \_/\ \ \_\ \ // 6 | // \ \___x___/'\ \__/.\_\\ \_\ \ \_\ \/`____ \ // 7 | // \/__//__/ \/__/\/_/ \/_/ \/_/ `/___/> \ // 8 | // /\___/ // 9 | // config \/__/ // 10 | { 11 | // Number of columns in launcher 12 | columns: 4, 13 | 14 | // Enable pywal colors 15 | enable_pywal: false, 16 | 17 | // Search box prompt 18 | search_prompt: 'search:', 19 | } 20 | -------------------------------------------------------------------------------- /res/default_style.css: -------------------------------------------------------------------------------- 1 | 2 | label { 3 | color: #fff; 4 | } 5 | 6 | .textview-label { 7 | padding-right: 0.5em; 8 | } 9 | 10 | .textview-label, 11 | textview { 12 | font-size: 2em; 13 | padding-top: 47px; 14 | padding-bottom: 47px; 15 | } 16 | 17 | .textview-label, 18 | textview text { 19 | color: #fff; 20 | } 21 | 22 | * { 23 | background-color: transparent; 24 | border: none; 25 | } 26 | 27 | #apps > button { 28 | min-width: 270px; 29 | 30 | border: none; 31 | transition: none; 32 | box-shadow: none; 33 | } 34 | 35 | /* default colors */ 36 | window { 37 | background: alpha(#000, .7); 38 | } 39 | 40 | #apps > button { 41 | background: alpha(#fff, .1); 42 | } 43 | 44 | #apps > button.active { 45 | background: alpha(#fff, .3); 46 | } 47 | 48 | .button-highlight { 49 | color: #00aaff; 50 | } 51 | 52 | /* pywal colors */ 53 | window.pywal { 54 | background: alpha(@background, .7); 55 | } 56 | 57 | window.pywal #apps > button { 58 | background: @background; 59 | } 60 | 61 | window.pywal #apps > button.active { 62 | background: @color5; 63 | } 64 | 65 | .button-highlight { 66 | color: @color3; 67 | } 68 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::OnceCell; 2 | use serde::Deserialize; 3 | use std::fs; 4 | use std::path::PathBuf; 5 | 6 | use super::resource::Resource; 7 | 8 | static CONFIG: OnceCell = OnceCell::new(); 9 | 10 | #[derive(Deserialize)] 11 | pub struct Config { 12 | pub columns: u8, 13 | pub search_prompt: String, 14 | pub enable_pywal: bool, 15 | } 16 | 17 | impl Config { 18 | pub fn create_dir() { 19 | if let Some(mut config_dir) = dirs::config_dir() { 20 | config_dir.push("waffy"); 21 | if !config_dir.exists() { 22 | let _ = fs::create_dir_all(config_dir); 23 | } 24 | } 25 | } 26 | 27 | pub fn default(path_to_save: Option) -> Self { 28 | // let file = Resource::get("default_config.json5").unwrap(); 29 | // let content = String::from_utf8(file.as_ref().to_vec()) 30 | let content = 31 | Resource::from_file("default_config.json5").expect("Cannot read default config"); 32 | 33 | let config = json5::from_str::(&content).expect("Cannot parse default config"); 34 | if let Some(path) = path_to_save { 35 | let _ = fs::write(path, &content); 36 | } 37 | 38 | config 39 | } 40 | 41 | pub fn get_uncached() -> Self { 42 | if let Some(mut config_path) = dirs::config_dir() { 43 | config_path.push("waffy"); 44 | config_path.push("config"); 45 | 46 | if !config_path.exists() { 47 | return Self::default(Some(config_path)); 48 | } 49 | 50 | let content = fs::read_to_string(config_path).expect("Could not read config"); 51 | let config = json5::from_str::(&content).expect("Could not parse config"); 52 | return config; 53 | } 54 | 55 | Self::default(None) 56 | } 57 | 58 | pub fn get() -> &'static Self { 59 | CONFIG.get_or_init(Self::get_uncached) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/desktop_entry.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use gtk::{IconTheme, IconThemeExt, Image, ImageExt, Label}; 5 | use std::path::PathBuf; 6 | use std::{fs, io}; 7 | 8 | use super::grid::GridButton; 9 | 10 | pub struct DesktopEntry { 11 | pub name: String, 12 | pub display_name: String, 13 | pub icon_path: Option, 14 | } 15 | 16 | impl DesktopEntry { 17 | pub fn empty() -> Self { 18 | Self { 19 | name: String::from(""), 20 | display_name: String::from(""), 21 | icon_path: None, 22 | } 23 | } 24 | 25 | pub fn from_file(path: PathBuf) -> io::Result> { 26 | let content = fs::read_to_string(path)?; 27 | 28 | let mut entry = Self::empty(); 29 | let mut entry_section = false; 30 | 31 | for line in content.lines() { 32 | let mut chars = line.chars(); 33 | let first_char = chars.next(); 34 | 35 | if let Some(chr) = first_char { 36 | if chr == '\0' || chr == '#' { 37 | continue; 38 | } 39 | } 40 | 41 | if entry_section { 42 | if let Some(chr) = first_char { 43 | if chr == '[' { 44 | break; 45 | } 46 | } 47 | 48 | let split = line.splitn(2, '=').map(String::from).collect::>(); 49 | 50 | let key = match split.get(0) { 51 | Some(key) => key, 52 | None => continue, 53 | }; 54 | 55 | let value = match split.get(1) { 56 | Some(value) => value, 57 | None => continue, 58 | }; 59 | 60 | if (key == "NoDisplay" || key == "Hidden") && value == "true" { 61 | return Ok(None); 62 | } 63 | 64 | if key == "Name" { 65 | entry.set_name(value); 66 | continue; 67 | } 68 | 69 | if key == "Icon" { 70 | entry.set_icon(value); 71 | continue; 72 | } 73 | } else if line == "[Desktop Entry]" { 74 | entry_section = true; 75 | } 76 | } 77 | 78 | Ok(Some(entry)) 79 | } 80 | 81 | pub fn get_dirs() -> Vec { 82 | let mut dirs: Vec = vec![ 83 | "/usr/share/applications".into(), 84 | "/usr/local/share/applications".into(), 85 | ]; 86 | 87 | if let Some(user_dir) = dirs::home_dir() { 88 | dirs.push(user_dir.join(".local/share/applications/")); 89 | } 90 | 91 | dirs 92 | } 93 | 94 | pub fn get_all() -> Vec>> { 95 | let mut desktop_entries: Vec>> = Vec::with_capacity(10); 96 | 97 | for dir in Self::get_dirs() { 98 | if let Ok(read_dir) = fs::read_dir(dir) { 99 | for entry in read_dir { 100 | if let Ok(entry) = entry { 101 | let path = entry.path(); 102 | let extension = path.extension(); 103 | 104 | match extension { 105 | Some(extension) => { 106 | if extension != "desktop" { 107 | continue; 108 | } 109 | } 110 | None => continue, 111 | } 112 | 113 | if let Ok(desktop_entry) = Self::from_file(path) { 114 | if let Some(desktop_entry) = desktop_entry { 115 | desktop_entries.push(Rc::new(RefCell::new(desktop_entry))); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | desktop_entries 124 | } 125 | 126 | pub fn set_name>(&mut self, name: S) { 127 | self.name = name.into(); 128 | self.display_name = self.name.clone(); 129 | } 130 | 131 | pub fn set_icon>(&mut self, icon: S) { 132 | self.icon_path = Some(icon.into()); 133 | } 134 | } 135 | 136 | impl GridButton for DesktopEntry { 137 | fn label(&self) -> &String { 138 | &self.name 139 | } 140 | 141 | fn display_label(&self) -> Label { 142 | Label::new(Some(&self.display_name)) 143 | } 144 | 145 | fn icon(&self) -> Image { 146 | if self.icon_path == None { 147 | // gtk::IconSize::Dialog is 48x48 148 | return Image::new_from_icon_name( 149 | Some("application-x-executable"), 150 | gtk::IconSize::Dialog, 151 | ); 152 | } 153 | 154 | let icon = self.icon_path.as_ref().unwrap(); 155 | if PathBuf::from(&icon).exists() { 156 | let image = Image::new_from_file(&icon); 157 | 158 | // Scale down to 48x48 159 | if let Some(pixbuf) = image.get_pixbuf() { 160 | if let Some(pixbuf) = pixbuf.scale_simple(48, 48, gdk_pixbuf::InterpType::Tiles) { 161 | return Image::new_from_pixbuf(Some(&pixbuf)); 162 | } 163 | } 164 | } 165 | 166 | if let Some(theme) = IconTheme::get_default() { 167 | let info = theme.load_icon(&icon, 48, gtk::IconLookupFlags::FORCE_SIZE); 168 | 169 | if let Ok(info) = info { 170 | if let Some(pixbuf) = info { 171 | return Image::new_from_pixbuf(Some(&pixbuf)); 172 | } 173 | } 174 | } 175 | 176 | // gtk::IconSize::Dialog is 48x48 177 | Image::new_from_icon_name(Some("application-x-executable"), gtk::IconSize::Dialog) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/grid.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use gtk::{ButtonExt, ContainerExt, Grid as GtkGrid, GridExt, LabelExt, ScrolledWindow as GtkWindow, StyleContextExt, Viewport as GtkViewport, WidgetExt}; 5 | 6 | use sublime_fuzzy::{ 7 | best_match as fuzzy_match, 8 | format_simple as fuzzy_format, 9 | }; 10 | 11 | use super::Config; 12 | 13 | pub const SHOW_ICON: u32 = 0b01; 14 | pub const SHOW_LABEL: u32 = 0b10; 15 | 16 | type GridButtonRc = Rc>; 17 | type GridButtonCallback = dyn Fn(GridButtonRc); 18 | 19 | pub trait GridButton { 20 | fn label(&self) -> &String; 21 | fn display_label(&self) -> gtk::Label; 22 | fn icon(&self) -> gtk::Image; 23 | } 24 | 25 | pub trait GridCursor { 26 | fn cursor_left(&mut self); 27 | fn cursor_right(&mut self); 28 | fn cursor_up(&mut self); 29 | fn cursor_down(&mut self); 30 | fn cursor_hide(&self); 31 | fn cursor_show(&self); 32 | fn cursor_set_pos(&mut self, x: usize, y: usize); 33 | fn cursor_set_index(&mut self, index: usize); 34 | } 35 | 36 | pub struct Grid { 37 | buttons: Vec, 38 | items: Vec, 39 | pub window: GtkWindow, 40 | pub grid: GtkGrid, 41 | flags: u32, 42 | cursor: (usize, usize), 43 | } 44 | 45 | impl Grid { 46 | pub fn new( 47 | items: Vec, 48 | flags: u32, 49 | click_callback: Rc, 50 | ) -> Self { 51 | let adjustment = None::<>k::Adjustment>; 52 | let window = GtkWindow::new(adjustment, adjustment); 53 | let viewport = GtkViewport::new(adjustment, adjustment); 54 | let grid = GtkGrid::new(); 55 | 56 | let config = Config::get(); 57 | 58 | // Initialize columns 59 | for i in 0..(config.columns - 1) { 60 | grid.insert_column(i as i32); 61 | } 62 | 63 | grid.set_column_spacing(17); 64 | grid.set_row_spacing(17); 65 | grid.set_halign(gtk::Align::Center); 66 | 67 | window.add(&viewport); 68 | viewport.add(&grid); 69 | 70 | let mut buttons: Vec = Vec::new(); 71 | 72 | let mut i = 0; 73 | let mut local_items = Vec::new(); 74 | for item in items { 75 | local_items.push(item.clone()); 76 | 77 | let col = i as i32 % config.columns as i32; 78 | let row = i as i32 / config.columns as i32; 79 | i += 1; 80 | 81 | let widget = gtk::Button::new(); 82 | let content = gtk::Grid::new(); 83 | 84 | content.set_column_spacing(17); 85 | 86 | if flags & SHOW_ICON > 0 { 87 | content.insert_column(0); 88 | content.attach(&item.borrow().icon(), 0, 0, 1, 1); 89 | } 90 | 91 | if flags & SHOW_LABEL > 0 { 92 | let label = item.borrow().display_label(); 93 | content.insert_column(1); 94 | content.attach(&label, 1, 0, 1, 1); 95 | 96 | label.set_max_width_chars(16); 97 | label.set_ellipsize(pango::EllipsizeMode::End); 98 | label.set_use_markup(true); 99 | } 100 | 101 | let callback = click_callback.clone(); 102 | 103 | widget.connect_clicked(move |_| { 104 | (callback)(item.clone()); 105 | }); 106 | 107 | widget.add(&content); 108 | grid.attach(&widget, col, row, 1, 1); 109 | buttons.push(widget); 110 | } 111 | 112 | let mut res = Self { 113 | buttons, 114 | items: local_items, 115 | window, 116 | grid, 117 | flags, 118 | cursor: (0, 0) 119 | }; 120 | 121 | for (i, widget) in res.buttons.iter().enumerate() { 122 | widget.connect_enter_notify_event(move |widget, _| { 123 | res.cursor_set_index(i); 124 | gtk::Inhibit(true) 125 | }); 126 | 127 | widget.connect_leave_notify_event(move |widget, _| { 128 | res.cursor_hide(); 129 | gtk::Inhibit(true) 130 | }); 131 | } 132 | 133 | res 134 | } 135 | 136 | pub fn filter(&self, needle: String) { 137 | self.update(needle); 138 | } 139 | 140 | pub fn update(&self, needle: String) { 141 | let mut sorted_entries = Vec::with_capacity(self.items.len()); 142 | let columns = Config::get().columns as i32; 143 | self.grid.foreach(|widget| self.grid.remove(widget)); 144 | 145 | for (i, item) in self.items.iter().enumerate() { 146 | let mut item = item.borrow_mut(); 147 | let label = item.label().clone(); 148 | let button = self.buttons.get(i).unwrap(); 149 | 150 | if needle == "" { 151 | sorted_entries.push(true); 152 | item.display_label().set_label(&label); 153 | continue; 154 | } 155 | 156 | if let Some(res) = fuzzy_match(needle.as_str(), label.as_str()) { 157 | let name = fuzzy_format( 158 | &res, 159 | label.as_str(), 160 | "[[", 161 | "]]" 162 | ); 163 | 164 | // TODO: Label is not setting for some weird reason 165 | item.display_label().set_label(name.as_str()); 166 | println!("{} {}", item.display_label().get_label().unwrap(), name.as_str()); 167 | 168 | sorted_entries.push(true); 169 | continue; 170 | } 171 | 172 | sorted_entries.push(false); 173 | } 174 | 175 | let mut idx = 0; 176 | for (i, visible) in sorted_entries.into_iter().enumerate() { 177 | let mut col = i as i32 % columns; 178 | let mut row = i as i32 / columns; 179 | let button = self.buttons.get(i).unwrap(); 180 | 181 | if visible { 182 | col = idx as i32 % columns; 183 | row = idx as i32 / columns; 184 | 185 | self.grid.attach(button, col, row, 1, 1); 186 | idx += 1; 187 | } 188 | 189 | } 190 | 191 | self.grid.show(); 192 | } 193 | } 194 | 195 | impl GridCursor for Grid { 196 | fn cursor_left(&mut self) { 197 | if self.cursor.0 > 0 { 198 | self.cursor_set_pos(self.cursor.0 - 1, self.cursor.1) 199 | } 200 | } 201 | 202 | fn cursor_right(&mut self) { 203 | self.cursor_set_pos(self.cursor.0 + 1, self.cursor.1) 204 | } 205 | 206 | fn cursor_up(&mut self) { 207 | if self.cursor.1 > 0 { 208 | self.cursor_set_pos(self.cursor.0, self.cursor.1 - 1) 209 | } 210 | } 211 | 212 | fn cursor_down(&mut self) { 213 | self.cursor_set_pos(self.cursor.0, self.cursor.1 + 1) 214 | } 215 | 216 | fn cursor_hide(&self) { 217 | let config = Config::get(); 218 | if let Some(widget) = self.buttons.get(self.cursor.1 * config.columns as usize + self.cursor.0) { 219 | let ctx = widget.get_style_context(); 220 | ctx.remove_class("active"); 221 | } 222 | } 223 | 224 | fn cursor_show(&self) { 225 | let config = Config::get(); 226 | if let Some(widget) = self.buttons.get(self.cursor.1 * config.columns as usize + self.cursor.0) { 227 | let ctx = widget.get_style_context(); 228 | ctx.add_class("active"); 229 | } 230 | } 231 | 232 | fn cursor_set_pos(&mut self, x: usize, y: usize) { 233 | let config = Config::get(); 234 | 235 | let idx = y * config.columns as usize + x; 236 | 237 | let mut x_next = idx % config.columns as usize; 238 | let mut y_next = idx / config.columns as usize; 239 | 240 | let y_max = self.buttons.len() - 1; 241 | if y_next > y_max { 242 | y_next = y_max; 243 | } 244 | 245 | if self.cursor.0 != x_next && self.cursor.1 != y_next { 246 | self.cursor_hide(); 247 | 248 | self.cursor.0 = x_next; 249 | self.cursor.1 = y_next; 250 | 251 | self.cursor_show(); 252 | } 253 | } 254 | 255 | fn cursor_set_index(&mut self, i: usize) { 256 | let config = Config::get(); 257 | 258 | let x = i % config.columns as usize; 259 | let y = i / config.columns as usize; 260 | 261 | self.cursor_set_pos(x, y); 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use gdk::*; 5 | use gtk::{BoxExt, ContainerExt, CssProvider, CssProviderExt, GtkWindowExt, Inhibit, Label, LabelExt, Orientation, StyleContext, StyleContextExt, TextBuffer, TextBufferExt, TextTagTable, TextView, WidgetExt}; 6 | use gtk_layer_shell_rs::*; 7 | 8 | use config::Config; 9 | use desktop_entry::DesktopEntry; 10 | 11 | use crate::grid::GridButton; 12 | 13 | mod config; 14 | mod grid; 15 | mod resource; 16 | mod style; 17 | 18 | mod desktop_entry; 19 | 20 | fn main() { 21 | Config::create_dir(); 22 | 23 | let config = Config::get(); 24 | let desktop_entries = DesktopEntry::get_all(); 25 | 26 | gtk::init().expect("Could not init GTK!"); 27 | 28 | let window = gtk::Window::new(gtk::WindowType::Toplevel); 29 | init_for_window(&window); 30 | set_layer(&window, Layer::Top); 31 | set_anchor(&window, Edge::Top, true); 32 | set_anchor(&window, Edge::Left, true); 33 | set_anchor(&window, Edge::Right, true); 34 | set_anchor(&window, Edge::Bottom, true); 35 | 36 | window.connect_key_release_event(|window, key| { 37 | use gdk::enums::key; 38 | 39 | match key.get_keyval() { 40 | key::Escape => { 41 | window.destroy(); 42 | std::process::exit(0) 43 | } 44 | _ => { 45 | Inhibit(true) 46 | }, 47 | } 48 | }); 49 | 50 | window.connect_enter_notify_event(|window, _event| { 51 | set_keyboard_interactivity(window, true); 52 | Inhibit(false) 53 | }); 54 | 55 | window.connect_leave_notify_event(|window, event| { 56 | if event.get_detail() == gdk::NotifyType::NonlinearVirtual { 57 | set_keyboard_interactivity(window, false); 58 | } 59 | 60 | Inhibit(false) 61 | }); 62 | 63 | window.set_resizable(false); 64 | window.set_decorated(false); 65 | 66 | let layout = gtk::Box::new(Orientation::Vertical, 0); 67 | window.add(&layout); 68 | 69 | let search_box = gtk::Box::new(Orientation::Horizontal, 0); 70 | layout.pack_start(&search_box, false, false, 0); 71 | 72 | let grid_width = 270 * 4 + (4 + -1) * 17; 73 | let spacer = gtk::Box::new(Orientation::Horizontal, 0); 74 | let width = get_monitor_width() / 2 - (grid_width as i32) / 2; 75 | spacer.set_size_request(width, 1); 76 | 77 | search_box.pack_start(&spacer, false, false, 0); 78 | 79 | let search_label = Label::new(None); 80 | search_label.set_markup(config.search_prompt.as_str()); 81 | search_box.pack_start(&search_label, false, false, 0); 82 | 83 | add_class(&search_label, "textview-label"); 84 | 85 | let buttons = desktop_entries 86 | .into_iter() 87 | .map(|entry| entry as Rc>) 88 | .collect::>(); 89 | 90 | let app_grid = Rc::new(RefCell::new(grid::Grid::new( 91 | buttons, 92 | grid::SHOW_ICON | grid::SHOW_LABEL, 93 | Rc::new(|entry| { 94 | println!("{:?}", entry.borrow().label()); 95 | }), 96 | ))); 97 | 98 | let buffer = TextBuffer::new(None::<&TextTagTable>); 99 | let cloned_app_grid = app_grid.clone(); 100 | 101 | buffer.connect_changed(move |buf| { 102 | let search_text = buf.get_text(&buf.get_start_iter(), &buf.get_end_iter(), false) 103 | .unwrap_or("".into()); 104 | 105 | if let Ok(grid) = cloned_app_grid.try_borrow() { 106 | println!("search text: {}", search_text); 107 | grid.filter(search_text.to_string()); 108 | } 109 | }); 110 | 111 | 112 | let search_input = TextView::new_with_buffer(&buffer); 113 | search_box.pack_start(&search_input, true, true, 0); 114 | 115 | let grid = &app_grid.borrow().grid; 116 | grid.set_widget_name("apps"); 117 | 118 | let gtk_window = &app_grid.borrow().window; 119 | layout.pack_start(gtk_window, true, true, 0); 120 | 121 | let css_provider = CssProvider::new(); 122 | 123 | if config.enable_pywal { 124 | add_class(&window, "pywal"); 125 | } 126 | 127 | let css = style::get_css(); 128 | 129 | if let Ok(_) = css_provider.load_from_data(css.as_ref()) { 130 | StyleContext::add_provider_for_screen( 131 | &gdk::Screen::get_default().expect("Error initializing css provider"), 132 | &css_provider, 133 | gtk::STYLE_PROVIDER_PRIORITY_USER, 134 | ); 135 | } 136 | 137 | if let Some(display) = window.get_display() { 138 | let arrow = 139 | Cursor::new_from_name(&display, "default").expect("Could not create 'default' cursor!"); 140 | 141 | let pointer = 142 | Cursor::new_from_name(&display, "pointer").expect("Could not create 'pointer' cursor!"); 143 | } 144 | 145 | window.set_title("waffy"); 146 | window.show_all(); 147 | 148 | gtk::main(); 149 | } 150 | 151 | fn get_monitor_width() -> i32 { 152 | return 1920; 153 | } 154 | 155 | fn add_class(widget: &W, class_name: &str) { 156 | let ctx = widget.get_style_context(); 157 | ctx.add_class(class_name); 158 | } 159 | -------------------------------------------------------------------------------- /src/resource.rs: -------------------------------------------------------------------------------- 1 | use rust_embed::RustEmbed; 2 | use std::string::FromUtf8Error; 3 | 4 | #[derive(RustEmbed)] 5 | #[folder = "res/"] 6 | pub struct Resource; 7 | 8 | impl Resource { 9 | pub fn from_file(filename: &str) -> Result { 10 | let file = Self::get(filename).unwrap(); 11 | String::from_utf8(file.as_ref().to_vec()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use super::resource::Resource; 5 | 6 | pub fn get_default_css(path_to_save: Option) -> String { 7 | // let file = Resource::get("default_style.css").unwrap(); 8 | // let content = String::from_utf8(file.as_ref().to_vec()) 9 | 10 | let content = Resource::from_file("default_style.css").expect("Cannot read default style"); 11 | 12 | if let Some(path) = path_to_save { 13 | let _ = fs::write(path, &content); 14 | } 15 | 16 | content 17 | } 18 | 19 | pub fn get_css() -> String { 20 | let mut content = String::from(""); 21 | if let Some(mut style_path) = dirs::config_dir() { 22 | style_path.push("waffy"); 23 | style_path.push("style.css"); 24 | 25 | if style_path.exists() { 26 | content = fs::read_to_string(style_path).expect("Could not read config"); 27 | } else { 28 | content = get_default_css(Some(style_path)) 29 | } 30 | } 31 | 32 | if content == "" { 33 | content = get_default_css(None); 34 | } 35 | 36 | // TODO: Check config option 37 | if let Some(mut pywal_path) = dirs::cache_dir() { 38 | pywal_path.push("wal"); 39 | pywal_path.push("colors-waybar.css"); 40 | 41 | if pywal_path.exists() { 42 | let mut pywal_content = fs::read_to_string(pywal_path).expect("Could not read config"); 43 | pywal_content.push_str(content.as_str()); 44 | content = pywal_content; 45 | } 46 | } 47 | 48 | content 49 | } 50 | --------------------------------------------------------------------------------