-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathutils.lua
1126 lines (922 loc) · 35.1 KB
/
utils.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local awful = require("awful")
local gears = require("gears")
local hotkeys_popup = require("awful.hotkeys_popup")
-- Common
round_number = function(a)
return math.floor(a + 0.5)
end
math_clamp = function(value, min, max)
return math.max(min, math.min(value, max))
end
clamp_percent = function(value)
return math.max(0, math.min(value, 100))
end
lpad_string = function(str, len, char)
if char == nil then
char = ' '
end
return string.rep(char, len - #str) .. str
end
rpad_string = function(str, len, char)
if char == nil then
char = ' '
end
return str .. string.rep(char, len - #str)
end
create_today = function()
local now = os.date("*t")
now.hour = 0
now.min = 0
now.sec = 0
return os.time(now)
end
create_local_datetime = function(year, month, day, hour, min, sec)
local now = os.date("*t")
if year ~= nil then
now.year = year
end
if month ~= nil then
now.month = month
end
if day ~= nil then
now.day = day
end
if hour ~= nil then
now.hour = hour
end
if min ~= nil then
now.min = min
end
if sec ~= nil then
now.sec = sec
end
now.isdst = false
return os.time(now) + get_timezone_offset()
end
get_timezone_offset = function()
local date_utc = os.date("!*t")
local date_local = os.date("*t")
date_local.isdst = false
return os.difftime(os.time(date_local), os.time(date_utc))
end
get_color_between = function(color1Hex, color2Hex, weight)
if color1Hex:len() ~= 7 or color2Hex:len() ~= 7 then
return nil
end
local weight1 = 1 - weight
local weight2 = weight
local color1Rgb = hex_to_rgb(color1Hex)
local color2Rgb = hex_to_rgb(color2Hex)
return rgb_to_hex({
round_number(color1Rgb[1] * weight1 + color2Rgb[1] * weight2),
round_number(color1Rgb[2] * weight1 + color2Rgb[2] * weight2),
round_number(color1Rgb[3] * weight1 + color2Rgb[3] * weight2),
})
end
hex_to_rgb = function(hexColor)
if hexColor:len() ~= 7 or string.sub(hexColor, 1, 1) ~= "#" then
return nil
end
local redHex = string.sub(hexColor, 2, 3)
local greenHex = string.sub(hexColor, 4, 5)
local blueHex = string.sub(hexColor, 6, 7)
local red = tonumber(redHex, 16)
local green = tonumber(greenHex, 16)
local blue = tonumber(blueHex, 16)
return {red, green, blue}
end
rgb_to_hex = function(rgbColor)
local result = "#"
for _,value in pairs(rgbColor) do
local hex = ''
while (value > 0) do
local index = math.fmod(value, 16) + 1
value = math.floor(value / 16)
hex = string.sub('0123456789ABCDEF', index, index) .. hex
end
if (string.len(hex) == 0) then
hex = '00'
elseif (string.len(hex) == 1) then
hex = '0' .. hex
end
result = result .. hex
end
return result
end
run_command_sync = function(command)
local result = ""
local command_subprocess = io.popen(command)
if command_subprocess ~= nil then
result = command_subprocess:read('*all')
command_subprocess:close()
end
return result
end
get_percent_number_digits_count = function(number)
return number < 10 and 1 or (number < 100 and 2 or 3)
end
read_file_content_async = function(file_path, callback)
awful.spawn.easy_async("cat " .. file_path, callback)
end
read_file_content = function(file_path)
local file_content
local file = io.open(file_path, "r")
if file ~= nil then
file_content = file:read("*all")
file:close()
end
return file_content
end
write_file_content = function(file_path, content)
local file = io.open(file_path, "w")
if file ~= nil then
file:write(content)
file:close()
end
end
table_map = function(tbl, f)
local t = {}
for k,v in pairs(tbl) do
t[k] = f(v)
end
return t
end
get_screen = function(index)
for s in screen do
if s.index == index then
return s
end
end
return nil
end
get_clients_names = function()
local client_names = {}
for s in screen do
for _,t in pairs(s.tags) do
for _,c in pairs(t:clients()) do
table.insert(client_names, c.name)
break
end
end
end
return client_names
end
is_client_in_tag = function(c, tag)
local ctags = c:tags()
for _, v in ipairs(ctags) do
if v == tag then
return true
end
end
return false
end
get_program_installed = function(program_name, callback)
awful.spawn.easy_async("which " .. program_name, function(stdout, stderr, exit_reason, exit_code)
callback(exit_code == 0)
end)
end
set_gtk_theme_mode = function(theme_mode)
local is_dark_theme = theme_mode == "dark"
awful.spawn("gsettings set org.gnome.desktop.interface color-scheme prefer-" .. theme_mode)
local gtk2_settings_file_path = gears.filesystem.get_xdg_config_home() .. "../.gtkrc-2.0"
local gtk2_settings_file_content = read_file_content(gtk2_settings_file_path)
local new_gtk2_settings_file_content = gtk2_settings_file_content
local gtk3_settings_file_path = gears.filesystem.get_xdg_config_home() .. "gtk-3.0/settings.ini"
local gtk3_settings_file_content = read_file_content(gtk3_settings_file_path)
if gtk3_settings_file_content ~= nil then
local new_gtk3_settings_file_content = gtk3_settings_file_content
local pattern = "gtk[-]application[-]prefer[-]dark[-]theme=(.-)%c"
local new_setting = "gtk-application-prefer-dark-theme=" .. tostring(is_dark_theme) .. "\n"
if new_gtk3_settings_file_content:match(pattern) ~= nil then
new_gtk3_settings_file_content = new_gtk3_settings_file_content:gsub(pattern, new_setting)
else
new_gtk3_settings_file_content = new_gtk3_settings_file_content .. "\n" .. new_setting
end
local pattern = "gtk[-]theme[-]name=(.-)%c"
local gtk_theme_name = new_gtk3_settings_file_content:match(pattern)
if gears.filesystem.is_dir("/usr/share/themes/" .. gtk_theme_name) then
local gtk_theme_base_name = gtk_theme_name:gsub("-dark", "")
local gtk_theme_dark_name = gtk_theme_base_name .. "-dark"
local gtk_theme_new_name = is_dark_theme and gtk_theme_dark_name or gtk_theme_base_name
if gears.filesystem.is_dir("/usr/share/themes/" .. gtk_theme_new_name) then
local new_setting = "gtk-theme-name=" .. tostring(gtk_theme_new_name) .. "\n"
new_gtk3_settings_file_content = new_gtk3_settings_file_content:gsub(pattern, new_setting)
awful.spawn("gsettings set org.gnome.desktop.interface gtk-theme " .. gtk_theme_new_name)
local pattern = "include \"/usr/share/themes/.-/gtk[-]2.0/gtkrc\"%c"
local new_setting = "include \"/usr/share/themes/" .. gtk_theme_new_name .. "/gtk-2.0/gtkrc\"" .. "\n"
new_gtk2_settings_file_content = new_gtk2_settings_file_content:gsub(pattern, new_setting)
end
end
local pattern = "gtk[-]icon[-]theme[-]name=(.-)%c"
local gtk_icon_theme_name = new_gtk3_settings_file_content:match(pattern)
if gears.filesystem.is_dir("/usr/share/icons/" .. gtk_icon_theme_name) then
local gtk_icon_theme_base_name = gtk_icon_theme_name:gsub("-dark", "")
local gtk_icon_theme_dark_name = gtk_icon_theme_base_name .. "-dark"
local gtk_icon_theme_new_name = is_dark_theme and gtk_icon_theme_dark_name or gtk_icon_theme_base_name
if gears.filesystem.is_dir("/usr/share/icons/" .. gtk_icon_theme_new_name) then
local new_setting = "gtk-icon-theme-name=" .. tostring(gtk_icon_theme_new_name) .. "\n"
new_gtk3_settings_file_content = new_gtk3_settings_file_content:gsub(pattern, new_setting)
awful.spawn("gsettings set org.gnome.desktop.interface icon-theme " .. gtk_icon_theme_new_name)
end
end
write_file_content(gtk3_settings_file_path, new_gtk3_settings_file_content)
write_file_content(gtk2_settings_file_path, new_gtk2_settings_file_content)
end
end
set_ulauncher_theme_mode = function(theme_mode)
local is_dark_theme = theme_mode == "dark"
local ulauncher_settings_file_path = gears.filesystem.get_xdg_config_home() .. "ulauncher/settings.json"
local ulauncher_settings_file_content = read_file_content(ulauncher_settings_file_path)
if ulauncher_settings_file_content == nil then
return
end
local pattern = "\"theme[-]name\": \"(.-)\"%c"
local ulauncher_theme_name = ulauncher_settings_file_content:match(pattern)
local new_theme_name = ulauncher_theme_name:gsub(is_dark_theme and "light" or "dark", theme_mode)
local new_setting = "\"theme-name\": \"" .. new_theme_name .. "\"" .. "\n"
local new_ulauncher_settings_file_content = ulauncher_settings_file_content:gsub(pattern, new_setting)
write_file_content(ulauncher_settings_file_path, new_ulauncher_settings_file_content)
if new_theme_name ~= ulauncher_theme_name then
awful.spawn("pkill ulauncher")
awful.spawn("ulauncher --hide-window")
end
end
set_obs_theme_mode = function(theme_mode)
local is_dark_theme = theme_mode == "dark"
local obs_settings_file_path = gears.filesystem.get_xdg_config_home() .. "obs-studio/global.ini"
local obs_settings_file_content = read_file_content(obs_settings_file_path)
if obs_settings_file_content == nil then
return
end
local pattern = "CurrentTheme2=(.-)%c"
local new_theme_name = is_dark_theme and "Dark" or "System"
local new_setting = "CurrentTheme2=" .. new_theme_name .. "\n"
local new_obs_settings_file_content = obs_settings_file_content:gsub(pattern, new_setting)
write_file_content(obs_settings_file_path, new_obs_settings_file_content)
end
set_remmina_theme_mode = function(theme_mode)
local is_dark_theme = theme_mode == "dark"
local remmina_settings_file_path = gears.filesystem.get_xdg_config_home() .. "remmina/remmina.pref"
local remmina_settings_file_content = read_file_content(remmina_settings_file_path)
if remmina_settings_file_content == nil then
return
end
local pattern = "dark_theme=(.-)%c"
local new_setting = "dark_theme=" .. tostring(is_dark_theme) .. "\n"
local new_remmina_settings_file_content = remmina_settings_file_content:gsub(pattern, new_setting)
write_file_content(remmina_settings_file_path, new_remmina_settings_file_content)
end
-- Brightness
get_system_brightness = function(callback)
awful.spawn.easy_async("xfpm-power-backlight-helper --get-brightness", function(current_brightness_string)
awful.spawn.easy_async("xfpm-power-backlight-helper --get-max-brightness", function(max_brightness_string)
local current_brightness = tonumber(current_brightness_string)
local max_brightness = tonumber(max_brightness_string)
callback(math.floor(current_brightness / max_brightness * 100))
end)
end)
end
set_system_brightness = function(step_percent, increase, callback)
awful.spawn.easy_async("xfpm-power-backlight-helper --get-brightness", function(current_brightness_string)
awful.spawn.easy_async("xfpm-power-backlight-helper --get-max-brightness", function(max_brightness_string)
local current_brightness = tonumber(current_brightness_string)
local max_brightness = tonumber(max_brightness_string)
local step = math.floor(step_percent * max_brightness / 100)
local new_brightness = 0
if increase then
new_brightness = current_brightness + step;
else
new_brightness = current_brightness - step;
end
local new_brightness = math.min(math.max(new_brightness, 0), max_brightness)
awful.spawn.easy_async("pkexec xfpm-power-backlight-helper --set-brightness=" .. new_brightness, function()
callback(math.floor(new_brightness / max_brightness * 100))
end)
end)
end)
end
-- Audio
local cached_audio_server = nil
get_audio_server = function(callback)
if cached_audio_server ~= nil then
callback(cached_audio_server)
else
get_program_installed("pactl", function(is_pactl_installed)
if is_pactl_installed then
cached_audio_server = "pactl"
callback("pactl")
else
get_program_installed("pacmd", function(is_pacmd_installed)
if is_pacmd_installed then
cached_audio_server = "pacmd"
callback("pacmd")
else
callback(nil)
end
end)
end
end)
end
end
get_audio_server_installed = function(callback)
get_audio_server(function(audio_server)
callback(audio_server ~= nil)
end)
end
get_default_sink = function(callback)
awful.spawn.easy_async("pactl get-default-sink", function(default_sink_stdout)
local default_sink = default_sink_stdout:gsub("\n", "")
callback(default_sink)
end)
end
get_default_source = function(callback)
awful.spawn.easy_async("pactl get-default-source", function(default_source_stdout)
local default_source = default_source_stdout:gsub("\n", "")
callback(default_source)
end)
end
get_sink_volume = function(callback)
get_default_sink(function(default_sink)
awful.spawn.easy_async("pactl get-sink-volume " .. default_sink, function(current_default_sink_volume_output)
local current_default_sink_volume = tonumber(current_default_sink_volume_output:match("(%d+)%%"))
callback(current_default_sink_volume)
end)
end)
end
get_source_volume = function(callback)
get_default_source(function(default_source)
awful.spawn.easy_async("pactl get-source-volume " .. default_source, function(current_default_source_volume_output)
local current_default_source_volume = tonumber(current_default_source_volume_output:match("(%d+)%%"))
callback(current_default_source_volume)
end)
end)
end
set_sink_volume = function(step, increase, callback)
get_default_sink(function(default_sink)
awful.spawn.easy_async("pactl get-sink-volume " .. default_sink, function(current_default_sink_volume_output)
local current_default_sink_volume = tonumber(current_default_sink_volume_output:match("(%d+)%%"))
local command = "pactl set-sink-volume " .. default_sink;
local new_volume_value = 0
if increase == nil then
new_volume_value = clamp_percent(step)
elseif increase then
new_volume_value = clamp_percent(current_default_sink_volume + step)
else
new_volume_value = clamp_percent(current_default_sink_volume - step)
end
command = command .. " " .. new_volume_value .. "%"
awful.spawn.easy_async(command, callback)
end)
end)
end
set_source_volume = function(step, increase, callback)
get_default_source(function(default_source)
awful.spawn.easy_async("pactl get-source-volume " .. default_source, function(current_default_source_volume_output)
local current_default_source_volume = tonumber(current_default_source_volume_output:match("(%d+)%%"))
local command = "pactl set-source-volume " .. default_source;
local new_volume_value = 0
if increase == nil then
new_volume_value = clamp_percent(step)
elseif increase then
new_volume_value = clamp_percent(current_default_source_volume + step)
else
new_volume_value = clamp_percent(current_default_source_volume - step)
end
command = command .. " " .. new_volume_value .. "%"
awful.spawn.easy_async(command, callback)
end)
end)
end
parse_audio_server_devices = function(audio_server_output, default_device)
local device_index_pattern = "^%a+ #(%d+)"
local devices = {}
local device
local properties
local profiles
local ports
local port
local port_properties
local in_device = false
local in_properties = false
local in_ports = false
local in_profiles = false
local in_port_properties = false
local matches = audio_server_output:gmatch("[^\r\n]+")
local lines = {}
for match in matches do
table.insert(lines, match)
end
for i = #lines - 1, 1, -1 do
local line = lines[i]
local next_line = lines[i + 1]
if not string.match(next_line, ":") and not string.match(next_line, "=") and not string.match(next_line, device_index_pattern) then
lines[i] = line .. next_line
table.remove(lines, i + 1)
end
end
for _,line in ipairs(lines) do
if string.match(line, device_index_pattern) then
in_device = true
in_properties = false
in_ports = false
in_profiles = false
in_port_properties = false
device = {
id = line:match(device_index_pattern)
}
table.insert(devices, device)
goto continue
end
if string.match(line, "%s+Properties:") then
in_device = false
in_properties = not in_ports
in_profiles = false
in_port_properties = in_ports
if in_ports then
port_properties = {}
ports[port].properties = port_properties
else
properties = {}
device.properties = properties
end
in_ports = false
goto continue
end
if string.match(line, "%s+Profiles:") then
in_device = false
in_properties = false
in_ports = false
in_profiles = true
in_port_properties = false
profiles = {}
device.profiles = profiles
goto continue
end
if string.match(line, "%s+Active Profile:") then
in_device = false
in_properties = false
in_ports = false
in_profiles = false
in_port_properties = false
device.active_profile = line:match(": (.+)"):gsub("<",""):gsub(">","")
goto continue
end
if string.match(line, "%s+Ports:") then
in_device = false
in_properties = false
in_ports = true
in_profiles = false
in_port_properties = false
ports = {}
device.ports = ports
goto continue
end
if string.match(line, "%s+Active Port: ") then
in_device = false
in_properties = false
in_ports = false
in_profiles = false
in_port_properties = false
device.active_port = line:match(": (.+)"):gsub("<",""):gsub(">","")
goto continue
end
if in_device then
local key = line:match("%s+(.+):")
local value = line:match(": (.+)"):gsub("<",""):gsub(">","")
device[key] = value
end
if in_properties then
local matches = string.gmatch(line, "([^=]+)")
local parts = {}
for match in matches do
table.insert(parts, match)
end
local key = parts[1]:gsub("%s", ""):gsub("%.", "_"):gsub("-", "_"):gsub(":", ""):gsub("%s+$", "")
local value = parts[2]
if value ~= nil then
value = value:gsub("\"", ""):gsub("^%s+", ""):gsub(" Analog Stereo", "")
end
properties[key] = value
end
if in_profiles then
local key = line:match("%s+(%S+):")
local value = line:match(": (.+) %(")
profiles[key] = value
end
if in_port_properties and string.match(line, ":") then
in_port_properties = false
in_ports = true
end
if in_port_properties then
local matches = string.gmatch(line, "([^=]+)")
local parts = {}
for match in matches do
table.insert(parts, match)
end
local key = parts[1]:gsub(" ", ""):gsub("%.", "_"):gsub("-", "_"):gsub(":", ""):gsub("%s+$", "")
local value = parts[2]
if value ~= nil then
value = value:gsub("\"", ""):gsub("^%s+", "")
end
port_properties[key] = value
end
if in_ports then
local key = line:match("(%S+):")
local value = line:match(": (.+) %(")
port = key
ports[port] = {
name = value
}
end
::continue::
end
for _,device in ipairs(devices) do
device.is_default = device["Name"] == default_device
end
return devices
end
audio_previous = function()
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous", false)
end
audio_next = function()
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next", false)
end
audio_toggle_play_pause = function()
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause", false)
end
audio_stop = function()
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Stop", false)
end
-- Bluetooth
local get_bluetooth_adapter_index = function(callback)
awful.spawn.easy_async("rfkill list bluetooth", function(rfkill_list_bluetooth_output)
callback(string.match(rfkill_list_bluetooth_output, "^(%d+): "))
end)
end
local bluetooth_status_subscribers = {}
subscribe_bluetooth_status = function(callback)
table.insert(bluetooth_status_subscribers, callback)
if #bluetooth_status_subscribers == 1 then
get_bluetooth_adapter_index(function(index)
awful.spawn.with_line_callback("rfkill event", {
stdout = function(line)
if string.match(line, "idx " .. index .. " ") then
for _,bluetooth_status_subscriber in ipairs(bluetooth_status_subscribers) do
bluetooth_status_subscriber(string.match(line, "soft 0 hard 0") ~= nil)
end
end
end
})
end)
end
end
is_bluetooth_device_connected = function(callback)
awful.spawn.easy_async("bluetoothctl info", function(bluetoothctl_info_output)
callback(string.match(bluetoothctl_info_output, "Missing device address argument\n") == nil)
end)
end
get_bluetooth_device = function(callback)
is_bluetooth_device_connected(function(is_connected)
if not is_connected then
callback({
connected = false
})
end
awful.spawn.easy_async("bluetoothctl info", function(bluetoothctl_info_output)
local bluetooth_device = {
mac_address = bluetoothctl_info_output:match("^Device (.-) "),
name = bluetoothctl_info_output:match("Name: (.-)\n"),
alias = bluetoothctl_info_output:match("Alias: (.-)\n"),
paired = bluetoothctl_info_output:match("Paired: yes") ~= nil,
bonded = bluetoothctl_info_output:match("Bonded: yes") ~= nil,
trusted = bluetoothctl_info_output:match("Trusted: yes") ~= nil,
blocked = bluetoothctl_info_output:match("Blocked: yes") ~= nil,
connected = bluetoothctl_info_output:match("Connected: yes") ~= nil,
battery_percentage = tonumber(bluetoothctl_info_output:match("Battery Percentage: %w+ %((%d-)%)"))
}
callback(bluetooth_device)
end)
end)
end
-- Show help
show_help = function()
hotkeys_popup.show_help(nil, awful.screen.focused())
end
-- Change focused client
change_focused_client = function(direction)
restore_clients()
awful.client.focus.byidx(direction)
if client.focus then client.focus:raise() end
end
-- Move client to next screen
move_client_to_next_screen = function(c)
c:move_to_screen()
end
-- Minimize client
minimize_client = function(c)
c.minimized = true
end
-- Maximize client
maximize_client = function(c)
c.maximized_horizontal = not c.maximized_horizontal
c.maximized_vertical = not c.maximized_vertical
end
maximize_client_to_multiple_monitor = function(c)
c.floating = true
local focused_screen_geometry = awful.screen.focused().geometry
focused_screen_geometry.x2 = focused_screen_geometry.x + focused_screen_geometry.width
focused_screen_geometry.y2 = focused_screen_geometry.y + focused_screen_geometry.height
for s in screen do
local screen_geometry = s.geometry
focused_screen_geometry.x = math.min(focused_screen_geometry.x, screen_geometry.x)
focused_screen_geometry.y = math.min(focused_screen_geometry.y, screen_geometry.y)
focused_screen_geometry.x2 = math.max(focused_screen_geometry.x2, screen_geometry.x + screen_geometry.width)
focused_screen_geometry.y2 = math.max(focused_screen_geometry.y2, screen_geometry.y + screen_geometry.height)
end
c:geometry{
x = focused_screen_geometry.x,
y = focused_screen_geometry.y,
width = focused_screen_geometry.x2 - focused_screen_geometry.x,
height = focused_screen_geometry.y2 - focused_screen_geometry.y
}
end
-- Toggle client fullscreen
toggle_fullscreen = function(c)
c.fullscreen = not c.fullscreen
end
-- Do action for tag by index if tag is defined
do_for_tag = function(tag_index, action)
local screen = awful.screen.focused()
local tag = screen.tags[tag_index]
if tag then
action(tag)
end
end
-- Minimize all clients on focused tag
minimize_clients = function()
local tag = awful.tag.selected()
for i = 1, #tag:clients() do
minimize_client(tag:clients()[i])
end
end
-- Restore all clients on focused tag
restore_clients = function()
local tag = awful.tag.selected()
for i = 1, #tag:clients() do
tag:clients()[i].minimized = false
end
end
-- Toggle minimize/restore all clients on focused tag
toogle_minimize_restore_clients = function()
if client.focus then
minimize_clients()
else
restore_clients()
end
end
-- Apply snap edge feature to client
snap_edge = function(client, where)
local screen_workarea = screen[client.screen].workarea
local workarea = {
x_min = screen_workarea.x,
x_max = screen_workarea.x + screen_workarea.width,
y_min = screen_workarea.y,
y_max = screen_workarea.y + screen_workarea.height,
width = screen_workarea.width,
height = screen_workarea.height,
half_width = screen_workarea.width * 0.5,
half_height = screen_workarea.height * 0.5
}
local client_geometry = client:geometry()
local axis_border_width = client.border_width * 2
if where == 'top' then
client_geometry.width = workarea.width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_min
client_geometry.y = workarea.y_min
awful.placement.center_horizontal(client)
elseif where == 'top_right' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_max - client_geometry.width
client_geometry.y = workarea.y_min
elseif where == 'right' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.height
client_geometry.x = workarea.x_max - client_geometry.width
client_geometry.y = workarea.y_min
elseif where == 'bottom_right' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_max - client_geometry.width
client_geometry.y = workarea.y_max - client_geometry.height
elseif where == 'bottom' then
client_geometry.width = workarea.width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_min
client_geometry.y = workarea.y_max - client_geometry.height
awful.placement.center_horizontal(client)
elseif where == 'bottom_left' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_min
client_geometry.y = workarea.y_max - client_geometry.height
elseif where == 'left' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.height
client_geometry.x = workarea.x_min
client_geometry.y = workarea.y_min
elseif where == 'top_left' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_min
client_geometry.y = workarea.y_min
elseif where == 'centered' then
client_geometry.width = workarea.half_width - axis_border_width
client_geometry.height = workarea.half_height - axis_border_width
client_geometry.x = workarea.x_min + (workarea.x_max - workarea.x_min - client_geometry.width) * 0.5
client_geometry.y = workarea.y_min + (workarea.y_max - workarea.y_min - client_geometry.height) * 0.5
elseif where == nil then
client:geometry(client_geometry)
return
end
client.floating = true
if client.maximized then
client.maximized = false
end
client:geometry(client_geometry)
awful.placement.no_offscreen(client)
end
-- Move client
move_client = function(c)
c.maximized = false
client.focus = c
c:raise()
awful.mouse.client.move(c)
end
-- Resize client
resize_client = function(c)
c.maximized = false
client.focus = c
c:raise()
awful.mouse.client.resize(c)
end
get_geolocation = function(callback)
awful.spawn.easy_async("/usr/lib/geoclue-2.0/demos/where-am-i", function(geoclue_stdout)
local latitude_string = geoclue_stdout:match("Latitude:%s+(%S+)"):gsub(",", "."):gsub("°", "")
local longitude_string = geoclue_stdout:match("Longitude:%s+(%S+)"):gsub(",", "."):gsub("°", "")
local latitude = tonumber(latitude_string)
local longitude = tonumber(longitude_string)
callback(latitude, longitude)
end)
end
local get_days_count = function(year)
return os.date("%j", os.time({
year = year,
month = 12,
day = 31
}))
end
local get_fractional_year = function(hour)
local day_of_year = os.date("%j")
local current_year_days_count = get_days_count(os.date("%Y"))
return 2 * math.pi / current_year_days_count * (day_of_year - 1 + (hour - 12) / 24)
end
local get_time_equation_minutes = function(fractional_year)
return 229.18 * (0.000075 + 0.001868 * math.cos(fractional_year) - 0.032077 * math.sin(fractional_year) - 0.014615 * math.cos(2 * fractional_year) - 0.040849 * math.sin(2 * fractional_year))
end
local get_declination_angle = function(fractional_year)
return 0.006918 - 0.399912 * math.cos(fractional_year) + 0.070257 * math.sin(fractional_year) - 0.006758 * math.cos(2 * fractional_year) + 0.000907 * math.sin(2 * fractional_year) - 0.002697 * math.cos(3 * fractional_year) + 0.00148 * math.sin(3 * fractional_year)
end
local get_hour_angle = function(hour, latitude, to_sunrise)
local fractional_year = get_fractional_year(hour)
local declination_angle = get_declination_angle(fractional_year)
local hour_angle_sign = to_sunrise and 1 or -1
return hour_angle_sign * math.acos(math.cos(math.rad(90.833)) / (math.cos(math.rad(latitude)) * math.cos(declination_angle)) - math.tan(math.rad(latitude)) * math.tan(declination_angle))
end
local get_sunrise_sunset_time_minutes = function(latitude, longitude, is_sunrise)
local hour = os.date("%H")
local hour_angle = get_hour_angle(hour, latitude, is_sunrise)
local fractional_year = get_fractional_year(hour)
local time_equation_minutes = get_time_equation_minutes(fractional_year)
return 720 - 4 * (longitude + math.deg(hour_angle)) - time_equation_minutes
end
get_sunrise_time_minutes = function(latitude, longitude)
return get_sunrise_sunset_time_minutes(latitude, longitude, true)
end
get_sunset_time_minutes = function(latitude, longitude)
return get_sunrise_sunset_time_minutes(latitude, longitude, false)
end
get_redshift_installed = function(callback)
get_program_installed("redshift", callback)
end
local cached_redshift_dawn_time = nil
get_redshift_dawn_time = function()
if cached_redshift_dawn_time ~= nil then
return cached_redshift_dawn_time
end
local redshift_settings_file_path = gears.filesystem.get_xdg_config_home() .. "redshift/redshift.conf"
local redshift_settings_file_content = read_file_content(redshift_settings_file_path)
if redshift_settings_file_content == nil then
return nil