diff options
author | Julien <contact@julienpro.com> | 2023-07-12 14:35:06 +0200 |
---|---|---|
committer | Julien <contact@julienpro.com> | 2023-07-12 14:35:06 +0200 |
commit | 61e83b072b92b3015b4ec53775130a72c3fab4f4 (patch) | |
tree | 87e53b90153ed901ad9644eaa5499094023fe262 | |
parent | 86145dea370ff85a7b78e8f633235101f3365ec8 (diff) |
Implement x-move-win-name-screen
-rw-r--r-- | CHANGELOG.md | 16 | ||||
-rw-r--r-- | README.md | 36 | ||||
-rw-r--r-- | command_callbacks.py | 59 | ||||
-rw-r--r-- | db.py | 12 | ||||
-rwxr-xr-x | lidecli.py | 14 |
5 files changed, 133 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a76e072..c402fad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,23 @@ # Changelog All changes to the project will be documented in this file. +## 1.0.2 - 2023.07.12 + +### Commands added + +- Added `x-move-win-name-screen` to move Windows on a specific screen + +### Other + +- Implement command callbacks in addition to parsing callbacks + ## 1.0.2 - 2023.07.11 -- Added command x-focus-name-nth to focus on the nth instance of the underlying window name +### Commands added + +- Added `command x-focus-name-nth` to focus on the nth instance of the underlying window name + +### Other - Added the -x option to wmctrl - Script can be executed without setting the working direcotry - Added this changelog @@ -40,6 +40,17 @@ The code is a Python 3 script. You just need to clone this repository. Some exam ./lidecli.py x-win-name-maximize Firefox ``` +**Focus on the nth Window of a specific program** +``` +./lidecli.py x-focus-name-nth kitty 1 +./lidecli.py x-focus-name-nth kitty 2 +``` +**Move a window to screen 3 (eg the third monitor)** +``` +./lidecli.py x-move-win-name-screen terminal 3 +``` + + ## Command currently implemented As I am currently using KDE Plasma (very great except its command-line API) on X, the current database contains around 170 ready-to-use commands, mainly using **KDE API with qdbus**, and **wmctrl**, **xprop** or **xwininfo**. @@ -151,7 +162,7 @@ If you launch `./lidecli.py x-get-winid Firefox`, then the command executed will If you just to put the argument(s) at the end of the command line, it's not mandatory to add `#1#`. By default, all needed arguments mentionned will be forwarded to the end of the command launched. -### Custom Python callbacks +### Custom Python parsing callbacks Lidecli supports custom Python callbacks, mainly to parse the output of a command. @@ -166,7 +177,7 @@ Let's take the example below: "versions_working": [("x11", "all")], "versions_not_working": [], "command": "xprop -id", - "callback": { + "callback_parser": { "function": "xprop_parser", "output_key": "win_maximized" }, @@ -180,6 +191,27 @@ A callback should return a dictionary of values. The result returned to the end- In this case, we just need to write one parser function for xprop, and then returns the relevant key in our various commands. +### Custom Pythong command callbacks + +Instead of calling a command line tool, you can call a Python function: + +``` +{ + "name": "x-move-win-name-screen", + "description": "Move a window specified by its name to a specific monitor", + "forwarded_arguments": [ + { "name": "WinName", "description": "The substring to match the Window name"}, + { "name": "Monitor", "description": "The index of the monitor"} + ], + "callback_command": "move_win_to_screen", + "versions_working": [("x11", "all")], + "versions_not_working": [], + "tags": ["x11", "windows", "wmctrl", "xrandr" ] +}, +``` + +Here, the function `move_win_to_screen` in the file `command_callbacks.py` will be executed. + ### Saving temporarily the results of commands It may be useful to save temporarily the result of a command. It's particularly useful to save the current focused Window to be restored later, so when you launch the command through a keybinding, your focused window is not lost. diff --git a/command_callbacks.py b/command_callbacks.py new file mode 100644 index 0000000..86b25a6 --- /dev/null +++ b/command_callbacks.py @@ -0,0 +1,59 @@ + +import subprocess +import re + +def exec_command(command): + try: + ret = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + print("Command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) + return False + ret = ret.decode('utf-8') + return ret + +def get_windows_coordinates(win_name): + command = 'wmctrl -G -l -x |grep {}'.format(win_name) + ret = exec_command(command) + lines = ret.split("\n") + windows = [] + for line in lines: + if line.strip() == '': + continue + sections = line.split() + win = {"id": sections[0], "x": int(sections[2]), "y": int(sections[3]), "width": int(sections[4]), "height": int(sections[5])} + windows.append(win) + return windows + +def get_screens(): + ret = exec_command('xrandr | grep " connected"') + lines = ret.split("\n") + screens = [] + for line in lines: + if line.strip() == '': + continue + # Example: eDP-1 connected 1920x1200+3840+0 (normal left inverted right x axis y axis) 288mm x 180mm + m = re.match(r'(.+?) connected (?:primary )?(\d+)x(\d+)\+(\d+)\+(\d+)', line) + screen = {"name": m.group(1), "h": int(m.group(2)), 'v': int(m.group(3)), 'offset_h': int(m.group(4)), 'offset_v': int(m.group(5))} + screens.append(screen) + ordered_screens = sorted(screens, key=lambda d: d['offset_h']) + return ordered_screens + +def move_win(win_id, pos_x, pos_y, width, height): + command = "wmctrl -i -r {} -e 0,{},{},{},{}".format(win_id, pos_x, pos_y, width, height) + ret = exec_command(command) + return ret + +def move_win_to_screen(args): + screens = get_screens() + windows = get_windows_coordinates(args.WinName) + if len(screens) < int(args.Monitor): + return False + for win in windows: + for screen in screens: + if win['x'] > screen['offset_h'] and win['x'] < screen['offset_h'] + screen['h']: + diff_h = win['x'] - screen['offset_h'] + print(diff_h) + new_pos_x = screens[int(args.Monitor)-1]['offset_h']+diff_h + move_win(win['id'], new_pos_x, win['y'], win['width'], win['height']) + + return "toto"
\ No newline at end of file @@ -303,6 +303,18 @@ def commands(): "tags": ["x11", "windows", "wmctrl" ] }, { + "name": "x-move-win-name-screen", + "description": "Move a window specified by its name to a specific monitor", + "forwarded_arguments": [ + { "name": "WinName", "description": "The substring to match the Window name"}, + { "name": "Monitor", "description": "The index of the monitor"} + ], + "callback_command": "move_win_to_screen", + "versions_working": [("x11", "all")], + "versions_not_working": [], + "tags": ["x11", "windows", "wmctrl", "xrandr" ] + }, + { "name": "kde-toggle-win-fullscreen", "description": "Toogle fullscreen for the current Window", "command": "qdbus org.kde.kglobalaccel /component/kwin org.kde.kglobalaccel.Component.invokeShortcut 'Window Fullscreen'", @@ -7,6 +7,7 @@ import json import os import configparser import parsing_callbacks +import command_callbacks import db version = "1.0.2" @@ -76,11 +77,21 @@ def exec_command(args): print(read_temporary_variable(int(args.index))) return command = get_command_by_name(args.command) + str_command = None if "command" in command: str_command = command["command"] - else: + elif "commands" in command: str_command = ' && '.join(command['commands']) + + # Callback command + if str_command == None: + function = getattr(command_callbacks, command["callback_command"]) + ret = function(args) + # ret = ret.decode('utf-8') + print(ret, end='') + return + for option in command.get('forwarded_options', []): param = option['param'].replace('-', '') if getattr(args, param) == True: @@ -116,6 +127,7 @@ def exec_command(args): function = getattr(parsing_callbacks, command["callback_parser"]["function"]) ret = function(ret) ret = ret[command['callback_parser']['output_key']] + print(ret, end='') # print(ret["win_maximized"], end='') else: print(ret, end='') |