From 2f7216db033b5d3b011fde589cbf9dbb1fb71217 Mon Sep 17 00:00:00 2001 From: Tiago Pomella Lobo Date: Mon, 5 Aug 2024 19:16:32 +0200 Subject: [PATCH] add indentation and small corrections --- restore_spaces/rs/applescript.lua | 253 ++++++++--------- restore_spaces/rs/environment.lua | 252 ++++++++--------- restore_spaces/rs/plumbing.lua | 446 +++++++++++++++--------------- restore_spaces/rs/porcelain.lua | 373 +++++++++++++------------ 4 files changed, 663 insertions(+), 661 deletions(-) diff --git a/restore_spaces/rs/applescript.lua b/restore_spaces/rs/applescript.lua index ecfe152..1ffb3b9 100644 --- a/restore_spaces/rs/applescript.lua +++ b/restore_spaces/rs/applescript.lua @@ -1,153 +1,156 @@ -- Initialize applescript functions return function(rs,hs) -rs.getPlistInfo = function(info) - local plist_path = rs.packagePath(rs.config_path .. ".plist") - if info == "path" then - return plist_path - else - local key = info - local plistTable = hs.plist.read(plist_path) - if not plistTable then - error("Failed to read plist file at: " .. plist_path) - end - if plistTable[key] ~= nil then - local value = plistTable[key] - return value + rs.getPlistInfo = function(info) + local plist_path = rs.packagePath(rs.config_path .. ".plist") + if info == "path" then + return plist_path else - -- Key does not exist, handle accordingly - return nil, "Key '" .. key .. "' does not exist in the plist file." + local key = info + local plistTable = hs.plist.read(plist_path) + if not plistTable then + error("Failed to read plist file at: " .. plist_path) + end + if plistTable[key] ~= nil then + local value = plistTable[key] + return value + else + -- Key does not exist, handle accordingly + return nil, "Key '" .. key .. "' does not exist in the plist file." + end end end -end -rs.processPlistConfig = function(case) - local plist_path = rs.getPlistInfo("path") + rs.processPlistConfig = function(case) + local plist_path = rs.getPlistInfo("path") - if case == 'create' then - local json_path = rs.packagePath(rs.config_path .. ".json") - local json_file = io.open(json_path, "r") - if not json_file then - error("Unable to locate: " .. json_path) - end - local json_contents = json_file:read("*a") - json_file:close() - - local json_table, _, err = hs.json.decode(json_contents) - if not json_table then - error("Failed to parse JSON: " .. err) - end + if case == 'create' then + local json_path = rs.packagePath(rs.config_path .. ".json") + local json_file = io.open(json_path, "r") + if not json_file then + error("Unable to locate: " .. json_path) + end + local json_contents = json_file:read("*a") + json_file:close() + + local json_table, _, err = hs.json.decode(json_contents) + if not json_table then + error("Failed to parse JSON: " .. err) + end - local success = hs.plist.write(plist_path, json_table, true) - if not success then - error("Failed to create PLIST file") - end + local success = hs.plist.write(plist_path, json_table, true) + if not success then + error("Failed to create PLIST file") + end + + elseif case == 'destroy' then - elseif case == 'destroy' then + if not plist_path then + error("PLIST path cannot be 'nil'") + end + local success, err = os.remove(plist_path) + if not success then + error("Failed to delete PLIST file: " .. err) + end - local success, err = os.remove(plist_path) - if not success then - error("Failed to delete PLIST file: " .. err) + else + error("Unknown routine to 'processPlistConfig' in case: " .. case) end - else - error("Unknown routine to 'processPlistConfig' in case: " .. case) end -end - -rs.buildTabList = function(output,report_delimiter) - local applescript_delimiter = ", " - local window_delimiter = report_delimiter .. report_delimiter - local start_pattern = window_delimiter .. applescript_delimiter - local end_pattern = applescript_delimiter .. window_delimiter - local list_delimiter = report_delimiter - - local newline_pattern = "\n+$" - local processed_string = string.gsub(output, newline_pattern, end_pattern) - - local tab_lists = {} - local window_pattern = start_pattern .. "(.-)" .. end_pattern - local tab_pattern = "([^" .. list_delimiter .. "]+)" - for window_string in processed_string:gmatch(window_pattern) do - local tab_titles = {} - for tab_string in window_string:gmatch(tab_pattern) do - table.insert(tab_titles, tab_string) - end - if #tab_titles > 0 then - table.insert(tab_lists, tab_titles) + rs.buildTabList = function(output,report_delimiter) + local applescript_delimiter = ", " + local window_delimiter = report_delimiter .. report_delimiter + local start_pattern = window_delimiter .. applescript_delimiter + local end_pattern = applescript_delimiter .. window_delimiter + local list_delimiter = report_delimiter + + local newline_pattern = "\n+$" + local processed_string = string.gsub(output, newline_pattern, end_pattern) + + local tab_lists = {} + local window_pattern = start_pattern .. "(.-)" .. end_pattern + local tab_pattern = "([^" .. list_delimiter .. "]+)" + for window_string in processed_string:gmatch(window_pattern) do + local tab_titles = {} + for tab_string in window_string:gmatch(tab_pattern) do + table.insert(tab_titles, tab_string) + end + if #tab_titles > 0 then + table.insert(tab_lists, tab_titles) + end end + + return tab_lists end - return tab_lists -end - - -rs.runApplescript = function(app) - local script_path = "scp/getVisibleTabs.applescript" - local abs_path = rs.packagePath(script_path) - local osascript = "/usr/bin/osascript" - local plist_path = rs.getPlistInfo("path") - - local args = app .. " " .. plist_path - local command = osascript .. " " .. abs_path .. " " .. args - local output, status, exitType, rc = hs.execute(command,true) - - local msg = "Status: "..tostring(status)..", exit: "..tostring(exitType) - msg = msg ..", rc: "..tostring(rc) - rs.issueVerbose(msg,rs.verbose) - rs.issueVerbose(hs.inspect(output),rs.verbose) - - local report_delimiter = rs.getPlistInfo("reportDelimiter") - local tab_list = rs.buildTabList(output,report_delimiter) - return tab_list -end - -rs.getAppTabsInSpace = function(app,id2title) - local app_tabs = id2title - local tab_list = rs.runApplescript(app) - for _, window_tabs in ipairs(tab_list) do - local first_tab = window_tabs[1] - for id, title in pairs(id2title) do - if first_tab == title then - app_tabs[id] = rs.list2dict(window_tabs) + + rs.runApplescript = function(app) + local script_path = "scp/getVisibleTabs.applescript" + local abs_path = rs.packagePath(script_path) + local osascript = "/usr/bin/osascript" + local plist_path = rs.getPlistInfo("path") + + local args = app .. " " .. plist_path + local command = osascript .. " " .. abs_path .. " " .. args + local output, status, exitType, rc = hs.execute(command,true) + + local msg = "Status: "..tostring(status)..", exit: "..tostring(exitType) + msg = msg ..", rc: "..tostring(rc) + rs.issueVerbose(msg,rs.verbose) + rs.issueVerbose(hs.inspect(output),rs.verbose) + + local report_delimiter = rs.getPlistInfo("reportDelimiter") + local tab_list = rs.buildTabList(output,report_delimiter) + return tab_list + end + + rs.getAppTabsInSpace = function(app,id2title) + local app_tabs = id2title + local tab_list = rs.runApplescript(app) + for _, window_tabs in ipairs(tab_list) do + local first_tab = window_tabs[1] + for id, title in pairs(id2title) do + if first_tab == title then + app_tabs[id] = rs.list2dict(window_tabs) + end end end + return app_tabs end - return app_tabs -end -rs.compareTabs = function(window_tabs,stored_tabs) - if (stored_tabs == nil) or next(stored_tabs) == nil then - return false - end + rs.compareTabs = function(window_tabs,stored_tabs) + if (stored_tabs == nil) or next(stored_tabs) == nil then + return false + end - local all_matches = {} - for _, tab_title in pairs(window_tabs) do - if rs.contains(stored_tabs,tab_title) then - table.insert(all_matches, true) + local all_matches = {} + for _, tab_title in pairs(window_tabs) do + if rs.contains(stored_tabs,tab_title) then + table.insert(all_matches, true) + end end - end - local window_ratio = #all_matches / #window_tabs - local stored_ratio = #all_matches / #stored_tabs - - local tab_limit - if #window_tabs <= rs.multitab_comparison["critical_tab_count"] then - tab_limit = rs.multitab_comparison["small_similarity_threshold"] - else - tab_limit = rs.multitab_comparison["large_similarity_threshold"] - end + local window_ratio = #all_matches / #window_tabs + local stored_ratio = #all_matches / #stored_tabs - local window_check = window_ratio >= tab_limit - if rs.isNaN(window_ratio) then - window_check = false - end - local stored_check = stored_ratio >= tab_limit - if rs.isNaN(stored_ratio) then - stored_check = false + local tab_limit + if #window_tabs <= rs.multitab_comparison["critical_tab_count"] then + tab_limit = rs.multitab_comparison["small_similarity_threshold"] + else + tab_limit = rs.multitab_comparison["large_similarity_threshold"] + end + + local window_check = window_ratio >= tab_limit + if rs.isNaN(window_ratio) then + window_check = false + end + local stored_check = stored_ratio >= tab_limit + if rs.isNaN(stored_ratio) then + stored_check = false + end + local match = window_check and stored_check + return match end - local match = window_check and stored_check - return match -end end \ No newline at end of file diff --git a/restore_spaces/rs/environment.lua b/restore_spaces/rs/environment.lua index a1f50b6..5b35926 100644 --- a/restore_spaces/rs/environment.lua +++ b/restore_spaces/rs/environment.lua @@ -108,154 +108,154 @@ return function(rs,hs) return answer end -rs.processEnvironment = function(save_flag) - local function sortByFrame(a, b) - return a:frame().x < b:frame().x - end - local function listKeys(table) - local keys_list = "" - for key, _ in pairs(table) do - keys_list = keys_list .. "'" .. key .. "'\n" + rs.processEnvironment = function(save_flag) + local function sortByFrame(a, b) + return a:frame().x < b:frame().x + end + local function listKeys(table) + local keys_list = "" + for key, _ in pairs(table) do + keys_list = keys_list .. "'" .. key .. "'\n" + end + return keys_list end - return keys_list - end - rs.data_envs = rs.processDataInFile("read","environments") - - local all_screens = rs.retrieveEnvironmentEntities("screens") - table.sort(all_screens, sortByFrame) - - local env = rs.detectEnvironment(all_screens) - local env_exists, env_name = rs.validateEnvironment(env) + rs.data_envs = rs.processDataInFile("read","environments") + + local all_screens = rs.retrieveEnvironmentEntities("screens") + table.sort(all_screens, sortByFrame) + + local env = rs.detectEnvironment(all_screens) + local env_exists, env_name = rs.validateEnvironment(env) - if env_exists then - rs.rebuildEnvironment(env, env_name, all_screens, save_flag) - --[[ - -- FOR TESTING ONLY: - local envs_list = listKeys(mod.data_envs) - env_name = mod.askEnvironmentName(envs_list, mod.verbose) - if not env_name then - error("Undefined environment name: !") - else - mod.data_envs[env_name] = env - end - --]] - else - local text = "Environment does not exist." - rs.issueVerbose(text, rs.verbose) - text = "Environment name undefined!" - if save_flag then - local envs_list = listKeys(rs.data_envs) - env_name = rs.askEnvironmentName(envs_list, rs.verbose) + if env_exists then + rs.rebuildEnvironment(env, env_name, all_screens, save_flag) + --[[ + -- FOR TESTING ONLY: + local envs_list = listKeys(mod.data_envs) + env_name = mod.askEnvironmentName(envs_list, mod.verbose) if not env_name then - error(text) + error("Undefined environment name: !") else - rs.data_envs[env_name] = env + mod.data_envs[env_name] = env end - rs.data_envs = rs.processDataInFile("write","environments") + --]] else - rs.notifyUser("environment") - error(text) + local text = "Environment does not exist." + rs.issueVerbose(text, rs.verbose) + text = "Environment name undefined!" + if save_flag then + local envs_list = listKeys(rs.data_envs) + env_name = rs.askEnvironmentName(envs_list, rs.verbose) + if not env_name then + error(text) + else + rs.data_envs[env_name] = env + end + rs.data_envs = rs.processDataInFile("write","environments") + else + rs.notifyUser("environment") + error(text) + end end - end - return env_name, env -end + return env_name, env + end -rs.detectEnvironment = function(all_screens) - local env = {} - for screen_i, screen in ipairs(all_screens) do - local screen_name = screen:name() - local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) - local screen_index = rs.paddedToStr(screen_i) - local space_map = {} - for space_i, space in ipairs(screen_spaces) do - local space_index = rs.paddedToStr(space_i) - --TODO: add docstrings that explain that the first value is - -- the original space id during `save`, and the second - -- is the current space id during `apply` - space_map[space_index] = {space, space} + rs.detectEnvironment = function(all_screens) + local env = {} + for screen_i, screen in ipairs(all_screens) do + local screen_name = screen:name() + local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) + local screen_index = rs.paddedToStr(screen_i) + local space_map = {} + for space_i, space in ipairs(screen_spaces) do + local space_index = rs.paddedToStr(space_i) + --TODO: add docstrings that explain that the first value is + -- the original space id during `save`, and the second + -- is the current space id during `apply` + space_map[space_index] = {space, space} + end + env[screen_index] = { + ["monitor"] = screen_name, + ["space_map"] = space_map + } end - env[screen_index] = { - ["monitor"] = screen_name, - ["space_map"] = space_map - } + return env end - return env -end -rs.validateEnvironment = function(env) - local env_exists = false - local env_name - for saved_name, saved_env in pairs(rs.data_envs) do - local saved_monitors = {} - for _, value in pairs(saved_env) do - table.insert(saved_monitors, value["monitor"]) - end + rs.validateEnvironment = function(env) + local env_exists = false + local env_name + for saved_name, saved_env in pairs(rs.data_envs) do + local saved_monitors = {} + for _, value in pairs(saved_env) do + table.insert(saved_monitors, value["monitor"]) + end - local current_monitors = {} - for _, value in pairs(env) do - table.insert(current_monitors, value["monitor"]) - end + local current_monitors = {} + for _, value in pairs(env) do + table.insert(current_monitors, value["monitor"]) + end - local check_monitors = ( - hs.inspect(saved_monitors) == hs.inspect(current_monitors) - ) - if check_monitors then - env_exists = true - env_name = saved_name - break + local check_monitors = ( + hs.inspect(saved_monitors) == hs.inspect(current_monitors) + ) + if check_monitors then + env_exists = true + env_name = saved_name + break + end end + return env_exists, env_name end - return env_exists, env_name -end -rs.rebuildEnvironment = function(env, env_name, all_screens, save_flag) - local function lengthTables(...) - local args = {...} - local all_counts = {} - for i, arg in ipairs(args) do - local count = 0 - for _ in pairs(arg) do count = count + 1 end - all_counts[i] = count + rs.rebuildEnvironment = function(env, env_name, all_screens, save_flag) + local function lengthTables(...) + local args = {...} + local all_counts = {} + for i, arg in ipairs(args) do + local count = 0 + for _ in pairs(arg) do count = count + 1 end + all_counts[i] = count + end + return table.unpack(all_counts) end - return table.unpack(all_counts) - end - if save_flag then - rs.issueVerbose("Overwriting space order and map...", rs.verbose) - else - rs.issueVerbose("Re-building environment...", rs.verbose) - local saved_env = rs.data_envs[env_name] - for screen_i, screen in ipairs(all_screens) do - local screen_index = rs.paddedToStr(screen_i) - local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) - local saved_map = saved_env[screen_index]["space_map"] - local n_screen, n_saved = lengthTables(screen_spaces, saved_map) - local close_MissionControl = false - while n_screen < n_saved do - hs.spaces.addSpaceToScreen(screen_i, close_MissionControl) - screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) - n_screen, n_saved = lengthTables(screen_spaces, saved_map) - end - while n_screen > n_saved do - local last_space_id = screen_spaces[n_screen] - hs.spaces.removeSpace(last_space_id, close_MissionControl) - screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) - n_screen, n_saved = lengthTables(screen_spaces, saved_map) - end - local screen_map = {} - for space_i, space in ipairs(screen_spaces) do - local space_index = rs.paddedToStr(space_i) - local original_space = saved_map[space_index][1] - screen_map[space_index] = {original_space, space} + if save_flag then + rs.issueVerbose("Overwriting space order and map...", rs.verbose) + else + rs.issueVerbose("Re-building environment...", rs.verbose) + local saved_env = rs.data_envs[env_name] + for screen_i, screen in ipairs(all_screens) do + local screen_index = rs.paddedToStr(screen_i) + local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) + local saved_map = saved_env[screen_index]["space_map"] + local n_screen, n_saved = lengthTables(screen_spaces, saved_map) + local close_MissionControl = false + while n_screen < n_saved do + hs.spaces.addSpaceToScreen(screen_i, close_MissionControl) + screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) + n_screen, n_saved = lengthTables(screen_spaces, saved_map) + end + while n_screen > n_saved do + local last_space_id = screen_spaces[n_screen] + hs.spaces.removeSpace(last_space_id, close_MissionControl) + screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) + n_screen, n_saved = lengthTables(screen_spaces, saved_map) + end + local screen_map = {} + for space_i, space in ipairs(screen_spaces) do + local space_index = rs.paddedToStr(space_i) + local original_space = saved_map[space_index][1] + screen_map[space_index] = {original_space, space} + end + env[screen_index]["space_map"] = screen_map + rs.issueVerbose("env: " .. hs.inspect(env), rs.verbose) end - env[screen_index]["space_map"] = screen_map - rs.issueVerbose("env: " .. hs.inspect(env), rs.verbose) end + rs.data_envs[env_name] = env + rs.processDataInFile("write","environments") end - rs.data_envs[env_name] = env - rs.processDataInFile("write","environments") -end end \ No newline at end of file diff --git a/restore_spaces/rs/plumbing.lua b/restore_spaces/rs/plumbing.lua index c00d3b8..e5de630 100644 --- a/restore_spaces/rs/plumbing.lua +++ b/restore_spaces/rs/plumbing.lua @@ -1,264 +1,264 @@ -- Initialize plumbing return function(rs,hs) -rs.issueVerbose = function(text, verbose) - verbose = verbose or rs.verbose - if verbose then - local info = debug.getinfo(2, "n") - local calling_function = info and info.name or "unknown" - print("(" .. calling_function .. ") " .. text) - end -end - -rs.notifyUser = function(case,verbose) - local text = nil - if case == "save" then - text = "Windows saved!" - elseif case == "apply" then - text = "Windows applied!" - elseif case == "environment" then - text = "Environment undefined!" - else - error("Unknown case: " .. case) + rs.issueVerbose = function(text, verbose) + verbose = verbose or rs.verbose + if verbose then + local info = debug.getinfo(2, "n") + local calling_function = info and info.name or "unknown" + print("(" .. calling_function .. ") " .. text) + end end - rs.issueVerbose(text, verbose) - local message = { - title="Restore Spaces", - informativeText=text - } - hs.notify.new(message):send() -end -rs.processDataInFile = function(case, data) - local function readFile(abs_path) - local file = io.open(abs_path, 'r') - if not file then - print("Failed to open file: " .. abs_path) - return {} + rs.notifyUser = function(case,verbose) + local text = nil + if case == "save" then + text = "Windows saved!" + elseif case == "apply" then + text = "Windows applied!" + elseif case == "environment" then + text = "Environment undefined!" + else + error("Unknown case: " .. case) end - local json_contents = file:read('*all') - file:close() - return hs.json.decode(json_contents) + rs.issueVerbose(text, verbose) + local message = { + title="Restore Spaces", + informativeText=text + } + hs.notify.new(message):send() end - local function writeFile(abs_path, contents) - if not contents then - print("No contents to write!") - return - end - --[[ - --TODO: fix recursive test to accept arrays (which are dicts in Lua) - if not mod.recursiveKeysAreStrings(contents) then - print("contents: " .. hs.inspect(contents)) - error("Keys in a table must be strings for JSON encoding!") + + rs.processDataInFile = function(case, data) + local function readFile(abs_path) + local file = io.open(abs_path, 'r') + if not file then + print("Failed to open file: " .. abs_path) + return {} + end + local json_contents = file:read('*all') + file:close() + return hs.json.decode(json_contents) end - --]] - local file = io.open(abs_path, 'w') - if not file then - print("Failed to open file: " .. abs_path) - return + local function writeFile(abs_path, contents) + if not contents then + print("No contents to write!") + return + end + --[[ + --TODO: fix recursive test to accept arrays (which are dicts in Lua) + if not mod.recursiveKeysAreStrings(contents) then + print("contents: " .. hs.inspect(contents)) + error("Keys in a table must be strings for JSON encoding!") + end + --]] + local file = io.open(abs_path, 'w') + if not file then + print("Failed to open file: " .. abs_path) + return + end + local json_contents = hs.json.encode( + contents, + true -- true: prettyprint + ) + file:write(json_contents) + file:close() end - local json_contents = hs.json.encode( - contents, - true -- true: prettyprint - ) - file:write(json_contents) - file:close() - end - local contents - if data == "windows" then - contents = rs.data_wins - elseif data == "environments" then - contents = rs.data_envs - else - error("Unknown data: " .. data) - end - local filepath = "tmp/data_" .. data.. ".json" - local abs_path = rs.packagePath(filepath) + local contents + if data == "windows" then + contents = rs.data_wins + elseif data == "environments" then + contents = rs.data_envs + else + error("Unknown data: " .. data) + end + local filepath = "tmp/data_" .. data.. ".json" + local abs_path = rs.packagePath(filepath) - if case == "write" then - writeFile(abs_path, contents) - elseif case == "read" then - contents = readFile(abs_path) - else - error("Unknown case: " .. case) + if case == "write" then + writeFile(abs_path, contents) + elseif case == "read" then + contents = readFile(abs_path) + else + error("Unknown case: " .. case) + end + return contents end - return contents -end -rs.validateSpaces = function(all_spaces, verbose) - verbose = verbose or rs.verbose - local function extractNestedTable(key_sequence, table) - local current_table = table - for _, key in ipairs(key_sequence) do - if type(current_table) ~= "table" then - error("Value is not a table at key: " .. key) - end - if current_table[key] then - current_table = current_table[key] - else - error("Key not found: " .. key) + rs.validateSpaces = function(all_spaces, verbose) + verbose = verbose or rs.verbose + local function extractNestedTable(key_sequence, table) + local current_table = table + for _, key in ipairs(key_sequence) do + if type(current_table) ~= "table" then + error("Value is not a table at key: " .. key) + end + if current_table[key] then + current_table = current_table[key] + else + error("Key not found: " .. key) + end end + return current_table end - return current_table - end - local plist_path = "~/Library/Preferences/com.apple.spaces.plist" - local plist_spaces = hs.plist.read(plist_path) - if not plist_spaces then - print("Failed to read plist file") - end + local plist_path = "~/Library/Preferences/com.apple.spaces.plist" + local plist_spaces = hs.plist.read(plist_path) + if not plist_spaces then + print("Failed to read plist file") + end - local count = 0 - local valid_spaces = {} - local all_screens = hs.screen.allScreens() - for screen_i in ipairs(all_screens) do - local key_sequence = { - "SpacesDisplayConfiguration", - "Management Data", - "Monitors", - screen_i, - "Spaces", - } - local all_spaces_info = extractNestedTable(key_sequence, plist_spaces) - for _, space_info in ipairs(all_spaces_info) do - local uuid = space_info["uuid"] - local id = space_info["id64"] - if uuid ~= "dashboard" then - count = count + 1 - valid_spaces[count] = id + local count = 0 + local valid_spaces = {} + local all_screens = hs.screen.allScreens() + for screen_i in ipairs(all_screens) do + local key_sequence = { + "SpacesDisplayConfiguration", + "Management Data", + "Monitors", + screen_i, + "Spaces", + } + local all_spaces_info = extractNestedTable(key_sequence, plist_spaces) + for _, space_info in ipairs(all_spaces_info) do + local uuid = space_info["uuid"] + local id = space_info["id64"] + if uuid ~= "dashboard" then + count = count + 1 + valid_spaces[count] = id + end + local text = "screen: " .. screen_i .. ", space id: " .. id + text = text .. ", space uuid: " .. uuid + rs.issueVerbose(text, verbose) end - local text = "screen: " .. screen_i .. ", space id: " .. id - text = text .. ", space uuid: " .. uuid - rs.issueVerbose(text, verbose) end - end - local validated_spaces = {} - for _, space in ipairs(all_spaces) do - if hs.fnutils.contains(valid_spaces, space) then - table.insert(validated_spaces, space) + local validated_spaces = {} + for _, space in ipairs(all_spaces) do + if hs.fnutils.contains(valid_spaces, space) then + table.insert(validated_spaces, space) + end end + + return validated_spaces end - return validated_spaces -end + rs.getWindowState = function(window) + --TODO: Change "WindowState" to "AppState" + local window_state = {} + local window_id = tostring(window:id()) -rs.getWindowState = function(window) - --TODO: Change "WindowState" to "AppState" - local window_state = {} - local window_id = tostring(window:id()) + --TODO: check if app window is hidden - --TODO: check if app window is hidden + local fullscreen_state, frame_state = rs.getFrameState(window) + window_state["fullscreen"] = fullscreen_state + window_state["frame"] = frame_state + window_state["title"] = window:title() + window_state["app"] = window:application():name() + if rs.contains(rs.multitab_apps, window_state["app"]) then + window_state["multitab"] = true + window_state["tabs"] = {} + else + window_state["multitab"] = false + window_state["tabs"] = nil + end - local fullscreen_state, frame_state = rs.getFrameState(window) - window_state["fullscreen"] = fullscreen_state - window_state["frame"] = frame_state - window_state["title"] = window:title() - window_state["app"] = window:application():name() - if rs.contains(rs.multitab_apps, window_state["app"]) then - window_state["multitab"] = true - window_state["tabs"] = {} - else - window_state["multitab"] = false - window_state["tabs"] = nil + --mod.issueVerbose("get window " .. window_id, mod.verbose) + return window_state, window_id end - --mod.issueVerbose("get window " .. window_id, mod.verbose) - return window_state, window_id -end - -rs.setWindowState = function(window,window_state,space_map) - if not window_state then - --TODO: use window title to identify window (this creastes a - -- problem if the window has multiple tabs) - --TODO: if not found, minimize window - window:minimize() - return - end - --local title = window_state["title"] - --local app = window_state["app"] - local frame_state = window_state["frame"] - local fullscreen_state = window_state["fullscreen"] - --local screen = window_state["screen"] - local space = window_state["space"] - local target_space = nil - if space_map then - for _, pair in pairs(space_map) do - local old_space = pair[1] - local new_space = pair[2] - if old_space == space then - target_space = new_space - break + rs.setWindowState = function(window,window_state,space_map) + if not window_state then + --TODO: use window title to identify window (this creastes a + -- problem if the window has multiple tabs) + --TODO: if not found, minimize window + window:minimize() + return + end + --local title = window_state["title"] + --local app = window_state["app"] + local frame_state = window_state["frame"] + local fullscreen_state = window_state["fullscreen"] + --local screen = window_state["screen"] + local space = window_state["space"] + local target_space = nil + if space_map then + for _, pair in pairs(space_map) do + local old_space = pair[1] + local new_space = pair[2] + if old_space == space then + target_space = new_space + break + end end + else + target_space = space end - else - target_space = space - end - --target_space = tonumber(target_space) + --target_space = tonumber(target_space) - if rs.spaces_fixed_after_macOS14_5 then - hs.spaces.moveWindowToSpace(window, target_space) - else - -- solution by `cunha` - -- (see: https://github.com/Hammerspoon/hammerspoon/pull/3638#issuecomment-2252826567) - local target_screen, _ = hs.spaces.spaceDisplay(target_space) - hs.spaces.moveWindowToSpace(window, target_space) - window:focus() - rs.delayExecution(0.4) - window:moveToScreen(target_screen) - window:focus() - end + if rs.spaces_fixed_after_macOS14_5 then + hs.spaces.moveWindowToSpace(window, target_space) + else + -- solution by `cunha` + -- (see: https://github.com/Hammerspoon/hammerspoon/pull/3638#issuecomment-2252826567) + local target_screen, _ = hs.spaces.spaceDisplay(target_space) + hs.spaces.moveWindowToSpace(window, target_space) + window:focus() + rs.delayExecution(0.4) + window:moveToScreen(target_screen) + window:focus() + end - rs.setFrameState(window, frame_state, fullscreen_state) - rs.issueVerbose("set window " .. window:id(), rs.verbose) -end + rs.setFrameState(window, frame_state, fullscreen_state) + rs.issueVerbose("set window " .. window:id(), rs.verbose) + end -rs.getFrameState = function(window) - local frame = window:frame() - local screen_frame = window:screen():frame() - local frame_state = { - ["x"] = frame.x, - ["y"] = frame.y, - ["w"] = frame.w, - ["h"] = frame.h, - } - local isLeftEdge = frame.x == 0 - local isRightEdge = frame.x + frame.w == screen_frame.w - local isLessThanFullWidth = frame.w < screen_frame.w + rs.getFrameState = function(window) + local frame = window:frame() + local screen_frame = window:screen():frame() + local frame_state = { + ["x"] = frame.x, + ["y"] = frame.y, + ["w"] = frame.w, + ["h"] = frame.h, + } + local isLeftEdge = frame.x == 0 + local isRightEdge = frame.x + frame.w == screen_frame.w + local isLessThanFullWidth = frame.w < screen_frame.w - local fullscreen_state = "no" - if window:isFullScreen() then - if isLessThanFullWidth then - if isLeftEdge then - fullscreen_state = "left" - elseif isRightEdge then - fullscreen_state = "right" + local fullscreen_state = "no" + if window:isFullScreen() then + if isLessThanFullWidth then + if isLeftEdge then + fullscreen_state = "left" + elseif isRightEdge then + fullscreen_state = "right" + end + else + fullscreen_state = "yes" end - else - fullscreen_state = "yes" end + return fullscreen_state, frame_state end - return fullscreen_state, frame_state -end -rs.setFrameState = function(window, frame_state, fullscreen_state) - if fullscreen_state == "yes" then - window:setFullScreen(true) - elseif fullscreen_state == "left" then - window:setFullScreen(true) - --TODO: find a way to make it left split-view - elseif fullscreen_state == "right" then - window:setFullScreen(true) - --TODO: find a way to make it right split-view - else - local frame = window:frame() - frame.x = frame_state["x"] - frame.y = frame_state["y"] - frame.w = frame_state["w"] - frame.h = frame_state["h"] - window:setFrame(frame) + rs.setFrameState = function(window, frame_state, fullscreen_state) + if fullscreen_state == "yes" then + window:setFullScreen(true) + elseif fullscreen_state == "left" then + window:setFullScreen(true) + --TODO: find a way to make it left split-view + elseif fullscreen_state == "right" then + window:setFullScreen(true) + --TODO: find a way to make it right split-view + else + local frame = window:frame() + frame.x = frame_state["x"] + frame.y = frame_state["y"] + frame.w = frame_state["w"] + frame.h = frame_state["h"] + window:setFrame(frame) + end end -end end \ No newline at end of file diff --git a/restore_spaces/rs/porcelain.lua b/restore_spaces/rs/porcelain.lua index 58a97b0..d91a76b 100644 --- a/restore_spaces/rs/porcelain.lua +++ b/restore_spaces/rs/porcelain.lua @@ -1,218 +1,217 @@ -- Initialize porcelain return function(rs,hs) -rs.saveEnvironmentState = function() - rs.processPlistConfig("create") - rs._saveEnvironmentState() - rs.processPlistConfig("destroy") -end - -rs.applyEnvironmentState = function() - rs.processPlistConfig("create") - rs._applyEnvironmentState() - rs.processPlistConfig("destroy") -end - ---[[ -function rs.refineWindowState(window_state) -end ---]] - -rs._saveEnvironmentState = function() - local save_new_env = true - local env_name = rs.processEnvironment(save_new_env) - if not env_name then - error("Undefined environment name!") + rs.saveEnvironmentState = function() + rs.processPlistConfig("create") + rs.getEnvironmentState() + rs.processPlistConfig("destroy") end - local env_state = {} - - local all_screens = rs.retrieveEnvironmentEntities("screens",nil) - for _, screen in ipairs(all_screens) do - local screen_id = tostring(screen:id()) - - local initial_space = hs.spaces.activeSpaceOnScreen(screen) - local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) - - rs.delayExecution(rs.screen_pause) - for _, space in pairs(screen_spaces) do - rs.issueVerbose( - "go to space: " .. space .. " on screen: " .. screen_id, - rs.verbose - ) - hs.spaces.gotoSpace(space) - rs.delayExecution(rs.space_pause) - - local space_state = {} - local space_windows = rs.retrieveEnvironmentEntities("windows", screen) - local multitab_windows = rs.list2table(rs.multitab_apps) - for _, window in ipairs(space_windows) do - local window_state, window_id = rs.getWindowState(window) - window_state["screen"] = tonumber(screen_id) - window_state["space"] = space - if window_state["title"] == "" then - local msg = "ignored (no title): " - msg = msg .. "\tapp (" .. window_state["app"] .. ")" - msg = msg .. "\twindow id (" .. window_id .. ")" - rs.issueVerbose(msg,rs.verbose) - else - rs.issueVerbose(hs.inspect(window_state),rs.verbose) - if window_state["multitab"] == true then - local app = window_state["app"] - local id_list = multitab_windows[app] - id_list[#id_list + 1] = window_id - multitab_windows[app] = id_list - end - space_state[window_id] = window_state - end - end - -- refineSpaceState: - for app, id_list in pairs(multitab_windows) do - if #id_list > 0 then - local id2title = {} - for _, window_id in ipairs(id_list) do - local window_state = space_state[window_id] - id2title[window_id] = window_state["title"] - end - local app_tabs = rs.getAppTabsInSpace(app,id2title) - for window_id, _ in pairs(id2title) do - local window_state = space_state[window_id] - window_state["tabs"] = app_tabs[window_id] - --print(hs.inspect(app_tabs[window_id])) - space_state[window_id] = window_state - end - end - end - - for window_id, window_state in pairs(space_state) do - env_state[window_id] = window_state - end - end - hs.spaces.gotoSpace(initial_space) + rs.applyEnvironmentState = function() + rs.processPlistConfig("create") + rs.setEnvironmentState() + rs.processPlistConfig("destroy") end - rs.data_wins[env_name] = env_state - rs.data_wins = rs.processDataInFile("write","windows") - rs.notifyUser("save") -end - -rs._applyEnvironmentState = function() - local save_new_env = false - local env_name, env = rs.processEnvironment(save_new_env) - if not env_name then - error("Undefined environment name!") - end - - --TODO: open apps if they are not open? - --TODO: close apps if they are not saved? - rs.data_wins = rs.processDataInFile("read","windows") - if not rs.data_wins[env_name] then - error("State for environment has never been saved!") + --[[ + function rs.refineWindowState(window_state) end - local env_state = rs.data_wins[env_name] - - local all_screens = rs.retrieveEnvironmentEntities("screens",nil) - for screen_i, screen in ipairs(all_screens) do - local screen_id = tostring(screen:id()) - local screen_index = rs.paddedToStr(screen_i) - local space_map = env[screen_index]["space_map"] - - local initial_space = hs.spaces.activeSpaceOnScreen(screen) - local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) - - rs.delayExecution(rs.screen_pause) - for _, space in pairs(screen_spaces) do - rs.issueVerbose( - "go to space: " .. space .. " on screen: " .. screen_id, - rs.verbose - ) - hs.spaces.gotoSpace(space) - rs.delayExecution(rs.space_pause) - - local space_state = {} - local space_windows = rs.retrieveEnvironmentEntities("windows", screen) - local multitab_windows = rs.list2table(rs.multitab_apps) - for _, window in ipairs(space_windows) do - local window_state, window_id = rs.getWindowState(window) - space_state[window_id] = window_state - - if window_state["title"] == "" then - local msg = "ignored (no title): " - msg = msg .. "\tapp (" .. window_state["app"] .. ")" - msg = msg .. "\twindow id (" .. window_id .. ")" - rs.issueVerbose(msg,rs.verbose) - else - if env_state[window_id] then - window_state = env_state[window_id] - rs.issueVerbose(hs.inspect(window_state),rs.verbose) - rs.setWindowState(window, window_state, space_map) + --]] + + rs.getEnvironmentState = function() + local save_new_env = true + local env_name = rs.processEnvironment(save_new_env) + if not env_name then + error("Undefined environment name!") + end + local env_state = {} + + local all_screens = rs.retrieveEnvironmentEntities("screens",nil) + for _, screen in ipairs(all_screens) do + local screen_id = tostring(screen:id()) + + local initial_space = hs.spaces.activeSpaceOnScreen(screen) + local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) + + rs.delayExecution(rs.screen_pause) + for _, space in pairs(screen_spaces) do + rs.issueVerbose( + "go to space: " .. space .. " on screen: " .. screen_id, + rs.verbose + ) + hs.spaces.gotoSpace(space) + rs.delayExecution(rs.space_pause) + + local space_state = {} + local space_windows = rs.retrieveEnvironmentEntities("windows", screen) + local multitab_windows = rs.list2table(rs.multitab_apps) + for _, window in ipairs(space_windows) do + local window_state, window_id = rs.getWindowState(window) + window_state["screen"] = tonumber(screen_id) + window_state["space"] = space + if window_state["title"] == "" then + local msg = "ignored (no title): " + msg = msg .. "\tapp (" .. window_state["app"] .. ")" + msg = msg .. "\twindow id (" .. window_id .. ")" + rs.issueVerbose(msg,rs.verbose) else + rs.issueVerbose(hs.inspect(window_state),rs.verbose) if window_state["multitab"] == true then local app = window_state["app"] local id_list = multitab_windows[app] id_list[#id_list + 1] = window_id multitab_windows[app] = id_list - else - local found_state = false - local window_title = window_state["title"] - for stored_id, stored_state in pairs(env_state) do - local stored_title = stored_state["title"] - if window_title == stored_title then - found_state = true - rs.rename_key(env_state,stored_id,window_id) - rs.setWindowState(window, stored_state, space_map) - end - end - if not found_state then - local msg = "Unknown state for non-multitab app " - msg = msg.."window ID ("..window_id..") and " - msg = msg.."title ("..window_title..")" - end end + space_state[window_id] = window_state end - --refineSpaceState: - for app, id_list in pairs(multitab_windows) do - if #id_list > 0 then - local id2title = {} - for _, window_id in ipairs(id_list) do - local window_state = space_state[window_id] - id2title[window_id] = window_state["title"] - end - local app_tabs = rs.getAppTabsInSpace(app,id2title) - for window_id, window_tabs in pairs(app_tabs) do - local window_matched = false + end + -- refineSpaceState: + for app, id_list in pairs(multitab_windows) do + if #id_list > 0 then + local id2title = {} + for _, window_id in ipairs(id_list) do + local window_state = space_state[window_id] + id2title[window_id] = window_state["title"] + end + local app_tabs = rs.getAppTabsInSpace(app,id2title) + for window_id, _ in pairs(id2title) do + local window_state = space_state[window_id] + window_state["tabs"] = app_tabs[window_id] + space_state[window_id] = window_state + end + end + end + + for window_id, window_state in pairs(space_state) do + env_state[window_id] = window_state + end + + end + hs.spaces.gotoSpace(initial_space) + end + rs.data_wins[env_name] = env_state + rs.data_wins = rs.processDataInFile("write","windows") + rs.notifyUser("save") + end + + rs.setEnvironmentState = function() + local save_new_env = false + local env_name, env = rs.processEnvironment(save_new_env) + if not env_name then + error("Undefined environment name!") + end + + --TODO: open apps if they are not open? + --TODO: close apps if they are not saved? + + rs.data_wins = rs.processDataInFile("read","windows") + if not rs.data_wins[env_name] then + error("State for environment has never been saved!") + end + local env_state = rs.data_wins[env_name] + + local all_screens = rs.retrieveEnvironmentEntities("screens",nil) + for screen_i, screen in ipairs(all_screens) do + local screen_id = tostring(screen:id()) + local screen_index = rs.paddedToStr(screen_i) + local space_map = env[screen_index]["space_map"] + + local initial_space = hs.spaces.activeSpaceOnScreen(screen) + local screen_spaces = rs.retrieveEnvironmentEntities("spaces", screen) + + rs.delayExecution(rs.screen_pause) + for _, space in pairs(screen_spaces) do + rs.issueVerbose( + "go to space: " .. space .. " on screen: " .. screen_id, + rs.verbose + ) + hs.spaces.gotoSpace(space) + rs.delayExecution(rs.space_pause) + + local space_state = {} + local space_windows = rs.retrieveEnvironmentEntities("windows", screen) + local multitab_windows = rs.list2table(rs.multitab_apps) + for _, window in ipairs(space_windows) do + local window_state, window_id = rs.getWindowState(window) + space_state[window_id] = window_state + + if window_state["title"] == "" then + local msg = "ignored (no title): " + msg = msg .. "\tapp (" .. window_state["app"] .. ")" + msg = msg .. "\twindow id (" .. window_id .. ")" + rs.issueVerbose(msg,rs.verbose) + else + if env_state[window_id] then + window_state = env_state[window_id] + rs.issueVerbose(hs.inspect(window_state),rs.verbose) + rs.setWindowState(window, window_state, space_map) + else + if window_state["multitab"] == true then + local app = window_state["app"] + local id_list = multitab_windows[app] + id_list[#id_list + 1] = window_id + multitab_windows[app] = id_list + else + local found_state = false + local window_title = window_state["title"] for stored_id, stored_state in pairs(env_state) do - local stored_tabs = stored_state["tabs"] - rs.delayExecution(rs.multitab_pause) - if rs.compareTabs(window_tabs,stored_tabs) then + local stored_title = stored_state["title"] + if window_title == stored_title then + found_state = true rs.rename_key(env_state,stored_id,window_id) rs.setWindowState(window, stored_state, space_map) - window_matched = true - break end end - if not window_matched then - local msg = "No state found for window: "..window_id - rs.issueVerbose(msg,rs.verbose) + if not found_state then + local msg = "Unknown state for non-multitab app " + msg = msg.."window ID ("..window_id..") and " + msg = msg.."title ("..window_title..")" + end + end + end + --refineSpaceState: + for app, id_list in pairs(multitab_windows) do + if #id_list > 0 then + local id2title = {} + for _, window_id in ipairs(id_list) do + local window_state = space_state[window_id] + id2title[window_id] = window_state["title"] + end + local app_tabs = rs.getAppTabsInSpace(app,id2title) + for window_id, window_tabs in pairs(app_tabs) do + local window_matched = false + for stored_id, stored_state in pairs(env_state) do + local stored_tabs = stored_state["tabs"] + rs.delayExecution(rs.multitab_pause) + if rs.compareTabs(window_tabs,stored_tabs) then + rs.rename_key(env_state,stored_id,window_id) + rs.setWindowState(window, stored_state, space_map) + window_matched = true + break + end + end + if not window_matched then + local msg = "No state found for window: "..window_id + rs.issueVerbose(msg,rs.verbose) + end end end end end + end - end + hs.spaces.gotoSpace(initial_space) end - hs.spaces.gotoSpace(initial_space) + rs.notifyUser("apply") + end + + --[[ + --TODO: function mod.turnoffEnvironment() + -- save environment state, close each app running, and turn-off + function mod.turnoff() + os.execute("osascript -e 'tell app \"System Events\" to shut down'") end - rs.notifyUser("apply") -end - ---[[ ---TODO: function mod.turnoffEnvironment() --- save environment state, close each app running, and turn-off -function mod.turnoff() - os.execute("osascript -e 'tell app \"System Events\" to shut down'") -end ---]] + --]] end \ No newline at end of file