diff --git a/README.md b/README.md index 5cca75d..38cd291 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# alfred-window-manager +# Alfred Window Manager Alfred Workflow to move/resize/position windows + +## Requirements + +* [Automation Tasks](https://www.alfredapp.com/help/workflows/automations/automation-task/) + +## Usage + +* ⇧↓ : Move to next screen +* ⇧↑ : Move to previous screen +* ^⌥M : Maximize +* ^⌥C : Center Window +* ^⌥← : Window to the left screen +* ^⌥→ : Window to the right screen +* ⌃⌥R : Reset Window +* ⇧⌥W : Scale up window +* ⇧⌥S : Scale down window +* ⇧⌥D : Scale up right side of the window +* ⇧⌥A : Scale up left side of the window +* ⌥ A : Move window left +* ⌥ D : Move window right +* ⌥ W : Move window up +* ⌥ S : Move window down \ No newline at end of file diff --git a/Window Manager.alfredworkflow b/Window Manager.alfredworkflow new file mode 100644 index 0000000..a908a5d Binary files /dev/null and b/Window Manager.alfredworkflow differ diff --git a/src/Alfred3.py b/src/Alfred3.py new file mode 100644 index 0000000..8205f7a --- /dev/null +++ b/src/Alfred3.py @@ -0,0 +1,562 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +import json +import os +import sys +import time +from plistlib import dump, load + +""" +Alfred Script Filter generator class +Version: 4.0 +Python 3 required! +""" + + +class Items(object): + """ + Alfred WF Items object to generate Script Filter object + + Returns: + + object: WF object + """ + + def __init__(self): + self.item = {} + self.items = [] + self.mods = {} + + def getItemsLengths(self) -> int: + return len(self.items) + + def setKv(self, key: str, value: str) -> None: + """ + Set a key value pair to item + + Args: + + key (str): Name of the Key + value (str): Value of the Key + """ + self.item.update({key: value}) + + def addItem(self) -> None: + """ + Add/commits an item to the Script Filter Object + + Note: addItem needs to be called after setItem, addMod, setIcon + """ + self.addModsToItem() + self.items.append(self.item) + self.item = {} + self.mods = {} + + def setItem(self, **kwargs: str) -> None: + """ + Add multiple key values to define an item + + Note: addItem needs to be called to submit a Script Filter item + to the Script Filter object + + Args: + + kwargs (kwargs): title,subtitle,arg,valid,quicklookurl,uid,automcomplete,type + """ + for key, value in kwargs.items(): + self.setKv(key, value) + + def getItem(self, d_type: str = "") -> str: + """ + Get current item definition for validation + + Args: + + d_type (str, optional): defines returned object format "JSON" if it needs to be readable . Defaults to "". + + Returns: + + str: JSON represenation of an item + """ + if d_type == "": + return self.item + else: + return json.dumps(self.item, default=str, indent=4) + + def getItems(self, response_type: str = "json") -> json: + """ + get the final items data for which represents the script filter output + + Args: + + response_type (str, optional): "dict"|"json". Defaults to "json". + + Raises: + + ValueError: If key is not "dict"|"json" + + Returns: + + str: returns the item representing script filter output + """ + valid_keys = {"json", "dict"} + if response_type not in valid_keys: + raise ValueError(f"Type must be in: {valid_keys}") + the_items = dict() + the_items.update({"items": self.items}) + if response_type == "dict": + return the_items + elif response_type == "json": + return json.dumps(the_items, default=str, indent=4) + + def setIcon(self, m_path: str, m_type: str = "") -> None: + """ + Set the icon of an item. + Needs to be called before addItem! + + Args: + + m_path (str): Path to the icon + m_type (str, optional): "icon"|"fileicon". Defaults to "". + """ + self.setKv("icon", self.__define_icon(m_path, m_type)) + + def __define_icon(self, path: str, m_type: str = "") -> dict: + """ + Private method to create icon set + + Args: + + path (str): Path to the icon file + + m_type (str, optional): "image"|"fileicon". Defaults to "". + + Returns: + + dict: icon and type + """ + icon = {} + if m_type != "": + icon.update({"type": m_type}) + icon.update({"path": path}) + return icon + + def addMod( + self, + key: str, + arg: str, + subtitle: str, + valid: bool = True, + icon_path: str = "", + icon_type: str = "", + ) -> None: + """ + Add a mod to an item + + Args: + + key (str): "alt"|"cmd"|"shift"|"fn"|"ctrl + arg (str): Value of Mod arg + subtitle (str): Subtitle + valid (bool, optional): Arg valid or not. Defaults to True. + icon_path (str, optional): Path to the icon relative to WF dir. Defaults to "". + icon_type (str, optional): "image"|"fileicon". Defaults to "". + + Raises: + + ValueError: if key is not in list + """ + valid_keys = {"alt", "cmd", "shift", "ctrl", "fn"} + if key not in valid_keys: + raise ValueError(f"Key must be in: {valid_keys}") + mod = {} + mod.update({"arg": arg}) + mod.update({"subtitle": subtitle}) + mod.update({"valid": valid}) + if icon_path != "": + the_icon = self.__define_icon(icon_path, icon_type) + mod.update({"icon": the_icon}) + self.mods.update({key: mod}) + + def addModsToItem(self) -> None: + """ + Adds mod to an item + """ + if bool(self.mods): + self.setKv("mods", self.mods) + self.mods = dict() + + def updateItem(self, id: int, key: str, value: str) -> None: + """ + Update an Alfred script filter item key with a new value + + Args: + + id (int): list indes + key (str): key which needs to be updated + value (str): new value + """ + dict_item = self.items[id] + kv = dict_item[key] + dict_item[key] = kv + value + self.items[id] = dict_item + + def write(self, response_type: str = "json") -> None: + """ + Generate Script Filter Output and write back to stdout + + Args: + + response_type (str, optional): json or dict as output format. Defaults to 'json'. + """ + output = self.getItems(response_type=response_type) + sys.stdout.write(output) + + +class Tools(object): + """ + Alfred Tools, helpful methos when dealing with Scripts in Alfred + + Args: + + object (obj): Object class + """ + @staticmethod + def logPyVersion() -> None: + """ + Log Python Version to shell + """ + Tools.log("PYTHON VERSION:", sys.version) + + @staticmethod + def log(*message) -> None: + """ + Log message to stderr + """ + sys.stderr.write(f'{" ".join(message)}\n') + + @staticmethod + def getEnv(var: str, default: str = str()) -> str: + """ + Reads environment variable + + Args: + + var (string}: Variable name + default (string, optional): fallback if None + + Returns: + + (str): Env value or string if not available + """ + return os.getenv(var) if os.getenv(var) is not None else default + + @staticmethod + def getEnvBool(var: str, default: bool = False) -> bool: + """ + Reads boolean env variable provided as text. + 0 will be treated as False + >1 will be treated as True + + Args: + + var (str): Name of the env variable + default (bool, optional): Default if not found. Defaults to False. + + Returns: + + bool: True or False as bool + """ + if os.getenv(var).isdigit(): + if os.getenv(var) == '0': + return False + else: + return True + if os.getenv(var).lower() == "true": + return True + else: + return default + + @staticmethod + def getArgv(i: int, default=str()) -> str: + """ + Get argument values from input in Alfred or empty if not available + + Args: + + i (int): index of argument + default (string, optional): Fallback if None, default string + + Returns: + + response_type (str) -- argv string or None + """ + try: + return sys.argv[i] + except IndexError: + return default + pass + + @staticmethod + def getDateStr(float_time: float, format: str = "%d.%m.%Y") -> str: + """ + Format float time to string + + Args: + + float_time (float): Time in float + + format (str, optional): format string. Defaults to '%d.%m.%Y'. + + Returns: + + str: Formatted Date String + """ + time_struct = time.gmtime(float_time) + return time.strftime(format, time_struct) + + @staticmethod + def getDateEpoch(float_time: float) -> str: + return time.strftime("%d.%m.%Y", time.gmtime(float_time / 1000)) + + @staticmethod + def sortListDict(list_dict: list, key: str, reverse: bool = True) -> list: + """ + Sort List with Dictionary based on given key in Dict + + Args: + + list_dict (list(dict)): List which contains unsorted dictionaries + + key (str): name of the key of the dict + + reverse (bool, optional): Reverse order. Defaults to True. + + Returns: + + list(dict): sorted list of dictionaries + """ + return sorted(list_dict, key=lambda k: k[key], reverse=reverse) + + @staticmethod + def sortListTuple(list_tuple: list, el: int, reverse: bool = True) -> list: + """ + Sort List with Tubles based on a given element in Tuple + + Args: + + list_tuple (list(tuble)): Sort List with Tubles based on a given element in Tuple + el (int): which element + reverse (bool, optional): Reverse order. Defaults to True. + + Returns: + + list(tuble) -- sorted list with tubles + """ + return sorted(list_tuple, key=lambda tup: tup[el], reverse=reverse) + + @staticmethod + def notify(title: str, text: str) -> None: + """ + Send Notification to mac Notification Center + + Arguments: + + title (str): Title String + text (str): The message + """ + os.system( + f""" + osascript -e 'display notification "{text}" with title "{title}"' + """ + ) + + @staticmethod + def strJoin(*args: str) -> str: + """Joins a list of strings + + Arguments: + + *args (list): List which contains strings + + Returns: + + str: joined str + """ + return str().join(args) + + @staticmethod + def chop(theString: str, ext: str) -> str: + """ + Cuts a string from the end and return the remaining + + Args: + + theString (str): The String to cut + ext (str): String which needs to be removed + + Returns: + + str: chopped string + """ + if theString.endswith(ext): + return theString[: -len(ext)] + return theString + + @staticmethod + def getEnvironment() -> dict: + """ + Get all environment variablse as a dict + + Returns: + + dict: Dict with env variables e.g. {"env1": "value"} + """ + environment = os.environ + env_dict = dict() + for k, v in environment.iteritems(): + env_dict.update({k: v}) + return env_dict + + @staticmethod + def getDataDir() -> str: + data_dir = os.getenv("alfred_workflow_data") + if not (os.path.isdir(data_dir)): + os.mkdir(data_dir) + return data_dir + + @staticmethod + def getCacheDir() -> str: + cache_dir = os.getenv("alfred_workflow_cache") + if not(os.path.isdir(cache_dir)): + os.mkdir(cache_dir) + return cache_dir + + +class Plist: + """ + Plist handling class + + Returns: + + object: A plist object + + + """ + + def __init__(self): + # Read info.plist into a standard Python dictionary + with open("info.plist", "rb") as fp: + self.info = load(fp) + + def getConfig(self) -> str: + return self.info["variables"] + + def getVariable(self, variable: str) -> str: + """ + Get Plist variable with name + + Args: + + variable (str): Name of the variable + + Returns: + + str: Value of variable with name + + """ + try: + return self.info["variables"][variable] + except KeyError: + pass + + def setVariable(self, variable: str, value: str) -> None: + """ + Set a Plist variable + + Args: + + variable (str): Name of Plist Variable + value (str): Value of Plist Variable + + """ + # Set a variable + self.info["variables"][variable] = value + self._saveChanges() + + def deleteVariable(self, variable: str) -> None: + """ + Delete a Plist variable with name + + Args: + + variable (str): Name of the Plist variable + + """ + try: + del self.info["variables"][variable] + self._saveChanges() + except KeyError: + pass + + def _saveChanges(self) -> None: + """ + Save changes to Plist + """ + with open("info.plist", "wb") as fp: + dump(self.info, fp) + + +class Keys(object): + CMD = u'\u2318' + SHIFT = u'\u21E7' + ENTER = u'\u23CE' + ARROW_RIGHT = u'\u2192' + + +class AlfJson(object): + + def __init__(self) -> None: + self.arg: dict = dict() + self.config: dict = dict() + self.variables: dict = dict() + + def add_args(self, d) -> None: + """ + Add arg dictionary + + Args: + + d (dict): Key-Value pairs of args + + """ + self.arg.update(d) + + def add_configs(self, d) -> None: + """ + Add config dictionary + + Args: + + d (dict): Key-Value pairs of configs + + """ + self.config.update(d) + + def add_variables(self, d) -> None: + """ + Add variables dictionary + + Args: + + d (dict): Key-Value pairs of variables + + """ + self.variables.update(d) + + def write_json(self) -> None: + """ + Write Alfred JSON config object to std out + """ + out = {"alfredworkflow": {"arg": self.arg, "config": self.config, "variables": self.variables}} + sys.stdout.write(json.dumps(out)) diff --git a/src/WindowManager.py b/src/WindowManager.py new file mode 100644 index 0000000..72515c0 --- /dev/null +++ b/src/WindowManager.py @@ -0,0 +1,126 @@ +#!/usr/bin/python3 + +import json +import os + + +class Dimensions(object): + + def __init__(self, file: str) -> None: + """ + Handles dimension.json file storage + + Args: + + file (str): path to .json + + """ + self.file = file + + def add_dimension(self, app_id: str, dim: dict) -> None: + """ + Add dimension app setting + + Args: + + app_id (str): Bundle ID of the app + dim (dict): Dimension Dictonary + + """ + jsn = dict() + if os.path.exists(self.file): + with open(self.file, "r") as f: + jsn: dict = json.load(f) + jsn.pop(app_id, False) + jsn[app_id] = dim + self._save_json_file(jsn) + + def get_dimension(self, app_id: str) -> dict: + """ + get dimension for an app id + + Args: + + app_id (str): app bundle id + + + Returns: + + dict: _description_ + + """ + jsn = self._read_json_file() + dimension = jsn.get(app_id, None) + return dimension + + def delete_dimension(self, app_id: str) -> None: + """ + Delete a dimension for an app id + + Args: + + app_id (str): _description_ + + """ + jsn = self._read_json_file() + jsn.pop(app_id, False) + self._save_json_file(jsn) + + def _file_check(self, file: str) -> str: + if not os.path.exists(file): + with open(file, "w") as f: + pass + return file + + def _read_json_file(self) -> dict: + jsn = dict() + if os.path.exists(self.file): + with open(self.file, "r") as f: + jsn: dict = json.load(f) + return jsn + + def _save_json_file(self, jsn: dict) -> None: + with open(self.file, "w") as f: + f.seek(0) + json.dump(jsn, f) + f.truncate() + + +class Window(object): + + def __init__(self, dim: str()) -> None: + self.dimension = json.loads(dim) + + def x_pos(self) -> int: + return int(self.dimension.get('x', None)) + + def y_pos(self) -> int: + return int(self.dimension.get('y', None)) + + def width(self) -> int: + return int(self.dimension.get('width', None)) + + def height(self) -> int: + return int(self.dimension.get('height', None)) + + def get_dimensions(self) -> dict: + return self.dimension + + +class Screen(object): + + def __init__(self) -> None: + self.screen_res = self._sys_profiler() + + def screen_width(self) -> int: + return self.screen_res[0] + + def screen_height(self) -> int: + return self.screen_res[1] + + def _sys_profiler(self) -> tuple: + sysinfo: dict = json.loads(os.popen("system_profiler SPDisplaysDataType -json").read()) + screen_dimensions = sysinfo.get('SPDisplaysDataType')[0].get('spdisplays_ndrvs')[0].get('_spdisplays_resolution') + res, freq = screen_dimensions.split(" @ ") + screen_width, screen_height = res.split(" x ") + return (int(screen_width), int(screen_height)) diff --git a/src/get_dimensions.py b/src/get_dimensions.py new file mode 100644 index 0000000..85c38d1 --- /dev/null +++ b/src/get_dimensions.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 + +import json +import os + +from Alfred3 import AlfJson, Tools +from WindowManager import Dimensions + +cache_dir = Tools.getCacheDir() +cache_file = os.path.join(cache_dir, "dimensions.json") + +app_id = Tools.getArgv(1) # get app id from previous wf step + +Dim = Dimensions(cache_file) +vars = Dim.get_dimension(app_id) + +if vars == None: + vars = json.loads('{"x":521,"y":404,"width":1073,"height":565}') # fallback + +aj = AlfJson() +aj.add_variables(vars) +aj.write_json() diff --git a/src/icon.png b/src/icon.png new file mode 100644 index 0000000..40a8293 Binary files /dev/null and b/src/icon.png differ diff --git a/src/info.plist b/src/info.plist new file mode 100644 index 0000000..62b8ac7 --- /dev/null +++ b/src/info.plist @@ -0,0 +1,1715 @@ + + + + + bundleid + com.apple.alfred.workflow.window_manager + category + Productivity + connections + + 0AA55BB7-9F32-4DA6-8F55-DAD10CF532FF + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + 0FB4C25D-DC38-42C7-8070-516CE4943789 + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + 3204E945-B6BF-4265-AAA8-C6C25741884E + + + destinationuid + EEB61DAA-AC41-493F-9CA0-93678C6BCD3B + modifiers + 0 + modifiersubtext + + vitoclose + + + + 3411AFF0-2041-4E95-B451-45847D3EFE3E + + + destinationuid + F9ED3B81-132C-4B76-AFED-DC01089EDD0D + modifiers + 0 + modifiersubtext + + vitoclose + + + + 34BFA0E9-B1E3-4F31-B594-AAEED8A8299D + + + destinationuid + AC6D5BAC-92B8-4ECE-A42D-FCED1768CBCC + modifiers + 0 + modifiersubtext + + vitoclose + + + + 38151F7B-8008-4180-B4E4-9C5D423C5066 + + + destinationuid + E90EB173-3B7E-4C6C-B148-E83B370D1154 + modifiers + 0 + modifiersubtext + + vitoclose + + + + 411DB4D7-6C73-4FA1-99A5-E2A2C8068D85 + + + destinationuid + D0BA191C-A3CE-4974-A263-B9979376FFEF + modifiers + 0 + modifiersubtext + + vitoclose + + + + 4F266713-42A1-460C-B59D-AA7839398302 + + + destinationuid + D5D03267-BC05-4901-A995-FF97DC4788D2 + modifiers + 0 + modifiersubtext + + vitoclose + + + + 5E6F5E3B-B9CB-449B-9724-E4E8B4F45CC1 + + + destinationuid + 61F8EE82-4398-4234-8CC2-A79D378F0D91 + modifiers + 0 + modifiersubtext + + vitoclose + + + + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + + + destinationuid + 3411AFF0-2041-4E95-B451-45847D3EFE3E + modifiers + 0 + modifiersubtext + + vitoclose + + + + 7903F372-2DC3-488B-9D7F-E575C0C1DFFE + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + 7AB06038-BFF3-4C09-87A1-2558A032D37A + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + 7AE7B102-6F0E-4086-A929-4CBC0F8F230F + + + destinationuid + 951B4211-41AD-4138-967E-9C35F483E46F + modifiers + 0 + modifiersubtext + + vitoclose + + + + 7FBAFD3E-D7E8-4F5E-B79C-08BAE6819634 + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + 888A02A3-2C70-4D4A-A926-A4954E8E9BD5 + + + destinationuid + 4F266713-42A1-460C-B59D-AA7839398302 + modifiers + 0 + modifiersubtext + + vitoclose + + + + 951B4211-41AD-4138-967E-9C35F483E46F + + + destinationuid + ED2BA858-527C-44E5-9FF2-D9421C2F823C + modifiers + 0 + modifiersubtext + + vitoclose + + + + AC6D5BAC-92B8-4ECE-A42D-FCED1768CBCC + + + destinationuid + 0B235D11-0F2E-4EE8-8F4E-53F2BADC8258 + modifiers + 0 + modifiersubtext + + vitoclose + + + + B112413B-7E57-4B10-8450-284C42B5E36F + + + destinationuid + B269FAEA-BEC7-42B2-8575-605D0C2C09EC + modifiers + 0 + modifiersubtext + + vitoclose + + + + B269FAEA-BEC7-42B2-8575-605D0C2C09EC + + + destinationuid + 34BFA0E9-B1E3-4F31-B594-AAEED8A8299D + modifiers + 0 + modifiersubtext + + vitoclose + + + + C57CE0D7-D6BB-49AC-83C0-7B26EF57644A + + + destinationuid + D9985E7C-DCF8-4D3B-8FBC-0F7B70D3F973 + modifiers + 0 + modifiersubtext + + vitoclose + + + + C76A989A-F196-41C9-BF7C-D85DE630794D + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + D0BA191C-A3CE-4974-A263-B9979376FFEF + + + destinationuid + B112413B-7E57-4B10-8450-284C42B5E36F + modifiers + 0 + modifiersubtext + + vitoclose + + + + D2302F7A-7011-4B69-995F-29395ABE636E + + + destinationuid + D0BA191C-A3CE-4974-A263-B9979376FFEF + modifiers + 0 + modifiersubtext + + vitoclose + + + + D5D03267-BC05-4901-A995-FF97DC4788D2 + + + destinationuid + C57CE0D7-D6BB-49AC-83C0-7B26EF57644A + modifiers + 0 + modifiersubtext + + vitoclose + + + + D9985E7C-DCF8-4D3B-8FBC-0F7B70D3F973 + + + destinationuid + CFAD3BF2-AB4B-4DA4-ADD9-3C68698ABA6E + modifiers + 0 + modifiersubtext + + vitoclose + + + + E71F89BE-1869-4DAF-BCDD-CA883A26CECF + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + ED2BA858-527C-44E5-9FF2-D9421C2F823C + + + destinationuid + 26D6E144-607A-492B-9D56-EE7349D443B4 + modifiers + 0 + modifiersubtext + + vitoclose + + + + F9ED3B81-132C-4B76-AFED-DC01089EDD0D + + + destinationuid + 10499795-894B-44C4-9953-016F46E1135D + modifiers + 0 + modifiersubtext + + vitoclose + + + + destinationuid + CDB9929D-6132-44B6-96BD-A3FBF2A5EAD4 + modifiers + 0 + modifiersubtext + + vitoclose + + + + FE582004-9AD4-40DD-AB0B-13BEABC1B505 + + + destinationuid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + modifiers + 0 + modifiersubtext + + vitoclose + + + + + createdby + Acidham + description + Move/Resize/Arrange Windows + disabled + + name + Window Manager + objects + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + hotkey + 121 + hotmod + 8519680 + hotstring + + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 5E6F5E3B-B9CB-449B-9724-E4E8B4F45CC1 + version + 2 + + + config + + tasksettings + + target_screen + next + + taskuid + com.alfredapp.automation.core/window-management/windows.current.screen.change + + type + alfred.workflow.automation.task + uid + 61F8EE82-4398-4234-8CC2-A79D378F0D91 + version + 1 + + + config + + tasksettings + + target_screen + previous + + taskuid + com.alfredapp.automation.core/window-management/windows.current.screen.change + + type + alfred.workflow.automation.task + uid + EEB61DAA-AC41-493F-9CA0-93678C6BCD3B + version + 1 + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + hotkey + 116 + hotmod + 8519680 + hotstring + + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 3204E945-B6BF-4265-AAA8-C6C25741884E + version + 2 + + + config + + tasksettings + + out_format + bundle_id + + taskuid + com.alfredapp.automation.core/macOS/app.current + + type + alfred.workflow.automation.task + uid + 4F266713-42A1-460C-B59D-AA7839398302 + version + 1 + + + config + + tasksettings + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds + + type + alfred.workflow.automation.task + uid + C57CE0D7-D6BB-49AC-83C0-7B26EF57644A + version + 1 + + + config + + tasksettings + + preset + maximise + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds.set.preset + + type + alfred.workflow.automation.task + uid + CFAD3BF2-AB4B-4DA4-ADD9-3C68698ABA6E + version + 1 + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + hotkey + 46 + hotmod + 786432 + hotstring + M + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 888A02A3-2C70-4D4A-A926-A4954E8E9BD5 + version + 2 + + + config + + concurrently + + escaping + 102 + script + query=$1 + +./save_dimensions.py $query + scriptargtype + 1 + scriptfile + + type + 5 + + type + alfred.workflow.action.script + uid + D9985E7C-DCF8-4D3B-8FBC-0F7B70D3F973 + version + 2 + + + config + + argument + + passthroughargument + + variables + + app_id + {query} + + + type + alfred.workflow.utility.argument + uid + D5D03267-BC05-4901-A995-FF97DC4788D2 + version + 1 + + + config + + tasksettings + + preset + centre + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds.set.preset + + type + alfred.workflow.automation.task + uid + E90EB173-3B7E-4C6C-B148-E83B370D1154 + version + 1 + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + hotkey + 8 + hotmod + 786432 + hotstring + C + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 38151F7B-8008-4180-B4E4-9C5D423C5066 + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + left + focusedappvariable + + focusedappvariablename + + hotkey + 123 + hotmod + 11272192 + hotstring + + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + D2302F7A-7011-4B69-995F-29395ABE636E + version + 2 + + + config + + concurrently + + escaping + 102 + script + query=$1 + +./window_pos.py $query + scriptargtype + 1 + scriptfile + + type + 5 + + type + alfred.workflow.action.script + uid + AC6D5BAC-92B8-4ECE-A42D-FCED1768CBCC + version + 2 + + + config + + tasksettings + + height + {var:height} + width + {var:width} + x + {var:x} + y + {var:y} + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds.set.custom + + type + alfred.workflow.automation.task + uid + 0B235D11-0F2E-4EE8-8F4E-53F2BADC8258 + version + 1 + + + config + + tasksettings + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds + + type + alfred.workflow.automation.task + uid + 34BFA0E9-B1E3-4F31-B594-AAEED8A8299D + version + 1 + + + config + + tasksettings + + out_format + bundle_id + + taskuid + com.alfredapp.automation.core/macOS/app.current + + type + alfred.workflow.automation.task + uid + B112413B-7E57-4B10-8450-284C42B5E36F + version + 1 + + + config + + argument + + passthroughargument + + variables + + app_id + {query} + + + type + alfred.workflow.utility.argument + uid + B269FAEA-BEC7-42B2-8575-605D0C2C09EC + version + 1 + + + config + + argument + + passthroughargument + + variables + + direction + {query} + + + type + alfred.workflow.utility.argument + uid + D0BA191C-A3CE-4974-A263-B9979376FFEF + version + 1 + + + config + + action + 0 + argument + 3 + argumenttext + right + focusedappvariable + + focusedappvariablename + + hotkey + 124 + hotmod + 11272192 + hotstring + + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 411DB4D7-6C73-4FA1-99A5-E2A2C8068D85 + version + 2 + + + config + + tasksettings + + out_format + bundle_id + + taskuid + com.alfredapp.automation.core/macOS/app.current + + type + alfred.workflow.automation.task + uid + 951B4211-41AD-4138-967E-9C35F483E46F + version + 1 + + + config + + concurrently + + escaping + 102 + script + query=$1 + +./get_dimensions.py $query + scriptargtype + 1 + scriptfile + + type + 5 + + type + alfred.workflow.action.script + uid + ED2BA858-527C-44E5-9FF2-D9421C2F823C + version + 2 + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + hotkey + 15 + hotmod + 786432 + hotstring + R + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 7AE7B102-6F0E-4086-A929-4CBC0F8F230F + version + 2 + + + config + + tasksettings + + height + {var:height} + width + {var:width} + x + {var:x} + y + {var:y} + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds.set.custom + + type + alfred.workflow.automation.task + uid + 26D6E144-607A-492B-9D56-EE7349D443B4 + version + 1 + + + config + + tasksettings + + height + {var:height} + width + {var:width} + x + {var:x} + y + {var:y} + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds.set.custom + + type + alfred.workflow.automation.task + uid + 10499795-894B-44C4-9953-016F46E1135D + version + 1 + + + config + + action + 0 + argument + 3 + argumenttext + up_size + focusedappvariable + + focusedappvariablename + + hotkey + 13 + hotmod + 655360 + hotstring + W + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + FE582004-9AD4-40DD-AB0B-13BEABC1B505 + version + 2 + + + config + + concurrently + + escaping + 102 + script + query=$1 + +./resize.py $query + scriptargtype + 1 + scriptfile + + type + 5 + + type + alfred.workflow.action.script + uid + F9ED3B81-132C-4B76-AFED-DC01089EDD0D + version + 2 + + + config + + tasksettings + + taskuid + com.alfredapp.automation.core/window-management/windows.current.bounds + + type + alfred.workflow.automation.task + uid + 3411AFF0-2041-4E95-B451-45847D3EFE3E + version + 1 + + + config + + argument + + passthroughargument + + variables + + direction + {query} + + + type + alfred.workflow.utility.argument + uid + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + version + 1 + + + config + + argument + '{query}', {variables} + cleardebuggertext + + processoutputs + + + type + alfred.workflow.utility.debug + uid + CDB9929D-6132-44B6-96BD-A3FBF2A5EAD4 + version + 1 + + + config + + action + 0 + argument + 3 + argumenttext + down_size + focusedappvariable + + focusedappvariablename + + hotkey + 1 + hotmod + 655360 + hotstring + S + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 0FB4C25D-DC38-42C7-8070-516CE4943789 + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + right_size + focusedappvariable + + focusedappvariablename + + hotkey + 2 + hotmod + 655360 + hotstring + D + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + C76A989A-F196-41C9-BF7C-D85DE630794D + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + left_size + focusedappvariable + + focusedappvariablename + + hotkey + 0 + hotmod + 655360 + hotstring + A + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 0AA55BB7-9F32-4DA6-8F55-DAD10CF532FF + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + left + focusedappvariable + + focusedappvariablename + + hotkey + 0 + hotmod + 524288 + hotstring + A + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 7903F372-2DC3-488B-9D7F-E575C0C1DFFE + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + right + focusedappvariable + + focusedappvariablename + + hotkey + 2 + hotmod + 524288 + hotstring + D + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 7AB06038-BFF3-4C09-87A1-2558A032D37A + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + up + focusedappvariable + + focusedappvariablename + + hotkey + 13 + hotmod + 524288 + hotstring + W + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 7FBAFD3E-D7E8-4F5E-B79C-08BAE6819634 + version + 2 + + + config + + action + 0 + argument + 3 + argumenttext + down + focusedappvariable + + focusedappvariablename + + hotkey + 1 + hotmod + 524288 + hotstring + S + leftcursor + + modsmode + 0 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + E71F89BE-1869-4DAF-BCDD-CA883A26CECF + version + 2 + + + readme + # Window Manager + +## Requirements + +* [Automation Tasks](https://www.alfredapp.com/help/workflows/automations/automation-task/) + +## Usage + +* ⇧↓ : Move to next screen +* ⇧↑ : Move to previous screen +* ^⌥M : Maximize +* ^⌥C : Center Window +* ^⌥← : Window to the left screen +* ^⌥→ : Window to the right screen +* ⌃⌥R : Reset Window +* ⇧⌥W : Scale up window +* ⇧⌥S : Scale down window +* ⇧⌥D : Scale up right side of the window +* ⇧⌥A : Scale up left side of the window +* ⌥ A : Move window left +* ⌥ D : Move window right +* ⌥ W : Move window up +* ⌥ S : Move window down + uidata + + 0AA55BB7-9F32-4DA6-8F55-DAD10CF532FF + + note + Scale up left side of the window + xpos + 60 + ypos + 1695 + + 0B235D11-0F2E-4EE8-8F4E-53F2BADC8258 + + xpos + 1095 + ypos + 755 + + 0FB4C25D-DC38-42C7-8070-516CE4943789 + + note + Scale down window + xpos + 60 + ypos + 1385 + + 10499795-894B-44C4-9953-016F46E1135D + + xpos + 880 + ypos + 1245 + + 26D6E144-607A-492B-9D56-EE7349D443B4 + + xpos + 640 + ypos + 990 + + 3204E945-B6BF-4265-AAA8-C6C25741884E + + colorindex + 9 + note + Move to previous screen + xpos + 60 + ypos + 195 + + 3411AFF0-2041-4E95-B451-45847D3EFE3E + + xpos + 405 + ypos + 1245 + + 34BFA0E9-B1E3-4F31-B594-AAEED8A8299D + + xpos + 645 + ypos + 755 + + 38151F7B-8008-4180-B4E4-9C5D423C5066 + + note + Center Window + xpos + 60 + ypos + 520 + + 411DB4D7-6C73-4FA1-99A5-E2A2C8068D85 + + colorindex + 3 + note + Window to the right screen + xpos + 60 + ypos + 830 + + 4F266713-42A1-460C-B59D-AA7839398302 + + xpos + 245 + ypos + 375 + + 5E6F5E3B-B9CB-449B-9724-E4E8B4F45CC1 + + colorindex + 9 + note + Move to next screen + xpos + 60 + ypos + 60 + + 61F8EE82-4398-4234-8CC2-A79D378F0D91 + + xpos + 375 + ypos + 60 + + 69CEBDAD-0632-4A61-9233-9F35F7E9F02C + + xpos + 315 + ypos + 1275 + + 7903F372-2DC3-488B-9D7F-E575C0C1DFFE + + note + Move window left + xpos + 60 + ypos + 1845 + + 7AB06038-BFF3-4C09-87A1-2558A032D37A + + note + Move window right + xpos + 60 + ypos + 1980 + + 7AE7B102-6F0E-4086-A929-4CBC0F8F230F + + colorindex + 3 + note + Reset Window + xpos + 60 + ypos + 990 + + 7FBAFD3E-D7E8-4F5E-B79C-08BAE6819634 + + note + Move window up + xpos + 60 + ypos + 2135 + + 888A02A3-2C70-4D4A-A926-A4954E8E9BD5 + + note + Maximize + xpos + 60 + ypos + 375 + + 951B4211-41AD-4138-967E-9C35F483E46F + + xpos + 255 + ypos + 990 + + AC6D5BAC-92B8-4ECE-A42D-FCED1768CBCC + + note + Save dimensions + xpos + 875 + ypos + 755 + + B112413B-7E57-4B10-8450-284C42B5E36F + + xpos + 420 + ypos + 755 + + B269FAEA-BEC7-42B2-8575-605D0C2C09EC + + xpos + 570 + ypos + 785 + + C57CE0D7-D6BB-49AC-83C0-7B26EF57644A + + xpos + 480 + ypos + 375 + + C76A989A-F196-41C9-BF7C-D85DE630794D + + note + Scale up right side of the window + xpos + 60 + ypos + 1540 + + CDB9929D-6132-44B6-96BD-A3FBF2A5EAD4 + + xpos + 815 + ypos + 1370 + + CFAD3BF2-AB4B-4DA4-ADD9-3C68698ABA6E + + xpos + 830 + ypos + 375 + + D0BA191C-A3CE-4974-A263-B9979376FFEF + + xpos + 335 + ypos + 785 + + D2302F7A-7011-4B69-995F-29395ABE636E + + colorindex + 4 + note + Window to the left screen + xpos + 60 + ypos + 695 + + D5D03267-BC05-4901-A995-FF97DC4788D2 + + xpos + 395 + ypos + 405 + + D9985E7C-DCF8-4D3B-8FBC-0F7B70D3F973 + + xpos + 665 + ypos + 375 + + E71F89BE-1869-4DAF-BCDD-CA883A26CECF + + note + Move window down + xpos + 60 + ypos + 2255 + + E90EB173-3B7E-4C6C-B148-E83B370D1154 + + xpos + 390 + ypos + 520 + + ED2BA858-527C-44E5-9FF2-D9421C2F823C + + xpos + 425 + ypos + 990 + + EEB61DAA-AC41-493F-9CA0-93678C6BCD3B + + xpos + 380 + ypos + 195 + + F9ED3B81-132C-4B76-AFED-DC01089EDD0D + + xpos + 605 + ypos + 1245 + + FE582004-9AD4-40DD-AB0B-13BEABC1B505 + + note + Scale up window + xpos + 60 + ypos + 1245 + + + userconfigurationconfig + + + config + + default + 0.1 + placeholder + 0.1 + required + + trim + + + description + Factor that will be applied when resizing window + label + Resizing factor + type + textfield + variable + resize_factor + + + config + + default + 80 + placeholder + 80 + required + + trim + + + description + Pixels to move windows + label + Window move + type + textfield + variable + move_steps + + + variablesdontexport + + version + 1.0.0 + webaddress + https://github.com/Acidham/alfred-window-manager + + diff --git a/src/resize.py b/src/resize.py new file mode 100644 index 0000000..10a1bf0 --- /dev/null +++ b/src/resize.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 + +import json +import sys + +from Alfred3 import AlfJson, Tools +from WindowManager import Window + +dimensions = Tools.getArgv(1) +direction = Tools.getEnv("direction") +resize_factor = float(Tools.getEnv("resize_factor", 0.1)) +move_steps = int(Tools.getEnv("move_steps", 80)) + +# Get window position from for frontmost app +Window = Window(dimensions) +x = Window.x_pos() +y = Window.y_pos() +width = Window.width() +height = Window.height() + +# Size window up and down +if direction == "up_size" or direction == "down_size": + resize_factor = resize_factor * -1 if direction == "down_size" else resize_factor + width = width+width*resize_factor + height = height+height*resize_factor +# Window bigger to the right +if direction == "right_size": + width = width+width*resize_factor +# Window smaller to the left +if direction == "left_size": + width = width - width*resize_factor +# Move Window left +if direction == "left": + x = x-move_steps +# Move Window right +if direction == "right": + x = x+move_steps +# Move Window up +if direction == "up": + y = y-move_steps +# Move window down +if direction == "down": + y = y+move_steps + +vars = {"x": x, "y": y, "width": width, "height": height} + +aj = AlfJson() +aj.add_variables(vars) +aj.write_json() diff --git a/src/save_dimensions.py b/src/save_dimensions.py new file mode 100644 index 0000000..a31f5cd --- /dev/null +++ b/src/save_dimensions.py @@ -0,0 +1,14 @@ +#!/usr/bin/python3 + +import os + +from Alfred3 import Tools +from WindowManager import Dimensions +import json + +app_id = Tools.getEnv('app_id') +window_dimensions = json.loads(Tools.getArgv(1)) +cache_file = os.path.join(Tools.getCacheDir(), "dimensions.json") + +Dim = Dimensions(cache_file) +Dim.add_dimension(app_id, window_dimensions) diff --git a/src/window_pos.py b/src/window_pos.py new file mode 100644 index 0000000..a396ad6 --- /dev/null +++ b/src/window_pos.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 + +import json +import os +import sys + +from Alfred3 import AlfJson, Tools +from WindowManager import Dimensions, Screen, Window + +cache_dir = Tools.getCacheDir() +cache_file = os.path.join(cache_dir, "dimensions.json") +Tools.log(f"Cache File: {cache_file}") +app_id = Tools.getEnv("app_id") +Tools.log(f"App ID: {app_id}") + + +window_postion = Tools.getArgv(1) +direction = Tools.getEnv("direction") + +# Get Window positions of Frontmost app +Win = Window(window_postion) +actual_window_pos = Win.get_dimensions() +window_x = Win.x_pos() +window_y = Win.y_pos() +window_width = Win.width() +windov_height = Win.height() + +# Persist Dimensions for frontmost app for reset +Dim = Dimensions(cache_file) +Dim.add_dimension(app_id, actual_window_pos) + +# Get Screen width and height +Scr = Screen() +screen_width = Scr.screen_width() +screen_height = Scr.screen_height() + +if direction == "left": # calculate dimensions for moving right side of the screen + window_x_new = 10 + window_y_new = 10 + window_width_new = int(screen_width/2) - 60 + window_height_new = int(screen_height) +if direction == "right": # calculate dimensions for moving right side of the screen + window_x_new = int(screen_width/2) + window_y_new = 10 + window_width_new = int(screen_width/2) + window_height_new = int(screen_height) + +vars = {"x": window_x_new, + "y": window_y_new, + "width": window_width_new, + "height": window_height_new + } +aj = AlfJson() +aj.add_variables(vars) +aj.write_json()