From 9049f5c51cab452fc41c190b2f3814385e92ec02 Mon Sep 17 00:00:00 2001 From: Kievits Rene Date: Tue, 31 May 2022 01:16:00 +0200 Subject: [PATCH] Add new notification-center module with various widgets, add rubato animations and other fixes and improvements --- awesome/crylia_bar/init.lua | 65 - awesome/rc.lua | 8 +- .../assets/icons/notifications/MEGAMIND.svg | 319 ++++ .../icons/notifications/bell-outline.svg | 5 + .../assets/icons/notifications/megamind.svg | 54 + .../icons/notifications/repeat-once.svg | 1 + .../src/assets/icons/notifications/repeat.svg | 1 + .../assets/icons/notifications/shuffle.svg | 1 + awesome/src/assets/icons/profile/clock.svg | 1 + awesome/src/assets/icons/profile/laptop.svg | 5 + awesome/src/assets/icons/profile/penguin.svg | 5 + awesome/src/assets/icons/profile/user.svg | 5 + awesome/src/assets/icons/weather/humidity.svg | 1 + .../icons/weather/weather-clear-night.svg | 5 + .../icons/weather/weather-cloudy-alert.svg | 5 + .../assets/icons/weather/weather-cloudy.svg | 5 + .../src/assets/icons/weather/weather-fog.svg | 5 + .../src/assets/icons/weather/weather-hail.svg | 5 + .../src/assets/icons/weather/weather-hazy.svg | 5 + .../icons/weather/weather-hurricane.svg | 5 + .../icons/weather/weather-lightning-rainy.svg | 5 + .../icons/weather/weather-lightning.svg | 5 + .../weather/weather-night-partly-cloudy.svg | 5 + .../icons/weather/weather-partly-cloudy.svg | 5 + .../weather/weather-partly-lightning.svg | 5 + .../icons/weather/weather-partly-rainy.svg | 5 + .../weather/weather-partly-snowy-rainy.svg | 5 + .../icons/weather/weather-partly-snowy.svg | 5 + .../assets/icons/weather/weather-pouring.svg | 5 + .../assets/icons/weather/weather-rainy.svg | 5 + .../icons/weather/weather-snowy-heavy.svg | 5 + .../icons/weather/weather-snowy-rainy.svg | 5 + .../assets/icons/weather/weather-snowy.svg | 5 + .../icons/weather/weather-sunny-alert.svg | 5 + .../assets/icons/weather/weather-sunny.svg | 5 + .../assets/icons/weather/weather-sunset.svg | 5 + .../assets/icons/weather/weather-tornado.svg | 5 + .../icons/weather/weather-windy-variant.svg | 5 + .../assets/icons/weather/weather-windy.svg | 5 + .../bindings}/bind_to_tags.lua | 2 +- .../bindings}/client_buttons.lua | 0 .../bindings}/client_keys.lua | 0 .../bindings}/global_buttons.lua | 0 .../bindings}/global_keys.lua | 0 awesome/src/core/error_handling.lua | 22 +- awesome/src/core/notifications.lua | 613 +++---- awesome/src/core/rules.lua | 20 +- awesome/src/lib/json-lua/json-lua.lua | 1554 +++++++++++++++++ awesome/src/lib/rubato | 1 + awesome/src/modules/brightness_osd.lua | 105 +- .../modules}/crylia_bar/center_bar.lua | 0 awesome/{ => src/modules}/crylia_bar/dock.lua | 0 awesome/src/modules/crylia_bar/init.lua | 52 + .../{ => src/modules}/crylia_bar/left_bar.lua | 0 .../modules}/crylia_bar/right_bar.lua | 0 awesome/src/modules/init.lua | 28 + .../src/modules/notification-center/init.lua | 371 ++++ .../notification-center/notification_list.lua | 261 +++ .../modules/notification-center/profile.lua | 207 +++ .../modules/notification-center/song_info.lua | 466 +++++ .../spacingline_widget.lua | 23 + .../notification-center/status_bars.lua | 712 ++++++++ .../modules/notification-center/time_date.lua | 65 + .../modules/notification-center/weather.lua | 219 +++ awesome/src/modules/volume_controller.lua | 10 + awesome/src/scripts/wifi.sh | 0 awesome/src/theme/user_variables.lua | 14 +- awesome/src/widgets/audio.lua | 1 + awesome/src/widgets/battery.lua | 2 +- awesome/src/widgets/cpu_info.lua | 6 +- awesome/src/widgets/gpu_info.lua | 2 + awesome/src/widgets/ram_info.lua | 5 +- awesome/src/widgets/systray.lua | 2 +- 73 files changed, 4914 insertions(+), 450 deletions(-) delete mode 100644 awesome/crylia_bar/init.lua create mode 100644 awesome/src/assets/icons/notifications/MEGAMIND.svg create mode 100644 awesome/src/assets/icons/notifications/bell-outline.svg create mode 100644 awesome/src/assets/icons/notifications/megamind.svg create mode 100644 awesome/src/assets/icons/notifications/repeat-once.svg create mode 100644 awesome/src/assets/icons/notifications/repeat.svg create mode 100644 awesome/src/assets/icons/notifications/shuffle.svg create mode 100644 awesome/src/assets/icons/profile/clock.svg create mode 100644 awesome/src/assets/icons/profile/laptop.svg create mode 100644 awesome/src/assets/icons/profile/penguin.svg create mode 100644 awesome/src/assets/icons/profile/user.svg create mode 100644 awesome/src/assets/icons/weather/humidity.svg create mode 100644 awesome/src/assets/icons/weather/weather-clear-night.svg create mode 100644 awesome/src/assets/icons/weather/weather-cloudy-alert.svg create mode 100644 awesome/src/assets/icons/weather/weather-cloudy.svg create mode 100644 awesome/src/assets/icons/weather/weather-fog.svg create mode 100644 awesome/src/assets/icons/weather/weather-hail.svg create mode 100644 awesome/src/assets/icons/weather/weather-hazy.svg create mode 100644 awesome/src/assets/icons/weather/weather-hurricane.svg create mode 100644 awesome/src/assets/icons/weather/weather-lightning-rainy.svg create mode 100644 awesome/src/assets/icons/weather/weather-lightning.svg create mode 100644 awesome/src/assets/icons/weather/weather-night-partly-cloudy.svg create mode 100644 awesome/src/assets/icons/weather/weather-partly-cloudy.svg create mode 100644 awesome/src/assets/icons/weather/weather-partly-lightning.svg create mode 100644 awesome/src/assets/icons/weather/weather-partly-rainy.svg create mode 100644 awesome/src/assets/icons/weather/weather-partly-snowy-rainy.svg create mode 100644 awesome/src/assets/icons/weather/weather-partly-snowy.svg create mode 100644 awesome/src/assets/icons/weather/weather-pouring.svg create mode 100644 awesome/src/assets/icons/weather/weather-rainy.svg create mode 100644 awesome/src/assets/icons/weather/weather-snowy-heavy.svg create mode 100644 awesome/src/assets/icons/weather/weather-snowy-rainy.svg create mode 100644 awesome/src/assets/icons/weather/weather-snowy.svg create mode 100644 awesome/src/assets/icons/weather/weather-sunny-alert.svg create mode 100644 awesome/src/assets/icons/weather/weather-sunny.svg create mode 100644 awesome/src/assets/icons/weather/weather-sunset.svg create mode 100644 awesome/src/assets/icons/weather/weather-tornado.svg create mode 100644 awesome/src/assets/icons/weather/weather-windy-variant.svg create mode 100644 awesome/src/assets/icons/weather/weather-windy.svg rename awesome/{mappings => src/bindings}/bind_to_tags.lua (97%) rename awesome/{mappings => src/bindings}/client_buttons.lua (100%) rename awesome/{mappings => src/bindings}/client_keys.lua (100%) rename awesome/{mappings => src/bindings}/global_buttons.lua (100%) rename awesome/{mappings => src/bindings}/global_keys.lua (100%) create mode 100644 awesome/src/lib/json-lua/json-lua.lua create mode 160000 awesome/src/lib/rubato rename awesome/{ => src/modules}/crylia_bar/center_bar.lua (100%) rename awesome/{ => src/modules}/crylia_bar/dock.lua (100%) create mode 100644 awesome/src/modules/crylia_bar/init.lua rename awesome/{ => src/modules}/crylia_bar/left_bar.lua (100%) rename awesome/{ => src/modules}/crylia_bar/right_bar.lua (100%) create mode 100644 awesome/src/modules/init.lua create mode 100644 awesome/src/modules/notification-center/notification_list.lua create mode 100644 awesome/src/modules/notification-center/profile.lua create mode 100644 awesome/src/modules/notification-center/song_info.lua create mode 100644 awesome/src/modules/notification-center/spacingline_widget.lua create mode 100644 awesome/src/modules/notification-center/status_bars.lua create mode 100644 awesome/src/modules/notification-center/time_date.lua create mode 100644 awesome/src/modules/notification-center/weather.lua create mode 100755 awesome/src/scripts/wifi.sh diff --git a/awesome/crylia_bar/init.lua b/awesome/crylia_bar/init.lua deleted file mode 100644 index c244c05..0000000 --- a/awesome/crylia_bar/init.lua +++ /dev/null @@ -1,65 +0,0 @@ --------------------------------------------------------------------------------------------------------------- --- This is the statusbar, every widget, module and so on is combined to all the stuff you see on the screen -- --------------------------------------------------------------------------------------------------------------- --- Awesome Libs -local awful = require("awful") - -awful.screen.connect_for_each_screen( --- For each screen this function is called once --- If you want to change the modules per screen use the indices --- e.g. 1 would be the primary screen and 2 the secondary screen. - function(s) - -- Create 9 tags - awful.layout.layouts = user_vars.layouts - awful.tag( - { "1", "2", "3", "4", "5", "6", "7", "8", "9" }, - s, - user_vars.layouts[1] - ) - - require("src.modules.powermenu")(s) - -- TODO: rewrite calendar osd, maybe write an own inplementation - -- require("src.modules.calendar_osd")(s) - require("src.modules.volume_osd")(s) - require("src.modules.brightness_osd")(s) - require("src.modules.titlebar") - require("src.modules.volume_controller")(s) - - -- Widgets - --s.battery = require("src.widgets.battery")() - s.audio = require("src.widgets.audio")(s) - s.date = require("src.widgets.date")() - s.clock = require("src.widgets.clock")() - --s.bluetooth = require("src.widgets.bluetooth")() - s.layoutlist = require("src.widgets.layout_list")() - s.powerbutton = require("src.widgets.power")() - s.kblayout = require("src.widgets.kblayout")(s) - s.taglist = require("src.widgets.taglist")(s) - s.tasklist = require("src.widgets.tasklist")(s) - --s.cpu_freq = require("src.widgets.cpu_info")("freq", "average") - - -- Add more of these if statements if you want to change - -- the modules/widgets per screen. - if s.index == 1 then - s.systray = require("src.widgets.systray")(s) - s.cpu_usage = require("src.widgets.cpu_info")("usage") - s.cpu_temp = require("src.widgets.cpu_info")("temp") - s.gpu_usage = require("src.widgets.gpu_info")("usage") - s.gpu_temp = require("src.widgets.gpu_info")("temp") - - require("crylia_bar.left_bar")(s, { s.layoutlist, s.systray, s.taglist }) - require("crylia_bar.center_bar")(s, { s.tasklist }) - require("crylia_bar.right_bar")(s, { s.gpu_usage, s.gpu_temp, s.cpu_usage, s.cpu_temp, s.audio, s.kblayout, s.date, s.clock, s.powerbutton }) - require("crylia_bar.dock")(s, user_vars.dock_programs) - end - - if s.index == 2 then - s.network = require("src.widgets.network")() - s.ram_info = require("src.widgets.ram_info")() - - require("crylia_bar.left_bar")(s, { s.layoutlist, s.taglist }) - require("crylia_bar.center_bar")(s, { s.tasklist }) - require("crylia_bar.right_bar")(s, { s.ram_info, s.audio, s.kblayout, s.network, s.date, s.clock, s.powerbutton }) - end -end -) diff --git a/awesome/rc.lua b/awesome/rc.lua index 0a1132f..07f5359 100644 --- a/awesome/rc.lua +++ b/awesome/rc.lua @@ -13,7 +13,7 @@ require("src.core.error_handling") require("src.core.signals") require("src.core.notifications") require("src.core.rules") -require("mappings.global_buttons") -require("mappings.bind_to_tags") -require("crylia_bar.init") -require("src.tools.auto_starter")(user_vars.autostart) +require("src.bindings.global_buttons") +require("src.bindings.bind_to_tags") +require("src.modules.init") +--require("src.tools.auto_starter")(user_vars.autostart) diff --git a/awesome/src/assets/icons/notifications/MEGAMIND.svg b/awesome/src/assets/icons/notifications/MEGAMIND.svg new file mode 100644 index 0000000..950fad7 --- /dev/null +++ b/awesome/src/assets/icons/notifications/MEGAMIND.svg @@ -0,0 +1,319 @@ + + + + +Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awesome/src/assets/icons/notifications/bell-outline.svg b/awesome/src/assets/icons/notifications/bell-outline.svg new file mode 100644 index 0000000..b306aab --- /dev/null +++ b/awesome/src/assets/icons/notifications/bell-outline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/notifications/megamind.svg b/awesome/src/assets/icons/notifications/megamind.svg new file mode 100644 index 0000000..edad7ea --- /dev/null +++ b/awesome/src/assets/icons/notifications/megamind.svg @@ -0,0 +1,54 @@ + + + + + +Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awesome/src/assets/icons/notifications/repeat-once.svg b/awesome/src/assets/icons/notifications/repeat-once.svg new file mode 100644 index 0000000..af2555f --- /dev/null +++ b/awesome/src/assets/icons/notifications/repeat-once.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/awesome/src/assets/icons/notifications/repeat.svg b/awesome/src/assets/icons/notifications/repeat.svg new file mode 100644 index 0000000..8086726 --- /dev/null +++ b/awesome/src/assets/icons/notifications/repeat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/awesome/src/assets/icons/notifications/shuffle.svg b/awesome/src/assets/icons/notifications/shuffle.svg new file mode 100644 index 0000000..dc35411 --- /dev/null +++ b/awesome/src/assets/icons/notifications/shuffle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/awesome/src/assets/icons/profile/clock.svg b/awesome/src/assets/icons/profile/clock.svg new file mode 100644 index 0000000..84984a8 --- /dev/null +++ b/awesome/src/assets/icons/profile/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/awesome/src/assets/icons/profile/laptop.svg b/awesome/src/assets/icons/profile/laptop.svg new file mode 100644 index 0000000..689816a --- /dev/null +++ b/awesome/src/assets/icons/profile/laptop.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/profile/penguin.svg b/awesome/src/assets/icons/profile/penguin.svg new file mode 100644 index 0000000..c977e37 --- /dev/null +++ b/awesome/src/assets/icons/profile/penguin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/profile/user.svg b/awesome/src/assets/icons/profile/user.svg new file mode 100644 index 0000000..0549b71 --- /dev/null +++ b/awesome/src/assets/icons/profile/user.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/humidity.svg b/awesome/src/assets/icons/weather/humidity.svg new file mode 100644 index 0000000..a6e7eb3 --- /dev/null +++ b/awesome/src/assets/icons/weather/humidity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/awesome/src/assets/icons/weather/weather-clear-night.svg b/awesome/src/assets/icons/weather/weather-clear-night.svg new file mode 100644 index 0000000..7cce7bc --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-clear-night.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-cloudy-alert.svg b/awesome/src/assets/icons/weather/weather-cloudy-alert.svg new file mode 100644 index 0000000..ffddf6a --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-cloudy-alert.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-cloudy.svg b/awesome/src/assets/icons/weather/weather-cloudy.svg new file mode 100644 index 0000000..5106bbb --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-cloudy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-fog.svg b/awesome/src/assets/icons/weather/weather-fog.svg new file mode 100644 index 0000000..3f1712d --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-fog.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-hail.svg b/awesome/src/assets/icons/weather/weather-hail.svg new file mode 100644 index 0000000..66d7a83 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-hail.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-hazy.svg b/awesome/src/assets/icons/weather/weather-hazy.svg new file mode 100644 index 0000000..2f91bb5 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-hazy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-hurricane.svg b/awesome/src/assets/icons/weather/weather-hurricane.svg new file mode 100644 index 0000000..745214d --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-hurricane.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-lightning-rainy.svg b/awesome/src/assets/icons/weather/weather-lightning-rainy.svg new file mode 100644 index 0000000..7ef4a8e --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-lightning-rainy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-lightning.svg b/awesome/src/assets/icons/weather/weather-lightning.svg new file mode 100644 index 0000000..663b0bd --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-lightning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-night-partly-cloudy.svg b/awesome/src/assets/icons/weather/weather-night-partly-cloudy.svg new file mode 100644 index 0000000..92ac071 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-night-partly-cloudy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-partly-cloudy.svg b/awesome/src/assets/icons/weather/weather-partly-cloudy.svg new file mode 100644 index 0000000..f2c4e07 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-partly-cloudy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-partly-lightning.svg b/awesome/src/assets/icons/weather/weather-partly-lightning.svg new file mode 100644 index 0000000..d95d06c --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-partly-lightning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-partly-rainy.svg b/awesome/src/assets/icons/weather/weather-partly-rainy.svg new file mode 100644 index 0000000..47c0c25 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-partly-rainy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-partly-snowy-rainy.svg b/awesome/src/assets/icons/weather/weather-partly-snowy-rainy.svg new file mode 100644 index 0000000..fe1b4ee --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-partly-snowy-rainy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-partly-snowy.svg b/awesome/src/assets/icons/weather/weather-partly-snowy.svg new file mode 100644 index 0000000..c8a5273 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-partly-snowy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-pouring.svg b/awesome/src/assets/icons/weather/weather-pouring.svg new file mode 100644 index 0000000..c92c5ef --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-pouring.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-rainy.svg b/awesome/src/assets/icons/weather/weather-rainy.svg new file mode 100644 index 0000000..cebfcb6 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-rainy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-snowy-heavy.svg b/awesome/src/assets/icons/weather/weather-snowy-heavy.svg new file mode 100644 index 0000000..c77b21a --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-snowy-heavy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-snowy-rainy.svg b/awesome/src/assets/icons/weather/weather-snowy-rainy.svg new file mode 100644 index 0000000..83da969 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-snowy-rainy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-snowy.svg b/awesome/src/assets/icons/weather/weather-snowy.svg new file mode 100644 index 0000000..e7835b8 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-snowy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-sunny-alert.svg b/awesome/src/assets/icons/weather/weather-sunny-alert.svg new file mode 100644 index 0000000..1d44f94 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-sunny-alert.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-sunny.svg b/awesome/src/assets/icons/weather/weather-sunny.svg new file mode 100644 index 0000000..6913b8d --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-sunny.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-sunset.svg b/awesome/src/assets/icons/weather/weather-sunset.svg new file mode 100644 index 0000000..0f85f81 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-sunset.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-tornado.svg b/awesome/src/assets/icons/weather/weather-tornado.svg new file mode 100644 index 0000000..1124ec3 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-tornado.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-windy-variant.svg b/awesome/src/assets/icons/weather/weather-windy-variant.svg new file mode 100644 index 0000000..9cacbea --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-windy-variant.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/src/assets/icons/weather/weather-windy.svg b/awesome/src/assets/icons/weather/weather-windy.svg new file mode 100644 index 0000000..f1f1a36 --- /dev/null +++ b/awesome/src/assets/icons/weather/weather-windy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome/mappings/bind_to_tags.lua b/awesome/src/bindings/bind_to_tags.lua similarity index 97% rename from awesome/mappings/bind_to_tags.lua rename to awesome/src/bindings/bind_to_tags.lua index ccba2c7..ebc6247 100644 --- a/awesome/mappings/bind_to_tags.lua +++ b/awesome/src/bindings/bind_to_tags.lua @@ -1,7 +1,7 @@ -- Awesome Libs local awful = require("awful") local gears = require("gears") -local globalkeys = require("../mappings/global_keys") +local globalkeys = require("src.bindings.global_keys") local modkey = user_vars.modkey for i = 1, 9 do diff --git a/awesome/mappings/client_buttons.lua b/awesome/src/bindings/client_buttons.lua similarity index 100% rename from awesome/mappings/client_buttons.lua rename to awesome/src/bindings/client_buttons.lua diff --git a/awesome/mappings/client_keys.lua b/awesome/src/bindings/client_keys.lua similarity index 100% rename from awesome/mappings/client_keys.lua rename to awesome/src/bindings/client_keys.lua diff --git a/awesome/mappings/global_buttons.lua b/awesome/src/bindings/global_buttons.lua similarity index 100% rename from awesome/mappings/global_buttons.lua rename to awesome/src/bindings/global_buttons.lua diff --git a/awesome/mappings/global_keys.lua b/awesome/src/bindings/global_keys.lua similarity index 100% rename from awesome/mappings/global_keys.lua rename to awesome/src/bindings/global_keys.lua diff --git a/awesome/src/core/error_handling.lua b/awesome/src/core/error_handling.lua index c1e0b25..07eae32 100644 --- a/awesome/src/core/error_handling.lua +++ b/awesome/src/core/error_handling.lua @@ -9,17 +9,17 @@ do awesome.connect_signal( "debug::error", function(err) - if in_error then - return - end - in_error = true + if in_error then + return + end + in_error = true - naughty.notify({ - preset = naughty.config.presets.critical, - title = "ERROR", - text = tostring(err) - }) - in_error = false - end + naughty.notify({ + preset = naughty.config.presets.critical, + title = "ERROR", + text = tostring(err) + }) + in_error = false + end ) end diff --git a/awesome/src/core/notifications.lua b/awesome/src/core/notifications.lua index 4580e9a..df1c2aa 100644 --- a/awesome/src/core/notifications.lua +++ b/awesome/src/core/notifications.lua @@ -10,6 +10,8 @@ local menubar = require('menubar') local naughty = require("naughty") local wibox = require("wibox") +local rubato = require("src.lib.rubato") + local icondir = awful.util.getdir("config") .. "src/assets/icons/notifications/" -- TODO: Figure out how to use hover effects without messing up the actions @@ -42,335 +44,343 @@ naughty.connect_signal( naughty.connect_signal( "request::display", function(n) - if n.urgency == "critical" then - n.title = string.format("%s", - color["RedA200"], n.title) or "" - n.message = string.format("%s", color["Red200"], n.message) or "" - n.app_name = string.format("%s", color["RedA400"], n.app_name) or "" - n.bg = color["Grey900"] + if user_vars.dnd then + n:destroy() else - n.title = string.format("%s", - color["Pink200"], n.title) or "" - n.message = string.format("%s", "#ffffffaa", n.message) or "" - n.bg = color["Grey900"] - n.timeout = n.timeout or 3 - end + if n.urgency == "critical" then + n.title = string.format("%s", + color["RedA200"], n.title) or "" + n.message = string.format("%s", color["Red200"], n.message) or "" + n.app_name = string.format("%s", color["RedA400"], n.app_name) or "" + n.bg = color["Grey900"] + else + n.title = string.format("%s", + color["Pink200"], n.title) or "" + n.message = string.format("%s", "#ffffffaa", n.message) or "" + n.bg = color["Grey900"] + n.timeout = n.timeout or 3 + end - local use_image = false + local use_image = false - if n.app_name == "Spotify" then - n.actions = { naughty.action { - program = "Spotify", - id = "skip-prev", - icon = gears.color.recolor_image(icondir .. "skip-prev.svg", color["Cyan200"]) - }, naughty.action { - program = "Spotify", - id = "play-pause", - icon = gears.color.recolor_image(icondir .. "play-pause.svg", color["Cyan200"]) - }, naughty.action { - program = "Spotify", - id = "skip-next", - icon = gears.color.recolor_image(icondir .. "skip-next.svg", color["Cyan200"]) - } } - use_image = true - end + if n.app_name == "Spotify" then + n.actions = { naughty.action { + program = "Spotify", + id = "skip-prev", + icon = gears.color.recolor_image(icondir .. "skip-prev.svg", color["Cyan200"]) + }, naughty.action { + program = "Spotify", + id = "play-pause", + icon = gears.color.recolor_image(icondir .. "play-pause.svg", color["Cyan200"]) + }, naughty.action { + program = "Spotify", + id = "skip-next", + icon = gears.color.recolor_image(icondir .. "skip-next.svg", color["Cyan200"]) + } } + use_image = true + end - local action_template_widget = {} + local action_template_widget = {} - if use_image then - action_template_widget = { - { - { - { - { - id = "icon_role", - widget = wibox.widget.imagebox - }, - id = "centered", - valign = "center", - halign = "center", - widget = wibox.container.place - }, - margins = dpi(5), - widget = wibox.container.margin - }, - forced_height = dpi(35), - forced_width = dpi(35), - fg = color["Cyan200"], - bg = color["Grey800"], - shape = function(cr, width, height) - gears.shape.rounded_rect(cr, width, height, dpi(6)) - end, - widget = wibox.container.background, - id = "bgrnd" - }, - id = "mrgn", - top = dpi(10), - bottom = dpi(10), - widget = wibox.container.margin - } - else - action_template_widget = { - { - { - { - { - id = "text_role", - font = "JetBrainsMono Nerd Font, Regular 12", - widget = wibox.widget.textbox - }, - id = "centered", - widget = wibox.container.place - }, - margins = dpi(5), - widget = wibox.container.margin - }, - fg = color["Green200"], - bg = color["Grey800"], - shape = function(cr, width, height) - gears.shape.rounded_rect(cr, width, height, dpi(6)) - end, - widget = wibox.container.background, - id = "bgrnd" - }, - id = "mrgn", - top = dpi(10), - bottom = dpi(10), - widget = wibox.container.margin - } - end - - local actions_template = wibox.widget { - notification = n, - base_layout = wibox.widget { - spacing = dpi(40), - layout = wibox.layout.fixed.horizontal - }, - widget_template = action_template_widget, - style = { - underline_normal = false, - underline_selected = true, - bg_normal = color["Grey100"], - bg_selected = color["Grey200"] - }, - widget = naughty.list.actions - } - - local w_template = wibox.widget { - { - { + if use_image then + action_template_widget = { { { { { - { - { - { - { - image = gears.color.recolor_image(icondir .. "notification-outline.svg", color["Teal200"]), - resize = false, - widget = wibox.widget.imagebox - }, - right = dpi(5), - widget = wibox.container.margin - }, - { - markup = n.app_name or 'System Notification', - align = "center", - valign = "center", - widget = wibox.widget.textbox - }, - layout = wibox.layout.fixed.horizontal - }, - fg = color["Teal200"], - widget = wibox.container.background - }, - margins = dpi(10), - widget = wibox.container.margin + id = "icon_role", + widget = wibox.widget.imagebox }, - nil, + id = "centered", + valign = "center", + halign = "center", + widget = wibox.container.place + }, + margins = dpi(5), + widget = wibox.container.margin + }, + forced_height = dpi(35), + forced_width = dpi(35), + fg = color["Cyan200"], + bg = color["Grey800"], + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(6)) + end, + widget = wibox.container.background, + id = "bgrnd" + }, + id = "mrgn", + top = dpi(10), + bottom = dpi(10), + widget = wibox.container.margin + } + else + action_template_widget = { + { + { + { + { + id = "text_role", + font = "JetBrainsMono Nerd Font, Regular 12", + widget = wibox.widget.textbox + }, + id = "centered", + widget = wibox.container.place + }, + margins = dpi(5), + widget = wibox.container.margin + }, + fg = color["Green200"], + bg = color["Grey800"], + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(6)) + end, + widget = wibox.container.background, + id = "bgrnd" + }, + id = "mrgn", + top = dpi(10), + bottom = dpi(10), + widget = wibox.container.margin + } + end + + local actions_template = wibox.widget { + notification = n, + base_layout = wibox.widget { + spacing = dpi(40), + layout = wibox.layout.fixed.horizontal + }, + widget_template = action_template_widget, + style = { + underline_normal = false, + underline_selected = true, + bg_normal = color["Grey100"], + bg_selected = color["Grey200"] + }, + widget = naughty.list.actions + } + + local w_template = wibox.widget { + { + { + { + { { - { - { - text = os.date("%H:%M"), - widget = wibox.widget.textbox - }, - id = "background", - fg = color["Teal200"], - widget = wibox.container.background - }, { { { { { - font = user_vars.font.specify .. ", 10", - text = "✕", - align = "center", - valign = "center", - widget = wibox.widget.textbox + image = gears.color.recolor_image(icondir .. "notification-outline.svg", color["Teal200"]), + resize = false, + widget = wibox.widget.imagebox }, - start_angle = 4.71239, - thickness = dpi(2), - min_value = 0, - max_value = 360, - value = 360, - widget = wibox.container.arcchart, - id = "arc_chart" + right = dpi(5), + widget = wibox.container.margin }, - id = "background", - fg = color["Teal200"], - bg = color["Grey900"], - widget = wibox.container.background + { + markup = n.app_name or 'System Notification', + align = "center", + valign = "center", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal }, - strategy = "exact", - width = dpi(20), - height = dpi(20), - widget = wibox.container.constraint, - id = "const" + fg = color["Teal200"], + widget = wibox.container.background }, margins = dpi(10), - widget = wibox.container.margin, - id = "arc_margin" + widget = wibox.container.margin }, - layout = wibox.layout.fixed.horizontal, - id = "arc_app_layout_2" + nil, + { + { + { + text = os.date("%H:%M"), + widget = wibox.widget.textbox + }, + id = "background", + fg = color["Teal200"], + widget = wibox.container.background + }, + { + { + { + { + { + font = user_vars.font.specify .. ", 10", + text = "✕", + align = "center", + valign = "center", + widget = wibox.widget.textbox + }, + start_angle = 4.71239, + thickness = dpi(2), + min_value = 0, + max_value = n.timeout or 10, + value = n.timeout or 10, + widget = wibox.container.arcchart, + id = "arc_chart" + }, + id = "background", + fg = color["Teal200"], + bg = color["Grey900"], + widget = wibox.container.background + }, + strategy = "exact", + width = dpi(20), + height = dpi(20), + widget = wibox.container.constraint, + id = "const" + }, + margins = dpi(10), + widget = wibox.container.margin, + id = "arc_margin" + }, + layout = wibox.layout.fixed.horizontal, + id = "arc_app_layout_2" + }, + id = "arc_app_layout", + layout = wibox.layout.align.horizontal }, - id = "arc_app_layout", - layout = wibox.layout.align.horizontal + id = "arc_app_bg", + border_color = color["Grey800"], + border_width = dpi(2), + widget = wibox.container.background }, - id = "arc_app_bg", - border_color = color["Grey800"], - border_width = dpi(2), - widget = wibox.container.background - }, - { { { { { - image = n.icon, - resize = true, - widget = wibox.widget.imagebox, - clip_shape = function(cr, width, height) - gears.shape.rounded_rect(cr, width, height, 10) - end + { + image = n.icon, + resize = true, + widget = wibox.widget.imagebox, + clip_shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 10) + end + }, + width = naughty.config.defaults.icon_size, + height = naughty.config.defaults.icon_size, + strategy = "exact", + widget = wibox.container.constraint }, - width = naughty.config.defaults.icon_size, - height = naughty.config.defaults.icon_size, - strategy = "exact", - widget = wibox.container.constraint - }, - halign = "center", - valign = "top", - widget = wibox.container.place - }, - left = dpi(20), - bottom = dpi(15), - top = dpi(15), - right = dpi(10), - widget = wibox.container.margin - }, - { - { - { - widget = naughty.widget.title, - align = "left" - }, - { - widget = naughty.widget.message, - align = "left" - }, - { - actions_template, + halign = "center", + valign = "top", widget = wibox.container.place }, - layout = wibox.layout.fixed.vertical + left = dpi(20), + bottom = dpi(15), + top = dpi(15), + right = dpi(10), + widget = wibox.container.margin }, - left = dpi(10), - bottom = dpi(10), - top = dpi(10), - right = dpi(20), - widget = wibox.container.margin + { + { + { + widget = naughty.widget.title, + align = "left" + }, + { + widget = naughty.widget.message, + align = "left" + }, + { + actions_template, + widget = wibox.container.place + }, + layout = wibox.layout.fixed.vertical + }, + left = dpi(10), + bottom = dpi(10), + top = dpi(10), + right = dpi(20), + widget = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal }, - layout = wibox.layout.fixed.horizontal + id = "widget_layout", + layout = wibox.layout.fixed.vertical }, - id = "widget_layout", - layout = wibox.layout.fixed.vertical + id = "min_size", + strategy = "min", + width = dpi(100), + widget = wibox.container.constraint }, - id = "min_size", - strategy = "min", - width = dpi(100), + id = "max_size", + strategy = "max", + width = Theme.notification_max_width or dpi(500), widget = wibox.container.constraint }, - id = "max_size", - strategy = "max", - width = Theme.notification_max_width or dpi(500), - widget = wibox.container.constraint - }, - id = "background", - bg = color["Grey900"], - border_color = color["Grey800"], - border_width = dpi(4), - shape = function(cr, width, height) - gears.shape.rounded_rect(cr, width, height, 4) - end, - widget = wibox.container.background - } - - local close = w_template.max_size.min_size.widget_layout.arc_app_bg.arc_app_layout.arc_app_layout_2.arc_margin.const.background - local arc = close.arc_chart - - local timeout = n.timeout - local remove_time = timeout - - if timeout ~= 0 then - arc.value = 360 - local arc_timer = gears.timer { - timeout = 0.1, - call_now = true, - autostart = true, - callback = function() - arc.value = (remove_time - 0) / (timeout - 0) * 360 - remove_time = remove_time - 0.1 - end + id = "background", + bg = color["Grey900"], + border_color = color["Grey800"], + border_width = dpi(4), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background } - w_template:connect_signal( - "mouse::enter", - function() - -- Setting to 0 doesn't work - arc_timer:stop() - n.timeout = 99999 - end - ) + local close = w_template.max_size.min_size.widget_layout.arc_app_bg.arc_app_layout.arc_app_layout_2.arc_margin.const.background + local arc = close.arc_chart - w_template:connect_signal( - "mouse::leave", - function() - arc_timer:start() - n.timeout = remove_time - end - ) - end + local timeout = n.timeout - Hover_signal(close, color["Grey900"], color["Teal200"]) + if timeout ~= 0 then - close:connect_signal( - "button::press", - function() - n:destroy() + local rubato_timer = rubato.timed { + duration = n.timeout, + pos = n.timeout, + easing = rubato.linear, + subscribed = function(value) + arc.value = value + end + } + + rubato_timer.target = 0 + + local last_position = n.timeout + + w_template:connect_signal( + "mouse::enter", + function() + n.timeout = 99999 + last_position = rubato_timer.pos + rubato_timer:abort() + end + ) + + w_template:connect_signal( + "mouse::leave", + function() + n.timeout = last_position + rubato_timer.pos = last_position + rubato_timer.duration = last_position + rubato_timer:reset() + rubato_timer.target = 0 + end + ) end - ) - w_template:connect_signal( - "button::press", - function(c, d, e, key) - if key == 3 then + Hover_signal(close, color["Grey900"], color["Teal200"]) + + close:connect_signal( + "button::press", + function() n:destroy() end - -- TODO: Find out how to get the associated client - --[[ if key == 1 then + ) + + w_template:connect_signal( + "button::press", + function(c, d, e, key) + if key == 3 then + n:destroy() + end + -- TODO: Find out how to get the associated client + --[[ if key == 1 then if n.clients then n.clients[1]:activate { switch_to_tag = true, @@ -378,22 +388,23 @@ naughty.connect_signal( } end end ]] - end - ) + end + ) - local box = naughty.layout.box { - notification = n, - timeout = 3, - type = "notification", - screen = awful.screen.focused(), - shape = function(cr, width, height) - gears.shape.rounded_rect(cr, width, height, 10) - end, - widget_template = w_template - } + local box = naughty.layout.box { + notification = n, + timeout = 3, + type = "notification", + screen = awful.screen.focused(), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 10) + end, + widget_template = w_template + } - box.buttons = {} - n.buttons = {} + box.buttons = {} + n.buttons = {} + end end ) @@ -405,13 +416,13 @@ naughty.connect_signal( ) -- Test notification ---[[naughty.notification { +--[[ naughty.notification { app_name = "System Notification", - title = "A notification 3", + title = "A notification 2", message = "This is very informative and overflowing", icon = "/home/crylia/.config/awesome/src/assets/userpfp/crylia.png", urgency = "normal", - timeout = 1, + timeout = 0, actions = { naughty.action { name = "Accept", @@ -423,7 +434,7 @@ naughty.connect_signal( name = "Ignore", }, } -}--]] +} ]] naughty.connect_signal( "invoked", diff --git a/awesome/src/core/rules.lua b/awesome/src/core/rules.lua index 8388f63..fe5106e 100644 --- a/awesome/src/core/rules.lua +++ b/awesome/src/core/rules.lua @@ -15,8 +15,8 @@ awful.rules.rules = { border_color = beautiful.border_normal, focus = awful.client.focus.filter, raise = true, - keys = require("../../mappings/client_keys"), - buttons = require("../../mappings/client_buttons"), + keys = require("src.bindings.client_keys"), + buttons = require("src.bindings.client_buttons"), screen = awful.screen.preferred, placement = awful.placement.no_overlap + awful.placement.no_offscreen } @@ -53,13 +53,13 @@ awful.rules.rules = { awful.spawn.easy_async_with_shell( "cat ~/.config/awesome/src/assets/rules.txt", function(stdout) - for class in stdout:gmatch("%a+") do - ruled.client.append_rule { - rule = { class = class }, - properties = { - floating = true - }, - } + for class in stdout:gmatch("%a+") do + ruled.client.append_rule { + rule = { class = class }, + properties = { + floating = true + }, + } + end end -end ) diff --git a/awesome/src/lib/json-lua/json-lua.lua b/awesome/src/lib/json-lua/json-lua.lua new file mode 100644 index 0000000..efc752e --- /dev/null +++ b/awesome/src/lib/json-lua/json-lua.lua @@ -0,0 +1,1554 @@ +-- -*- coding: utf-8 -*- +-- +-- Simple JSON encoding and decoding in pure Lua. +-- +-- Copyright 2010-2016 Jeffrey Friedl +-- http://regex.info/blog/ +-- Latest version: http://regex.info/blog/lua/json +-- +-- This code is released under a Creative Commons CC-BY "Attribution" License: +-- http://creativecommons.org/licenses/by/3.0/deed.en_US +-- +-- It can be used for any purpose so long as: +-- 1) the copyright notice above is maintained +-- 2) the web-page links above are maintained +-- 3) the 'AUTHOR_NOTE' string below is maintained +-- +local VERSION = '20161109.21' -- version history at end of file +local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20161109.21 ]-" + +-- +-- The 'AUTHOR_NOTE' variable exists so that information about the source +-- of the package is maintained even in compiled versions. It's also +-- included in OBJDEF below mostly to quiet warnings about unused variables. +-- +local OBJDEF = { + VERSION = VERSION, + AUTHOR_NOTE = AUTHOR_NOTE, +} + + +-- +-- Simple JSON encoding and decoding in pure Lua. +-- JSON definition: http://www.json.org/ +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- +-- +-- +-- DECODING (from a JSON string to a Lua table) +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- If the JSON text is for an object or an array, e.g. +-- { "what": "books", "count": 3 } +-- or +-- [ "Larry", "Curly", "Moe" ] +-- +-- the result is a Lua table, e.g. +-- { what = "books", count = 3 } +-- or +-- { "Larry", "Curly", "Moe" } +-- +-- +-- The encode and decode routines accept an optional second argument, +-- "etc", which is not used during encoding or decoding, but upon error +-- is passed along to error handlers. It can be of any type (including nil). +-- +-- +-- +-- ERROR HANDLING +-- +-- With most errors during decoding, this code calls +-- +-- JSON:onDecodeError(message, text, location, etc) +-- +-- with a message about the error, and if known, the JSON text being +-- parsed and the byte count where the problem was discovered. You can +-- replace the default JSON:onDecodeError() with your own function. +-- +-- The default onDecodeError() merely augments the message with data +-- about the text and the location if known (and if a second 'etc' +-- argument had been provided to decode(), its value is tacked onto the +-- message as well), and then calls JSON.assert(), which itself defaults +-- to Lua's built-in assert(), and can also be overridden. +-- +-- For example, in an Adobe Lightroom plugin, you might use something like +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- LrErrors.throwUserError("Internal Error: invalid JSON data") +-- end +-- +-- or even just +-- +-- function JSON.assert(message) +-- LrErrors.throwUserError("Internal Error: " .. message) +-- end +-- +-- If JSON:decode() is passed a nil, this is called instead: +-- +-- JSON:onDecodeOfNilError(message, nil, nil, etc) +-- +-- and if JSON:decode() is passed HTML instead of JSON, this is called: +-- +-- JSON:onDecodeOfHTMLError(message, text, nil, etc) +-- +-- The use of the fourth 'etc' argument allows stronger coordination +-- between decoding and error reporting, especially when you provide your +-- own error-handling routines. Continuing with the the Adobe Lightroom +-- plugin example: +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- local note = "Internal Error: invalid JSON data" +-- if type(etc) = 'table' and etc.photo then +-- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') +-- end +-- LrErrors.throwUserError(note) +-- end +-- +-- : +-- : +-- +-- for i, photo in ipairs(photosToProcess) do +-- : +-- : +-- local data = JSON:decode(someJsonText, { photo = photo }) +-- : +-- : +-- end +-- +-- +-- +-- If the JSON text passed to decode() has trailing garbage (e.g. as with the JSON "[123]xyzzy"), +-- the method +-- +-- JSON:onTrailingGarbage(json_text, location, parsed_value, etc) +-- +-- is invoked, where: +-- +-- json_text is the original JSON text being parsed, +-- location is the count of bytes into json_text where the garbage starts (6 in the example), +-- parsed_value is the Lua result of what was successfully parsed ({123} in the example), +-- etc is as above. +-- +-- If JSON:onTrailingGarbage() does not abort, it should return the value decode() should return, +-- or nil + an error message. +-- +-- local new_value, error_message = JSON:onTrailingGarbage() +-- +-- The default handler just invokes JSON:onDecodeError("trailing garbage"...), but you can have +-- this package ignore trailing garbage via +-- +-- function JSON:onTrailingGarbage(json_text, location, parsed_value, etc) +-- return parsed_value +-- end +-- +-- +-- DECODING AND STRICT TYPES +-- +-- Because both JSON objects and JSON arrays are converted to Lua tables, +-- it's not normally possible to tell which original JSON type a +-- particular Lua table was derived from, or guarantee decode-encode +-- round-trip equivalency. +-- +-- However, if you enable strictTypes, e.g. +-- +-- JSON = assert(loadfile "JSON.lua")() --load the routines +-- JSON.strictTypes = true +-- +-- then the Lua table resulting from the decoding of a JSON object or +-- JSON array is marked via Lua metatable, so that when re-encoded with +-- JSON:encode() it ends up as the appropriate JSON type. +-- +-- (This is not the default because other routines may not work well with +-- tables that have a metatable set, for example, Lightroom API calls.) +-- +-- +-- ENCODING (from a lua table to a JSON string) +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) +-- +-- On error during encoding, this code calls: +-- +-- JSON:onEncodeError(message, etc) +-- +-- which you can override in your local JSON object. +-- +-- The 'etc' in the error call is the second argument to encode() +-- and encode_pretty(), or nil if it wasn't provided. +-- +-- +-- ENCODING OPTIONS +-- +-- An optional third argument, a table of options, can be provided to encode(). +-- +-- encode_options = { +-- -- options for making "pretty" human-readable JSON (see "PRETTY-PRINTING" below) +-- pretty = true, +-- indent = " ", +-- align_keys = false, +-- array_newline = false, +-- +-- -- other output-related options +-- null = "\0", -- see "ENCODING JSON NULL VALUES" below +-- stringsAreUtf8 = false, -- see "HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA" below +-- } +-- +-- json_string = JSON:encode(mytable, etc, encode_options) +-- +-- +-- +-- For reference, the defaults are: +-- +-- pretty = false +-- null = nil, +-- stringsAreUtf8 = false, +-- array_newline = false, +-- +-- +-- +-- PRETTY-PRINTING +-- +-- Enabling the 'pretty' encode option helps generate human-readable JSON. +-- +-- pretty = JSON:encode(val, etc, { +-- pretty = true, +-- indent = " ", +-- align_keys = false, +-- }) +-- +-- encode_pretty() is also provided: it's identical to encode() except +-- that encode_pretty() provides a default options table if none given in the call: +-- +-- { pretty = true, align_keys = false, indent = " " } +-- +-- For example, if +-- +-- JSON:encode(data) +-- +-- produces: +-- +-- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} +-- +-- then +-- +-- JSON:encode_pretty(data) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- The following three lines return identical results: +-- JSON:encode_pretty(data) +-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " }) +-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " }) +-- +-- An example of setting your own indent string: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) +-- +-- produces: +-- +-- { +-- | "city": "Kyoto", +-- | "climate": { +-- | | "avg_temp": 16, +-- | | "humidity": "high", +-- | | "snowfall": "minimal" +-- | }, +-- | "country": "Japan", +-- | "wards": 11 +-- } +-- +-- An example of setting align_keys to true: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- which I must admit is kinda ugly, sorry. This was the default for +-- encode_pretty() prior to version 20141223.14. +-- +-- +-- HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA +-- +-- If the 'stringsAreUtf8' encode option is set to true, consider Lua strings not as a sequence of bytes, +-- but as a sequence of UTF-8 characters. +-- +-- Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH +-- separators, if found in a string, are encoded with a JSON escape instead of being dumped as is. +-- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON +-- to also be valid Java. +-- +-- AMBIGUOUS SITUATIONS DURING THE ENCODING +-- +-- During the encode, if a Lua table being encoded contains both string +-- and numeric keys, it fits neither JSON's idea of an object, nor its +-- idea of an array. To get around this, when any string key exists (or +-- when non-positive numeric keys exist), numeric keys are converted to +-- strings. +-- +-- For example, +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- produces the JSON object +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To prohibit this conversion and instead make it an error condition, set +-- JSON.noKeyConversion = true +-- +-- +-- ENCODING JSON NULL VALUES +-- +-- Lua tables completely omit keys whose value is nil, so without special handling there's +-- no way to get a field in a JSON object with a null value. For example +-- JSON:encode({ username = "admin", password = nil }) +-- produces +-- {"username":"admin"} +-- +-- In order to actually produce +-- {"username":"admin", "password":null} +-- one can include a string value for a "null" field in the options table passed to encode().... +-- any Lua table entry with that value becomes null in the JSON output: +-- JSON:encode({ username = "admin", password = "xyzzy" }, nil, { null = "xyzzy" }) +-- produces +-- {"username":"admin", "password":null} +-- +-- Just be sure to use a string that is otherwise unlikely to appear in your data. +-- The string "\0" (a string with one null byte) may well be appropriate for many applications. +-- +-- The "null" options also applies to Lua tables that become JSON arrays. +-- JSON:encode({ "one", "two", nil, nil }) +-- produces +-- ["one","two"] +-- while +-- NULL = "\0" +-- JSON:encode({ "one", "two", NULL, NULL}, nil, { null = NULL }) +-- produces +-- ["one","two",null,null] +-- +-- +-- +-- +-- HANDLING LARGE AND/OR PRECISE NUMBERS +-- +-- +-- Without special handling, numbers in JSON can lose precision in Lua. +-- For example: +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- produces +-- +-- small: number 12345 +-- big: number 1.2345678901235e+28 +-- precise: number 9876.6789012346 +-- +-- Precision is lost with both 'big' and 'precise'. +-- +-- This package offers ways to try to handle this better (for some definitions of "better")... +-- +-- The most precise method is by setting the global: +-- +-- JSON.decodeNumbersAsObjects = true +-- +-- When this is set, numeric JSON data is encoded into Lua in a form that preserves the exact +-- JSON numeric presentation when re-encoded back out to JSON, or accessed in Lua as a string. +-- +-- (This is done by encoding the numeric data with a Lua table/metatable that returns +-- the possibly-imprecise numeric form when accessed numerically, but the original precise +-- representation when accessed as a string. You can also explicitly access +-- via JSON:forceString() and JSON:forceNumber()) +-- +-- Consider the example above, with this option turned on: +-- +-- JSON.decodeNumbersAsObjects = true +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- This now produces: +-- +-- small: table 12345 +-- big: table 12345678901234567890123456789 +-- precise: table 9876.67890123456789012345 +-- +-- However, within Lua you can still use the values (e.g. T.precise in the example above) in numeric +-- contexts. In such cases you'll get the possibly-imprecise numeric version, but in string contexts +-- and when the data finds its way to this package's encode() function, the original full-precision +-- representation is used. +-- +-- Even without using the JSON.decodeNumbersAsObjects option, you can encode numbers +-- in your Lua table that retain high precision upon encoding to JSON, by using the JSON:asNumber() +-- function: +-- +-- T = { +-- imprecise = 123456789123456789.123456789123456789, +-- precise = JSON:asNumber("123456789123456789.123456789123456789") +-- } +-- +-- print(JSON:encode_pretty(T)) +-- +-- This produces: +-- +-- { +-- "precise": 123456789123456789.123456789123456789, +-- "imprecise": 1.2345678912346e+17 +-- } +-- +-- +-- +-- A different way to handle big/precise JSON numbers is to have decode() merely return +-- the exact string representation of the number instead of the number itself. +-- This approach might be useful when the numbers are merely some kind of opaque +-- object identifier and you want to work with them in Lua as strings anyway. +-- +-- This approach is enabled by setting +-- +-- JSON.decodeIntegerStringificationLength = 10 +-- +-- The value is the number of digits (of the integer part of the number) at which to stringify numbers. +-- +-- Consider our previous example with this option set to 10: +-- +-- JSON.decodeIntegerStringificationLength = 10 +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- This produces: +-- +-- small: number 12345 +-- big: string 12345678901234567890123456789 +-- precise: number 9876.6789012346 +-- +-- The long integer of the 'big' field is at least JSON.decodeIntegerStringificationLength digits +-- in length, so it's converted not to a Lua integer but to a Lua string. Using a value of 0 or 1 ensures +-- that all JSON numeric data becomes strings in Lua. +-- +-- Note that unlike +-- JSON.decodeNumbersAsObjects = true +-- this stringification is simple and unintelligent: the JSON number simply becomes a Lua string, and that's the end of it. +-- If the string is then converted back to JSON, it's still a string. After running the code above, adding +-- print(JSON:encode(T)) +-- produces +-- {"big":"12345678901234567890123456789","precise":9876.6789012346,"small":12345} +-- which is unlikely to be desired. +-- +-- There's a comparable option for the length of the decimal part of a number: +-- +-- JSON.decodeDecimalStringificationLength +-- +-- This can be used alone or in conjunction with +-- +-- JSON.decodeIntegerStringificationLength +-- +-- to trip stringification on precise numbers with at least JSON.decodeIntegerStringificationLength digits after +-- the decimal point. +-- +-- This example: +-- +-- JSON.decodeIntegerStringificationLength = 10 +-- JSON.decodeDecimalStringificationLength = 5 +-- +-- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') +-- +-- print("small: ", type(T.small), T.small) +-- print("big: ", type(T.big), T.big) +-- print("precise: ", type(T.precise), T.precise) +-- +-- produces: +-- +-- small: number 12345 +-- big: string 12345678901234567890123456789 +-- precise: string 9876.67890123456789012345 +-- +-- +-- +-- +-- +-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT +-- +-- assert +-- onDecodeError +-- onDecodeOfNilError +-- onDecodeOfHTMLError +-- onTrailingGarbage +-- onEncodeError +-- +-- If you want to create a separate Lua JSON object with its own error handlers, +-- you can reload JSON.lua or use the :new() method. +-- +--------------------------------------------------------------------------- + +local default_pretty_indent = " " +local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } + +local isArray = { __tostring = function() return "JSON array" end } +isArray.__index = isArray +local isObject = { __tostring = function() return "JSON object" end } +isObject.__index = isObject + +function OBJDEF:newArray(tbl) + return setmetatable(tbl or {}, isArray) +end + +function OBJDEF:newObject(tbl) + return setmetatable(tbl or {}, isObject) +end + +local function getnum(op) + return type(op) == 'number' and op or op.N +end + +local isNumber = { + __tostring = function(T) return T.S end, + __unm = function(op) return getnum(op) end, + + __concat = function(op1, op2) return tostring(op1) .. tostring(op2) end, + __add = function(op1, op2) return getnum(op1) + getnum(op2) end, + __sub = function(op1, op2) return getnum(op1) - getnum(op2) end, + __mul = function(op1, op2) return getnum(op1) * getnum(op2) end, + __div = function(op1, op2) return getnum(op1) / getnum(op2) end, + __mod = function(op1, op2) return getnum(op1) % getnum(op2) end, + __pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end, + __lt = function(op1, op2) return getnum(op1) < getnum(op2) end, + __eq = function(op1, op2) return getnum(op1) == getnum(op2) end, + __le = function(op1, op2) return getnum(op1) <= getnum(op2) end, +} +isNumber.__index = isNumber + +function OBJDEF:asNumber(item) + + if getmetatable(item) == isNumber then + -- it's already a JSON number object. + return item + elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then + -- it's a number-object table that lost its metatable, so give it one + return setmetatable(item, isNumber) + else + -- the normal situation... given a number or a string representation of a number.... + local holder = { + S = tostring(item), -- S is the representation of the number as a string, which remains precise + N = tonumber(item), -- N is the number as a Lua number. + } + return setmetatable(holder, isNumber) + end +end + +-- +-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above, +-- return the string version. This shouldn't be needed often because the 'isNumber' object should autoconvert +-- to a string in most cases, but it's here to allow it to be forced when needed. +-- +function OBJDEF:forceString(item) + if type(item) == 'table' and type(item.S) == 'string' then + return item.S + else + return tostring(item) + end +end + +-- +-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above, +-- return the numeric version. +-- +function OBJDEF:forceNumber(item) + if type(item) == 'table' and type(item.N) == 'number' then + return item.N + else + return tonumber(item) + end +end + +local function unicode_codepoint_as_utf8(codepoint) + -- + -- codepoint is a number + -- + if codepoint <= 127 then + return string.char(codepoint) + + elseif codepoint <= 2047 then + -- + -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 + -- + local highpart = math.floor(codepoint / 0x40) + local lowpart = codepoint - (0x40 * highpart) + return string.char(0xC0 + highpart, + 0x80 + lowpart) + + elseif codepoint <= 65535 then + -- + -- 1110yyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x1000) + local remainder = codepoint - 0x1000 * highpart + local midpart = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midpart + + highpart = 0xE0 + highpart + midpart = 0x80 + midpart + lowpart = 0x80 + lowpart + + -- + -- Check for an invalid character (thanks Andy R. at Adobe). + -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 + -- + if (highpart == 0xE0 and midpart < 0xA0) or + (highpart == 0xED and midpart > 0x9F) or + (highpart == 0xF0 and midpart < 0x90) or + (highpart == 0xF4 and midpart > 0x8F) + then + return "?" + else + return string.char(highpart, + midpart, + lowpart) + end + + else + -- + -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x40000) + local remainder = codepoint - 0x40000 * highpart + local midA = math.floor(remainder / 0x1000) + remainder = remainder - 0x1000 * midA + local midB = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midB + + return string.char(0xF0 + highpart, + 0x80 + midA, + 0x80 + midB, + 0x80 + lowpart) + end +end + +function OBJDEF:onDecodeError(message, text, location, etc) + if text then + if location then + message = string.format("%s at byte %d of: %s", message, location, text) + else + message = string.format("%s: %s", message, text) + end + end + + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +function OBJDEF:onTrailingGarbage(json_text, location, parsed_value, etc) + return self:onDecodeError("trailing garbage", json_text, location, etc) +end + +OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError +OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError + +function OBJDEF:onEncodeError(message, etc) + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +local function grok_number(self, text, start, options) + -- + -- Grab the integer part + -- + local integer_part = text:match('^-?[1-9]%d*', start) + or text:match("^-?0", start) + + if not integer_part then + self:onDecodeError("expected number", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = start + integer_part:len() + + -- + -- Grab an optional decimal part + -- + local decimal_part = text:match('^%.%d+', i) or "" + + i = i + decimal_part:len() + + -- + -- Grab an optional exponential part + -- + local exponent_part = text:match('^[eE][-+]?%d+', i) or "" + + i = i + exponent_part:len() + + local full_number_text = integer_part .. decimal_part .. exponent_part + + if options.decodeNumbersAsObjects then + return OBJDEF:asNumber(full_number_text), i + end + + -- + -- If we're told to stringify under certain conditions, so do. + -- We punt a bit when there's an exponent by just stringifying no matter what. + -- I suppose we should really look to see whether the exponent is actually big enough one + -- way or the other to trip stringification, but I'll be lazy about it until someone asks. + -- + if (options.decodeIntegerStringificationLength + and + (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)) + + or + + (options.decodeDecimalStringificationLength + and + (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)) + then + return full_number_text, i -- this returns the exact string representation seen in the original JSON + end + + + + local as_number = tonumber(full_number_text) + + if not as_number then + self:onDecodeError("bad number", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + return as_number, i +end + +local function grok_string(self, text, start, options) + + if text:sub(start, start) ~= '"' then + self:onDecodeError("expected string's opening quote", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = start + 1 -- +1 to bypass the initial quote + local text_len = text:len() + local VALUE = "" + while i <= text_len do + local c = text:sub(i, i) + if c == '"' then + return VALUE, i + 1 + end + if c ~= '\\' then + VALUE = VALUE .. c + i = i + 1 + elseif text:match('^\\b', i) then + VALUE = VALUE .. "\b" + i = i + 2 + elseif text:match('^\\f', i) then + VALUE = VALUE .. "\f" + i = i + 2 + elseif text:match('^\\n', i) then + VALUE = VALUE .. "\n" + i = i + 2 + elseif text:match('^\\r', i) then + VALUE = VALUE .. "\r" + i = i + 2 + elseif text:match('^\\t', i) then + VALUE = VALUE .. "\t" + i = i + 2 + else + local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if hex then + i = i + 6 -- bypass what we just read + + -- We have a Unicode codepoint. It could be standalone, or if in the proper range and + -- followed by another in a specific range, it'll be a two-code surrogate pair. + local codepoint = tonumber(hex, 16) + if codepoint >= 0xD800 and codepoint <= 0xDBFF then + -- it's a hi surrogate... see whether we have a following low + local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if lo_surrogate then + i = i + 6 -- bypass the low surrogate we just read + codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) + else + -- not a proper low, so we'll just leave the first codepoint as is and spit it out. + end + end + VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) + + else + + -- just pass through what's escaped + VALUE = VALUE .. text:match('^\\(.)', i) + i = i + 2 + end + end + end + + self:onDecodeError("unclosed string", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible +end + +local function skip_whitespace(text, start) + + local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 + if match_end then + return match_end + 1 + else + return start + end +end + +local grok_one -- assigned later + +local function grok_object(self, text, start, options) + + if text:sub(start, start) ~= '{' then + self:onDecodeError("expected '{'", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' + + local VALUE = self.strictTypes and self:newObject {} or {} + + if text:sub(i, i) == '}' then + return VALUE, i + 1 + end + local text_len = text:len() + while i <= text_len do + local key, new_i = grok_string(self, text, i, options) + + i = skip_whitespace(text, new_i) + + if text:sub(i, i) ~= ':' then + self:onDecodeError("expected colon", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + + i = skip_whitespace(text, i + 1) + + local new_val, new_i = grok_one(self, text, i, options) + + VALUE[key] = new_val + + -- + -- Expect now either '}' to end things, or a ',' to allow us to continue. + -- + i = skip_whitespace(text, new_i) + + local c = text:sub(i, i) + + if c == '}' then + return VALUE, i + 1 + end + + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '}'", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + + i = skip_whitespace(text, i + 1) + end + + self:onDecodeError("unclosed '{'", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible +end + +local function grok_array(self, text, start, options) + if text:sub(start, start) ~= '[' then + self:onDecodeError("expected '['", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' + local VALUE = self.strictTypes and self:newArray {} or {} + if text:sub(i, i) == ']' then + return VALUE, i + 1 + end + + local VALUE_INDEX = 1 + + local text_len = text:len() + while i <= text_len do + local val, new_i = grok_one(self, text, i, options) + + -- can't table.insert(VALUE, val) here because it's a no-op if val is nil + VALUE[VALUE_INDEX] = val + VALUE_INDEX = VALUE_INDEX + 1 + + i = skip_whitespace(text, new_i) + + -- + -- Expect now either ']' to end things, or a ',' to allow us to continue. + -- + local c = text:sub(i, i) + if c == ']' then + return VALUE, i + 1 + end + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or ']'", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + i = skip_whitespace(text, i + 1) + end + self:onDecodeError("unclosed '['", text, start, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible +end + +grok_one = function(self, text, start, options) + -- Skip any whitespace + start = skip_whitespace(text, start) + + if start > text:len() then + self:onDecodeError("unexpected end of string", text, nil, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end + + if text:find('^"', start) then + return grok_string(self, text, start, options.etc) + + elseif text:find('^[-0123456789 ]', start) then + return grok_number(self, text, start, options) + + elseif text:find('^%{', start) then + return grok_object(self, text, start, options) + + elseif text:find('^%[', start) then + return grok_array(self, text, start, options) + + elseif text:find('^true', start) then + return true, start + 4 + + elseif text:find('^false', start) then + return false, start + 5 + + elseif text:find('^null', start) then + return nil, start + 4 + + else + self:onDecodeError("can't parse JSON", text, start, options.etc) + return nil, 1 -- in case the error method doesn't abort, return something sensible + end +end + +function OBJDEF:decode(text, etc, options) + -- + -- If the user didn't pass in a table of decode options, make an empty one. + -- + if type(options) ~= 'table' then + options = {} + end + + -- + -- If they passed in an 'etc' argument, stuff it into the options. + -- (If not, any 'etc' field in the options they passed in remains to be used) + -- + if etc ~= nil then + options.etc = etc + end + + + if type(self) ~= 'table' or self.__index ~= OBJDEF then + local error_message = "JSON:decode must be called in method format" + OBJDEF:onDecodeError(error_message, nil, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + if text == nil then + local error_message = "nil passed to JSON:decode()" + self:onDecodeOfNilError(error_message, nil, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + + elseif type(text) ~= 'string' then + local error_message = "expected string argument to JSON:decode()" + self:onDecodeError(string.format("%s, got %s", error_message, type(text)), nil, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + if text:match('^%s*$') then + -- an empty string is nothing, but not an error + return nil + end + + if text:match('^%s*<') then + -- Can't be JSON... we'll assume it's HTML + local error_message = "HTML passed to JSON:decode()" + self:onDecodeOfHTMLError(error_message, text, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + -- + -- Ensure that it's not UTF-32 or UTF-16. + -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), + -- but this package can't handle them. + -- + if text:sub(1, 1):byte() == 0 or (text:len() >= 2 and text:sub(2, 2):byte() == 0) then + local error_message = "JSON package groks only UTF-8, sorry" + self:onDecodeError(error_message, text, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end + + -- + -- apply global options + -- + if options.decodeNumbersAsObjects == nil then + options.decodeNumbersAsObjects = self.decodeNumbersAsObjects + end + if options.decodeIntegerStringificationLength == nil then + options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength + end + if options.decodeDecimalStringificationLength == nil then + options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength + end + + -- + -- Finally, go parse it + -- + local success, value, next_i = pcall(grok_one, self, text, 1, options) + + if success then + + local error_message = nil + if next_i ~= #text + 1 then + -- something's left over after we parsed the first thing.... whitespace is allowed. + next_i = skip_whitespace(text, next_i) + + -- if we have something left over now, it's trailing garbage + if next_i ~= #text + 1 then + value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc) + end + end + return value, error_message + + else + + -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received + -- the error message here as "value", so pass it along as an assert. + local error_message = value + if self.assert then + self.assert(false, error_message) + else + assert(false, error_message) + end + -- ...and if we're still here (because the assert didn't throw an error), + -- return a nil and throw the error message on as a second arg + return nil, error_message + + end +end + +local function backslash_replacement_function(c) + if c == "\n" then + return "\\n" + elseif c == "\r" then + return "\\r" + elseif c == "\t" then + return "\\t" + elseif c == "\b" then + return "\\b" + elseif c == "\f" then + return "\\f" + elseif c == '"' then + return '\\"' + elseif c == '\\' then + return '\\\\' + else + return string.format("\\u%04x", c:byte()) + end +end + +local chars_to_be_escaped_in_JSON_string = '[' + .. '"' -- class sub-pattern to match a double quote + .. '%\\' -- class sub-pattern to match a backslash + .. '%z' -- class sub-pattern to match a null + .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters + .. ']' + + +local LINE_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2028) +local PARAGRAPH_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2029) +local function json_string_literal(value, options) + local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) + if options.stringsAreUtf8 then + -- + -- This feels really ugly to just look into a string for the sequence of bytes that we know to be a particular utf8 character, + -- but utf8 was designed purposefully to make this kind of thing possible. Still, feels dirty. + -- I'd rather decode the byte stream into a character stream, but it's not technically needed so + -- not technically worth it. + -- + newval = newval:gsub(LINE_SEPARATOR_as_utf8, '\\u2028'):gsub(PARAGRAPH_SEPARATOR_as_utf8, '\\u2029') + end + return '"' .. newval .. '"' +end + +local function object_or_array(self, T, etc) + -- + -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON + -- object. If there are only numbers, it's a JSON array. + -- + -- If we'll be converting to a JSON object, we'll want to sort the keys so that the + -- end result is deterministic. + -- + local string_keys = {} + local number_keys = {} + local number_keys_must_be_strings = false + local maximum_number_key + + for key in pairs(T) do + if type(key) == 'string' then + table.insert(string_keys, key) + elseif type(key) == 'number' then + table.insert(number_keys, key) + if key <= 0 or key >= math.huge then + number_keys_must_be_strings = true + elseif not maximum_number_key or key > maximum_number_key then + maximum_number_key = key + end + else + self:onEncodeError("can't encode table with a key of type " .. type(key), etc) + end + end + + if #string_keys == 0 and not number_keys_must_be_strings then + -- + -- An empty table, or a numeric-only array + -- + if #number_keys > 0 then + return nil, maximum_number_key -- an array + elseif tostring(T) == "JSON array" then + return nil + elseif tostring(T) == "JSON object" then + return {} + else + -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects + return nil + end + end + + table.sort(string_keys) + + local map + if #number_keys > 0 then + -- + -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array + -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. + -- + + if self.noKeyConversion then + self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) + end + + -- + -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings + -- + map = {} + for key, val in pairs(T) do + map[key] = val + end + + table.sort(number_keys) + + -- + -- Throw numeric keys in there as strings + -- + for _, number_key in ipairs(number_keys) do + local string_key = tostring(number_key) + if map[string_key] == nil then + table.insert(string_keys, string_key) + map[string_key] = T[number_key] + else + self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) + end + end + end + + return string_keys, nil, map +end + +-- +-- Encode +-- +-- 'options' is nil, or a table with possible keys: +-- +-- pretty -- If true, return a pretty-printed version. +-- +-- indent -- A string (usually of spaces) used to indent each nested level. +-- +-- align_keys -- If true, align all the keys when formatting a table. +-- +-- null -- If this exists with a string value, table elements with this value are output as JSON null. +-- +-- stringsAreUtf8 -- If true, consider Lua strings not as a sequence of bytes, but as a sequence of UTF-8 characters. +-- (Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH +-- separators, if found in a string, are encoded with a JSON escape instead of as raw UTF-8. +-- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON +-- to also be valid Java.) +-- +-- +local encode_value -- must predeclare because it calls itself +function encode_value(self, value, parents, etc, options, indent, for_key) + + -- + -- keys in a JSON object can never be null, so we don't even consider options.null when converting a key value + -- + if value == nil or (not for_key and options and options.null and value == options.null) then + return 'null' + + elseif type(value) == 'string' then + return json_string_literal(value, options) + + elseif type(value) == 'number' then + if value ~= value then + -- + -- NaN (Not a Number). + -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. + -- + return "null" + elseif value >= math.huge then + -- + -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should + -- really be a package option. Note: at least with some implementations, positive infinity + -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. + -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" + -- case first. + -- + return "1e+9999" + elseif value <= -math.huge then + -- + -- Negative infinity. + -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. + -- + return "-1e+9999" + else + return tostring(value) + end + + elseif type(value) == 'boolean' then + return tostring(value) + + elseif type(value) ~= 'table' then + self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) + + elseif getmetatable(value) == isNumber then + return tostring(value) + else + -- + -- A table to be converted to either a JSON object or array. + -- + local T = value + + if type(options) ~= 'table' then + options = {} + end + if type(indent) ~= 'string' then + indent = "" + end + + if parents[T] then + self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) + else + parents[T] = true + end + + local result_value + + local object_keys, maximum_number_key, map = object_or_array(self, T, etc) + if maximum_number_key then + -- + -- An array... + -- + local ITEMS = {} + local key_indent = indent .. tostring(options.indent or "") + for i = 1, maximum_number_key do + if not options.array_newline then + table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) + else + table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, key_indent)) + end + end + + if options.pretty then + if not options.array_newline then + result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" + else + result_value = "[\n" .. key_indent .. table.concat(ITEMS, ",\n" .. key_indent) .. "\n" .. indent .. "]" + end + else + result_value = "[" .. table.concat(ITEMS, ",") .. "]" + end + + elseif object_keys then + -- + -- An object + -- + local TT = map or T + + if options.pretty then + + local KEYS = {} + local max_key_length = 0 + for _, key in ipairs(object_keys) do + local encoded = encode_value(self, tostring(key), parents, etc, options, indent, true) + if options.align_keys then + max_key_length = math.max(max_key_length, #encoded) + end + table.insert(KEYS, encoded) + end + local key_indent = indent .. tostring(options.indent or "") + local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") + local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" + + local COMBINED_PARTS = {} + for i, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) + table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) + end + result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" + + else + + local PARTS = {} + for _, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) + local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent, true) + table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) + end + result_value = "{" .. table.concat(PARTS, ",") .. "}" + + end + else + -- + -- An empty array/object... we'll treat it as an array, though it should really be an option + -- + result_value = "[]" + end + + parents[T] = false + return result_value + end +end + +local function top_level_encode(self, value, etc, options) + local val = encode_value(self, value, {}, etc, options) + if val == nil then + --PRIVATE("may need to revert to the previous public verison if I can't figure out what the guy wanted") + return val + else + return val + end +end + +function OBJDEF:encode(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) + end + + -- + -- If the user didn't pass in a table of decode options, make an empty one. + -- + if type(options) ~= 'table' then + options = {} + end + + return top_level_encode(self, value, etc, options) +end + +function OBJDEF:encode_pretty(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) + end + + -- + -- If the user didn't pass in a table of decode options, use the default pretty ones + -- + if type(options) ~= 'table' then + options = default_pretty_options + end + + return top_level_encode(self, value, etc, options) +end + +function OBJDEF.__tostring() + return "JSON encode/decode package" +end + +OBJDEF.__index = OBJDEF + +function OBJDEF:new(args) + local new = {} + + if args then + for key, val in pairs(args) do + new[key] = val + end + end + + return setmetatable(new, OBJDEF) +end + +return OBJDEF:new() + +-- +-- Version history: +-- +-- 20161109.21 Oops, had a small boo-boo in the previous update. +-- +-- 20161103.20 Used to silently ignore trailing garbage when decoding. Now fails via JSON:onTrailingGarbage() +-- http://seriot.ch/parsing_json.php +-- +-- Built-in error message about "expected comma or ']'" had mistakenly referred to '[' +-- +-- Updated the built-in error reporting to refer to bytes rather than characters. +-- +-- The decode() method no longer assumes that error handlers abort. +-- +-- Made the VERSION string a string instead of a number +-- + +-- 20160916.19 Fixed the isNumber.__index assignment (thanks to Jack Taylor) +-- +-- 20160730.18 Added JSON:forceString() and JSON:forceNumber() +-- +-- 20160728.17 Added concatenation to the metatable for JSON:asNumber() +-- +-- 20160709.16 Could crash if not passed an options table (thanks jarno heikkinen ). +-- +-- Made JSON:asNumber() a bit more resilient to being passed the results of itself. +-- +-- 20160526.15 Added the ability to easily encode null values in JSON, via the new "null" encoding option. +-- (Thanks to Adam B for bringing up the issue.) +-- +-- Added some support for very large numbers and precise floats via +-- JSON.decodeNumbersAsObjects +-- JSON.decodeIntegerStringificationLength +-- JSON.decodeDecimalStringificationLength +-- +-- Added the "stringsAreUtf8" encoding option. (Hat tip to http://lua-users.org/wiki/JsonModules ) +-- +-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really +-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines +-- more flexible, and changed the default encode_pretty() to be more generally useful. +-- +-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control +-- how the encoding takes place. +-- +-- Updated docs to add assert() call to the loadfile() line, just as good practice so that +-- if there is a problem loading JSON.lua, the appropriate error message will percolate up. +-- +-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, +-- so that the source of the package, and its version number, are visible in compiled copies. +-- +-- 20140911.12 Minor lua cleanup. +-- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. +-- (Thanks to SmugMug's David Parry for these.) +-- +-- 20140418.11 JSON nulls embedded within an array were being ignored, such that +-- ["1",null,null,null,null,null,"seven"], +-- would return +-- {1,"seven"} +-- It's now fixed to properly return +-- {1, nil, nil, nil, nil, nil, "seven"} +-- Thanks to "haddock" for catching the error. +-- +-- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. +-- +-- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", +-- and this caused some problems. +-- +-- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, +-- and had of course diverged (encode_pretty didn't get the fixes that encode got, so +-- sometimes produced incorrect results; thanks to Mattie for the heads up). +-- +-- Handle encoding tables with non-positive numeric keys (unlikely, but possible). +-- +-- If a table has both numeric and string keys, or its numeric keys are inappropriate +-- (such as being non-positive or infinite), the numeric keys are turned into +-- string keys appropriate for a JSON object. So, as before, +-- JSON:encode({ "one", "two", "three" }) +-- produces the array +-- ["one","two","three"] +-- but now something with mixed key types like +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- instead of throwing an error produces an object: +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To maintain the prior throw-an-error semantics, set +-- JSON.noKeyConversion = true +-- +-- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. +-- +-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can +-- be found, so that folks who come across the code outside of my blog can find updates +-- more easily. +-- +-- 20111207.5 Added support for the 'etc' arguments, for better error reporting. +-- +-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. +-- +-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: +-- +-- * When encoding lua for JSON, Sparse numeric arrays are now handled by +-- spitting out full arrays, such that +-- JSON:encode({"one", "two", [10] = "ten"}) +-- returns +-- ["one","two",null,null,null,null,null,null,null,"ten"] +-- +-- In 20100810.2 and earlier, only up to the first non-null value would have been retained. +-- +-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". +-- Version 20100810.2 and earlier created invalid JSON in both cases. +-- +-- * Unicode surrogate pairs are now detected when decoding JSON. +-- +-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding +-- +-- 20100731.1 initial public release +-- diff --git a/awesome/src/lib/rubato b/awesome/src/lib/rubato new file mode 160000 index 0000000..7ed12e1 --- /dev/null +++ b/awesome/src/lib/rubato @@ -0,0 +1 @@ +Subproject commit 7ed12e183583a7ce3b59714452217af9a1f02ce6 diff --git a/awesome/src/modules/brightness_osd.lua b/awesome/src/modules/brightness_osd.lua index 728eef1..d90730b 100644 --- a/awesome/src/modules/brightness_osd.lua +++ b/awesome/src/modules/brightness_osd.lua @@ -17,10 +17,10 @@ BACKLIGHT_SEPS = 0 awful.spawn.easy_async_with_shell( "pkexec xfpm-power-backlight-helper --get-max-brightness", function(stdout) - BACKLIGHT_MAX_BRIGHTNESS = tonumber(stdout) - BACKLIGHT_SEPS = BACKLIGHT_MAX_BRIGHTNESS / 100 - BACKLIGHT_SEPS = math.floor(BACKLIGHT_SEPS) -end + BACKLIGHT_MAX_BRIGHTNESS = tonumber(stdout) + BACKLIGHT_SEPS = BACKLIGHT_MAX_BRIGHTNESS / 100 + BACKLIGHT_SEPS = math.floor(BACKLIGHT_SEPS) + end ) return function(s) @@ -112,60 +112,61 @@ return function(s) brightness_osd_widget.container.osd_layout.icon_slider_layout.slider_layout.brightness_slider:connect_signal( "property::value", function() - awful.spawn.easy_async_with_shell( - "pkexec xfpm-power-backlight-helper --get-brightness", - function(stdout) - local brightness_value = math.floor((tonumber(stdout) - 1) / (BACKLIGHT_MAX_BRIGHTNESS - 1) * 100) - brightness_osd_widget.container.osd_layout.icon_slider_layout.label_value_layout.value:set_text(tostring(brightness_value) .. "%") + awful.spawn.easy_async_with_shell( + "pkexec xfpm-power-backlight-helper --get-brightness", + function(stdout) + local brightness_value = math.floor((tonumber(stdout) - 1) / (BACKLIGHT_MAX_BRIGHTNESS - 1) * 100) + brightness_osd_widget.container.osd_layout.icon_slider_layout.label_value_layout.value:set_text(tostring(brightness_value) .. "%") - awesome.emit_signal( - "widget::brightness:update", - brightness_value + awesome.emit_signal( + "widget::brightness:update", + brightness_value + ) + + if awful.screen.focused().show_brightness_osd then + awesome.emit_signal( + "module::brightness_osd:show", + true + ) + end + + local icon = icondir .. "brightness" + if brightness_value >= 0 and brightness_value < 34 then + icon = icon .. "-low" + elseif brightness_value >= 34 and brightness_value < 67 then + icon = icon .. "-medium" + elseif brightness_value >= 67 then + icon = icon .. "-high" + end + brightness_osd_widget.container.osd_layout.icon_slider_layout.icon_margin1.icon_margin2.icon:set_image(icon .. ".svg") + awesome.emit_signal("update::backlight_widget", brightness_value, icon .. ".svg") + end ) - - if awful.screen.focused().show_brightness_osd then - awesome.emit_signal( - "module::brightness_osd:show", - true - ) - end - - local icon = icondir .. "brightness" - if brightness_value >= 0 and brightness_value < 34 then - icon = icon .. "-low" - elseif brightness_value >= 34 and brightness_value < 67 then - icon = icon .. "-medium" - elseif brightness_value >= 67 then - icon = icon .. "-high" - end - brightness_osd_widget.container.osd_layout.icon_slider_layout.icon_margin1.icon_margin2.icon:set_image(icon .. ".svg") end - ) - end ) local update_slider = function() awful.spawn.easy_async_with_shell( [[ pkexec xfpm-power-backlight-helper --get-brightness ]], function(stdout) - stdout = math.floor((tonumber(stdout) - 1) / (BACKLIGHT_MAX_BRIGHTNESS - 1) * 100) - brightness_osd_widget.container.osd_layout.icon_slider_layout.slider_layout.brightness_slider:set_value(stdout) - end + stdout = math.floor((tonumber(stdout) - 1) / (BACKLIGHT_MAX_BRIGHTNESS - 1) * 100) + brightness_osd_widget.container.osd_layout.icon_slider_layout.slider_layout.brightness_slider:set_value(stdout) + end ) end awesome.connect_signal( "module::brightness_slider:update", function() - update_slider() - end + update_slider() + end ) awesome.connect_signal( "widget::brightness:update", function(value) - brightness_osd_widget.container.osd_layout.icon_slider_layout.slider_layout.brightness_slider:set_value(tonumber(value)) - end + brightness_osd_widget.container.osd_layout.icon_slider_layout.slider_layout.brightness_slider:set_value(tonumber(value)) + end ) update_slider() @@ -199,36 +200,36 @@ return function(s) awesome.connect_signal( "widget::brightness_osd:rerun", function() - if hide_brightness_osd.started then - hide_brightness_osd:again() - else - hide_brightness_osd:start() + if hide_brightness_osd.started then + hide_brightness_osd:again() + else + hide_brightness_osd:start() + end end - end ) awesome.connect_signal( "module::brightness_osd:show", function() - if s == mouse.screen then - brightness_container.visible = true + if s == mouse.screen then + brightness_container.visible = true + end end - end ) brightness_container:connect_signal( "mouse::enter", function() - brightness_container.visible = true - hide_brightness_osd:stop() - end + brightness_container.visible = true + hide_brightness_osd:stop() + end ) brightness_container:connect_signal( "mouse::leave", function() - brightness_container.visible = true - hide_brightness_osd:again() - end + brightness_container.visible = true + hide_brightness_osd:again() + end ) end diff --git a/awesome/crylia_bar/center_bar.lua b/awesome/src/modules/crylia_bar/center_bar.lua similarity index 100% rename from awesome/crylia_bar/center_bar.lua rename to awesome/src/modules/crylia_bar/center_bar.lua diff --git a/awesome/crylia_bar/dock.lua b/awesome/src/modules/crylia_bar/dock.lua similarity index 100% rename from awesome/crylia_bar/dock.lua rename to awesome/src/modules/crylia_bar/dock.lua diff --git a/awesome/src/modules/crylia_bar/init.lua b/awesome/src/modules/crylia_bar/init.lua new file mode 100644 index 0000000..6dbda0a --- /dev/null +++ b/awesome/src/modules/crylia_bar/init.lua @@ -0,0 +1,52 @@ +-------------------------------------------------------------------------------------------------------------- +-- This is the statusbar, every widget, module and so on is combined to all the stuff you see on the screen -- +-------------------------------------------------------------------------------------------------------------- + +return function(s) + + -- Every Widget + --[[ + If you are going to use a widget on a single screen only, put it inside the s.index == X where X is the screen number. + This will lead to better performance and prevent widgets to be loaded but not used + --]] + s.audio = require("src.widgets.audio")(s) + s.date = require("src.widgets.date")() + s.clock = require("src.widgets.clock")() + s.layoutlist = require("src.widgets.layout_list")() + s.powerbutton = require("src.widgets.power")() + s.kblayout = require("src.widgets.kblayout")(s) + s.taglist = require("src.widgets.taglist")(s) + s.tasklist = require("src.widgets.tasklist")(s) + -- s.battery = require("src.widgets.battery")() + -- s.bluetooth = require("src.widgets.bluetooth")() + -- s.cpu_freq = require("src.widgets.cpu_info")("freq", "average") + -- s.systray = require("src.widgets.systray")(s) + -- s.cpu_usage = require("src.widgets.cpu_info")("usage") + -- s.cpu_temp = require("src.widgets.cpu_info")("temp") + -- s.gpu_usage = require("src.widgets.gpu_info")("usage") + -- s.gpu_temp = require("src.widgets.gpu_info")("temp") + -- s.network = require("src.widgets.network")() + -- s.ram_info = require("src.widgets.ram_info")() + + if s.index == 1 then + s.systray = require("src.widgets.systray")(s) + s.cpu_usage = require("src.widgets.cpu_info")("usage") + s.cpu_temp = require("src.widgets.cpu_info")("temp") + s.gpu_usage = require("src.widgets.gpu_info")("usage") + s.gpu_temp = require("src.widgets.gpu_info")("temp") + + require("src.modules.crylia_bar.left_bar")(s, { s.layoutlist, s.systray, s.taglist }) + require("src.modules.crylia_bar.center_bar")(s, { s.tasklist }) + require("src.modules.crylia_bar.right_bar")(s, { s.gpu_usage, s.gpu_temp, s.cpu_usage, s.cpu_temp, s.audio, s.kblayout, s.date, s.clock, s.powerbutton }) + require("src.modules.crylia_bar.dock")(s, user_vars.dock_programs) + end + + if s.index == 2 then + s.network = require("src.widgets.network")() + s.ram_info = require("src.widgets.ram_info")() + + require("src.modules.crylia_bar.left_bar")(s, { s.layoutlist, s.taglist }) + require("src.modules.crylia_bar.center_bar")(s, { s.tasklist }) + require("src.modules.crylia_bar.right_bar")(s, { s.ram_info, s.audio, s.kblayout, s.network, s.date, s.clock, s.powerbutton }) + end +end diff --git a/awesome/crylia_bar/left_bar.lua b/awesome/src/modules/crylia_bar/left_bar.lua similarity index 100% rename from awesome/crylia_bar/left_bar.lua rename to awesome/src/modules/crylia_bar/left_bar.lua diff --git a/awesome/crylia_bar/right_bar.lua b/awesome/src/modules/crylia_bar/right_bar.lua similarity index 100% rename from awesome/crylia_bar/right_bar.lua rename to awesome/src/modules/crylia_bar/right_bar.lua diff --git a/awesome/src/modules/init.lua b/awesome/src/modules/init.lua new file mode 100644 index 0000000..f0fe395 --- /dev/null +++ b/awesome/src/modules/init.lua @@ -0,0 +1,28 @@ +-------------------------------------------------------------------------------------------------------------- +-- This is the statusbar, every widget, module and so on is combined to all the stuff you see on the screen -- +-------------------------------------------------------------------------------------------------------------- +-- Awesome Libs +local awful = require("awful") + +awful.screen.connect_for_each_screen( +-- For each screen this function is called once +-- If you want to change the modules per screen use the indices +-- e.g. 1 would be the primary screen and 2 the secondary screen. + function(s) + -- Create 9 tags + awful.layout.layouts = user_vars.layouts + awful.tag( + { "1", "2", "3", "4", "5", "6", "7", "8", "9" }, + s, + user_vars.layouts[1] + ) + + require("src.modules.powermenu")(s) + require("src.modules.volume_osd")(s) + require("src.modules.brightness_osd")(s) + require("src.modules.titlebar") + require("src.modules.volume_controller")(s) + require("src.modules.crylia_bar.init")(s) + require("src.modules.notification-center.init")(s) + end +) diff --git a/awesome/src/modules/notification-center/init.lua b/awesome/src/modules/notification-center/init.lua index e69de29..ffd01db 100644 --- a/awesome/src/modules/notification-center/init.lua +++ b/awesome/src/modules/notification-center/init.lua @@ -0,0 +1,371 @@ +------------------------------------- +-- This is the notification-center -- +------------------------------------- + +-- Awesome Libs +local awful = require("awful") +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local gears = require("gears") +local wibox = require("wibox") + +-- Icon directory path +local icondir = awful.util.getdir("config") .. "src/assets/icons/notifications/" + +return function(s) + + --#region Activation area + + local activation_area = awful.popup { + bg = '#00000000', + widget = wibox.container.background, + ontop = true, + screen = s, + type = 'dock', + placement = function(c) + awful.placement.top(c) + end, + } + + activation_area:setup({ + widget = wibox.container.background, + forced_height = dpi(1), + forced_width = dpi(300), + bg = '#00000000', + layout = wibox.layout.fixed.horizontal + }) + + --#endregion + + --#region Widgets + local nl = require("src.modules.notification-center.notification_list").notification_list + local music_widget = require("src.modules.notification-center.song_info")() + local time_date = require("src.modules.notification-center.time_date")() + local weather_widget = require("src.modules.notification-center.weather")() + local profile_widget = require("src.modules.notification-center.profile")() + local status_bars_widget = require("src.modules.notification-center.status_bars")() + --#endregion + + --#region Notification buttons + local clear_all_widget = wibox.widget { -- Clear all button + { + { + { + text = "Clear", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "clearall" + }, + id = "background4", + fg = color["Grey900"], + bg = color["Blue200"], + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 12) + end, + forced_width = dpi(80), + forced_height = dpi(40), + widget = wibox.container.background + }, + id = "margin3", + margins = dpi(10), + widget = wibox.container.margin + }, + id = "place", + widget = wibox.container.place, + valign = "bottom", + halign = "right", + } + + local left_button = wibox.widget { + { + { + widget = wibox.container.background, + bg = color["Grey700"], + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(8)) + end, + forced_height = dpi(30), + forced_width = dpi(30), + id = "circle" + }, + left = dpi(5), + right = dpi(5), + widget = wibox.container.margin, + id = "margin" + }, + visible = true, + valign = "center", + halign = "left", + widget = wibox.container.place, + } + + local right_button = wibox.widget { + { + { + widget = wibox.container.background, + bg = color["Purple200"], + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(8)) + end, + forced_height = dpi(30), + forced_width = dpi(30), + id = "circle" + }, + left = dpi(5), + right = dpi(5), + widget = wibox.container.margin, + id = "margin" + }, + valign = "center", + halign = "right", + visible = false, + widget = wibox.container.place, + } + + local toggle_button = wibox.widget { + { + left_button, + right_button, + widget = wibox.layout.flex.horizontal + }, + active = false, + widget = wibox.container.background, + bg = color["Grey900"], + border_color = color["Grey800"], + border_width = dpi(2), + forced_height = dpi(40), + forced_width = dpi(80), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(10)) + end, + } + + toggle_button:connect_signal( + "button::press", + function() + if toggle_button.active then + left_button.visible = true + right_button.visible = false + toggle_button.active = not toggle_button.active + toggle_button.border_color = color["Grey800"] + user_vars.dnd = false + else + left_button.visible = false + right_button.visible = true + toggle_button.active = not toggle_button.active + toggle_button.border_color = color["Purple200"] + user_vars.dnd = true + end + end + ) + + local dnd = wibox.widget { -- Clear all button + { + { + { + { + text = "Do Not Disturb", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "clearall" + }, + toggle_button, + spacing = dpi(10), + layout = wibox.layout.fixed.horizontal, + id = "layout12" + }, + id = "background4", + fg = color["Pink200"], + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 12) + end, + forced_height = dpi(40), + widget = wibox.container.background + }, + id = "margin3", + margins = dpi(10), + widget = wibox.container.margin + }, + id = "place", + widget = wibox.container.place, + valign = "bottom", + halign = "right", + } + + -- TODO: Add rubato animation. For this the widget needs to be rewritten to use a single moving square + local no_notification_widget = wibox.widget { + { + { + valign = "center", + halign = "center", + resize = true, + forced_height = dpi(200), + forced_width = dpi(200), + image = icondir .. "megamind.svg", + widget = wibox.widget.imagebox, + id = "icon" + }, + { + id = "txt", + markup = "No Notifications?", + valign = "center", + halign = "center", + widget = wibox.widget.textbox + }, + id = "lay", + layout = wibox.layout.fixed.vertical + }, + valign = "center", + halign = "center", + widget = wibox.container.place + } + + --#endregion + + --#region Notification center + local notification_center = awful.popup { + widget = wibox.container.background, + bg = color["Grey900"], + border_color = color["Grey800"], + border_width = dpi(4), + placement = function(c) + awful.placement.top(c, { margins = dpi(10) }) + end, + ontop = true, + screen = s, + visible = false, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 12) + end, + } + + -- TODO: Currently awesome doesn't come with a scroll container, there is a PR(#3309) and once its merged we can use it + local function notification_center_setup() + notification_center:setup({ + widget = notification_center, + -- Custom widgets + { + time_date, + require("src.modules.notification-center.spacingline_widget")(), + { + { + weather_widget, + { + profile_widget, + layout = wibox.layout.fixed.vertical + }, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.fixed.horizontal + }, + status_bars_widget, + music_widget, + layout = wibox.layout.fixed.vertical + }, + -- Notification list + { + { + { + nl, + height = dpi(680), + strategy = "max", + widget = wibox.container.constraint + }, + { + no_notification_widget, + strategy = "max", + height = dpi(400), + widget = wibox.container.constraint + }, + { + dnd, + nil, + clear_all_widget, + layout = wibox.layout.align.horizontal + }, + id = "layout5", + layout = wibox.layout.align.vertical + }, + id = "margin6", + margins = dpi(20), + widget = wibox.container.margin + }, + id = "yes", + spacing_widget = { + { + fg = color["Grey800"], + bg = color["Grey800"], + widget = wibox.container.background + }, + top = dpi(40), + bottom = dpi(40), + widget = wibox.container.margin + }, + spacing = dpi(1), + forced_height = dpi(800), + forced_width = dpi(1000), + layout = wibox.layout.flex.horizontal + }) + end + + --#endregion + + --#region Signals + -- Toggle notification_center visibility when mouse is over activation_area + activation_area:connect_signal( + "mouse::enter", + function() + notification_center.visible = true + notification_center_setup() + end + ) + + -- Update the notification center popup and check if there are no notifications + awesome.connect_signal( + "notification_center:update::needed", + function() + if #nl == 0 then + math.randomseed(os.time()) + local prob = math.random(1, 10) + + if (prob == 5) or (prob == 6) then + no_notification_widget.lay.icon.image = icondir .. "megamind.svg" + no_notification_widget.lay.txt.markup = "No Notifications?" + else + no_notification_widget.lay.icon.image = icondir .. "bell-outline.svg" + no_notification_widget.lay.txt.markup = "No Notification" + end + no_notification_widget.visible = true + else + no_notification_widget.visible = false + end + notification_center_setup() + end + ) + + -- Hide notification_center when mouse leaves it + notification_center:connect_signal( + "mouse::leave", + function() + notification_center.visible = false + end + ) + + -- Clear all notifications on button press + clear_all_widget:connect_signal( + "button::press", + function() + local size = #nl + for i = 0, size do + nl[i] = nil + end + awesome.emit_signal("notification_center:update::needed") + end + ) + + Hover_signal(clear_all_widget, color["Blue200"], color["Grey900"]) + --#endregion + +end diff --git a/awesome/src/modules/notification-center/notification_list.lua b/awesome/src/modules/notification-center/notification_list.lua new file mode 100644 index 0000000..7ee0f86 --- /dev/null +++ b/awesome/src/modules/notification-center/notification_list.lua @@ -0,0 +1,261 @@ +------------------------------------- +-- This is the notification-center -- +------------------------------------- + +-- Awesome Libs +local awful = require("awful") +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local gears = require("gears") +local wibox = require("wibox") +local naughty = require("naughty") + +-- Icon directory path +local icondir = awful.util.getdir("config") .. "src/assets/icons/notifications/" + +local nl = {} + +nl.notification_list = { layout = wibox.layout.fixed.vertical, spacing = dpi(20) } + +-- @param {table} notification +-- @return {widget} notifications_list +function nl.create_notification(n) + + n.time = os.time() + + local time_ago_text = "- ago" + + local timer_widget = wibox.widget { + { + { + text = time_ago_text, + widget = wibox.widget.textbox, + id = "txt" + }, + id = "background", + fg = color["Teal200"], + widget = wibox.container.background + }, + margins = dpi(10), + widget = wibox.container.margin, + } + + gears.timer { + timeout = 1, + autostart = true, + call_now = true, + callback = function() + local time_ago = math.floor(os.time() - n.time) + local timer_text = timer_widget.background.txt + if time_ago < 5 then + timer_text:set_text("now") + elseif time_ago < 60 then + timer_text:set_text(time_ago .. "s ago") + elseif time_ago < 3600 then + timer_text:set_text(math.floor(time_ago / 60) .. "m ago") + elseif time_ago < 86400 then + timer_text:set_text(math.floor(time_ago / 3600) .. "h ago") + else + timer_text:set_text(math.floor(time_ago / 86400) .. "d ago") + end + end + } + + local close_widget = wibox.widget { + { + { + { + { + font = user_vars.font.specify .. ", 10", + text = "✕", + align = "center", + valign = "center", + widget = wibox.widget.textbox + }, + start_angle = 4.71239, + thickness = dpi(2), + min_value = 0, + max_value = 360, + value = 360, + widget = wibox.container.arcchart, + id = "arc_chart" + }, + id = "background", + fg = color["Teal200"], + widget = wibox.container.background + }, + strategy = "exact", + width = dpi(20), + height = dpi(20), + widget = wibox.container.constraint, + id = "const" + }, + margins = dpi(10), + widget = wibox.container.margin, + id = "arc_margin" + } + + local timer_close_widget = timer_widget + + local notification = wibox.widget { + { + { + { + { + { + { + { + { + { + { + image = gears.color.recolor_image(icondir .. "notification-outline.svg", color["Teal200"]), + resize = false, + widget = wibox.widget.imagebox + }, + right = dpi(5), + widget = wibox.container.margin + }, + { + markup = n.app_name or 'System Notification', + align = "center", + valign = "center", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal + }, + fg = color["Teal200"], + widget = wibox.container.background + }, + margins = dpi(10), + widget = wibox.container.margin + }, + nil, + { + timer_widget, + layout = wibox.layout.fixed.horizontal, + id = "arc_app_layout_2" + }, + id = "arc_app_layout", + layout = wibox.layout.align.horizontal + }, + id = "arc_app_bg", + border_color = color["Grey800"], + border_width = dpi(2), + widget = wibox.container.background + }, + { + { + { + { + { + image = n.icon, + resize = true, + widget = wibox.widget.imagebox, + clip_shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 10) + end + }, + width = naughty.config.defaults.icon_size, + height = naughty.config.defaults.icon_size, + strategy = "exact", + widget = wibox.container.constraint + }, + halign = "center", + valign = "top", + widget = wibox.container.place + }, + id = "margin01", + left = dpi(20), + bottom = dpi(15), + top = dpi(15), + right = dpi(10), + widget = wibox.container.margin + }, + { + { + { + markup = n.title, + widget = wibox.widget.textbox, + align = "left" + }, + { + markup = n.message, + widget = wibox.widget.textbox, + align = "left" + }, + layout = wibox.layout.fixed.vertical + }, + left = dpi(10), + bottom = dpi(10), + top = dpi(10), + right = dpi(20), + widget = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal + }, + id = "widget_layout", + layout = wibox.layout.fixed.vertical + }, + id = "min_size", + strategy = "min", + width = dpi(100), + widget = wibox.container.constraint + }, + id = "max_size", + strategy = "max", + width = Theme.notification_max_width or dpi(500), + widget = wibox.container.constraint + }, + pk = #nl.notification_list + 1, + bg = color["Grey900"], + border_color = color["Grey800"], + border_width = dpi(4), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 8) + end, + widget = wibox.container.background + } + + close_widget:connect_signal( + "button::press", + function(_, _, _, button) + if button == 1 then + for i, b in pairs(nl.notification_list) do + if b.pk == notification.pk then + table.remove(nl.notification_list, i) + awesome.emit_signal("notification_center:update::needed") + break + end + end + end + end + ) + + Hover_signal(close_widget.const.background, color["Grey900"], color["Teal200"]) + + notification:connect_signal( + "mouse::enter", + function() + notification:get_children_by_id("arc_app_layout_2")[1]:set(1, close_widget) + end + ) + + notification:connect_signal( + "mouse::leave", + function() + notification:get_children_by_id("arc_app_layout_2")[1]:set(1, timer_close_widget) + end + ) + + table.insert(nl.notification_list, notification) +end + +naughty.connect_signal( + "request::display", + function(n) + nl.create_notification(n) + awesome.emit_signal("notification_center:update::needed") + end +) + +return nl diff --git a/awesome/src/modules/notification-center/profile.lua b/awesome/src/modules/notification-center/profile.lua new file mode 100644 index 0000000..3f5fe3a --- /dev/null +++ b/awesome/src/modules/notification-center/profile.lua @@ -0,0 +1,207 @@ +-------------------------------- +-- This is the profile widget -- +-------------------------------- + +-- Awesome Libs +local awful = require("awful") +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local gears = require("gears") +local wibox = require("wibox") + +-- Icon directory path +local icondir = awful.util.getdir("config") .. "src/assets/icons/profile/" + +return function() + + local profile_widget = wibox.widget { + { + { + { + { + { + { + image = gears.surface.load_uncached(awful.util.getdir("config") .. "src/assets/userpfp/crylia.png"), + id = "icon", + valign = "center", + halign = "center", + clip_shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(12)) + end, + widget = wibox.widget.imagebox + }, + strategy = "exact", + widget = wibox.container.constraint + }, + id = "icon_margin", + margins = dpi(20), + widget = wibox.container.margin + }, + { + { + { + { + { -- Username + id = "username_prefix", + image = gears.color.recolor_image(icondir .. "user.svg", color["Blue200"]), + valign = "center", + halign = "left", + resize = false, + widget = wibox.widget.imagebox + }, + { -- Username + id = "username", + valign = "center", + align = "left", + widget = wibox.widget.textbox + }, + spacing = dpi(10), + layout = wibox.layout.fixed.horizontal + }, + { + { + id = "os_prefix", + image = gears.color.recolor_image(icondir .. "laptop.svg", color["Blue200"]), + valign = "center", + halign = "left", + resize = false, + widget = wibox.widget.imagebox + }, + { -- OS + id = "os", + valign = "center", + align = "left", + widget = wibox.widget.textbox + }, + spacing = dpi(10), + layout = wibox.layout.fixed.horizontal + }, + { + { + id = "kernel_prefix", + image = gears.color.recolor_image(icondir .. "penguin.svg", color["Blue200"]), + valign = "center", + halign = "left", + resize = false, + widget = wibox.widget.imagebox + }, + { -- Kernel + id = "kernel", + valign = "center", + align = "left", + widget = wibox.widget.textbox + }, + spacing = dpi(10), + layout = wibox.layout.fixed.horizontal + }, + { + { + id = "uptime_prefix", + image = gears.color.recolor_image(icondir .. "clock.svg", color["Blue200"]), + valign = "center", + halign = "left", + resize = false, + widget = wibox.widget.imagebox + }, + { -- Uptime + id = "uptime", + valign = "center", + align = "left", + widget = wibox.widget.textbox + }, + spacing = dpi(10), + id = "uptime_layout", + layout = wibox.layout.fixed.horizontal + }, + spacing = dpi(5), + id = "info_layout", + layout = wibox.layout.flex.vertical + }, + id = "text_margin", + widget = wibox.container.constraint + }, + id = "text_container", + bottom = dpi(20), + left = dpi(20), + widget = wibox.container.margin + }, + id = "text_container_wrapper", + widget = wibox.layout.fixed.vertical + }, + id = "wrapper", + fg = color["Green200"], + border_color = color["Grey800"], + border_width = dpi(4), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(8)) + end, + widget = wibox.container.background + }, + id = "const", + strategy = "exact", + width = dpi(250), + height = dpi(350), + widget = wibox.container.constraint + }, + top = dpi(20), + left = dpi(10), + right = dpi(20), + bottom = dpi(10), + widget = wibox.container.margin + } + + local function get_os_name_pretty() + awful.spawn.easy_async_with_shell( + "cat /etc/os-release | grep -w NAME", + function(stdout) + profile_widget:get_children_by_id("os")[1].text = stdout:match("\"(.+)\"") + end + ) + end + + -- function to get and set the kernel version + local function get_kernel_version() + awful.spawn.easy_async_with_shell( + "uname -r", + function(stdout) + profile_widget:get_children_by_id("kernel")[1].text = stdout:match("(%d+%.%d+%.%d+)") + end + ) + end + + --function to get the username and hostname + local function get_user_hostname() + awful.spawn.easy_async_with_shell( + "echo $USER@$(hostname)", + function(stdout) + profile_widget:get_children_by_id("username")[1].text = stdout:gsub("\n", "") or "" + end + ) + + end + + -- function to fetch uptime async + local function get_uptime() + awful.spawn.easy_async_with_shell("uptime -p", function(stdout) + + local hours = stdout:match("(%d+) hours") or 0 + local minutes = stdout:match("(%d+) minutes") or 0 + + profile_widget:get_children_by_id("uptime")[1].text = hours .. "h, " .. minutes .. "m" + end) + end + + get_os_name_pretty() + get_kernel_version() + get_user_hostname() + + gears.timer { + timeout = 60, + autostart = true, + call_now = true, + callback = get_uptime + } + + return profile_widget + +end diff --git a/awesome/src/modules/notification-center/song_info.lua b/awesome/src/modules/notification-center/song_info.lua new file mode 100644 index 0000000..3824037 --- /dev/null +++ b/awesome/src/modules/notification-center/song_info.lua @@ -0,0 +1,466 @@ +--------------------------- +-- This is the song-info -- +--------------------------- + +-- Awesome Libs +local awful = require("awful") +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local gears = require("gears") +local wibox = require("wibox") +local naughty = require("naughty") + +-- Icon directory path +local icondir = awful.util.getdir("config") .. "src/assets/icons/notifications/" + +return function(s) + + --#region Music control button widgets + + local function button_hover_effect(widget, svg, color, color2) + local mouse_enter = function() + widget.image = gears.surface.load_uncached(gears.color.recolor_image(icondir .. svg, color2)) + local w = mouse.current_wibox + if w then + w.cursor = "hand1" + end + end + + local mouse_leave = function() + widget.image = gears.surface.load_uncached(gears.color.recolor_image(icondir .. svg, color)) + mouse.cursor = "left_ptr" + local w = mouse.current_wibox + if w then + w.cursor = "left_ptr" + end + end + + widget:disconnect_signal("mouse::enter", mouse_enter) + widget:connect_signal("mouse::enter", mouse_enter) + widget:disconnect_signal("mouse::leave", mouse_leave) + widget:connect_signal("mouse::leave", mouse_leave) + end + + local shuffle_button = wibox.widget { + resize = false, + image = gears.color.recolor_image(icondir .. "shuffle.svg", color["Grey800"]), + widget = wibox.widget.imagebox, + } + + local function suffle_handler() + awful.spawn.easy_async_with_shell( + "playerctl shuffle", + function(stdout) + if stdout:match("On") then + awful.spawn.with_shell("playerctl shuffle off") + shuffle_button.image = gears.color.recolor_image(icondir .. "shuffle.svg", color["Grey800"]) + else + awful.spawn.with_shell("playerctl shuffle on") + shuffle_button.image = gears.color.recolor_image(icondir .. "shuffle.svg", color["Green200"]) + end + end + ) + end + + local function update_shuffle() + awful.spawn.easy_async_with_shell( + "playerctl shuffle", + function(stdout) + if stdout:match("On") then + shuffle_button.image = gears.color.recolor_image(icondir .. "shuffle.svg", color["Green200"]) + else + shuffle_button.image = gears.color.recolor_image(icondir .. "shuffle.svg", color["Grey800"]) + end + end + ) + end + + update_shuffle() + + local repeat_button = wibox.widget { + resize = false, + image = gears.color.recolor_image(icondir .. "repeat.svg", color["Grey800"]), + widget = wibox.widget.imagebox, + id = "imagebox" + } + + -- On first time load set the correct loop + local function update_loop() + awful.spawn.easy_async_with_shell( + "playerctl loop", + function(stdout) + local loop_mode = stdout:gsub("\n", "") + if loop_mode == "Track" then + repeat_button.image = gears.color.recolor_image(gears.surface.load_uncached(icondir .. "repeat-once.svg"), color["Green200"]) + elseif loop_mode == "None" then + repeat_button.image = gears.color.recolor_image(gears.surface.load_uncached(icondir .. "repeat.svg"), color["Grey800"]) + elseif loop_mode == "Playlist" then + repeat_button.image = gears.color.recolor_image(gears.surface.load_uncached(icondir .. "repeat.svg"), color["Green200"]) + end + end + ) + end + + update_loop() + -- Activate shuffle when button is clicked + shuffle_button:buttons(gears.table.join( + awful.button({}, 1, suffle_handler))) + + local prev_button = wibox.widget { + resize = false, + image = gears.color.recolor_image(icondir .. "skip-prev.svg", color["Teal200"]), + widget = wibox.widget.imagebox + } + + -- Activate previous song when button is clicked + prev_button:buttons(gears.table.join( + awful.button({}, 1, function() + awful.spawn.easy_async_with_shell( + "playerctl previous && sleep 1", + function() + update_loop() + end + ) + end) + )) + + local pause_play_button = wibox.widget { + resize = false, + image = gears.color.recolor_image(icondir .. "play-pause.svg", color["Teal200"]), + widget = wibox.widget.imagebox + } + + -- Activate play/pause when button is clicked + pause_play_button:buttons(gears.table.join( + awful.button({}, 1, function() + awful.spawn.with_shell("playerctl play-pause") + end) + )) + + local next_button = wibox.widget { + resize = false, + image = gears.color.recolor_image(icondir .. "skip-next.svg", color["Teal200"]), + widget = wibox.widget.imagebox + } + + -- Activate next song when button is clicked + next_button:buttons(gears.table.join( + awful.button({}, 1, function() + awful.spawn.easy_async_with_shell( + "playerctl next && sleep 1", + function() + update_loop() + end + ) + end) + )) + + --- This function updates the repeat button svg and changes the mode on click + local function loop_handler() + awful.spawn.easy_async_with_shell( + "playerctl loop", + function(stdout) + local loop_mode = stdout:gsub("\n", "") + if loop_mode == "None" then + awful.spawn.with_shell("playerctl loop playlist") + repeat_button.image = gears.color.recolor_image(gears.surface.load_uncached(icondir .. "repeat.svg"), color["Green200"]) + elseif loop_mode == "Playlist" then + awful.spawn.with_shell("playerctl loop track") + repeat_button.image = gears.color.recolor_image(gears.surface.load_uncached(icondir .. "repeat-once.svg"), color["Green200"]) + elseif loop_mode == "Track" then + awful.spawn.with_shell("playerctl loop none") + repeat_button.image = gears.color.recolor_image(gears.surface.load_uncached(icondir .. "repeat.svg"), color["Grey800"]) + end + end + ) + end + + repeat_button:buttons(gears.table.join(awful.button({}, 1, loop_handler))) + + button_hover_effect(prev_button, "skip-prev.svg", color["Teal200"], color["Teal300"]) + button_hover_effect(pause_play_button, "play-pause.svg", color["Teal200"], color["Teal300"]) + button_hover_effect(next_button, "skip-next.svg", color["Teal200"], color["Teal300"]) + + --#endregion + + -- Main music widget + local music_widget = wibox.widget { + { + { + { + { + { + { -- Album art + { + image = "default image", + resize = true, + clip_shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(8)) + end, + widget = wibox.widget.imagebox, + id = "imagebox" + }, + width = dpi(80), + height = dpi(80), + strategy = "exact", + widget = wibox.container.constraint, + id = "const" + }, + { + { + { + { + { --Title + halign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "textbox4" + }, + fg = color["Pink200"], + id = "textbox5", + widget = wibox.container.background + }, + strategy = "max", + width = dpi(400), + widget = wibox.container.constraint + }, + halign = "center", + valign = "center", + id = "textbox_container4", + widget = wibox.container.place + }, + { + { + { + { --Artist + halign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "textbox3" + }, + fg = color["Teal200"], + id = "background", + widget = wibox.container.background + }, + strategy = "max", + width = dpi(400), + widget = wibox.container.constraint + }, + halign = "center", + valign = "center", + id = "artist_container", + widget = wibox.container.place + }, + { --Buttons + { + { + shuffle_button, + prev_button, + pause_play_button, + next_button, + repeat_button, + spacing = dpi(15), + layout = wibox.layout.fixed.horizontal, + id = "layout5" + }, + halign = "center", + widget = wibox.container.place, + id = "place2" + }, + widget = wibox.container.margin, + id = "margin6" + }, + layout = wibox.layout.flex.vertical, + id = "layout4" + }, + fill_space = true, + spacing = dpi(10), + layout = wibox.layout.fixed.horizontal, + id = "layout3" + }, + widget = wibox.container.margin, + id = "margin5" + }, + { --Song Duration + { + { + { + markup = "0:00", + widget = wibox.widget.textbox, + id = "textbox2" + }, + fg = color["Lime200"], + widget = wibox.container.background, + id = "background3" + }, + right = dpi(10), + widget = wibox.container.margin, + id = "margin4" + }, + { -- Progressbar + { + color = color["Purple200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(5), + shape = function(cr, width) + gears.shape.rounded_bar(cr, width, dpi(5)) + end, + widget = wibox.widget.progressbar, + id = "progressbar1" + }, + valign = "center", + halign = "center", + widget = wibox.container.place, + id = "place1" + }, + { + { + { + text = "00:00", + widget = wibox.widget.textbox, + id = "text1" + }, + id = "background2", + fg = color["Lime200"], + widget = wibox.container.background + }, + id = "margin3", + left = dpi(10), + widget = wibox.container.margin + }, + id = "layout2", + layout = wibox.layout.align.horizontal + }, + id = "layout1", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + }, + id = "margin2", + widget = wibox.container.margin, + margins = dpi(10) + }, + id = "background1", + border_color = color["Grey800"], + border_width = dpi(4), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(8)) + end, + widget = wibox.container.background + }, + id = "margin1", + widget = wibox.container.margin, + top = dpi(10), + bottom = dpi(20), + left = dpi(20), + right = dpi(20) + } + + -- Used to check if the music changed and if everthing should be updated + local trackid = "" + local artist = "" + local title = "" + + -- Function to get spotify title, artist, album, album_art, length and track_id + local function get_spotify_metadata(skip_check) + skip_check = skip_check or false + awful.spawn.easy_async_with_shell( + "playerctl metadata", + function(stdout) + -- Only fetch info if the track changed or if the title/artist is empty + if skip_check or (not stdout:match(trackid)) or (not stdout:match(artist)) or (not stdout:match(title)) then + update_loop() + update_shuffle() + -- Get the song title + awful.spawn.easy_async_with_shell( + "playerctl metadata xesam:title", + function(stdout2) + local tit = stdout2:gsub("\n", "") + title = tit + music_widget:get_children_by_id("textbox4")[1].text = tit + end + ) + + -- Get the song artist + awful.spawn.easy_async_with_shell( + "playerctl metadata xesam:artist", + function(stdout2) + local art = stdout2:gsub("\n", "") + artist = art + music_widget:get_children_by_id("textbox3")[1].text = art + end + ) + + -- Get the song album image + awful.spawn.easy_async_with_shell( + "playerctl metadata mpris:artUrl", + function(album_art) + local url = album_art:gsub("\n", "") + awful.spawn.easy_async_with_shell( + -- TODO: curl does not stdout and is returns before it finished. This causes the image to sometimes not show correctly. + -- !Find a better solution than sleep 0.1 + -- Maybe cache the image? Not sure if that would be a waste of space or not. + "curl -s " .. url .. " -o /tmp/album_art.jpg && echo /tmp/album_art.jpg && sleep 0.5", + function() + music_widget:get_children_by_id("imagebox")[1].image = gears.surface.load_uncached("/tmp/album_art.jpg") + end + ) + end + ) + + -- Get the length of the song + awful.spawn.easy_async_with_shell( + "playerctl metadata mpris:length", + function(stdout2) + local length = stdout2:gsub("\n", "") + if length ~= "" then + local length_formated = string.format("%02d:%02d", math.floor(tonumber(length or 1) / 60000000) or 0, (math.floor(tonumber(length or 1) / 1000000) % 60) or 0) + music_widget:get_children_by_id("progressbar1")[1].max_value = tonumber(math.floor(tonumber(length) / 1000000)) + music_widget:get_children_by_id("text1")[1].markup = string.format("%s", color["Teal200"], length_formated) + end + end + ) + end + + awful.spawn.easy_async_with_shell( + "playerctl metadata mpris:trackid", + function(stdout2) + trackid = stdout2:gsub("\n", "") + end + ) + -- Update track id + trackid, artist, title = stdout, music_widget:get_children_by_id("textbox3")[1].text, music_widget:get_children_by_id("textbox4")[1].text + end + ) + -- Always update the current song progression + awful.spawn.easy_async_with_shell( + "playerctl position", + function(stdout) + local time = stdout:gsub("\n", "") + if time ~= "" then + local time_formated = string.format("%02d:%02d", math.floor(tonumber(time or "1") / 60), math.floor(tonumber(time or "1")) % 60) + music_widget:get_children_by_id("textbox2")[1].markup = string.format("%s", color["Teal200"], time_formated) + music_widget:get_children_by_id("progressbar1")[1].value = tonumber(time) + end + end + ) + end + + -- Call every second, if performance is bad, set the timer to a higher value + gears.timer { + timeout = 1, + autostart = true, + call_now = true, + callback = function() + get_spotify_metadata() + end + } + + -- get_spotify_metadata() on awesome reload + awesome.connect_signal("startup", function() + get_spotify_metadata(true) + end) + + return music_widget +end diff --git a/awesome/src/modules/notification-center/spacingline_widget.lua b/awesome/src/modules/notification-center/spacingline_widget.lua new file mode 100644 index 0000000..275148e --- /dev/null +++ b/awesome/src/modules/notification-center/spacingline_widget.lua @@ -0,0 +1,23 @@ +------------------------------------------------ +-- This is the spacing widget under the clock -- +------------------------------------------------ + +-- Awesome Libs +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local wibox = require("wibox") + +return function() + + return wibox.widget { + { + forced_height = dpi(2), + bg = color["Grey800"], + widget = wibox.container.background + }, + left = dpi(80), + right = dpi(80), + widget = wibox.container.margin + } + +end diff --git a/awesome/src/modules/notification-center/status_bars.lua b/awesome/src/modules/notification-center/status_bars.lua new file mode 100644 index 0000000..0c12663 --- /dev/null +++ b/awesome/src/modules/notification-center/status_bars.lua @@ -0,0 +1,712 @@ +------------------------------------ +-- This is the status_bars widget -- +------------------------------------ + +-- Awesome Libs +local awful = require("awful") +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local gears = require("gears") +local wibox = require("wibox") + +local rubato = require("src.lib.rubato") + +-- Icon directory path +local icondir = awful.util.getdir("config") .. "src/assets/icons/" + +--- Signal bars widget for the notification-center +---@return wibox.widget +return function() + + ---Creates a layout with bar widgets based on the given table + ---@param widget_table string{} + ---@return table @{layout} + local function create_bar_layout(widget_table) + local bar_layout = { layout = wibox.layout.flex.horizontal, spacing = dpi(10) } + + for _, widget in pairs(widget_table) do + local w + if widget == "cpu_usage" then + w = wibox.widget { + { + { + { --Bar + color = color["Blue200"], + background_color = color["Grey800"], + max_value = 100, + value = 0, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + image = gears.color.recolor_image(icondir .. "cpu/cpu.svg", color["Cyan200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox, + id = "icon1", + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "cpu_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::cpu_usage_widget", + function(cpu_usage) + w:get_children_by_id("progressbar1")[1].value = cpu_usage + tooltip.text = "CPU Usage: " .. cpu_usage .. "%" + rubato_timer.target = cpu_usage + end + ) + elseif widget == "cpu_temp" then + w = wibox.widget { + { + { + { --Bar + color = color["Blue200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + id = "icon1", + image = gears.color.recolor_image(icondir .. "cpu/thermometer.svg", color["Cyan200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "cpu_temp_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::cpu_temp_widget", + function(cpu_temp, cpu_temp_icon) + w:get_children_by_id("progressbar1")[1].value = cpu_temp + w:get_children_by_id("icon1")[1].image = gears.color.recolor_image(cpu_temp_icon, color["Blue200"]) + tooltip.text = "CPU Temp: " .. cpu_temp .. "°C" + rubato_timer.target = cpu_temp + end + ) + elseif widget == "ram_usage" then + w = wibox.widget { + { + { + { --Bar + color = color["Red200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + image = gears.color.recolor_image(icondir .. "cpu/ram.svg", color["Red200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "ram_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::ram_widget", + function(ram_usage) + w:get_children_by_id("progressbar1")[1].value = ram_usage + tooltip.text = "RAM Usage: " .. ram_usage .. "%" + rubato_timer.target = ram_usage + end + ) + elseif widget == "gpu_usage" then + w = wibox.widget { + { + { + { --Bar + color = color["Green200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + image = gears.color.recolor_image(icondir .. "cpu/gpu.svg", color["Green200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "gpu_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::gpu_usage_widget", + function(gpu_usage) + w:get_children_by_id("progressbar1")[1].value = gpu_usage + tooltip.text = "GPU Usage: " .. gpu_usage .. "%" + rubato_timer.target = gpu_usage + end + ) + elseif widget == "gpu_temp" then + w = wibox.widget { + { + { + { --Bar + color = color["Green200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + id = "icon1", + image = gears.color.recolor_image(icondir .. "cpu/gpu.svg", color["Green200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "gpu_temp_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::gpu_temp_widget", + function(gpu_temp, gpu_temp_icon) + w:get_children_by_id("progressbar1")[1].value = gpu_temp + w:get_children_by_id("icon1")[1].image = gears.color.recolor_image(gpu_temp_icon, color["Green200"]) + tooltip.text = "GPU Temp: " .. gpu_temp .. "°C" + rubato_timer.target = gpu_temp + end + ) + elseif widget == "volume" then + w = wibox.widget { + { + { + { --Bar + color = color["Yellow200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + id = "icon1", + image = gears.color.recolor_image(icondir .. "audio/volume-high.svg", color["Yellow200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "volume_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::volume_widget", + function(volume, volume_icon) + w:get_children_by_id("progressbar1")[1].value = volume + w:get_children_by_id("icon1")[1].image = gears.color.recolor_image(volume_icon, color["Yellow200"]) + tooltip.text = "Volume: " .. volume .. "%" + rubato_timer.target = volume + end + ) + elseif widget == "microphone" then + w = wibox.widget { + { + { + { --Bar + color = color["Purple200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + id = "icon1", + image = gears.color.recolor_image(icondir .. "audio/microphone.svg", color["Purple200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "microphone_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::microphone_widget", + function(microphone, microphone_icon) + w:get_children_by_id("progressbar1")[1].value = microphone + w:get_children_by_id("icon1")[1].image = gears.color.recolor_image(microphone_icon, color["Purple200"]) + tooltip.text = "Microphone: " .. microphone .. "%" + rubato_timer.target = microphone + end + ) + elseif widget == "backlight" then + w = wibox.widget { + { + { + { --Bar + color = color["Pink200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + id = "icon1", + image = gears.color.recolor_image(icondir .. "brightness/brightness-high.svg", color["Pink200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "brightness_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::backlight_widget", + function(backlight, backlight_icon) + w:get_children_by_id("progressbar1")[1].value = backlight + w:get_children_by_id("icon1")[1].image = gears.color.recolor_image(backlight_icon, color["Pink200"]) + tooltip.text = "Backlight: " .. backlight .. "%" + rubato_timer.target = backlight + end + ) + elseif widget == "battery" then + w = wibox.widget { + { + { + { --Bar + color = color["Purple200"], + background_color = color["Grey800"], + max_value = 100, + value = 50, + forced_height = dpi(8), + shape = function(cr, width, heigth) + gears.shape.rounded_bar(cr, dpi(58), dpi(8)) + end, + id = "progressbar1", + widget = wibox.widget.progressbar + }, + id = "background1", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background2", + forced_height = dpi(58), --120 Base size - (10+10) margin - (4+4) Border - 24 Icon - 10 spacing = 58 + forced_width = dpi(24), + direction = "east", + widget = wibox.container.rotate + }, + { + { --Icon + id = "icon1", + image = gears.color.recolor_image(icondir .. "battery/battery.svg", color["Purple200"]), + halign = "center", + valign = "center", + widget = wibox.widget.imagebox + }, + id = "background3", + height = dpi(24), + width = dpi(24), + widget = wibox.container.constraint + }, + id = "battery_layout", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + } + + local bar = w:get_children_by_id("progressbar1")[1] + + local rubato_timer = rubato.timed { + duration = 1, + pos = bar.value, + easing = rubato.linear, + subscribed = function(v) + bar.value = v + end + } + + local tooltip = awful.tooltip { + objects = { w }, + mode = "inside", + preferred_alignments = "middle", + margins = dpi(10) + } + + awesome.connect_signal( + "update::battery_widget", + function(battery, battery_icon) + w:get_children_by_id("progressbar1")[1].value = battery + w:get_children_by_id("icon1")[1].image = gears.color.recolor_image(battery_icon, color["Purple200"]) + tooltip.text = "Battery: " .. battery .. "%" + rubato_timer.target = battery + end + ) + end + table.insert(bar_layout, w) + end + + return bar_layout + end + + local signal_bars = wibox.widget { + { + { + { + { + create_bar_layout({ "cpu_usage", "cpu_temp", "ram_usage", "battery", "microphone", "backlight", "volume", "gpu_temp", "gpu_usage" }), + width = dpi(480), + strategy = "exact", + widget = wibox.container.constraint + }, + halign = "center", + valign = "center", + widget = wibox.container.place + }, + magins = dpi(10), + layout = wibox.container.margin + }, + forced_height = dpi(120), + forced_width = dpi(500), + border_color = color["Grey800"], + border_width = dpi(4), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(10)) + end, + widget = wibox.container.background + }, + top = dpi(10), + left = dpi(20), + right = dpi(20), + bottom = dpi(10), + widget = wibox.container.margin + } + + return signal_bars + +end diff --git a/awesome/src/modules/notification-center/time_date.lua b/awesome/src/modules/notification-center/time_date.lua new file mode 100644 index 0000000..b7c11f6 --- /dev/null +++ b/awesome/src/modules/notification-center/time_date.lua @@ -0,0 +1,65 @@ +---------------------------------- +-- This is the time_date widget -- +---------------------------------- + +-- Awesome Libs +local dpi = require("beautiful").xresources.apply_dpi +local wibox = require("wibox") + +return function() + + local time_date = wibox.widget { + { + { + { + { -- Time + { + id = "label", + align = "center", + valign = "center", + format = "%H:%M", + widget = wibox.widget.textclock + }, + widget = wibox.container.margin + }, + { -- Date and Day + { -- Date + { + id = "label", + align = "left", + valign = "bottom", + format = "%e %b %Y", + widget = wibox.widget.textclock + }, + widget = wibox.container.margin + }, + { -- Day + { + id = "label", + align = "left", + valign = "top", + format = "%A", + widget = wibox.widget.textclock + }, + widget = wibox.container.margin + }, + layout = wibox.layout.flex.vertical + }, + spacing = dpi(20), + layout = wibox.layout.fixed.horizontal + }, + valign = "center", + halign = "center", + widget = wibox.container.place + }, + id = "background", + widget = wibox.container.background + }, + id = "margin", + margins = dpi(20), + widget = wibox.container.margin + } + + return time_date + +end diff --git a/awesome/src/modules/notification-center/weather.lua b/awesome/src/modules/notification-center/weather.lua new file mode 100644 index 0000000..6fd28e0 --- /dev/null +++ b/awesome/src/modules/notification-center/weather.lua @@ -0,0 +1,219 @@ +-------------------------------- +-- This is the weather widget -- +-------------------------------- + +-- Awesome Libs +local awful = require("awful") +local color = require("src.theme.colors") +local dpi = require("beautiful").xresources.apply_dpi +local gears = require("gears") +local wibox = require("wibox") +local naughty = require("naughty") + +local json_lua = require("src.lib.json-lua.json-lua") + +-- Icon directory path +local icondir = awful.util.getdir("config") .. "src/assets/icons/weather/" + +return function() + + local api_secrets = { + key = user_vars.weather_secrets.key, + city_id = user_vars.weather_secrets.city_id, + unit = user_vars.weather_secrets.unit + } + + local weather_widget = wibox.widget { + { + { + { + { + { + { -- Icon + valign = "center", + align = "center", + resize = true, + forced_width = dpi(64), + forced_height = dpi(64), + widget = wibox.widget.imagebox, + id = "icon" + }, + id = "place2", + valing = "center", + halign = "center", + widget = wibox.container.place + }, + { -- Temperature + text = "0°C", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + font = "JetBrains Mono Bold 24", + id = "temp" + }, + { -- City, Country + text = "City, Country", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "city_country", + }, + { + { -- Description + text = "Description", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "description" + }, + fg = color["LightBlue200"], + widget = wibox.container.background + }, + { -- line + forced_height = dpi(4), + forced_width = dpi(10), + bg = color["Grey800"], + widget = wibox.container.background, + id = "line" + }, + { + { -- Speed + { + image = gears.color.recolor_image(icondir .. "weather-windy.svg", color["OrangeA200"]), + resize = true, + forced_width = dpi(24), + forced_height = dpi(24), + widget = wibox.widget.imagebox + }, + { + text = "", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "speed" + }, + spacing = dpi(10), + id = "layout3", + layout = wibox.layout.fixed.horizontal + }, + id = "place4", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + { + { -- Humidity + { + forced_width = dpi(24), + forced_height = dpi(24), + widget = wibox.widget.imagebox, + image = gears.color.recolor_image(icondir .. "humidity.svg", color["OrangeA200"]), + id = "humidity_icon" + }, + { + text = "", + valign = "center", + align = "center", + widget = wibox.widget.textbox, + id = "humidity" + }, + spacing = dpi(10), + id = "layoutHum", + layout = wibox.layout.fixed.horizontal + }, + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "lyt", + spacing = dpi(10), + layout = wibox.layout.fixed.vertical + }, + margins = dpi(20), + widget = wibox.container.margin, + }, + id = "center", + halign = "center", + valign = "center", + widget = wibox.container.place + }, + id = "background", + border_color = color["Grey800"], + border_width = dpi(4), + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, dpi(12)) + end, + widget = wibox.container.background + }, + id = "margin", + top = dpi(20), + left = dpi(20), + right = dpi(10), + bottom = dpi(10), + forced_width = dpi(250), + widget = wibox.container.margin + } + + local function fetch_weather_data() + awful.spawn.easy_async_with_shell( + "curl -sf 'http://api.openweathermap.org/data/2.5/weather?id=" .. api_secrets.city_id .. "&units=" .. api_secrets.unit .. "&appid=" .. api_secrets.key .. "'", + function(stdout) + if not stdout:match('error') then + local weather_metadata = json_lua:decode(stdout) + if weather_metadata then + local temp = weather_metadata.main.temp + local humidity = weather_metadata.main.humidity + local city = weather_metadata.name + local country = weather_metadata.sys.country + local weather_icon = weather_metadata.weather[1].icon + local description = weather_metadata.weather[1].description + local speed = weather_metadata.wind.speed + + local icon_table = { + ["01d"] = "weather-sunny", + ["01n"] = "weather-clear-night", + ["02d"] = "weather-partly-cloudy", + ["02n"] = "weather-night-partly-cloudy", + ["03d"] = "weather-cloudy", + ["03n"] = "weather-clouds-night", + ["04d"] = "weather-cloudy", + ["04n"] = "weather-cloudy", + ["09d"] = "weather-rainy", + ["09n"] = "weather-rainy", + ["10d"] = "weather-partly-rainy", + ["10n"] = "weather-partly-rainy", + ["11d"] = "weather-pouring", + ["11n"] = "weather-pouring", + ["13d"] = "weather-snowy", + ["13n"] = "weather-snowy", + ["50d"] = "weather-fog", + ["50n"] = "weather-fog" + } + + weather_widget:get_children_by_id("icon")[1].image = icondir .. icon_table[weather_icon] .. ".svg" + weather_widget:get_children_by_id("temp")[1].text = math.floor(temp + 0.5) .. "°C" + weather_widget:get_children_by_id("city_country")[1].text = city .. ", " .. country + weather_widget:get_children_by_id("description")[1].text = description:sub(1, 1):upper() .. description:sub(2) + weather_widget:get_children_by_id("line")[1].bg = color["Grey800"] + weather_widget:get_children_by_id("speed")[1].text = speed .. " m/s" + weather_widget:get_children_by_id("humidity")[1].text = humidity .. "%" + + end + end + end + ) + end + + fetch_weather_data() + + gears.timer { + timeout = 900, + autostart = true, + callback = function() + fetch_weather_data() + end + } + + return weather_widget + +end diff --git a/awesome/src/modules/volume_controller.lua b/awesome/src/modules/volume_controller.lua index a2b7cdb..ae437e2 100644 --- a/awesome/src/modules/volume_controller.lua +++ b/awesome/src/modules/volume_controller.lua @@ -706,10 +706,18 @@ return function(s) stdout = function(line) get_input_devices() get_source_devices() + awful.spawn.with_shell("pkill pactl && pkill grep") end } ) + awesome.connect_signal( + "exit", + function() + awful.spawn.with_shell("pkill pactl && pkill grep") + end + ) + -- Get microphone volume local function get_mic_volume() awful.spawn.easy_async_with_shell( @@ -834,8 +842,10 @@ return function(s) function(volume) if volume > 0 then volume_controller:get_children_by_id("mic_volume_margin")[1].mic_volume.icon:set_image(gears.color.recolor_image(icondir .. "microphone.svg", color["LightBlue200"])) + awesome.emit_signal("update::microphone_widget", tonumber(volume), icondir .. "microphone.svg") else volume_controller:get_children_by_id("mic_volume_margin")[1].mic_volume.icon:set_image(gears.color.recolor_image(icondir .. "microphone-off.svg", color["LightBlue200"])) + awesome.emit_signal("update::microphone_widget", tonumber(volume), icondir .. "microphone-off.svg") end end ) diff --git a/awesome/src/scripts/wifi.sh b/awesome/src/scripts/wifi.sh new file mode 100755 index 0000000..e69de29 diff --git a/awesome/src/theme/user_variables.lua b/awesome/src/theme/user_variables.lua index af0494f..efac15f 100644 --- a/awesome/src/theme/user_variables.lua +++ b/awesome/src/theme/user_variables.lua @@ -8,6 +8,17 @@ local home = os.getenv("HOME") -- If you want different default programs, wallpaper path or modkey; edit this file. user_vars = { + -- Uses the openweather api https://home.openweathermap.org/api_keys + -- City ID is also from there + weather_secrets = { + key = "e71b00168ca7219563dde4514a425b14", + city_id = "2864118", + unit = "metric" -- "metric" or "imperial" + }, + + -- Do not Disturb will turn off all notifications but keep the notification-list in the notification-center + dnd = false, + -- Autotiling layouts layouts = { awful.layout.suit.tile, @@ -38,7 +49,8 @@ user_vars = { "flatpak run com.spotify.Client", "discord", "/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1", - "setxkbmap -option caps:swapescape" + "setxkbmap -option caps:swapescape", + "whatsdesk" }, -- Type 'ip a' and check your wlan and ethernet name diff --git a/awesome/src/widgets/audio.lua b/awesome/src/widgets/audio.lua index a584772..09dc3f6 100644 --- a/awesome/src/widgets/audio.lua +++ b/awesome/src/widgets/audio.lua @@ -79,6 +79,7 @@ return function(s) audio_widget.container.audio_layout.icon_margin.icon_layout.icon:set_image( gears.color.recolor_image(icon .. ".svg", color["Grey900"])) awesome.emit_signal("get::volume", volume) + awesome.emit_signal("update::volume_widget", volume, icon .. ".svg") end ) end diff --git a/awesome/src/widgets/battery.lua b/awesome/src/widgets/battery.lua index ea80907..6ed2c55 100644 --- a/awesome/src/widgets/battery.lua +++ b/awesome/src/widgets/battery.lua @@ -163,7 +163,7 @@ return function() battery_widget.container.battery_layout.icon_margin.icon_layout.icon:set_image(gears.surface.load_uncached( gears.color.recolor_image(icondir .. icon .. '.svg', "#212121"))) - + awesome.emit_signal("update::battery_widget", battery_percentage, icondir .. icon .. ".svg") end ) end diff --git a/awesome/src/widgets/cpu_info.lua b/awesome/src/widgets/cpu_info.lua index f9b6318..1a5df70 100644 --- a/awesome/src/widgets/cpu_info.lua +++ b/awesome/src/widgets/cpu_info.lua @@ -155,6 +155,7 @@ return function(widget, clock_mode) local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 cpu_usage_widget.container.cpu_layout.label.text = tostring(math.floor(diff_usage)) .. "%" + awesome.emit_signal("update::cpu_usage_widget", math.floor(diff_usage + 0.5)) total_prev = total idle_prev = idle @@ -185,6 +186,7 @@ return function(widget, clock_mode) cpu_temp.container.cpu_layout.icon_margin.icon_layout.icon:set_image(temp_icon) cpu_temp:set_bg(temp_color) cpu_temp.container.cpu_layout.label.text = math.floor(temp_num) .. "°C" + awesome.emit_signal("update::cpu_temp_widget", temp_num, temp_icon) end ) @@ -205,9 +207,9 @@ return function(widget, clock_mode) average = average + cpu_freq[i] end average = math.floor(average / #cpu_freq) - cpu_clock.container.cpu_layout.label.text = tonumber(average) .. "Mhz" + cpu_clock.container.cpu_layout.label.text = average .. "Mhz" elseif clock_mode then - cpu_clock.container.cpu_layout.label.text = tonumber(cpu_freq[clock_mode]) .. "Mhz" + cpu_clock.container.cpu_layout.label.text = cpu_freq[clock_mode] .. "Mhz" end end ) diff --git a/awesome/src/widgets/gpu_info.lua b/awesome/src/widgets/gpu_info.lua index 69b88c9..16608d0 100644 --- a/awesome/src/widgets/gpu_info.lua +++ b/awesome/src/widgets/gpu_info.lua @@ -103,6 +103,7 @@ return function(widget) 3, function(_, stdout) gpu_usage_widget.container.gpu_layout.label.text = stdout:gsub("\n", "") .. "%" + awesome.emit_signal("update::gpu_usage_widget", tonumber(stdout)) end ) @@ -131,6 +132,7 @@ return function(widget) gpu_temp_widget.container.gpu_layout.icon_margin.icon_layout.icon:set_image(temp_icon) gpu_temp_widget:set_bg(temp_color) gpu_temp_widget.container.gpu_layout.label.text = tostring(temp_num) .. "°C" + awesome.emit_signal("update::gpu_temp_widget", temp_num, temp_icon) end ) diff --git a/awesome/src/widgets/ram_info.lua b/awesome/src/widgets/ram_info.lua index 09b9c2e..14e8c74 100644 --- a/awesome/src/widgets/ram_info.lua +++ b/awesome/src/widgets/ram_info.lua @@ -64,7 +64,10 @@ return function() local MemTotal, MemFree, MemAvailable = stdout:match("(%d+)\n(%d+)\n(%d+)\n") - ram_widget.container.ram_layout.label.text = tostring(string.format("%.1f", ((MemTotal - MemAvailable) / 1024 / 1024)) .. "/" .. string.format("%.1f", (MemTotal / 1024 / 1024)) .. "GB"):gsub(",", ".") + local ram_string = tostring(string.format("%.1f", ((MemTotal - MemAvailable) / 1024 / 1024)) .. "/" .. string.format("%.1f", (MemTotal / 1024 / 1024)) .. "GB"):gsub(",", ".") + + ram_widget.container.ram_layout.label.text = ram_string + awesome.emit_signal("update::ram_widget", math.floor(((MemTotal - MemAvailable) / MemTotal * 100) + 0.5)) end ) diff --git a/awesome/src/widgets/systray.lua b/awesome/src/widgets/systray.lua index 95111d7..41945f4 100644 --- a/awesome/src/widgets/systray.lua +++ b/awesome/src/widgets/systray.lua @@ -20,7 +20,7 @@ return function(s) id = 'st' }, strategy = "exact", - layout = wibox.container.constraint, + widget = wibox.container.constraint, id = "container" }, widget = wibox.container.background,