local wibox = require('wibox') local gears = require('gears') local awful = require('awful') local naughty = require('naughty') local beautiful = require('beautiful') local filesystem = gears.filesystem local config_dir = filesystem.get_configuration_dir() local dpi = beautiful.xresources.apply_dpi local apps = require('configuration.apps') local widget_icon_dir = config_dir .. 'configuration/user-profile/' local config = require('configuration.config') -- Add paths to package.cpath package.cpath = package.cpath .. ';' .. config_dir .. '/library/?.so;' .. '/usr/lib/lua-pam/?.so;' -- Configuration table local locker_config = { -- Clock mode military_clock = config.module.lockscreen.military_clock or false, -- Fallback password fallback_password = function() return config.module.lockscreen.fallback_password or 'toor' end, -- Capture a picture using webcam capture_intruder = config.module.lockscreen.capture_intruder or false, -- Save location, auto creates face_capture_dir = config.module.lockscreen.face_capture_dir or '$(xdg-user-dir PICTURES)/Intruders/', -- Blur background blur_background = config.module.lockscreen.blur_background or false, -- Background directory bg_dir = config.module.lockscreen.bg_dir or (config_dir .. 'theme/wallpapers/'), -- Default background bg_image = config.module.lockscreen.bg_image or 'morning-wallpaper.jpg', -- /tmp directory tmp_wall_dir = config.module.lockscreen.tmp_wall_dir or '/tmp/awesomewm/' .. os.getenv('USER') .. '/' } -- Useful variables (DO NOT TOUCH THESE) local input_password = nil local lock_again = nil local type_again = true local capture_now = locker_config.capture_intruder local locked_tag = nil local uname_text = wibox.widget { id = 'uname_text', markup = '$USER', font = 'Inter Bold 12', align = 'center', valign = 'center', widget = wibox.widget.textbox } local caps_text = wibox.widget { id = 'uname_text', markup = 'Caps Lock is on', font = 'Inter Italic 10', align = 'center', valign = 'center', opacity = 0.0, widget = wibox.widget.textbox } local profile_imagebox = wibox.widget { id = 'user_icon', image = widget_icon_dir .. 'default.svg', resize = true, forced_height = dpi(130), forced_width = dpi(130), clip_shape = gears.shape.circle, widget = wibox.widget.imagebox } local clock_format = '%H:%M' if not locker_config.military_clock then clock_format = '%I:%M %p' end -- Create clock widget local time = wibox.widget.textclock(clock_format, 1) local wanted_text = wibox.widget { markup = 'INTRUDER ALERT!', font = 'Inter Bold 12', align = 'center', valign = 'center', widget = wibox.widget.textbox } local msg_table = { 'This incident will be reported.', 'We are watching you.', 'We know where you live.', 'RUN!', 'Yamete, Oniichan~ uwu', 'This will self-destruct in 5 seconds!', 'Image successfully sent!', 'You\'re doomed!', 'Authentication failed!', 'I am watching you.', 'I know where you live.', 'RUN!', 'Your parents must be proud of you.' } local wanted_msg = wibox.widget { markup = 'This incident will be reported!', font = 'Inter Regular 10', align = 'center', valign = 'center', widget = wibox.widget.textbox } local wanted_image = wibox.widget { image = widget_icon_dir .. 'default.svg', resize = true, forced_height = dpi(120), clip_shape = gears.shape.rounded_rect, widget = wibox.widget.imagebox } local date_value = function() local ordinal = nil local date = os.date('%d') local day = os.date('%A') local month = os.date('%B') local first_digit = string.sub(date, 0, 1) local last_digit = string.sub(date, -1) if first_digit == '0' then date = last_digit end if last_digit == '1' and date ~= '11' then ordinal = 'st' elseif last_digit == '2' and date ~= '12' then ordinal = 'nd' elseif last_digit == '3' and date ~= '13' then ordinal = 'rd' else ordinal = 'th' end return date .. ordinal .. ' of ' .. month .. ', ' .. day end local date = wibox.widget { markup = date_value(), font = 'Inter Bold 20', align = 'center', valign = 'center', widget = wibox.widget.textbox } local circle_container = wibox.widget { bg = beautiful.transparent, forced_width = dpi(140), forced_height = dpi(140), shape = gears.shape.circle, widget = wibox.container.background } local locker_arc = wibox.widget { bg = beautiful.transparent, forced_width = dpi(140), forced_height = dpi(140), shape = function(cr, width, height) gears.shape.arc(cr, width, height, dpi(5), 0, (math.pi / 2), false, false) end, widget = wibox.container.background } local rotate_container = wibox.container.rotate() local locker_widget = wibox.widget { { locker_arc, widget = rotate_container }, layout = wibox.layout.fixed.vertical } -- Rotation direction table local rotation_direction = {'north', 'west', 'south', 'east'} -- Red, Green, Yellow, Blue local red = beautiful.system_red_dark local green = beautiful.system_green_dark local yellow = beautiful.system_yellow_dark local blue = beautiful.system_blue_dark -- Color table local arc_color = {red, green, yellow, blue} -- Processes local locker = function(s) local lockscreen = wibox { screen = s, visible = false, ontop = true, type = 'splash', width = s.geometry.width, height = s.geometry.height, bg = beautiful.background, fg = beautiful.fg_normal } -- Update username textbox awful.spawn.easy_async_with_shell( [[ sh -c ' fullname="$(getent passwd `whoami` | cut -d ':' -f 5 | cut -d ',' -f 1 | tr -d "\n")" if [ -z "$fullname" ]; then printf "$(whoami)@$(hostname)" else printf "$fullname" fi ' ]], function(stdout) stdout = stdout:gsub('%\n','') uname_text:set_markup(stdout) end ) local update_profile_pic = function() awful.spawn.easy_async_with_shell( apps.utils.update_profile, function(stdout) stdout = stdout:gsub('%\n','') if not stdout:match('default') then profile_imagebox:set_image(stdout) else profile_imagebox:set_image(widget_icon_dir .. 'default.svg') end end ) end -- Update image gears.timer.start_new( 2, function() update_profile_pic() end ) local wanted_poster = awful.popup { widget = { { { wanted_text, { nil, wanted_image, nil, expand = 'none', layout = wibox.layout.align.horizontal }, wanted_msg, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, margins = dpi(20), widget = wibox.container.margin }, bg = beautiful.background, shape = gears.shape.rounded_rect, widget = wibox.container.background }, bg = beautiful.transparent, type = 'utility', ontop = true, shape = gears.shape.rectangle, maximum_width = dpi(250), maximum_height = dpi(250), hide_on_right_click = false, preferred_anchors = {'middle'}, visible = false } -- Place wanted poster at the bottom of primary screen awful.placement.top( wanted_poster, { margins = { top = dpi(10) } } ) -- Check Capslock state local check_caps = function() awful.spawn.easy_async_with_shell( 'xset q | grep Caps | cut -d: -f3 | cut -d0 -f1 | tr -d \' \'', function(stdout) if stdout:match('on') then caps_text.opacity = 1.0 else caps_text.opacity = 0.0 end caps_text:emit_signal('widget::redraw_needed') end ) end -- Rotate the color arc on random direction local locker_arc_rotate = function() local direction = rotation_direction[math.random(#rotation_direction)] local color = arc_color[math.random(#arc_color)] rotate_container.direction = direction locker_arc.bg = color rotate_container:emit_signal('widget::redraw_needed') locker_arc:emit_signal('widget::redraw_needed') locker_widget:emit_signal('widget::redraw_needed') end -- Check webcam local check_webcam = function() awful.spawn.easy_async_with_shell( 'ls -l /dev/video* | grep /dev/video0', function(stdout) if not locker_config.capture_intruder then capture_now = false return end if not stdout:match('/dev/video0') then capture_now = false else capture_now = true end end ) end check_webcam() -- Snap an image of the intruder local intruder_capture = function() local capture_image = [[ save_dir="]] .. locker_config.face_capture_dir .. [[" date="$(date +%Y%m%d_%H%M%S)" file_loc="${save_dir}SUSPECT-${date}.png" if [ ! -d "$save_dir" ]; then mkdir -p "$save_dir"; fi ffmpeg -f video4linux2 -s 800x600 -i /dev/video0 -ss 0:0:2 -frames 1 "${file_loc}" canberra-gtk-play -i camera-shutter & echo "${file_loc}" ]] -- Capture the filthy intruder face awful.spawn.easy_async_with_shell( capture_image, function(stdout) circle_container.bg = beautiful.transparent -- Humiliate the intruder by showing his/her hideous face wanted_image:set_image(stdout:gsub('%\n','')) wanted_msg:set_markup(msg_table[math.random(#msg_table)]) wanted_poster.visible= true awful.placement.top( wanted_poster, { margins = { top = dpi(10) } } ) wanted_image:emit_signal('widget::redraw_needed') type_again = true end ) end -- Login failed local stoprightthereyoucriminalscum = function() circle_container.bg = red .. 'AA' if capture_now then intruder_capture() else gears.timer.start_new( 1, function() circle_container.bg = beautiful.transparent type_again = true end ) end end -- Login successful local generalkenobi_ohhellothere = function() circle_container.bg = green .. 'AA' -- Add a little delay before unlocking completely gears.timer.start_new( 1, function() if capture_now then -- Hide wanted poster wanted_poster.visible = false end -- Hide all the lockscreen on all screen for s in screen do if s.index == 1 then s.lockscreen.visible = false else s.lockscreen_extended.visible = false end end circle_container.bg = beautiful.transparent lock_again = true type_again = true -- Select old tag -- And restore minimized focused client if there's any if locked_tag then locked_tag.selected = true locked_tag = nil end local c = awful.client.restore() if c then c:emit_signal('request::activate') c:raise() end end ) end -- A backdoor. -- Sometimes I'm too lazy to type so I decided to create this. -- Sometimes my genius is... it's almost frightening. local back_door = function() generalkenobi_ohhellothere() end -- Check module if valid local module_check = function(name) if package.loaded[name] then return true else for _, searcher in ipairs(package.searchers or package.loaders) do local loader = searcher(name) if type(loader) == 'function' then package.preload[name] = loader return true end end return false end end -- Password/key grabber local password_grabber = awful.keygrabber { auto_start = true, stop_event = 'release', mask_event_callback = true, keybindings = { awful.key { modifiers = {'Control'}, key = 'u', on_press = function() input_password = nil end }, awful.key { modifiers = {'Mod1', 'Mod4', 'Shift', 'Control'}, key = 'Return', on_press = function(self) if not type_again then return end self:stop() -- Call backdoor back_door() end } }, keypressed_callback = function(self, mod, key, command) if not type_again then return end -- Clear input string if key == 'Escape' then -- Clear input threshold input_password = nil return end -- Accept only the single charactered key -- Ignore 'Shift', 'Control', 'Return', 'F1', 'F2', etc., etc. if #key == 1 then locker_arc_rotate() if input_password == nil then input_password = key return end input_password = input_password .. key end end, keyreleased_callback = function(self, mod, key, command) locker_arc.bg = beautiful.transparent locker_arc:emit_signal('widget::redraw_needed') if key == 'Caps_Lock' then check_caps() return end if not type_again then return end -- Validation if key == 'Return' then -- Validate password local authenticated = false if input_password ~= nil then -- If lua-pam library is 'okay' if module_check('liblua_pam') then local pam = require('liblua_pam') authenticated = pam:auth_current_user(input_password) else -- Library doesn't exist or returns an error due to some reasons (read the manual) -- Use fallback password data authenticated = input_password == locker_config.fallback_password() local rtfm = naughty.action { name = 'Read Wiki', icon_only = false } local dismiss = naughty.action { name = 'Dismiss', icon_only = false } rtfm:connect_signal( 'invoked', function() awful.spawn( [[sh -c " xdg-open 'https://github.com/manilarome/the-glorious-dotfiles/wiki' "]], false ) end ) naughty.notification({ app_name = 'Security', title = 'WARNING', message = 'You\'re using the fallback password! It\'s recommended to use the PAM Integration!', urgency = 'critical', actions = { rtfm, dismiss } }) end end if authenticated then -- Come in! self:stop() generalkenobi_ohhellothere() else -- F*ck off, you [REDACTED]! stoprightthereyoucriminalscum() end -- Allow typing again and empty password container type_again = false input_password = nil end end } lockscreen : setup { layout = wibox.layout.align.vertical, expand = 'none', nil, { layout = wibox.layout.align.horizontal, expand = 'none', nil, { layout = wibox.layout.fixed.vertical, expand = 'none', spacing = dpi(20), { { layout = wibox.layout.align.horizontal, expand = 'none', nil, time, nil }, { layout = wibox.layout.align.horizontal, expand = 'none', nil, date, nil }, expand = 'none', layout = wibox.layout.fixed.vertical }, { layout = wibox.layout.fixed.vertical, { circle_container, locker_widget, { layout = wibox.layout.align.vertical, expand = 'none', nil, { layout = wibox.layout.align.horizontal, expand = 'none', nil, profile_imagebox, nil }, nil, }, layout = wibox.layout.stack }, uname_text, caps_text }, }, nil }, nil } local show_lockscreen = function() -- Why is there a lock_again variable? -- It prevents the user to spam locking while in a process of authentication -- Prevents a potential bug/problem if lock_again == true or lock_again == nil then -- Force update clock widget time:emit_signal('widget::redraw_needed') -- Check capslock status check_caps() -- Check webcam status check_webcam() -- Show all the lockscreen on each screen for s in screen do if s.index == 1 then s.lockscreen.visible = true else s.lockscreen_extended.visible = true end end -- Start keygrabbing, but with a little delay to -- give some extra time for the free_keygrab function gears.timer.start_new( 0.5, function() -- Start key grabbing for password password_grabber:start() end ) -- Dont lock again lock_again = false end end local free_keygrab = function() -- Kill rofi instance. awful.spawn.with_shell('kill -9 $(pgrep rofi)') -- Check if there's a keygrabbing instance. -- If yes, stop it. local keygrabbing_instance = awful.keygrabber.current_instance if keygrabbing_instance then keygrabbing_instance:stop() end -- Unselect all tags and minimize the focused client -- These will fix the problem with virtualbox or -- any other program that has keygrabbing enabled if client.focus then client.focus.minimized = true end for _, t in ipairs(mouse.screen.selected_tags) do locked_tag = t t.selected = false end end awesome.connect_signal( 'module::lockscreen_show', function() if lock_again == true or lock_again == nil then free_keygrab() show_lockscreen() end end ) return lockscreen end -- This lockscreen is for the extra/multi monitor local locker_ext = function(s) local extended_lockscreen = wibox { screen = s, visible = false, ontop = true, ontype = 'true', x = s.geometry.x, y = s.geometry.y, width = s.geometry.width, height = s.geometry.height, bg = beautiful.background, fg = beautiful.fg_normal } return extended_lockscreen end -- Create lockscreen for each screen local create_lock_screens = function(s) if s.index == 1 then s.lockscreen = locker(s) else s.lockscreen_extended = locker_ext(s) end end -- Don't show notification popups if the screen is locked local check_lockscreen_visibility = function() focused = awful.screen.focused() if focused.lockscreen and focused.lockscreen.visible then return true end if focused.lockscreen_extended and focused.lockscreen_extended.visible then return true end return false end -- Notifications signal naughty.connect_signal( 'request::display', function(_) if check_lockscreen_visibility() then naughty.destroy_all_notifications(nil, 1) end end ) -- Filter background image local filter_bg_image = function(wall_name, index, ap, width, height) -- Checks if the blur has to be blurred local blur_filter_param = '' if locker_config.blur_background then blur_filter_param = '-filter Gaussian -blur 0x10' end -- Create imagemagick command local magic = [[ sh -c " if [ ! -d ]] .. locker_config.tmp_wall_dir ..[[ ]; then mkdir -p ]] .. locker_config.tmp_wall_dir .. [[; fi convert -quality 100 -brightness-contrast -20x0 ]] .. ' ' .. blur_filter_param .. ' '.. locker_config.bg_dir .. wall_name .. [[ -gravity center -crop ]] .. ap .. [[:1 +repage -resize ]] .. width .. 'x' .. height .. [[! ]] .. locker_config.tmp_wall_dir .. index .. wall_name .. [[ "]] return magic end -- Apply lockscreen background image local apply_ls_bg_image = function(wall_name) -- Iterate through all the screens and create a lockscreen for each of it for s in screen do local index = s.index .. '-' -- Get screen geometry local screen_width = s.geometry.width local screen_height = s.geometry.height -- Get the right resolution/aspect ratio that will be use as the background local aspect_ratio = screen_width / screen_height aspect_ratio = math.floor(aspect_ratio * 100) / 100 -- Create image filter command local cmd = nil cmd = filter_bg_image(wall_name, index, aspect_ratio, screen_width, screen_height) -- Asign lockscreen to each screen if s.index == 1 then -- Primary screen awful.spawn.easy_async_with_shell( cmd, function() s.lockscreen.bgimage = locker_config.tmp_wall_dir .. index .. wall_name end ) else -- Multihead screen/s awful.spawn.easy_async_with_shell( cmd, function() s.lockscreen_extended.bgimage = locker_config.tmp_wall_dir .. index .. wall_name end ) end end end -- Create a lockscreen and its background for each screen on start-up screen.connect_signal( 'request::desktop_decoration', function(s) create_lock_screens(s) apply_ls_bg_image(locker_config.bg_image) end ) -- Regenerate lockscreens and its background if a screen was removed to avoid errors screen.connect_signal( 'removed', function(s) create_lock_screens(s) apply_ls_bg_image(locker_config.bg_image) end )