xbar 앱 그룹 실행 플러그인
이 플러그인은 macOS용 무료 메뉴바 유틸리티인 xbar를 사용하여 자주 사용하는 애플리케이션 그룹을 한 번에 실행할 수 있도록 도와줍니다. 개발, 디자인, 멀티미디어 작업 등 특정 목적에 맞게 앱 그룹을 설정해두면 메뉴바에서 클릭 한 번으로 필요한 모든 앱을 편리하게 실행할 수 있습니다.
github 주소
설치 방법
- xbar를 설치합니다.
- 위의 동작을 통해 연 폴더에 아래 파이썬 스크립트 코드를 복사하여
app-launcher.py
와 같은 이름으로 저장합니다. - xbar를 새로고침하거나 재시작하면 메뉴바에 플러그인이 나타납니다.
사용 방법
플러그인을 설치하고 나면 메뉴바에 설정한 이름으로 플러그인이 표시됩니다. 클릭하면 현재 설정된 앱 그룹 목록과 관리 메뉴가 나타납니다.
새 그룹 만들기
- 플러그인 메뉴에서 “앱 그룹 관리” > “새 그룹 만들기”를 선택합니다.
- 그룹 이름을 입력하라는 창이 나타납니다. (아래 이미지 참고)
- 그룹 이름을 입력하고 “확인”을 누르면, 그룹에 추가할 애플리케이션을 선택하는 창이 나타납니다. (아래 이미지 참고) 여러 개의 앱을 동시에 선택할 수 있습니다.
- 원하는 앱들을 선택하고 “선택”을 누르면 새로운 앱 그룹이 생성되고 설정 파일에 저장됩니다.
그룹 실행하기
메뉴바에서 플러그인을 클릭하면 설정된 앱 그룹 목록이 표시됩니다. 실행하고 싶은 그룹 이름을 클릭하면 해당 그룹에 속한 모든 애플리케이션이 실행됩니다.
그룹 앱 보기
설정된 각 그룹에 어떤 앱들이 포함되어 있는지 확인하려면 “앱 그룹 관리” > “그룹 앱 보기” 메뉴를 선택합니다. 각 그룹 아래에 포함된 앱 목록이 표시됩니다. (아래 이미지 참고)
그룹 삭제하기
더 이상 사용하지 않는 그룹은 삭제할 수 있습니다. “앱 그룹 관리” > “그룹 삭제” 메뉴를 선택하면 현재 설정된 그룹 목록이 나타납니다. 삭제하려는 그룹 이름을 클릭하면 확인 창이 나타나고, “삭제”를 선택하면 해당 그룹이 삭제됩니다.
코드 설명 (선택 사항)
플러그인 코드는 파이썬으로 작성되었으며, 주요 기능은 다음과 같습니다.
load_app_groups
/save_app_groups
:~/.xbar_app_groups.json
파일에서 앱 그룹 설정을 읽고 저장합니다.launch_applications
:subprocess.Popen(["open", app_path])
명령을 사용하여 지정된 경로의 앱을 실행합니다.main
: xbar 플러그인의 진입점입니다.sys.argv
를 통해 전달된 인자를 파싱하여 그룹 실행, 새 그룹 생성 프롬프트, 그룹 직접 삭제 등의 동작을 수행합니다.osascript
를 사용하여 macOS의 기본 대화 상자를 띄워 사용자 입력을 받거나 확인 메시지를 표시합니다.
이 플러그인을 통해 macOS에서 앱 실행 워크플로우를 더욱 효율적으로 관리할 수 있습니다.
#!/usr/bin/env python3
# <xbar.title>App Group Launcher</xbar.title>
# <xbar.version>v1.0</xbar.version>
# <xbar.author>ParkSangYong</xbar.author>
# <xbar.author.github>gkdis6</xbar.author.github>
# <xbar.github>https://github.com/gkdis6/app-group</xbar.github>
# <xbar.image>https://raw.githubusercontent.com/gkdis6/app-group/refs/heads/main/images/app-group-management.png</xbar.image>
# <xbar.desc>Launch predefined groups of applications. Allows easy configuration of app groups.</xbar.desc>
# <xbar.dependencies>python3</xbar.dependencies>
import os
import json
import sys
import subprocess
# Configuration file for app groups
CONFIG_FILE = os.path.expanduser("~/.xbar_app_groups.json")
# Get the full path of this script
SCRIPT_PATH = os.path.realpath(__file__)
def load_app_groups():
"""Loads app groups from the configuration file."""
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
return json.load(f)
return {}
def save_app_groups(app_groups):
"""Saves app groups to the configuration file."""
with open(CONFIG_FILE, 'w') as f:
json.dump(app_groups, f, indent=2)
def launch_applications(app_paths):
"""Launches a list of applications."""
for app_path in app_paths:
try:
subprocess.Popen(["open", app_path])
except Exception as e:
print(f"DEBUG: 앱 실행 실패: {app_path}, 오류: {e}")
def main():
if len(sys.argv) > 1 and sys.argv[1] == "launch_group":
group_name = sys.argv[2]
app_groups = load_app_groups()
if group_name in app_groups:
launch_applications(app_groups[group_name])
else:
print(f"오류: {group_name} 그룹을 찾을 수 없습니다.")
sys.exit(0)
elif len(sys.argv) > 1 and sys.argv[1] == "new_group_prompt":
script_name = 'tell app "System Events" to display dialog "새 앱 그룹 이름을 입력하세요:" default answer "" with title "새 그룹 만들기" buttons {"확인"} default button 1'
try:
result_name = subprocess.run(["/usr/bin/osascript", "-e", script_name], capture_output=True, text=True, check=True)
group_name = result_name.stdout.strip().split("text returned:")[1].strip()
if group_name:
script_apps = 'tell application "Finder" to set selectedItems to choose file of type "app" with prompt "그룹에 추가할 앱을 선택하세요:" with multiple selections allowed'
try:
result_apps = subprocess.run(["/usr/bin/osascript", "-e", script_apps], capture_output=True, text=True, check=True)
selected_apps_raw = result_apps.stdout.strip()
selected_app_paths = []
if selected_apps_raw:
try:
raw_paths = selected_apps_raw.split(", ")
for app_path in raw_paths:
if "alias" in app_path:
parts = app_path.split(":")
clean_path = "/" + "/".join(parts[1:])
else:
clean_path = app_path
clean_path = clean_path.strip('"')
if os.path.exists(clean_path):
selected_app_paths.append(clean_path)
else:
print(f"DEBUG: 경로가 존재하지 않음: '{clean_path}'")
except Exception as e:
print(f"DEBUG: 앱 경로 처리 중 오류: {e}")
app_groups = load_app_groups()
app_groups[group_name] = selected_app_paths
save_app_groups(app_groups)
print(f"새 그룹 '{group_name}'이(가) 생성되었습니다. | refresh=true")
except subprocess.CalledProcessError as e:
print(f"앱 선택이 취소되었거나 오류 발생: {e.stderr} | refresh=true")
except Exception as e:
print(f"앱 선택 중 예외 발생: {e} | refresh=true")
else:
print("그룹 이름이 입력되지 않았습니다. | refresh=true")
except subprocess.CalledProcessError as e:
print(f"그룹 생성이 취소되었거나 오류 발생: {e.stderr} | refresh=true")
except Exception as e:
print(f"그룹 이름 입력 중 예외 발생: {e} | refresh=true")
sys.exit(0)
elif len(sys.argv) > 1 and sys.argv[1] == "delete_group_direct":
group_name = sys.argv[2]
app_groups = load_app_groups()
if group_name in app_groups:
script_confirm = f'tell app "System Events" to display dialog "정말로 \'{group_name}\' 그룹을 삭제하시겠습니까?" buttons {{"취소", "삭제"}} default button 1 with icon caution'
try:
result = subprocess.run(["/usr/bin/osascript", "-e", script_confirm], capture_output=True, text=True, check=True)
button_pressed = result.stdout.strip()
if "삭제" in button_pressed:
del app_groups[group_name]
save_app_groups(app_groups)
print(f"'{group_name}' 그룹이 삭제되었습니다. | refresh=true")
else:
print(f"'{group_name}' 그룹 삭제가 취소되었습니다. | refresh=true")
except subprocess.CalledProcessError:
print("삭제가 취소되었습니다. | refresh=true")
else:
print(f"오류: '{group_name}' 그룹을 찾을 수 없습니다. | refresh=true")
sys.exit(0)
print("앱 그룹 실행")
print("---")
app_groups = load_app_groups()
if not app_groups:
print("설정된 앱 그룹이 없습니다.")
print("---")
else:
for group_name, apps in app_groups.items():
print(f"{group_name} 그룹 실행 | bash=python3 param1=\"{SCRIPT_PATH}\" param2=launch_group param3={group_name} terminal=false refresh=true")
print("---")
print("앱 그룹 관리")
print(f"-- 새 그룹 만들기 | bash=python3 param1=\"{SCRIPT_PATH}\" param2=new_group_prompt terminal=false refresh=true")
if app_groups:
print("-- 그룹 앱 보기")
for group_name, apps in app_groups.items():
print(f"---- {group_name}")
for app_path in apps:
try:
if not app_path:
print("------ (경로 없음)")
continue
clean_path = app_path.rstrip('/')
basename = os.path.basename(clean_path)
if basename.endswith('.app'):
app_name = basename.replace(".app", "")
elif '/' in clean_path:
app_name = clean_path.split('/')[-1].replace(".app", "")
else:
app_name = clean_path.replace(".app", "")
print(f"------ {app_name or '(이름 추출 실패)'}")
except Exception as e:
print(f"------ 오류: {e}")
print("-- 그룹 삭제")
for group_name in app_groups.keys():
print(f"---- {group_name} | bash=python3 param1=\"{SCRIPT_PATH}\" param2=delete_group_direct param3={group_name} terminal=false refresh=true")
else:
print("-- 그룹 앱 보기 (설정된 그룹 없음)")
print("-- 그룹 삭제 (설정된 그룹 없음)")
if __name__ == "__main__":
main()