From 7724fb905ac733ef23ee7bb1a4b0c60c354d842d Mon Sep 17 00:00:00 2001 From: Doron Somech Date: Sun, 29 Mar 2020 13:22:38 +0300 Subject: [PATCH] first commit --- .gitignore | 3 + .travis.yml | 4 + CHANGELOG.md | 9 + LICENSE.txt | 21 ++ README.md | 67 ++++++ doc/spacebar.1 | 123 +++++++++++ doc/spacebar.asciidoc | 98 +++++++++ examples/spacebarrc | 12 ++ makefile | 46 ++++ scripts/codesign | 47 ++++ src/application.c | 95 ++++++++ src/application.h | 38 ++++ src/application_manager.c | 81 +++++++ src/application_manager.h | 35 +++ src/bar.c | 418 ++++++++++++++++++++++++++++++++++++ src/bar.h | 62 ++++++ src/bar_manager.c | 229 ++++++++++++++++++++ src/bar_manager.h | 46 ++++ src/display.c | 106 +++++++++ src/display.h | 24 +++ src/display_manager.c | 187 ++++++++++++++++ src/display_manager.h | 49 +++++ src/event.c | 237 ++++++++++++++++++++ src/event.h | 110 ++++++++++ src/event_loop.c | 154 +++++++++++++ src/event_loop.h | 43 ++++ src/manifest.m | 66 ++++++ src/message.c | 229 ++++++++++++++++++++ src/message.h | 13 ++ src/misc/hashtable.h | 147 +++++++++++++ src/misc/helpers.h | 185 ++++++++++++++++ src/misc/log.h | 36 ++++ src/misc/macros.h | 33 +++ src/misc/memory_pool.h | 42 ++++ src/misc/notify.h | 47 ++++ src/misc/sbuffer.h | 36 ++++ src/misc/socket.c | 175 +++++++++++++++ src/misc/socket.h | 39 ++++ src/misc/timing.h | 29 +++ src/process_manager.c | 222 +++++++++++++++++++ src/process_manager.h | 51 +++++ src/spacebar.c | 268 +++++++++++++++++++++++ src/window.c | 440 ++++++++++++++++++++++++++++++++++++++ src/window.h | 78 +++++++ src/workspace.h | 13 ++ src/workspace.m | 83 +++++++ 46 files changed, 4576 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 doc/spacebar.1 create mode 100644 doc/spacebar.asciidoc create mode 100755 examples/spacebarrc create mode 100644 makefile create mode 100755 scripts/codesign create mode 100644 src/application.c create mode 100644 src/application.h create mode 100644 src/application_manager.c create mode 100644 src/application_manager.h create mode 100644 src/bar.c create mode 100644 src/bar.h create mode 100644 src/bar_manager.c create mode 100644 src/bar_manager.h create mode 100644 src/display.c create mode 100644 src/display.h create mode 100644 src/display_manager.c create mode 100644 src/display_manager.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100644 src/event_loop.c create mode 100644 src/event_loop.h create mode 100644 src/manifest.m create mode 100644 src/message.c create mode 100644 src/message.h create mode 100644 src/misc/hashtable.h create mode 100644 src/misc/helpers.h create mode 100644 src/misc/log.h create mode 100644 src/misc/macros.h create mode 100644 src/misc/memory_pool.h create mode 100644 src/misc/notify.h create mode 100644 src/misc/sbuffer.h create mode 100644 src/misc/socket.c create mode 100644 src/misc/socket.h create mode 100644 src/misc/timing.h create mode 100644 src/process_manager.c create mode 100644 src/process_manager.h create mode 100644 src/spacebar.c create mode 100644 src/window.c create mode 100644 src/window.h create mode 100644 src/workspace.h create mode 100644 src/workspace.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a39af62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/bin +/archive +/.idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9118284 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: c +os: osx +compiler: clang +script: make install diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..19bf1ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/somdoron/spacebar/compare/master...HEAD \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2c4dbfc --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Åsmund Vikane + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..faccb94 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ + +

+ Status Bar for the Mac. +

+

+ + CI Status Badge + + + License Badge + + + Changelog Badge + + + Version Badge + +

+ +## About + +spacebar is a status bar for [↗ yabai][gh-yabai] tiling window management. + +## Installation and Configuration + +- TODO: release package + +- Sample configuration files can be found in the [↗ examples][spacebar-examples] directory. Refer to the [↗ documentation][spacebar-docs]. + + +## Requirements and Caveats + +Please read the below requirements carefully. +Make sure you fulfil all of them before filing an issue. + +|Requirement|Note| +|-:|:-| +|Operating System|macOS Catalina 10.15.0+ is supported.| +|Accessibility API|spacebar must be given permission to utilize the Accessibility API and will request access upon launch. The application must be restarted after access has been granted.| + +Please also take note of the following caveats. + +|Caveat|Note| +|-:|:-| +|Code Signing|When building from source (or installing from HEAD), it is recommended to codesign the binary so it retains its accessibility and automation privileges when updated or rebuilt.| +|Mission Control|In the Mission Control preferences pane in System Preferences, the setting "Automatically rearrange Spaces based on most recent use" should be disabled.| + +## License and Attribution + +spacebar is licensed under the [↗ MIT License][spacebar-license], a short and simple permissive license with conditions only requiring preservation of copyright and license notices. +Licensed works, modifications, and larger works may be distributed under different terms and without source code. + +Thanks to [@koekeishiya][gh-koekeishiya] for creating yabai. + +## Disclaimer + +Use at your own discretion. +I take no responsibility if anything should happen to your machine while trying to install, test or otherwise use this software in any form. + + +[spacebar-license]: LICENSE.txt +[spacebar-examples]: https://github.com/somdoron/spacebar/tree/master/examples +[spacebar-docs]: https://github.com/somdoron/spacebar/blob/master/doc/spacebar.asciidoc + + +[gh-koekeishiya]: https://github.com/koekeishiya +[gh-yabai]: https://github.com/koekeishiya/yabai diff --git a/doc/spacebar.1 b/doc/spacebar.1 new file mode 100644 index 0000000..707e679 --- /dev/null +++ b/doc/spacebar.1 @@ -0,0 +1,123 @@ +'\" t +.\" Title: spacebar +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.10 +.\" Date: 2020-03-29 +.\" Manual: Spacebar Manual +.\" Source: Spacebar +.\" Language: English +.\" +.TH "SPACEBAR" "1" "2020-03-29" "Spacebar" "Spacebar Manual" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +spacebar \- status bar for yabai tile window management utility +.SH "SYNOPSIS" +.sp +\fBspacebar\fP [\fB\-v\fP,\fB\-\-version\fP|\fB\-V\fP,\fB\-\-verbose\fP|\fB\-m\fP,\fB\-\-message\fP \fImsg\fP|\fB\-c\fP,\fB\-\-config\fP \fIconfig_file\fP] +.SH "DESCRIPTION" +.sp +\fBspacebar\fP is a tiling window manager for macOS based on binary space partitioning. +.SH "OPTIONS" +.sp +\fB\-v\fP, \fB\-\-version\fP +.RS 4 +Print the version and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-verbose\fP +.RS 4 +Output debug information to stdout. +.RE +.sp +\fB\-m\fP, \fB\-\-message\fP \fI\fP +.RS 4 +Send message to a running instance of spacebar. +.RE +.sp +\fB\-c\fP, \fB\-\-config\fP \fI\fP +.RS 4 +Use the specified configuration file. +.RE +.SH "CONFIG" +.SS "General Syntax" +.sp +spacebar \-m config +.RS 4 +Get or set the value of . +.RE +.SS "Settings" +.sp +\fBdebug_output\fP [\fI\fP] +.RS 4 +Enable output of debug information to stdout. +.RE +.sp +\fBstatus_bar_text_font\fP [\fI::\fP] +.RS 4 +Specify name, style and size of font to use for drawing text. +.br +Use \fIFont Book.app\fP to identify the correct name. +.RE +.sp +\fBstatus_bar_icon_font\fP [\fI::\fP] +.RS 4 +Specify name, style and size of font to use for drawing icon symbols. +.br +Use \fIFont Book.app\fP to identify the correct name. +.RE +.sp +\fBstatus_bar_background_color\fP [\fI\fP] +.RS 4 +Color to use for drawing status bar background. +.RE +.sp +\fBstatus_bar_foreground_color\fP [\fI\fP] +.RS 4 +Color to use for drawing status bar elements. +.RE +.sp +\fBstatus_bar_space_icon_strip\fP [\fI \fP] +.RS 4 +Specify symbols separated by whitespace to be used for visualizing spaces. +.RE +.sp +\fBstatus_bar_power_icon_strip\fP [\fI \fP] +.RS 4 +Specify two symbols separated by whitespace. +.br +The first symbol represents battery power and the second symbol indicates AC. +.RE +.sp +\fBstatus_bar_space_icon\fP [\fI\fP] +.RS 4 +Specify a general symbol to use for any given space that does not have a match in \fIstatus_bar_space_icon_strip\fP. +.RE +.sp +\fBstatus_bar_clock_icon\fP [\fI\fP] +.RS 4 +Specify a symbol to represent the current time. +.RE +.SH "EXIT CODES" +.sp +If \fBspacebar\fP can\(cqt handle a message, it will return a non\-zero exit code. +.SH "AUTHOR" +.sp +Doron Somech \ No newline at end of file diff --git a/doc/spacebar.asciidoc b/doc/spacebar.asciidoc new file mode 100644 index 0000000..7882de9 --- /dev/null +++ b/doc/spacebar.asciidoc @@ -0,0 +1,98 @@ +:man source: Spacebar +:man version: {revnumber} +:man manual: Spacebar Manual + +ifdef::env-github[] +:toc: +:toc-title: +:toc-placement!: +:numbered: +endif::[] + +spacebar(1) +=========== + +ifdef::env-github[] +toc::[] +endif::[] + +Name +---- + +spacebar - Status bar for mac + +Synopsis +-------- + +*spacebar* [*-v*,*--version*|*-V*,*--verbose*|*-m*,*--message* 'msg'|*-c*,*--config* 'config_file'] + +Description +----------- + +*spacebar* is a tiling window manager for macOS based on binary space partitioning. + +Options +------- +*-v*, *--version*:: + Print the version and exit. + +*-V*, *--verbose*:: + Output debug information to stdout. + +*-m*, *--message* '':: + Send message to a running instance of spacebar. + +*-c*, *--config* '':: + Use the specified configuration file. + +Config +------ + +General Syntax +~~~~~~~~~~~~~~ + +spacebar -m config :: + Get or set the value of . + +Settings +~~~~~~~~ + +*debug_output* ['']:: + Enable output of debug information to stdout. + +*status_bar_text_font* ['::']:: + Specify name, style and size of font to use for drawing text. + + Use 'Font Book.app' to identify the correct name. + +*status_bar_icon_font* ['::']:: + Specify name, style and size of font to use for drawing icon symbols. + + Use 'Font Book.app' to identify the correct name. + +*status_bar_background_color* ['']:: + Color to use for drawing status bar background. + +*status_bar_foreground_color* ['']:: + Color to use for drawing status bar elements. + +*status_bar_space_icon_strip* [' ']:: + Specify symbols separated by whitespace to be used for visualizing spaces. + +*status_bar_power_icon_strip* [' ']:: + Specify two symbols separated by whitespace. + + The first symbol represents battery power and the second symbol indicates AC. + +*status_bar_space_icon* ['']:: + Specify a general symbol to use for any given space that does not have a match in 'status_bar_space_icon_strip'. + +*status_bar_clock_icon* ['']:: + Specify a symbol to represent the current time. + +Exit Codes +---------- + +If *spacebar* can't handle a message, it will return a non-zero exit code. + +Author +------ + +Doron Somech \ No newline at end of file diff --git a/examples/spacebarrc b/examples/spacebarrc new file mode 100755 index 0000000..4f99cbe --- /dev/null +++ b/examples/spacebarrc @@ -0,0 +1,12 @@ +#!/usr/bin/env sh + +spacebar -m config status_bar_text_font "Helvetica Neue:Bold:12.0" +spacebar -m config status_bar_icon_font "FontAwesome:Regular:12.0" +spacebar -m config status_bar_background_color 0xff202020 +spacebar -m config status_bar_foreground_color 0xffa8a8a8 +spacebar -m config status_bar_space_icon_strip I II III IV V VI VII VIII IX X +spacebar -m config status_bar_power_icon_strip   +spacebar -m config status_bar_space_icon  +spacebar -m config status_bar_clock_icon  + +echo "spacebar configuration loaded.." diff --git a/makefile b/makefile new file mode 100644 index 0000000..75e7958 --- /dev/null +++ b/makefile @@ -0,0 +1,46 @@ +FRAMEWORK_PATH = -F/System/Library/PrivateFrameworks +FRAMEWORK = -framework Carbon -framework Cocoa -framework CoreServices -framework SkyLight -framework ScriptingBridge -framework IOKit +BUILD_FLAGS = -std=c99 -Wall -DDEBUG -g -O0 -fvisibility=hidden -mmacosx-version-min=10.13 +BUILD_PATH = ./bin +DOC_PATH = ./doc +SCRIPT_PATH = ./scripts +ASSET_PATH = ./assets +SMP_PATH = ./examples +ARCH_PATH = ./archive +SPACEBAR_SRC = ./src/manifest.m +BINS = $(BUILD_PATH)/spacebar + +.PHONY: all clean install sign archive man + +all: clean $(BINS) + +install: BUILD_FLAGS=-std=c99 -Wall -DNDEBUG -O2 -fvisibility=hidden -mmacosx-version-min=10.13 +install: clean $(BINS) + +stats: BUILD_FLAGS=-std=c99 -Wall -DSTATS -DNDEBUG -O2 -fvisibility=hidden -mmacosx-version-min=10.13 +stats: clean $(BINS) + +man: + asciidoctor -b manpage $(DOC_PATH)/spacebar.asciidoc -o $(DOC_PATH)/spacebar.1 + +icon: + python $(SCRIPT_PATH)/seticon.py $(ASSET_PATH)/icon/2x/icon-512px@2x.png $(BUILD_PATH)/spacebar + +archive: man install sign icon + rm -rf $(ARCH_PATH) + mkdir -p $(ARCH_PATH) + cp -r $(BUILD_PATH) $(ARCH_PATH)/ + cp -r $(DOC_PATH) $(ARCH_PATH)/ + cp -r $(SMP_PATH) $(ARCH_PATH)/ + tar -cvzf $(BUILD_PATH)/$(shell $(BUILD_PATH)/spacebar --version).tar.gz $(ARCH_PATH) + rm -rf $(ARCH_PATH) + +sign: + codesign -fs "spacebar-cert" $(BUILD_PATH)/spacebar + +clean: + rm -rf $(BUILD_PATH) + +$(BUILD_PATH)/spacebar: $(SPACEBAR_SRC) + mkdir -p $(BUILD_PATH) + clang $^ $(BUILD_FLAGS) $(FRAMEWORK_PATH) $(FRAMEWORK) -o $@ diff --git a/scripts/codesign b/scripts/codesign new file mode 100755 index 0000000..a184252 --- /dev/null +++ b/scripts/codesign @@ -0,0 +1,47 @@ +#! /usr/bin/env bash + +identities="$(security find-identity -v -p codesigning | sed '$d')" +id_count="$(echo "${identities}" | wc -l)" +target="$(command -v "${1:-}")" + +function error() { + { + echo "$(tput bold)Error: ${@}$(tput sgr0)" + echo + echo "Usage: ./scripts/codesign []" + echo "Available codesigning certificates:" + echo "${identities}" + } >&2 + exit 1 +} + +if [ "${id_count}" -lt 1 ]; then + >&2 echo "Unable to find a codesigning identity" + exit 1 +elif [ "${id_count}" -eq 1 ]; then + selection="1" +elif [ "${id_count}" -gt 1 ] && [ "${#}" -eq 2 ]; then + selection="${2}" +elif [ -x "${target}" ]; then + error "Unable to auto-select codesigning certificate" +else + error "Unable to find executable \"${target}\"" +fi + +if [[ "${selection}" =~ ^[0-9]+$ ]]; then + certificate="$(echo "${identities}" \ + | awk "NR==${selection} {print \$2}")" +else + certificate="$(echo "${identities}" \ + | awk 'BEGIN{FS=OFS="\""} {gsub(/ /,"_",$2)} 1' \ + | awk "\$3 ~ /${selection// /_}/ {print \$2}")" +fi + +if [ -z "${certificate}" ]; then + error "Unable to find codesigning certificate \"${selection}\"" +elif [ "$(echo "${certificate}" | wc -l)" -ne 1 ]; then + error "Unable to uniquely identify codesigning certificate \"${selection}\"" +fi + +command codesign --deep --force --verbose --sign "${certificate}" "${target}" + diff --git a/src/application.c b/src/application.c new file mode 100644 index 0000000..ef934d8 --- /dev/null +++ b/src/application.c @@ -0,0 +1,95 @@ +#include "application.h" + +extern struct event_loop g_event_loop; + +static OBSERVER_CALLBACK(application_notification_handler) +{ + if (CFEqual(notification, kAXFocusedWindowChangedNotification)) { + uint32_t window_id = ax_window_id(element); + if (!window_id) return; + + struct event *event = event_create(&g_event_loop, WINDOW_FOCUSED, (void *)(intptr_t) window_id); + event_loop_post(&g_event_loop, event); + } else if (CFEqual(notification, kAXTitleChangedNotification)) { + uint32_t window_id = ax_window_id(element); + if (!window_id) return; + + struct event *event = event_create(&g_event_loop, WINDOW_TITLE_CHANGED, (void *)(intptr_t) window_id); + event_loop_post(&g_event_loop, event); + } +} + +static void +application_observe_notification(struct application *application, int notification) +{ + AXError result = AXObserverAddNotification(application->observer_ref, application->ref, ax_application_notification[notification], application); + if (result == kAXErrorSuccess || result == kAXErrorNotificationAlreadyRegistered) { + application->notification |= 1 << notification; + } else if (result != kAXErrorNotImplemented) { + application->retry = true; + } +} + +static void +application_unobserve_notification(struct application *application, int notification) +{ + AXObserverRemoveNotification(application->observer_ref, application->ref, ax_application_notification[notification]); + application->notification &= ~(1 << notification); +} + +bool application_observe(struct application *application) +{ + if (AXObserverCreate(application->pid, application_notification_handler, &application->observer_ref) == kAXErrorSuccess) { + for (int i = 0; i < array_count(ax_application_notification); ++i) { + application_observe_notification(application, i); + } + + application->is_observing = true; + CFRunLoopAddSource(CFRunLoopGetMain(), AXObserverGetRunLoopSource(application->observer_ref), kCFRunLoopDefaultMode); + } + + return (application->notification & AX_APPLICATION_ALL) == AX_APPLICATION_ALL; +} + +void application_unobserve(struct application *application) +{ + if (application->is_observing) { + for (int i = 0; i < array_count(ax_application_notification); ++i) { + if (!(application->notification & (1 << i))) continue; + application_unobserve_notification(application, i); + } + + application->is_observing = false; + CFRunLoopSourceInvalidate(AXObserverGetRunLoopSource(application->observer_ref)); + CFRelease(application->observer_ref); + } +} + +uint32_t application_focused_window(struct application *application) +{ + CFTypeRef window_ref = NULL; + AXUIElementCopyAttributeValue(application->ref, kAXFocusedWindowAttribute, &window_ref); + if (!window_ref) return 0; + + uint32_t window_id = ax_window_id(window_ref); + CFRelease(window_ref); + + return window_id; +} + +struct application *application_create(struct process *process) +{ + struct application *application = malloc(sizeof(struct application)); + memset(application, 0, sizeof(struct application)); + application->ref = AXUIElementCreateApplication(process->pid); + application->psn = process->psn; + application->pid = process->pid; + application->name = process->name; + return application; +} + +void application_destroy(struct application *application) +{ + CFRelease(application->ref); + free(application); +} diff --git a/src/application.h b/src/application.h new file mode 100644 index 0000000..4d90a3d --- /dev/null +++ b/src/application.h @@ -0,0 +1,38 @@ +#ifndef APPLICATION_H +#define APPLICATION_H + +#define OBSERVER_CALLBACK(name) void name(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void *context) +typedef OBSERVER_CALLBACK(observer_callback); + +#define AX_APPLICATION_WINDOW_FOCUSED_INDEX 0 +#define AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX 1 + +#define AX_APPLICATION_WINDOW_FOCUSED (1 << AX_APPLICATION_WINDOW_FOCUSED_INDEX) +#define AX_APPLICATION_WINDOW_TITLE_CHANGED (1 << AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX) +#define AX_APPLICATION_ALL (AX_APPLICATION_WINDOW_FOCUSED |\ + AX_APPLICATION_WINDOW_TITLE_CHANGED) +static CFStringRef ax_application_notification[] = +{ + [AX_APPLICATION_WINDOW_FOCUSED_INDEX] = kAXFocusedWindowChangedNotification, + [AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX] = kAXTitleChangedNotification, +}; + +struct application +{ + AXUIElementRef ref; + ProcessSerialNumber psn; + uint32_t pid; + char *name; + AXObserverRef observer_ref; + uint8_t notification; + bool is_observing; + bool retry; +}; + +uint32_t application_focused_window(struct application *application); +bool application_observe(struct application *application); +void application_unobserve(struct application *application); +struct application *application_create(struct process *process); +void application_destroy(struct application *application); + +#endif diff --git a/src/application_manager.c b/src/application_manager.c new file mode 100644 index 0000000..5c2c9bd --- /dev/null +++ b/src/application_manager.c @@ -0,0 +1,81 @@ +#include "application_manager.h" + +extern struct process_manager g_process_manager; +extern struct mouse_state g_mouse_state; +extern char g_sa_socket_file[MAXLEN]; + +static TABLE_HASH_FUNC(hash_application) +{ + unsigned long result = *(uint32_t *) key; + result = (result + 0x7ed55d16) + (result << 12); + result = (result ^ 0xc761c23c) ^ (result >> 19); + result = (result + 0x165667b1) + (result << 5); + result = (result + 0xd3a2646c) ^ (result << 9); + result = (result + 0xfd7046c5) + (result << 3); + result = (result ^ 0xb55a4f09) ^ (result >> 16); + return result; +} + +static TABLE_COMPARE_FUNC(compare_application) +{ + return *(uint32_t *) key_a == *(uint32_t *) key_b; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +struct application *application_manager_focused_application(struct application_manager *application_manager) +{ + ProcessSerialNumber psn = {}; + _SLPSGetFrontProcess(&psn); + + pid_t pid; + GetProcessPID(&psn, &pid); + + return application_manager_find_application(application_manager, pid); +} +#pragma clang diagnostic pop + +struct application *application_manager_find_application(struct application_manager *application_manager, pid_t pid) +{ + return table_find(&application_manager->application, &pid); +} + +void application_manager_remove_application(struct application_manager *application_manager, pid_t pid) +{ + table_remove(&application_manager->application, &pid); +} + +void application_manager_add_application(struct application_manager *application_manager, struct application *application) +{ + table_add(&application_manager->application, &application->pid, application); +} + +void application_manager_init(struct application_manager *application_manager) +{ + application_manager->system_element = AXUIElementCreateSystemWide(); + AXUIElementSetMessagingTimeout(application_manager->system_element, 1.0); + + table_init(&application_manager->application, 150, hash_application, compare_application); +} + +void application_manager_begin(struct application_manager *application_manager) +{ + for (int process_index = 0; process_index < g_process_manager.process.capacity; ++process_index) { + struct bucket *bucket = g_process_manager.process.buckets[process_index]; + while (bucket) { + if (bucket->value) { + struct process *process = bucket->value; + struct application *application = application_create(process); + + if (application_observe(application)) { + application_manager_add_application(application_manager, application); + } else { + application_unobserve(application); + application_destroy(application); + } + } + + bucket = bucket->next; + } + } +} diff --git a/src/application_manager.h b/src/application_manager.h new file mode 100644 index 0000000..5a65fac --- /dev/null +++ b/src/application_manager.h @@ -0,0 +1,35 @@ +#ifndef APPLICATION_MANAGER_H +#define APPLICATION_MANAGER_H + +extern CFTypeRef SLSWindowQueryWindows(int cid, CFArrayRef windows, int count); +extern CFTypeRef SLSWindowQueryResultCopyWindows(CFTypeRef window_query); +extern CGError SLSWindowIteratorAdvance(CFTypeRef iterator); +extern uint32_t SLSWindowIteratorGetParentID(CFTypeRef iterator); +extern uint32_t SLSWindowIteratorGetWindowID(CFTypeRef iterator); +extern OSStatus _SLPSGetFrontProcess(ProcessSerialNumber *psn); +extern CGError SLSGetWindowOwner(int cid, uint32_t wid, int *wcid); +extern CGError SLSGetConnectionPSN(int cid, ProcessSerialNumber *psn); +extern CGError SLSConnectionGetPID(int cid, pid_t *pid); +extern CGError _SLPSSetFrontProcessWithOptions(ProcessSerialNumber *psn, uint32_t wid, uint32_t mode); +extern CGError SLPSPostEventRecordTo(ProcessSerialNumber *psn, uint8_t *bytes); +extern OSStatus SLSFindWindowByGeometry(int cid, int zero, int one, int zero_again, CGPoint *screen_point, CGPoint *window_point, uint32_t *wid, int *wcid); +extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point); + +#define kCPSAllWindows 0x100 +#define kCPSUserGenerated 0x200 +#define kCPSNoWindows 0x400 + +struct application_manager +{ + AXUIElementRef system_element; + struct table application; +}; + +struct application *application_manager_focused_application(struct application_manager *application_manager); +struct application *application_manager_find_application(struct application_manager *application_manager, pid_t pid); +void application_manager_remove_application(struct application_manager *application_manager, pid_t pid); +void application_manager_add_application(struct application_manager *application_manager, struct application *application); +void application_manager_begin(struct application_manager *application_manager); +void application_manager_init(struct application_manager *application_manager); + +#endif diff --git a/src/bar.c b/src/bar.c new file mode 100644 index 0000000..2155e7d --- /dev/null +++ b/src/bar.c @@ -0,0 +1,418 @@ +#include "bar.h" + +extern struct event_loop g_event_loop; +//extern struct space_manager g_space_manager; +extern struct bar_manager g_bar_manager; + +static POWER_CALLBACK(power_handler) +{ + struct event *event = event_create(&g_event_loop, BAR_REFRESH, NULL); + event_loop_post(&g_event_loop, event); +} + +static TIMER_CALLBACK(timer_handler) +{ + struct event *event = event_create(&g_event_loop, BAR_REFRESH, NULL); + event_loop_post(&g_event_loop, event); +} + +static int bar_find_battery_life(bool *has_battery, bool *charging) +{ + CFTypeRef ps_info = IOPSCopyPowerSourcesInfo(); + CFTypeRef ps_list = IOPSCopyPowerSourcesList(ps_info); + + int ps_count = CFArrayGetCount(ps_list); + if (!ps_count) return 0; + + int cur_capacity = 0; + int max_capacity = 0; + int percent = 0; + + for (int i = 0; i < ps_count; ++i) { + CFDictionaryRef ps = IOPSGetPowerSourceDescription(ps_info, CFArrayGetValueAtIndex(ps_list, i)); + if (!ps) continue; + + CFTypeRef ps_type = CFDictionaryGetValue(ps, CFSTR(kIOPSTypeKey)); + if (!ps_type || !CFEqual(ps_type, CFSTR(kIOPSInternalBatteryType))) continue; + + CFTypeRef ps_cur = CFDictionaryGetValue(ps, CFSTR(kIOPSCurrentCapacityKey)); + if (!ps_cur) continue; + + CFTypeRef ps_max = CFDictionaryGetValue(ps, CFSTR(kIOPSMaxCapacityKey)); + if (!ps_max) continue; + + CFTypeRef ps_charging = CFDictionaryGetValue(ps, CFSTR(kIOPSPowerSourceStateKey)); + if (!ps_charging) continue; + + CFNumberGetValue((CFNumberRef) ps_cur, kCFNumberSInt32Type, &cur_capacity); + CFNumberGetValue((CFNumberRef) ps_max, kCFNumberSInt32Type, &max_capacity); + *charging = !CFEqual(ps_charging, CFSTR(kIOPSBatteryPowerValue)); + *has_battery = true; + percent = (int)((double) cur_capacity / (double) max_capacity * 100); + break; + } + + CFRelease(ps_list); + CFRelease(ps_info); + return percent; +} + +static CTFontRef bar_create_font(char *cstring) +{ + float size = 10.0f; + char font_properties[2][255] = { {}, {} }; + sscanf(cstring, "%254[^:]:%254[^:]:%f", font_properties[0], font_properties[1], &size); + CFStringRef font_family_name = CFStringCreateWithCString(NULL, font_properties[0], kCFStringEncodingUTF8); + CFStringRef font_style_name = CFStringCreateWithCString(NULL, font_properties[1], kCFStringEncodingUTF8); + CFNumberRef font_size = CFNumberCreate(NULL, kCFNumberFloat32Type, &size); + + const void *keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute, kCTFontSizeAttribute }; + const void *values[] = { font_family_name, font_style_name, font_size }; + CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes(attributes); + CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL); + + CFRelease(descriptor); + CFRelease(attributes); + CFRelease(font_size); + CFRelease(font_style_name); + CFRelease(font_family_name); + + return font; +} + +static CGPoint bar_align_line(struct bar *bar, struct bar_line line, int align_x, int align_y) +{ + float x = 0, y = 0; + + if (align_x == ALIGN_NONE) { + x = CGContextGetTextPosition(bar->context).x; + } else if (align_x == ALIGN_LEFT) { + x = 20; + } else if (align_x == ALIGN_CENTER) { + x = (bar->frame.size.width / 2) - (line.bounds.size.width / 2); + } else if (align_x == ALIGN_RIGHT) { + x = bar->frame.size.width - line.bounds.size.width - 20; + } + + if (align_y == ALIGN_NONE) { + y = CGContextGetTextPosition(bar->context).y; + } else if (align_y == ALIGN_TOP) { + y = bar->frame.size.height; + } else if (align_y == ALIGN_CENTER) { + y = (bar->frame.size.height / 2) - ((line.ascent - line.descent) / 2); + } else if (align_y == ALIGN_BOTTOM) { + y = line.descent; + } + + return (CGPoint) { x, y }; +} + +static void bar_draw_line(struct bar *bar, struct bar_line line, float x, float y) +{ + CGContextSetRGBFillColor(bar->context, line.color.r, line.color.g, line.color.b, line.color.a); + CGContextSetTextPosition(bar->context, x, y); + CTLineDraw(line.line, bar->context); +} + +static void bar_destroy_line(struct bar_line line) +{ + CFRelease(line.line); +} + +static struct bar_line bar_prepare_line(CTFontRef font, char *cstring, struct rgba_color color) +{ + const void *keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName }; + const void *values[] = { font, kCFBooleanTrue }; + CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFStringRef string = CFStringCreateWithCString(NULL, cstring, kCFStringEncodingUTF8); + CFAttributedStringRef attr_string = CFAttributedStringCreate(NULL, string, attributes); + CTLineRef line = CTLineCreateWithAttributedString(attr_string); + + CGFloat ascent, descent; + CTLineGetTypographicBounds(line, &ascent, &descent, NULL); + CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds); + + CFRelease(string); + CFRelease(attributes); + CFRelease(attr_string); + + return (struct bar_line) { + .line = line, + .ascent = ascent, + .descent = descent, + .bounds = bounds, + .color = color + }; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +static char * focused_window_title() +{ + ProcessSerialNumber psn = {}; + _SLPSGetFrontProcess(&psn); + + pid_t pid; + GetProcessPID(&psn, &pid); + + AXUIElementRef application_ref = AXUIElementCreateApplication(pid); + if (!application_ref) + return NULL; + + CFTypeRef window_ref = NULL; + AXUIElementCopyAttributeValue(application_ref, kAXFocusedWindowAttribute, &window_ref); + if (!window_ref) { + CFRelease(application_ref); + return NULL; + } + + char *title = NULL; + CFTypeRef value = NULL; + AXUIElementCopyAttributeValue(window_ref, kAXTitleAttribute, &value); + if (value) { + title = cfstring_copy(value); + CFRelease(value); + } + + CFRelease(window_ref); + CFRelease(application_ref); + + return title; +} +#pragma clang diagnostic pop + +void bar_refresh(struct bar *bar) +{ + SLSDisableUpdate(g_connection); + SLSOrderWindow(g_connection, bar->id, -1, 0); + CGContextClearRect(bar->context, bar->frame); + CGContextSetRGBFillColor(bar->context, g_bar_manager.background_color.r, g_bar_manager.background_color.g, g_bar_manager.background_color.b, g_bar_manager.background_color.a); + CGContextFillRect(bar->context, bar->frame); + CGContextStrokePath(bar->context); + + // + // BAR LEFT + // + + int final_bar_left_x = 10; + int space_count; + uint64_t *space_list = display_space_list(bar->did, &space_count); + if (space_list) { + uint64_t sid = display_space_id(bar->did); + + for (int i = 0; i < space_count; ++i) { + CGPoint pos = CGContextGetTextPosition(bar->context); + struct bar_line space_line = i >= buf_len(g_bar_manager.space_icon_strip) + ? g_bar_manager.space_icon + : g_bar_manager.space_icon_strip[i]; + if (i == 0) { + pos = bar_align_line(bar, space_line, ALIGN_LEFT, ALIGN_CENTER); + } else { + pos.x += 25; + } + + bar_draw_line(bar, space_line, pos.x, pos.y); + + if (sid == space_list[i]) { + CGPoint new_pos = CGContextGetTextPosition(bar->context); + struct bar_line mark_line = g_bar_manager.space_underline; + CGPoint mark_pos = bar_align_line(bar, mark_line, 0, ALIGN_BOTTOM); + mark_pos.x = mark_pos.x - mark_line.bounds.size.width / 2 - space_line.bounds.size.width / 2; + bar_draw_line(bar, mark_line, mark_pos.x, mark_pos.y); + CGContextSetTextPosition(bar->context, new_pos.x, new_pos.y); + } + + final_bar_left_x = pos.x + space_line.bounds.size.width + 10; + } + + free(space_list); + } + + // + // BAR RIGHT + // + + int initial_bar_right_x = bar->frame.size.width - 10; + time_t rawtime; + time(&rawtime); + float time_line_width = 0; + struct tm *timeinfo = localtime(&rawtime); + if (timeinfo) { + char time[255]; + snprintf(time, sizeof(time), "%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min); + struct bar_line time_line = bar_prepare_line(g_bar_manager.t_font, time, g_bar_manager.foreground_color); + CGPoint t_pos = bar_align_line(bar, time_line, ALIGN_RIGHT, ALIGN_CENTER); + bar_draw_line(bar, time_line, t_pos.x, t_pos.y); + + CGPoint ti_pos = bar_align_line(bar, g_bar_manager.clock_icon, 0, ALIGN_CENTER); + ti_pos.x = t_pos.x - g_bar_manager.clock_icon.bounds.size.width - 5; + + CGPoint tu_pos = bar_align_line(bar, g_bar_manager.clock_underline, 0, ALIGN_BOTTOM); + tu_pos.x = tu_pos.x - g_bar_manager.clock_underline.bounds.size.width / 2 - time_line.bounds.size.width / 2 - (g_bar_manager.clock_icon.bounds.size.width + 5) / 2; + + bar_draw_line(bar, g_bar_manager.clock_icon, ti_pos.x, ti_pos.y); + bar_draw_line(bar, g_bar_manager.clock_underline, tu_pos.x, tu_pos.y); + bar_destroy_line(time_line); + + initial_bar_right_x = tu_pos.x - 10; + } + + bool has_batt = false; + bool charging = false; + int percent = bar_find_battery_life(&has_batt, &charging); + if (has_batt) { + char batt[255]; + snprintf(batt, sizeof(batt), "%' '3d%%", percent); + + struct bar_line batt_line = bar_prepare_line(g_bar_manager.t_font, batt, g_bar_manager.foreground_color); + CGPoint p_pos = bar_align_line(bar, batt_line, ALIGN_RIGHT, ALIGN_CENTER); + p_pos.x = p_pos.x - time_line_width - g_bar_manager.clock_underline.bounds.size.width - 20; + bar_draw_line(bar, batt_line, p_pos.x, p_pos.y); + + struct bar_line batt_icon = charging ? g_bar_manager.power_icon : g_bar_manager.battr_icon; + CGPoint pi_pos = bar_align_line(bar, batt_icon, 0, ALIGN_CENTER); + pi_pos.x = p_pos.x - batt_icon.bounds.size.width - 5; + + CGPoint pu_pos = bar_align_line(bar, g_bar_manager.power_underline, 0, ALIGN_BOTTOM); + pu_pos.x = pu_pos.x - g_bar_manager.power_underline.bounds.size.width / 2 - batt_line.bounds.size.width / 2 - (batt_icon.bounds.size.width + 5) / 2; + + bar_draw_line(bar, batt_icon, pi_pos.x, pi_pos.y); + bar_draw_line(bar, g_bar_manager.power_underline, pu_pos.x, pu_pos.y); + bar_destroy_line(batt_line); + + initial_bar_right_x = pu_pos.x - 10; + } + + // BAR CENTER + char *title = focused_window_title(); + if (title) { + int overlap_left = 0; + int overlap_right = 0; + + struct bar_line title_line = bar_prepare_line(g_bar_manager.t_font, title, g_bar_manager.foreground_color); + CGPoint pos = bar_align_line(bar, title_line, ALIGN_CENTER, ALIGN_CENTER); + + if (final_bar_left_x >= pos.x) { + overlap_left = final_bar_left_x - pos.x; + } + + assert(overlap_left >= 0); + + if (overlap_left > 0) { + pos.x = final_bar_left_x; + } + + if (initial_bar_right_x <= pos.x + title_line.bounds.size.width) { + overlap_right = pos.x + title_line.bounds.size.width - initial_bar_right_x; + } + + assert(overlap_right >= 0); + + if (overlap_right > 0) { + int truncated_width = (int)title_line.bounds.size.width - overlap_right; + if (truncated_width > 0) { + CTLineRef truncated_line = CTLineCreateTruncatedLine(title_line.line, truncated_width, kCTLineTruncationEnd, NULL); + CFRelease(title_line.line); + title_line.line = truncated_line; + } else { + goto free_title; + } + } + + bar_draw_line(bar, title_line, pos.x, pos.y); +free_title: + bar_destroy_line(title_line); + free(title); + } + + CGContextFlush(bar->context); + SLSOrderWindow(g_connection, bar->id, 1, bar->id); + SLSReenableUpdate(g_connection); +} + +static CGPoint bar_create_frame(struct bar *bar, CFTypeRef *frame_region) +{ + CGRect bounds = display_bounds(bar->did); + CGPoint origin = bounds.origin; + + if (!display_manager_menu_bar_hidden()) { + CGRect menu = display_manager_menu_bar_rect(bar->did); + origin.y += menu.size.height; + } + + bar->frame = (CGRect) {{0, 0},{bounds.size.width, 26}}; + CGSNewRegionWithRect(&bar->frame, frame_region); + + return origin; +} + +void bar_resize(struct bar *bar) +{ + CFTypeRef frame_region; + CGPoint origin = bar_create_frame(bar, &frame_region); + + SLSDisableUpdate(g_connection); + SLSOrderWindow(g_connection, bar->id, -1, 0); + SLSSetWindowShape(g_connection, bar->id, origin.x, origin.y, frame_region); + bar_refresh(bar); + SLSOrderWindow(g_connection, bar->id, 1, 0); + SLSReenableUpdate(g_connection); + CFRelease(frame_region); +} + +struct bar *bar_create(uint32_t did) +{ + struct bar *bar = malloc(sizeof(struct bar)); + memset(bar, 0, sizeof(struct bar)); + bar->did = did; + + uint32_t set_tags[2] = { + kCGSStickyTagBit | + kCGSModalWindowTagBit | + kCGSDisableShadowTagBit | + kCGSHighQualityResamplingTagBit | + kCGSIgnoreForExposeTagBit + }; + + uint32_t clear_tags[2] = { 0, 0 }; + *((int8_t *)(clear_tags) + 0x5) = 0x20; + + CFTypeRef frame_region; + CGPoint origin = bar_create_frame(bar, &frame_region); + + SLSNewWindow(g_connection, 2, origin.x, origin.y, frame_region, &bar->id); + CFRelease(frame_region); + + SLSSetWindowResolution(g_connection, bar->id, 2.0f); + SLSSetWindowTags(g_connection, bar->id, set_tags, 64); + SLSClearWindowTags(g_connection, bar->id, clear_tags, 64); + SLSSetWindowOpacity(g_connection, bar->id, 0); + SLSSetMouseEventEnableFlags(g_connection, bar->id, false); + SLSSetWindowLevel(g_connection, bar->id, CGWindowLevelForKey(4)); + bar->context = SLWindowContextCreate(g_connection, bar->id, 0); + + int refresh_frequency = 5; + bar->power_source = IOPSNotificationCreateRunLoopSource(power_handler, NULL); + bar->refresh_timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + refresh_frequency, refresh_frequency, 0, 0, timer_handler, NULL); + + CFRunLoopAddSource(CFRunLoopGetMain(), bar->power_source, kCFRunLoopCommonModes); + CFRunLoopAddTimer(CFRunLoopGetMain(), bar->refresh_timer, kCFRunLoopCommonModes); + + bar_refresh(bar); + + return bar; +} + +void bar_destroy(struct bar *bar) +{ + CFRunLoopRemoveSource(CFRunLoopGetMain(), bar->power_source, kCFRunLoopCommonModes); + CFRunLoopSourceInvalidate(bar->power_source); + + CFRunLoopRemoveTimer(CFRunLoopGetMain(), bar->refresh_timer, kCFRunLoopCommonModes); + CFRunLoopTimerInvalidate(bar->refresh_timer); + + CGContextRelease(bar->context); + SLSReleaseWindow(g_connection, bar->id); + free(bar); +} diff --git a/src/bar.h b/src/bar.h new file mode 100644 index 0000000..943f815 --- /dev/null +++ b/src/bar.h @@ -0,0 +1,62 @@ +#ifndef BAR_H +#define BAR_H + +extern CGError SLSDisableUpdate(int cid); +extern CGError SLSReenableUpdate(int cid); +extern CGError SLSNewWindow(int cid, int type, float x, float y, CFTypeRef region, uint32_t *wid); +extern CGError SLSReleaseWindow(int cid, uint32_t wid); +extern CGError SLSSetWindowTags(int cid, uint32_t wid, uint32_t tags[2], int tag_size); +extern CGError SLSClearWindowTags(int cid, uint32_t wid, uint32_t tags[2], int tag_size); +extern CGError SLSSetWindowShape(int cid, uint32_t wid, float x_offset, float y_offset, CFTypeRef shape); +extern CGError SLSSetWindowResolution(int cid, uint32_t wid, double res); +extern CGError SLSSetWindowOpacity(int cid, uint32_t wid, bool isOpaque); +extern CGError SLSSetMouseEventEnableFlags(int cid, uint32_t wid, bool shouldEnable); +extern CGError SLSOrderWindow(int cid, uint32_t wid, int mode, uint32_t relativeToWID); +extern CGError SLSSetWindowLevel(int cid, uint32_t wid, int level); +extern CGContextRef SLWindowContextCreate(int cid, uint32_t wid, CFDictionaryRef options); +extern CGError CGSNewRegionWithRect(CGRect *rect, CFTypeRef *outRegion); + +#define kCGSModalWindowTagBit (1 << 31) +#define kCGSDisableShadowTagBit (1 << 3) +#define kCGSHighQualityResamplingTagBit (1 << 4) +#define kCGSIgnoreForExposeTagBit (1 << 7) +#define kCGSStickyTagBit (1 << 11) + +#define POWER_CALLBACK(name) void name(void *context) +typedef POWER_CALLBACK(power_callback); + +#define TIMER_CALLBACK(name) void name(CFRunLoopTimerRef timer, void *context) +typedef TIMER_CALLBACK(timer_callback); + +#define ALIGN_NONE 0 +#define ALIGN_LEFT 1 +#define ALIGN_RIGHT 2 +#define ALIGN_TOP 3 +#define ALIGN_BOTTOM 4 +#define ALIGN_CENTER 5 + +struct bar_line +{ + CTLineRef line; + CGFloat ascent; + CGFloat descent; + CGRect bounds; + struct rgba_color color; +}; + +struct bar +{ + uint32_t id; + uint32_t did; + CGContextRef context; + CFRunLoopSourceRef power_source; + CFRunLoopTimerRef refresh_timer; + CGRect frame; +}; + +void bar_refresh(struct bar *bar); +void bar_resize(struct bar *bar); +struct bar *bar_create(uint32_t did); +void bar_destroy(struct bar *bar); + +#endif diff --git a/src/bar_manager.c b/src/bar_manager.c new file mode 100644 index 0000000..f590686 --- /dev/null +++ b/src/bar_manager.c @@ -0,0 +1,229 @@ +#include "bar_manager.h" + +void bar_manager_set_foreground_color(struct bar_manager *bar_manager, uint32_t color) +{ + bar_manager->foreground_color = rgba_color_from_hex(color); + if (bar_manager->_space_icon_strip) bar_manager_set_space_strip(bar_manager, bar_manager->_space_icon_strip); + if (bar_manager->_power_icon_strip) bar_manager_set_power_strip(bar_manager, bar_manager->_power_icon_strip); + if (bar_manager->_clock_icon) bar_manager_set_clock_icon(bar_manager, bar_manager->_clock_icon); + if (bar_manager->_space_icon) bar_manager_set_space_icon(bar_manager, bar_manager->_space_icon); + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_background_color(struct bar_manager *bar_manager, uint32_t color) +{ + bar_manager->background_color = rgba_color_from_hex(color); + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_text_font(struct bar_manager *bar_manager, char *font_string) +{ + if (bar_manager->t_font) { + CFRelease(bar_manager->t_font); + } + + if (bar_manager->space_underline.line) { + bar_destroy_line(bar_manager->space_underline); + } + + if (bar_manager->power_underline.line) { + bar_destroy_line(bar_manager->power_underline); + } + + if (bar_manager->clock_underline.line) { + bar_destroy_line(bar_manager->clock_underline); + } + + if (font_string != bar_manager->t_font_prop) { + if (bar_manager->t_font_prop) { + free(bar_manager->t_font_prop); + } + + bar_manager->t_font_prop = font_string; + } + + bar_manager->t_font = bar_create_font(bar_manager->t_font_prop); + bar_manager->space_underline = bar_prepare_line(bar_manager->t_font, "______", rgba_color_from_hex(0xffd4d232)); + bar_manager->power_underline = bar_prepare_line(bar_manager->t_font, "__________", rgba_color_from_hex(0xffd75f5f)); + bar_manager->clock_underline = bar_prepare_line(bar_manager->t_font, "__________", rgba_color_from_hex(0xff458588)); + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_icon_font(struct bar_manager *bar_manager, char *font_string) +{ + if (bar_manager->i_font) { + CFRelease(bar_manager->i_font); + } + + if (font_string != bar_manager->i_font_prop) { + if (bar_manager->i_font_prop) { + free(bar_manager->i_font_prop); + } + + bar_manager->i_font_prop = font_string; + } + + bar_manager->i_font = bar_create_font(bar_manager->i_font_prop); + if (bar_manager->_space_icon_strip) bar_manager_set_space_strip(bar_manager, bar_manager->_space_icon_strip); + if (bar_manager->_power_icon_strip) bar_manager_set_power_strip(bar_manager, bar_manager->_power_icon_strip); + if (bar_manager->_clock_icon) bar_manager_set_clock_icon(bar_manager, bar_manager->_clock_icon); + if (bar_manager->_space_icon) bar_manager_set_space_icon(bar_manager, bar_manager->_space_icon); + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_space_strip(struct bar_manager *bar_manager, char **icon_strip) +{ + for (int i = 0; i < buf_len(bar_manager->space_icon_strip); ++i) { + bar_destroy_line(bar_manager->space_icon_strip[i]); + } + + buf_free(bar_manager->space_icon_strip); + bar_manager->space_icon_strip = NULL; + + if (icon_strip != bar_manager->_space_icon_strip) { + for (int i = 0; i < buf_len(bar_manager->_space_icon_strip); ++i) { + free(bar_manager->_space_icon_strip[i]); + } + + buf_free(bar_manager->_space_icon_strip); + bar_manager->_space_icon_strip = icon_strip; + } + + for (int i = 0; i < buf_len(bar_manager->_space_icon_strip); ++i) { + struct bar_line space_line = bar_prepare_line(bar_manager->i_font, bar_manager->_space_icon_strip[i], bar_manager->foreground_color); + buf_push(bar_manager->space_icon_strip, space_line); + } + + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_power_strip(struct bar_manager *bar_manager, char **icon_strip) +{ + if (bar_manager->battr_icon.line) { + bar_destroy_line(bar_manager->battr_icon); + } + + if (bar_manager->power_icon.line) { + bar_destroy_line(bar_manager->power_icon); + } + + if (icon_strip != bar_manager->_power_icon_strip) { + for (int i = 0; i < buf_len(bar_manager->_power_icon_strip); ++i) { + free(bar_manager->_power_icon_strip[i]); + } + + buf_free(bar_manager->_power_icon_strip); + bar_manager->_power_icon_strip = icon_strip; + } + + if (buf_len(bar_manager->_power_icon_strip) == 2) { + bar_manager->battr_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_power_icon_strip[0], rgba_color_from_hex(0xffd75f5f)); + bar_manager->power_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_power_icon_strip[1], rgba_color_from_hex(0xffcd950c)); + } else { + bar_manager->battr_icon = bar_prepare_line(bar_manager->i_font, "", rgba_color_from_hex(0xffd75f5f)); + bar_manager->power_icon = bar_prepare_line(bar_manager->i_font, "", rgba_color_from_hex(0xffcd950c)); + } + + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_clock_icon(struct bar_manager *bar_manager, char *icon) +{ + if (bar_manager->clock_icon.line) { + bar_destroy_line(bar_manager->clock_icon); + } + + if (icon != bar_manager->_clock_icon) { + if (bar_manager->_clock_icon) { + free(bar_manager->_clock_icon); + } + + bar_manager->_clock_icon = icon; + } + + bar_manager->clock_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_clock_icon, bar_manager->foreground_color); + + bar_manager_refresh(bar_manager); +} + +void bar_manager_set_space_icon(struct bar_manager *bar_manager, char *icon) +{ + if (bar_manager->space_icon.line) { + bar_destroy_line(bar_manager->space_icon); + } + + if (icon != bar_manager->_space_icon) { + if (bar_manager->_space_icon) { + free(bar_manager->_space_icon); + } + + bar_manager->_space_icon = icon; + } + + bar_manager->space_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_space_icon, bar_manager->foreground_color); + + bar_manager_refresh(bar_manager); +} + +void bar_manager_add_display(struct bar_manager *bar_manager, uint32_t did) +{ + for (int i = 0; i < bar_manager->bar_count; ++i) { + if (bar_manager->bars[i]->did == did) + return; + } + + bar_manager->bar_count++; + bar_manager->bars = realloc(bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count); + bar_manager->bars[bar_manager->bar_count - 1] = bar_create(did); +} + +void bar_manager_remove_display(struct bar_manager *bar_manager, uint32_t did) +{ + for (int i = 0; i < bar_manager->bar_count; ++i) + { + if (bar_manager->bars[i]->did == did) { + free (bar_manager->bars[i]); + bar_manager->bars[i] = bar_manager->bars[bar_manager->bar_count - 1]; + bar_manager->bar_count--; + bar_manager->bars = realloc(bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count); + return; + } + } +} + +void bar_manager_refresh(struct bar_manager *bar_manager) +{ + for (int i = 0; i < bar_manager->bar_count; ++i) + bar_refresh(bar_manager->bars[i]); +} + +void bar_manager_resize(struct bar_manager *bar_manager) +{ + for (int i = 0; i < bar_manager->bar_count; ++i) + bar_resize(bar_manager->bars[i]); +} + +void bar_manager_init(struct bar_manager *bar_manager) +{ + bar_manager->bars = NULL; + bar_manager->bar_count = 0; + bar_manager_set_text_font(bar_manager, string_copy("Helvetica Neue:Regular:10.0")); + bar_manager_set_icon_font(bar_manager, string_copy("FontAwesome:Regular:10.0")); + bar_manager_set_background_color(bar_manager, 0xff202020); + bar_manager_set_foreground_color(bar_manager, 0xffa8a8a8); + bar_manager_set_clock_icon(bar_manager, string_copy(" ")); + bar_manager_set_space_icon(bar_manager, string_copy("*")); + bar_manager_set_power_strip(bar_manager, NULL); +} + +void bar_manager_begin(struct bar_manager *bar_manager) +{ + bar_manager->bar_count = display_manager_active_display_count(); + bar_manager->bars = (struct bar **) malloc(sizeof(struct bar *) * bar_manager->bar_count); + + for (uint32_t index=1; index <= bar_manager->bar_count; index++) + { + uint32_t did = display_manager_arrangement_display_id(index); + bar_manager->bars[index - 1] = bar_create(did); + } +} \ No newline at end of file diff --git a/src/bar_manager.h b/src/bar_manager.h new file mode 100644 index 0000000..e846e1e --- /dev/null +++ b/src/bar_manager.h @@ -0,0 +1,46 @@ +#ifndef BAR_MANAGER_H +#define BAR_MANAGER_H + +struct bar_manager +{ + struct bar **bars; + int bar_count; + char *t_font_prop; + char *i_font_prop; + CTFontRef t_font; + CTFontRef i_font; + char **_space_icon_strip; + char **_power_icon_strip; + char *_clock_icon; + char *_space_icon; + struct rgba_color foreground_color; + struct rgba_color background_color; + struct rgba_color background_color_dim; + struct bar_line *space_icon_strip; + struct bar_line space_icon; + struct bar_line clock_icon; + struct bar_line battr_icon; + struct bar_line power_icon; + struct bar_line space_underline; + struct bar_line power_underline; + struct bar_line clock_underline; +}; + +void bar_manager_set_foreground_color(struct bar_manager *bar_manager, uint32_t color); +void bar_manager_set_background_color(struct bar_manager *bar_manager, uint32_t color); +void bar_manager_set_text_font(struct bar_manager *bar_manager, char *font_string); +void bar_manager_set_icon_font(struct bar_manager *bar_manager, char *font_string); +void bar_manager_set_space_strip(struct bar_manager *bar_manager, char **icon_strip); +void bar_manager_set_power_strip(struct bar_manager *bar_manager, char **icon_strip); +void bar_manager_set_clock_icon(struct bar_manager *bar_manager, char *icon); +void bar_manager_set_space_icon(struct bar_manager *bar_manager, char *icon); + +void bar_manager_add_display(struct bar_manager *bar_manager, uint32_t did); +void bar_manager_remove_display(struct bar_manager *bar_manager, uint32_t did); +void bar_manager_refresh(struct bar_manager *bar_manager); +void bar_manager_resize(struct bar_manager *bar_manager); +void bar_manager_begin(struct bar_manager *bar_manager); +void bar_manager_init(struct bar_manager *bar_manager); + + +#endif diff --git a/src/display.c b/src/display.c new file mode 100644 index 0000000..b784151 --- /dev/null +++ b/src/display.c @@ -0,0 +1,106 @@ +#include "display.h" + +extern struct event_loop g_event_loop; +extern struct bar g_bar; +extern int g_connection; + +static DISPLAY_EVENT_HANDLER(display_handler) +{ + if (flags & kCGDisplayAddFlag) { + struct event *event = event_create(&g_event_loop, DISPLAY_ADDED, (void *)(intptr_t) did); + event_loop_post(&g_event_loop, event); + } else if (flags & kCGDisplayRemoveFlag) { + struct event *event = event_create(&g_event_loop, DISPLAY_REMOVED, (void *)(intptr_t) did); + event_loop_post(&g_event_loop, event); + } else if (flags & kCGDisplayMovedFlag) { + struct event *event = event_create(&g_event_loop, DISPLAY_MOVED, (void *)(intptr_t) did); + event_loop_post(&g_event_loop, event); + } else if (flags & kCGDisplayDesktopShapeChangedFlag) { + struct event *event = event_create(&g_event_loop, DISPLAY_RESIZED, (void *)(intptr_t) did); + event_loop_post(&g_event_loop, event); + } +} + +CFStringRef display_uuid(uint32_t did) +{ + CFUUIDRef uuid_ref = CGDisplayCreateUUIDFromDisplayID(did); + if (!uuid_ref) return NULL; + + CFStringRef uuid_str = CFUUIDCreateString(NULL, uuid_ref); + CFRelease(uuid_ref); + + return uuid_str; +} + +CGRect display_bounds(uint32_t did) +{ + return CGDisplayBounds(did); +} + +uint64_t display_space_id(uint32_t did) +{ + CFStringRef uuid = display_uuid(did); + if (!uuid) return 0; + + uint64_t sid = SLSManagedDisplayGetCurrentSpace(g_connection, uuid); + CFRelease(uuid); + return sid; +} + +uint64_t *display_space_list(uint32_t did, int *count) +{ + CFStringRef uuid = display_uuid(did); + if (!uuid) return NULL; + + CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection); + if (!display_spaces_ref) return NULL; + + uint64_t *space_list = NULL; + int display_spaces_count = CFArrayGetCount(display_spaces_ref); + + for (int i = 0; i < display_spaces_count; ++i) { + CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i); + CFStringRef identifier = CFDictionaryGetValue(display_ref, CFSTR("Display Identifier")); + if (!CFEqual(uuid, identifier)) continue; + + CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces")); + int spaces_count = CFArrayGetCount(spaces_ref); + + space_list = malloc(sizeof(uint64_t) * spaces_count); + *count = spaces_count; + + for (int j = 0; j < spaces_count; ++j) { + CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j); + CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64")); + CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &space_list[j]); + } + } + + CFRelease(display_spaces_ref); + CFRelease(uuid); + + return space_list; +} + +int display_arrangement(uint32_t did) +{ + CFStringRef uuid = display_uuid(did); + if (!uuid) return 0; + + CFArrayRef displays = SLSCopyManagedDisplays(g_connection); + if (!displays) return 0; + + int result = 0; + int displays_count = CFArrayGetCount(displays); + + for (int i = 0; i < displays_count; ++i) { + if (CFEqual(CFArrayGetValueAtIndex(displays, i), uuid)) { + result = i + 1; + break; + } + } + + CFRelease(displays); + CFRelease(uuid); + return result; +} diff --git a/src/display.h b/src/display.h new file mode 100644 index 0000000..8c08823 --- /dev/null +++ b/src/display.h @@ -0,0 +1,24 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +extern int SLSGetSpaceManagementMode(int cid); +extern CFArrayRef SLSCopyManagedDisplaySpaces(int cid); +extern CGError SLSProcessAssignToSpace(int cid, pid_t pid, uint64_t sid); +extern CGError SLSProcessAssignToAllSpaces(int cid, pid_t pid); +extern void SLSMoveWindowsToManagedSpace(int cid, CFArrayRef window_list, uint64_t sid); +extern CGError CoreDockSendNotification(CFStringRef notification, int unknown); + +#define DISPLAY_EVENT_HANDLER(name) void name(uint32_t did, CGDisplayChangeSummaryFlags flags, void *context) +typedef DISPLAY_EVENT_HANDLER(display_callback); + +extern CFUUIDRef CGDisplayCreateUUIDFromDisplayID(uint32_t did); +extern CFArrayRef SLSCopyManagedDisplays(int cid); +extern uint64_t SLSManagedDisplayGetCurrentSpace(int cid, CFStringRef uuid); + +CFStringRef display_uuid(uint32_t did); +CGRect display_bounds(uint32_t did); +uint64_t display_space_id(uint32_t did); +uint64_t *display_space_list(uint32_t did, int *count); +int display_arrangement(uint32_t did); + +#endif diff --git a/src/display_manager.c b/src/display_manager.c new file mode 100644 index 0000000..7ba98a3 --- /dev/null +++ b/src/display_manager.c @@ -0,0 +1,187 @@ +#include "display_manager.h" + +extern struct window_manager g_window_manager; +extern int g_connection; + +uint32_t display_manager_main_display_id(void) +{ + return CGMainDisplayID(); +} + +CFStringRef display_manager_active_display_uuid(void) +{ + return SLSCopyActiveMenuBarDisplayIdentifier(g_connection); +} + +uint32_t display_manager_active_display_id(void) +{ + uint32_t result = 0; + CFStringRef uuid = display_manager_active_display_uuid(); + CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid); + result = CGDisplayGetDisplayIDFromUUID(uuid_ref); + CFRelease(uuid_ref); + CFRelease(uuid); + return result; +} + +CFStringRef display_manager_dock_display_uuid(void) +{ + CGRect dock = display_manager_dock_rect(); + return SLSCopyBestManagedDisplayForRect(g_connection, dock); +} + +uint32_t display_manager_dock_display_id(void) +{ + CFStringRef uuid = display_manager_dock_display_uuid(); + if (!uuid) return 0; + + CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid); + uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref); + CFRelease(uuid_ref); + CFRelease(uuid); + return result; +} + +CFStringRef display_manager_cursor_display_uuid(void) +{ + CGPoint cursor; + SLSGetCurrentCursorLocation(g_connection, &cursor); + return SLSCopyBestManagedDisplayForPoint(g_connection, cursor); +} + +uint32_t display_manager_cursor_display_id(void) +{ + CFStringRef uuid = display_manager_cursor_display_uuid(); + if (!uuid) return 0; + + CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid); + uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref); + CFRelease(uuid_ref); + CFRelease(uuid); + return result; +} + +CFStringRef display_manager_arrangement_display_uuid(int arrangement) +{ + CFStringRef result = NULL; + CFArrayRef displays = SLSCopyManagedDisplays(g_connection); + + int displays_count = CFArrayGetCount(displays); + for (int i = 0; i < displays_count; ++i) { + if ((i+1) != arrangement) continue; + result = CFRetain(CFArrayGetValueAtIndex(displays, i)); + break; + } + + CFRelease(displays); + return result; +} + +uint32_t display_manager_arrangement_display_id(int arrangement) +{ + uint32_t result = 0; + CFArrayRef displays = SLSCopyManagedDisplays(g_connection); + + int displays_count = CFArrayGetCount(displays); + for (int i = 0; i < displays_count; ++i) { + if ((i+1) != arrangement) continue; + CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, CFArrayGetValueAtIndex(displays, i)); + result = CGDisplayGetDisplayIDFromUUID(uuid_ref); + CFRelease(uuid_ref); + break; + } + + CFRelease(displays); + return result; +} + +uint32_t display_manager_first_display_id(void) +{ + return display_manager_arrangement_display_id(1); +} + +uint32_t display_manager_last_display_id(void) +{ + int arrangement = display_manager_active_display_count(); + return display_manager_arrangement_display_id(arrangement); +} + +bool display_manager_menu_bar_hidden(void) +{ + int status = 0; + SLSGetMenuBarAutohideEnabled(g_connection, &status); + return status; +} + +CGRect display_manager_menu_bar_rect(uint32_t did) +{ + CGRect bounds = {}; + SLSGetRevealedMenuBarBounds(&bounds, g_connection, display_space_id(did)); + return bounds; +} + +bool display_manager_dock_hidden(void) +{ + return CoreDockGetAutoHideEnabled(); +} + +int display_manager_dock_orientation(void) +{ + int pinning = 0; + int orientation = 0; + CoreDockGetOrientationAndPinning(&orientation, &pinning); + return orientation; +} + +CGRect display_manager_dock_rect(void) +{ + int reason = 0; + CGRect bounds = {}; + SLSGetDockRectWithReason(g_connection, &bounds, &reason); + return bounds; +} + +bool display_manager_active_display_is_animating(void) +{ + CFStringRef uuid = display_manager_active_display_uuid(); + bool result = SLSManagedDisplayIsAnimating(g_connection, uuid); + CFRelease(uuid); + return result; +} + +bool display_manager_display_is_animating(uint32_t did) +{ + CFStringRef uuid = display_uuid(did); + if (!uuid) return false; + + bool result = SLSManagedDisplayIsAnimating(g_connection, uuid); + CFRelease(uuid); + return result; +} + +uint32_t display_manager_active_display_count(void) +{ + uint32_t count; + CGGetActiveDisplayList(0, NULL, &count); + return count; +} + +uint32_t *display_manager_active_display_list(uint32_t *count) +{ + int display_count = display_manager_active_display_count(); + uint32_t *result = malloc(sizeof(uint32_t) * display_count); + CGGetActiveDisplayList(display_count, result, count); + return result; +} + +bool display_manager_begin(struct display_manager *dm) +{ + dm->current_display_id = display_manager_active_display_id(); + dm->last_display_id = dm->current_display_id; + return CGDisplayRegisterReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess; +} + +bool display_manager_end(void) +{ + return CGDisplayRemoveReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess; +} diff --git a/src/display_manager.h b/src/display_manager.h new file mode 100644 index 0000000..35f038a --- /dev/null +++ b/src/display_manager.h @@ -0,0 +1,49 @@ +#ifndef DISPLAY_MANAGER_H +#define DISPLAY_MANAGER_H + +extern CFStringRef SLSCopyActiveMenuBarDisplayIdentifier(int cid); +extern CFStringRef SLSCopyBestManagedDisplayForPoint(int cid, CGPoint point); +extern bool SLSManagedDisplayIsAnimating(int cid, CFStringRef uuid); +extern CGError SLSGetMenuBarAutohideEnabled(int cid, int *enabled); +extern CGError SLSGetRevealedMenuBarBounds(CGRect *rect, int cid, uint64_t sid); +extern CGError SLSGetDockRectWithReason(int cid, CGRect *rect, int *reason); +extern Boolean CoreDockGetAutoHideEnabled(void); +extern void CoreDockGetOrientationAndPinning(int *orientation, int *pinning); + +#define DOCK_ORIENTATION_BOTTOM 2 +#define DOCK_ORIENTATION_LEFT 3 +#define DOCK_ORIENTATION_RIGHT 4 + +struct display_manager +{ + uint32_t current_display_id; + uint32_t last_display_id; +}; + +uint32_t display_manager_main_display_id(void); +CFStringRef display_manager_active_display_uuid(void); +uint32_t display_manager_active_display_id(void); +CFStringRef display_manager_dock_display_uuid(void); +uint32_t display_manager_dock_display_id(void); +CFStringRef display_manager_cursor_display_uuid(void); +uint32_t display_manager_cursor_display_id(void); +CFStringRef display_manager_arrangement_display_uuid(int arrangement); +uint32_t display_manager_arrangement_display_id(int arrangement); +uint32_t display_manager_prev_display_id(uint32_t did); +uint32_t display_manager_next_display_id(uint32_t did); +uint32_t display_manager_first_display_id(void); +uint32_t display_manager_last_display_id(void); +bool display_manager_menu_bar_hidden(void); +CGRect display_manager_menu_bar_rect(uint32_t did); +bool display_manager_dock_hidden(void); +int display_manager_dock_orientation(void); +CGRect display_manager_dock_rect(void); +bool display_manager_active_display_is_animating(void); +bool display_manager_display_is_animating(uint32_t did); +uint32_t display_manager_active_display_count(void); +uint32_t *display_manager_active_display_list(uint32_t *count); +//void display_manager_focus_display(uint32_t did); +bool display_manager_begin(struct display_manager *dm); +bool display_manager_end(void); + +#endif diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..0e2edd8 --- /dev/null +++ b/src/event.c @@ -0,0 +1,237 @@ +#include "event.h" + +extern struct event_loop g_event_loop; +extern struct process_manager g_process_manager; +extern struct display_manager g_display_manager; +extern struct bar_manager g_bar_manager; +extern struct application_manager g_application_manager; +extern bool g_mission_control_active; +extern int g_connection; + +enum event_type event_type_from_string(const char *str) +{ + for (int i = EVENT_TYPE_UNKNOWN + 1; i < EVENT_TYPE_COUNT; ++i) { + if (string_equals(str, event_type_str[i])) return i; + } + + return EVENT_TYPE_UNKNOWN; +} + +struct event *event_create(struct event_loop *event_loop, enum event_type type, void *context) +{ + struct event *event = memory_pool_push(&event_loop->pool, struct event); + event->type = type; + event->context = context; + event->param1 = 0; + event->info = 0; +#ifdef DEBUG + uint64_t count = __sync_add_and_fetch(&event_loop->count, 1); + assert(count > 0 && count < EVENT_MAX_COUNT); +#endif + return event; +} + +struct event *event_create_p1(struct event_loop *event_loop, enum event_type type, void *context, int param1) +{ + struct event *event = memory_pool_push(&event_loop->pool, struct event); + event->type = type; + event->context = context; + event->param1 = param1; + event->info = 0; +#ifdef DEBUG + uint64_t count = __sync_add_and_fetch(&event_loop->count, 1); + assert(count > 0 && count < EVENT_MAX_COUNT); +#endif + return event; +} + +void event_destroy(struct event_loop *event_loop, struct event *event) +{ + switch (event->type) { + default: break; + case APPLICATION_TERMINATED: { + process_destroy(event->context); + } break; + } + +#ifdef DEBUG + uint64_t count = __sync_sub_and_fetch(&event_loop->count, 1); + assert(count >= 0 && count < EVENT_MAX_COUNT); +#endif +} + + +static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_LAUNCHED) +{ + struct process *process = context; + debug("%s: %s\n", __FUNCTION__, process->name); + + if ((process->terminated) || (kill(process->pid, 0) == -1)) { + debug("%s: %s terminated during launch\n", __FUNCTION__, process->name); + return EVENT_FAILURE; + } + + struct application *application = application_create(process); + if (application_observe(application)) { + application_manager_add_application(&g_application_manager, application); + + return EVENT_SUCCESS; + } else { + bool retry_ax = application->retry; + application_unobserve(application); + application_destroy(application); + debug("%s: could not observe %s (%d)\n", __FUNCTION__, process->name, retry_ax); + + if (retry_ax) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + struct event *event = event_create(&g_event_loop, APPLICATION_LAUNCHED, process); + event_loop_post(&g_event_loop, event); + }); + } + + return EVENT_FAILURE; + } +} + +static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_TERMINATED) +{ + struct process *process = context; + struct application *application = application_manager_find_application(&g_application_manager, process->pid); + + if (!application) { + debug("%s: %s (not observed)\n", __FUNCTION__, process->name); + return EVENT_FAILURE; + } + + debug("%s: %s\n", __FUNCTION__, process->name); + application_manager_remove_application(&g_application_manager, application->pid); + + application_unobserve(application); + application_destroy(application); + + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_FRONT_SWITCHED) +{ + debug("%s\n", __FUNCTION__); + bar_manager_refresh(&g_bar_manager); + + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_FOCUSED) +{ + debug("%s\n", __FUNCTION__); + bar_manager_refresh(&g_bar_manager); + + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_TITLE_CHANGED) +{ + debug("%s\n", __FUNCTION__); + + // TODO: we can optimize by checking if it the focused window + bar_manager_refresh(&g_bar_manager); + + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_SPACE_CHANGED) +{ + debug("%s\n", __FUNCTION__); + + bar_manager_refresh(&g_bar_manager); + + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_CHANGED) +{ + g_display_manager.last_display_id = g_display_manager.current_display_id; + g_display_manager.current_display_id = display_manager_active_display_id(); + + debug("%s: %d\n", __FUNCTION__, g_display_manager.current_display_id); + + bar_manager_refresh(&g_bar_manager); + + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_ADDED) +{ + uint32_t did = (uint32_t)(intptr_t) context; + debug("%s: %d\n", __FUNCTION__, did); + bar_manager_add_display(&g_bar_manager, did); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_REMOVED) +{ + uint32_t did = (uint32_t)(intptr_t) context; + debug("%s: %d\n", __FUNCTION__, did); + bar_manager_remove_display(&g_bar_manager, did); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_MOVED) +{ + uint32_t did = (uint32_t)(intptr_t) context; + debug("%s: %d\n", __FUNCTION__, did); + bar_manager_resize(&g_bar_manager); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_RESIZED) +{ + uint32_t did = (uint32_t)(intptr_t) context; + debug("%s: %d\n", __FUNCTION__, did); + bar_manager_resize(&g_bar_manager); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED) +{ + debug("%s:\n", __FUNCTION__); + bar_manager_resize(&g_bar_manager); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_SYSTEM_WOKE) +{ + debug("%s:\n", __FUNCTION__); + bar_manager_refresh(&g_bar_manager); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_BAR_REFRESH) +{ + bar_manager_refresh(&g_bar_manager); + return EVENT_SUCCESS; +} + +static EVENT_CALLBACK(EVENT_HANDLER_DAEMON_MESSAGE) +{ + FILE *rsp = fdopen(param1, "w"); + if (!rsp) goto out; + + if (g_verbose) { + fprintf(stdout, "%s:", __FUNCTION__); + for (char *message = context; *message;) { + message += fprintf(stdout, " %s", message); + } + putc('\n', stdout); + fflush(stdout); + } + + handle_message(rsp, context); + fflush(rsp); + fclose(rsp); + +out: + socket_close(param1); + free(context); + + return EVENT_SUCCESS; +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..90260f2 --- /dev/null +++ b/src/event.h @@ -0,0 +1,110 @@ +#ifndef EVENT_LOOP_EVENT_H +#define EVENT_LOOP_EVENT_H + +#define EVENT_CALLBACK(name) uint32_t name(void *context, int param1) +typedef EVENT_CALLBACK(event_callback); + +static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_LAUNCHED); +static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_TERMINATED); +static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_FRONT_SWITCHED); +static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_FOCUSED); +static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_TITLE_CHANGED); +static EVENT_CALLBACK(EVENT_HANDLER_SPACE_CHANGED); +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_ADDED); +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_REMOVED); +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_MOVED); +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_RESIZED); +static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_CHANGED); +static EVENT_CALLBACK(EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED); +static EVENT_CALLBACK(EVENT_HANDLER_SYSTEM_WOKE); +static EVENT_CALLBACK(EVENT_HANDLER_BAR_REFRESH); +static EVENT_CALLBACK(EVENT_HANDLER_DAEMON_MESSAGE); + +#define EVENT_QUEUED 0x0 +#define EVENT_PROCESSED 0x1 + +#define EVENT_SUCCESS 0x0 +#define EVENT_FAILURE 0x1 +#define EVENT_MOUSE_IGNORE 0x2 + +#define event_status(e) ((e) & 0x1) +#define event_result(e) ((e) >> 0x1) + +enum event_type +{ + EVENT_TYPE_UNKNOWN, + APPLICATION_LAUNCHED, + APPLICATION_TERMINATED, + APPLICATION_FRONT_SWITCHED, + WINDOW_FOCUSED, + WINDOW_TITLE_CHANGED, + SPACE_CHANGED, + DISPLAY_ADDED, + DISPLAY_REMOVED, + DISPLAY_MOVED, + DISPLAY_RESIZED, + DISPLAY_CHANGED, + MENU_BAR_HIDDEN_CHANGED, + SYSTEM_WOKE, + BAR_REFRESH, + DAEMON_MESSAGE, + + EVENT_TYPE_COUNT +}; + +static const char *event_type_str[] = +{ + [EVENT_TYPE_UNKNOWN] = "event_type_unknown", + + [APPLICATION_LAUNCHED] = "application_launched", + [APPLICATION_TERMINATED] = "application_terminated", + [APPLICATION_FRONT_SWITCHED] = "application_front_switched", + [WINDOW_FOCUSED] = "window_focused", + [WINDOW_TITLE_CHANGED] = "window_title_changed", + [SPACE_CHANGED] = "space_changed", + [DISPLAY_ADDED] = "display_added", + [DISPLAY_REMOVED] = "display_removed", + [DISPLAY_MOVED] = "display_moved", + [DISPLAY_RESIZED] = "display_resized", + [DISPLAY_CHANGED] = "display_changed", + [MENU_BAR_HIDDEN_CHANGED] = "menu_bar_hidden_changed", + [SYSTEM_WOKE] = "system_woke", + [BAR_REFRESH] = "bar_refresh", + [DAEMON_MESSAGE] = "daemon_message", + + [EVENT_TYPE_COUNT] = "event_type_count" +}; + +static event_callback *event_handler[] = +{ + [APPLICATION_LAUNCHED] = EVENT_HANDLER_APPLICATION_LAUNCHED, + [APPLICATION_TERMINATED] = EVENT_HANDLER_APPLICATION_TERMINATED, + [APPLICATION_FRONT_SWITCHED] = EVENT_HANDLER_APPLICATION_FRONT_SWITCHED, + [WINDOW_FOCUSED] = EVENT_HANDLER_WINDOW_FOCUSED, + [WINDOW_TITLE_CHANGED] = EVENT_HANDLER_WINDOW_TITLE_CHANGED, + [SPACE_CHANGED] = EVENT_HANDLER_SPACE_CHANGED, + [DISPLAY_ADDED] = EVENT_HANDLER_DISPLAY_ADDED, + [DISPLAY_REMOVED] = EVENT_HANDLER_DISPLAY_REMOVED, + [DISPLAY_MOVED] = EVENT_HANDLER_DISPLAY_MOVED, + [DISPLAY_RESIZED] = EVENT_HANDLER_DISPLAY_RESIZED, + [DISPLAY_CHANGED] = EVENT_HANDLER_DISPLAY_CHANGED, + [MENU_BAR_HIDDEN_CHANGED] = EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED, + [SYSTEM_WOKE] = EVENT_HANDLER_SYSTEM_WOKE, + [BAR_REFRESH] = EVENT_HANDLER_BAR_REFRESH, + [DAEMON_MESSAGE] = EVENT_HANDLER_DAEMON_MESSAGE, +}; + +struct event +{ + void *context; + volatile uint32_t *info; + enum event_type type; + int param1; +}; + +struct event *event_create(struct event_loop *event_loop, enum event_type type, void *context); +struct event *event_create_p1(struct event_loop *event_loop, enum event_type type, void *context, int param1); +void event_destroy(struct event_loop *event_loop, struct event *event); +enum event_type event_type_from_string(const char *str); + +#endif diff --git a/src/event_loop.c b/src/event_loop.c new file mode 100644 index 0000000..e238f9d --- /dev/null +++ b/src/event_loop.c @@ -0,0 +1,154 @@ +#include "event_loop.h" + +#ifdef STATS +struct cycle_counter +{ + uint64_t cycle_count; + uint64_t hit_count; +}; + +static struct cycle_counter queue_counters[2]; +static struct cycle_counter event_counters[EVENT_TYPE_COUNT]; + +static inline void cycle_counter_tick(const char *name, struct cycle_counter *counter, uint64_t elapsed_cycles) +{ + uint64_t cycle_count = __sync_add_and_fetch(&counter->cycle_count, elapsed_cycles); + uint64_t hit_count = __sync_add_and_fetch(&counter->hit_count, 1); + fprintf(stdout, "%30s: hits %'25lld | cur %'25lld | avg %'25lld\n", + name, hit_count, elapsed_cycles, cycle_count / hit_count) +} +#endif + +static bool queue_init(struct queue *queue) +{ + if (!memory_pool_init(&queue->pool, QUEUE_POOL_SIZE)) return false; + queue->head = memory_pool_push(&queue->pool, struct queue_item); + queue->head->data = NULL; + queue->head->next = NULL; + queue->tail = queue->head; +#ifdef DEBUG + queue->count = 0; +#endif + return true; +}; + +static void queue_push(struct queue *queue, struct event *event) +{ + bool success; + struct queue_item *tail, *new_tail; + +#ifdef STATS + uint64_t begin_cycles = __rdtsc(); +#endif + + new_tail = memory_pool_push(&queue->pool, struct queue_item); + new_tail->data = event; + new_tail->next = NULL; + __asm__ __volatile__ ("" ::: "memory"); + + do { + tail = queue->tail; + success = __sync_bool_compare_and_swap(&tail->next, NULL, new_tail); + if (!success) __sync_bool_compare_and_swap(&queue->tail, tail, tail->next); + } while (!success); + __sync_bool_compare_and_swap(&queue->tail, tail, new_tail); + +#ifdef DEBUG + uint64_t count = __sync_add_and_fetch(&queue->count, 1); + assert(count > 0 && count < QUEUE_MAX_COUNT); +#endif + +#ifdef STATS + cycle_counter_tick(__FUNCTION__, &queue_counters[0], __rdtsc() - begin_cycles); +#endif +} + +static struct event *queue_pop(struct queue *queue) +{ + struct queue_item *head; + +#ifdef STATS + uint64_t begin_cycles = __rdtsc(); +#endif + + do { + head = queue->head; + if (!head->next) return NULL; + } while (!__sync_bool_compare_and_swap(&queue->head, head, head->next)); + +#ifdef DEBUG + uint64_t count = __sync_sub_and_fetch(&queue->count, 1); + assert(count >= 0 && count < QUEUE_MAX_COUNT); +#endif + +#ifdef STATS + cycle_counter_tick(__FUNCTION__, &queue_counters[1], __rdtsc() - begin_cycles); +#endif + + return head->next->data; +} + +static void *event_loop_run(void *context) +{ + struct event_loop *event_loop = (struct event_loop *) context; + struct queue *queue = (struct queue *) &event_loop->queue; + + while (event_loop->is_running) { + struct event *event = queue_pop(queue); + if (event) { +#ifdef STATS + uint64_t begin_cycles = __rdtsc(); +#endif + uint32_t result = event_handler[event->type](event->context, event->param1); +#ifdef STATS + cycle_counter_tick(event_type_str[event->type], &event_counters[event->type], __rdtsc() - begin_cycles); +#endif + if (event->info) *event->info = (result << 0x1) | EVENT_PROCESSED; + + event_destroy(event_loop, event); + } else { + sem_wait(event_loop->semaphore); + } + } + + return NULL; +} + +void event_loop_post(struct event_loop *event_loop, struct event *event) +{ + assert(event_loop->is_running); + queue_push(&event_loop->queue, event); + sem_post(event_loop->semaphore); +} + +bool event_loop_init(struct event_loop *event_loop) +{ + if (!queue_init(&event_loop->queue)) return false; + if (!memory_pool_init(&event_loop->pool, EVENT_POOL_SIZE)) return false; + event_loop->is_running = false; +#ifdef DEBUG + event_loop->count = 0; +#endif +#ifdef STATS + setlocale(LC_ALL, ""); // For fprintf digit grouping +#endif + event_loop->semaphore = sem_open("spacebar_event_loop_semaphore", O_CREAT, 0600, 0); + sem_unlink("spacebar_event_loop_semaphore"); + return event_loop->semaphore != SEM_FAILED; +} + +bool event_loop_begin(struct event_loop *event_loop) +{ + if (event_loop->is_running) return false; + event_loop->is_running = true; + pthread_create(&event_loop->thread, NULL, &event_loop_run, event_loop); + return true; +} + +bool event_loop_end(struct event_loop *event_loop) +{ + if (!event_loop->is_running) return false; + event_loop->is_running = false; + pthread_join(event_loop->thread, NULL); + return true; +} diff --git a/src/event_loop.h b/src/event_loop.h new file mode 100644 index 0000000..24d1625 --- /dev/null +++ b/src/event_loop.h @@ -0,0 +1,43 @@ +#ifndef EVENT_LOOP_H +#define EVENT_LOOP_H + +#define EVENT_POOL_SIZE KILOBYTES(36) +#define EVENT_MAX_COUNT ((EVENT_POOL_SIZE) / (sizeof(struct event))) + +#define QUEUE_POOL_SIZE KILOBYTES(16) +#define QUEUE_MAX_COUNT ((QUEUE_POOL_SIZE) / (sizeof(struct queue_item))) + +struct queue_item +{ + struct event *data; + struct queue_item *next; +}; + +struct queue +{ + struct memory_pool pool; + struct queue_item *head; + struct queue_item *tail; +#ifdef DEBUG + volatile uint64_t count; +#endif +}; + +struct event_loop +{ + bool is_running; + pthread_t thread; + sem_t *semaphore; + struct queue queue; + struct memory_pool pool; +#ifdef DEBUG + volatile uint64_t count; +#endif +}; + +bool event_loop_init(struct event_loop *event_loop); +bool event_loop_begin(struct event_loop *event_loop); +bool event_loop_end(struct event_loop *event_loop); +void event_loop_post(struct event_loop *event_loop, struct event *event); + +#endif diff --git a/src/manifest.m b/src/manifest.m new file mode 100644 index 0000000..eaff77a --- /dev/null +++ b/src/manifest.m @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc/timing.h" +#include "misc/macros.h" +#include "misc/notify.h" +#include "misc/log.h" +#include "misc/helpers.h" +#include "misc/memory_pool.h" +#include "misc/sbuffer.h" +#define HASHTABLE_IMPLEMENTATION +#include "misc/hashtable.h" +#undef HASHTABLE_IMPLEMENTATION +#include "misc/socket.h" +#include "misc/socket.c" + +//#include "osax/sa.h" +//#include "osax/sa_loader.c" +//#include "osax/sa_payload.c" +//#include "osax/sa.m" + +#include "event_loop.h" +#include "event.h" +#include "workspace.h" +#include "message.h" +#include "display.h" +#include "process_manager.h" +#include "application.h" +#include "display_manager.h" +#include "application_manager.h" +#include "bar.h" +#include "bar_manager.h" + +#include "event_loop.c" +#include "event.c" +#include "workspace.m" +#include "message.c" +#include "display.c" +#include "process_manager.c" +#include "application.c" +#include "display_manager.c" +#include "bar.c" +#include "bar_manager.c" +#include "application_manager.c" + +#include "spacebar.c" diff --git a/src/message.c b/src/message.c new file mode 100644 index 0000000..5e1a56f --- /dev/null +++ b/src/message.c @@ -0,0 +1,229 @@ +#include "message.h" + +extern struct event_loop g_event_loop; +extern struct display_manager g_display_manager; +extern struct space_manager g_space_manager; +extern struct window_manager g_window_manager; +extern struct mouse_state g_mouse_state; +extern struct bar_manager g_bar_manager; +extern bool g_verbose; + +#define DOMAIN_CONFIG "config" + +/* --------------------------------DOMAIN CONFIG-------------------------------- */ +#define COMMAND_CONFIG_DEBUG_OUTPUT "debug_output" +#define COMMAND_CONFIG_BAR_TEXT_FONT "status_bar_text_font" +#define COMMAND_CONFIG_BAR_ICON_FONT "status_bar_icon_font" +#define COMMAND_CONFIG_BAR_BACKGROUND "status_bar_background_color" +#define COMMAND_CONFIG_BAR_FOREGROUND "status_bar_foreground_color" +#define COMMAND_CONFIG_BAR_SPACE_STRIP "status_bar_space_icon_strip" +#define COMMAND_CONFIG_BAR_POWER_STRIP "status_bar_power_icon_strip" +#define COMMAND_CONFIG_BAR_SPACE_ICON "status_bar_space_icon" +#define COMMAND_CONFIG_BAR_CLOCK_ICON "status_bar_clock_icon" + +/* --------------------------------COMMON ARGUMENTS----------------------------- */ +#define ARGUMENT_COMMON_VAL_ON "on" +#define ARGUMENT_COMMON_VAL_OFF "off" + +static bool token_equals(struct token token, char *match) +{ + char *at = match; + for (int i = 0; i < token.length; ++i, ++at) { + if ((*at == 0) || (token.text[i] != *at)) { + return false; + } + } + return *at == 0; +} + +static bool token_is_valid(struct token token) +{ + return token.text && token.length > 0; +} + +static char *token_to_string(struct token token) +{ + char *result = malloc(token.length + 1); + if (!result) return NULL; + + memcpy(result, token.text, token.length); + result[token.length] = '\0'; + return result; +} + +static uint32_t token_to_uint32t(struct token token) +{ + uint32_t result = 0; + char buffer[token.length + 1]; + memcpy(buffer, token.text, token.length); + buffer[token.length] = '\0'; + sscanf(buffer, "%x", &result); + return result; +} + +static struct token get_token(char **message) +{ + struct token token; + + token.text = *message; + while (**message) { + ++(*message); + } + token.length = *message - token.text; + + if ((*message)[0] == '\0' && (*message)[1] != '\0') { + ++(*message); + } else { + // NOTE(koekeishiya): don't go past the null-terminator + } + + return token; +} + +static void daemon_fail(FILE *rsp, char *fmt, ...) +{ + if (!rsp) return; + + fprintf(rsp, FAILURE_MESSAGE); + + va_list ap; + va_start(ap, fmt); + vfprintf(rsp, fmt, ap); + va_end(ap); +} + +#define VIEW_SET_PROPERTY(p) \ + int p_val = 0; \ + if (token_to_int(value, &p_val)) { \ + view->custom_##p = true; \ + view->p = p_val; \ + view_update(view); \ + view_flush(view); \ + } + +static void handle_domain_config(FILE *rsp, struct token domain, char *message) +{ + struct token command = get_token(&message); + + if (token_equals(command, COMMAND_CONFIG_DEBUG_OUTPUT)) { + struct token value = get_token(&message); + if (!token_is_valid(value)) { + fprintf(rsp, "%s\n", bool_str[g_verbose]); + } else if (token_equals(value, ARGUMENT_COMMON_VAL_OFF)) { + g_verbose = false; + } else if (token_equals(value, ARGUMENT_COMMON_VAL_ON)) { + g_verbose = true; + } else { + daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text); + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_TEXT_FONT)) { + int length = strlen(message); + if (length <= 0) { + fprintf(rsp, "%s\n", g_bar_manager.t_font_prop); + } else { + bar_manager_set_text_font(&g_bar_manager, string_copy(message)); + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_ICON_FONT)) { + int length = strlen(message); + if (length <= 0) { + fprintf(rsp, "%s\n", g_bar_manager.i_font_prop); + } else { + bar_manager_set_icon_font(&g_bar_manager, string_copy(message)); + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_BACKGROUND)) { + struct token value = get_token(&message); + if (!token_is_valid(value)) { + fprintf(rsp, "0x%x\n", g_bar_manager.background_color.p); + } else { + uint32_t color = token_to_uint32t(value); + if (color) { + bar_manager_set_background_color(&g_bar_manager, color); + } else { + daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text); + } + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_FOREGROUND)) { + struct token value = get_token(&message); + if (!token_is_valid(value)) { + fprintf(rsp, "0x%x\n", g_bar_manager.foreground_color.p); + } else { + uint32_t color = token_to_uint32t(value); + if (color) { + bar_manager_set_foreground_color(&g_bar_manager, color); + } else { + daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text); + } + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_SPACE_STRIP)) { + char **icon_strip = NULL; + struct token token = get_token(&message); + while (token.text && token.length > 0) { + buf_push(icon_strip, token_to_string(token)); + token = get_token(&message); + } + bar_manager_set_space_strip(&g_bar_manager, icon_strip); + } else if (token_equals(command, COMMAND_CONFIG_BAR_POWER_STRIP)) { + char **icon_strip = NULL; + struct token token = get_token(&message); + while (token.text && token.length > 0) { + buf_push(icon_strip, token_to_string(token)); + token = get_token(&message); + } + bar_manager_set_power_strip(&g_bar_manager, icon_strip); + if (buf_len(g_bar_manager._power_icon_strip) != 2) { + daemon_fail(rsp, "value for '%.*s' must contain exactly two symbols separated by whitespace.\n", command.length, command.text); + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_SPACE_ICON)) { + struct token token = get_token(&message); + if (!token_is_valid(token)) { + fprintf(rsp, "%s\n", g_bar_manager._space_icon ? g_bar_manager._space_icon : ""); + } else { + bar_manager_set_space_icon(&g_bar_manager, token_to_string(token)); + } + } else if (token_equals(command, COMMAND_CONFIG_BAR_CLOCK_ICON)) { + struct token token = get_token(&message); + if (!token_is_valid(token)) { + fprintf(rsp, "%s\n", g_bar_manager._clock_icon ? g_bar_manager._clock_icon : ""); + } else { + bar_manager_set_clock_icon(&g_bar_manager, token_to_string(token)); + } + } else { + daemon_fail(rsp, "unknown command '%.*s' for domain '%.*s'\n", command.length, command.text, domain.length, domain.text); + } +} + +#undef VIEW_SET_PROPERTY + +struct selector +{ + struct token token; + bool did_parse; + + union { + int dir; + uint32_t did; + uint64_t sid; + struct window *window; + }; +}; + +enum label_type +{ + LABEL_SPACE, +}; + +void handle_message(FILE *rsp, char *message) +{ + struct token domain = get_token(&message); + if (token_equals(domain, DOMAIN_CONFIG)) { + handle_domain_config(rsp, domain, message); + } else { + daemon_fail(rsp, "unknown domain '%.*s'\n", domain.length, domain.text); + } +} + +static SOCKET_DAEMON_HANDLER(message_handler) +{ + struct event *event = event_create_p1(&g_event_loop, DAEMON_MESSAGE, message, sockfd); + event_loop_post(&g_event_loop, event); +} diff --git a/src/message.h b/src/message.h new file mode 100644 index 0000000..2ea1231 --- /dev/null +++ b/src/message.h @@ -0,0 +1,13 @@ +#ifndef MESSAGE_H +#define MESSAGE_H + +struct token +{ + char *text; + unsigned int length; +}; + +static SOCKET_DAEMON_HANDLER(message_handler); +void handle_message(FILE *rsp, char *message); + +#endif diff --git a/src/misc/hashtable.h b/src/misc/hashtable.h new file mode 100644 index 0000000..aac0e94 --- /dev/null +++ b/src/misc/hashtable.h @@ -0,0 +1,147 @@ +#ifndef HASHTABLE_H +#define HASHTABLE_H + +#define TABLE_HASH_FUNC(name) unsigned long name(void *key) +typedef TABLE_HASH_FUNC(table_hash_func); + +#define TABLE_COMPARE_FUNC(name) int name(void *key_a, void *key_b) +typedef TABLE_COMPARE_FUNC(table_compare_func); + +struct bucket +{ + void *key; + void *value; + struct bucket *next; +}; +struct table +{ + int count; + int capacity; + float max_load; + table_hash_func *hash; + table_compare_func *cmp; + struct bucket **buckets; +}; + +void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func cmp); +void table_free(struct table *table); + +#define table_add(table, key, value) _table_add(table, key, sizeof(*key), value) +void _table_add(struct table *table, void *key, int key_size, void *value); +void table_remove(struct table *table, void *key); +void *table_find(struct table *table, void *key); + +#endif + +#ifdef HASHTABLE_IMPLEMENTATION +void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func cmp) +{ + table->count = 0; + table->capacity = capacity; + table->max_load = 0.75f; + table->hash = hash; + table->cmp = cmp; + table->buckets = malloc(sizeof(struct bucket *) * capacity); + memset(table->buckets, 0, sizeof(struct bucket *) * capacity); +} + +void table_free(struct table *table) +{ + for (int i = 0; i < table->capacity; ++i) { + struct bucket *next, *bucket = table->buckets[i]; + while (bucket) { + next = bucket->next; + free(bucket->key); + free(bucket); + bucket = next; + } + } + + if (table->buckets) { + free(table->buckets); + table->buckets = NULL; + } +} + +static struct bucket ** +table_get_bucket(struct table *table, void *key) +{ + struct bucket **bucket = table->buckets + (table->hash(key) % table->capacity); + while (*bucket) { + if (table->cmp((*bucket)->key, key)) { + break; + } + bucket = &(*bucket)->next; + } + return bucket; +} + +static void +table_rehash(struct table *table) +{ + struct bucket **old_buckets = table->buckets; + int old_capacity = table->capacity; + + table->count = 0; + table->capacity = 2 * table->capacity; + table->buckets = malloc(sizeof(struct bucket *) * table->capacity); + memset(table->buckets, 0, sizeof(struct bucket *) * table->capacity); + + for (int i = 0; i < old_capacity; ++i) { + struct bucket *next_bucket, *old_bucket = old_buckets[i]; + while (old_bucket) { + struct bucket **new_bucket = table_get_bucket(table, old_bucket->key); + *new_bucket = malloc(sizeof(struct bucket)); + (*new_bucket)->key = old_bucket->key; + (*new_bucket)->value = old_bucket->value; + (*new_bucket)->next = NULL; + ++table->count; + next_bucket = old_bucket->next; + free(old_bucket); + old_bucket = next_bucket; + } + } + + free(old_buckets); +} + +void _table_add(struct table *table, void *key, int key_size, void *value) +{ + struct bucket **bucket = table_get_bucket(table, key); + if (*bucket) { + if (!(*bucket)->value) { + (*bucket)->value = value; + } + } else { + *bucket = malloc(sizeof(struct bucket)); + (*bucket)->key = malloc(key_size); + (*bucket)->value = value; + memcpy((*bucket)->key, key, key_size); + (*bucket)->next = NULL; + ++table->count; + + float load = (1.0f * table->count) / table->capacity; + if (load > table->max_load) { + table_rehash(table); + } + } +} + +void table_remove(struct table *table, void *key) +{ + struct bucket *next, **bucket = table_get_bucket(table, key); + if (*bucket) { + free((*bucket)->key); + next = (*bucket)->next; + free(*bucket); + *bucket = next; + --table->count; + } +} + +void *table_find(struct table *table, void *key) +{ + struct bucket *bucket = *table_get_bucket(table, key); + return bucket ? bucket->value : NULL; +} +#endif diff --git a/src/misc/helpers.h b/src/misc/helpers.h new file mode 100644 index 0000000..cae409f --- /dev/null +++ b/src/misc/helpers.h @@ -0,0 +1,185 @@ +#ifndef HELPERS_H +#define HELPERS_H + +extern AXError _AXUIElementGetWindow(AXUIElementRef ref, uint32_t *wid); + +static const char *bool_str[] = { "off", "on" }; + +struct signal_args +{ + char name[2][255]; + char value[2][255]; + void *entity; + void *param1; +}; + +struct rgba_color +{ + bool is_valid; + uint32_t p; + float r; + float g; + float b; + float a; +}; + +static struct rgba_color +rgba_color_from_hex(uint32_t color) +{ + struct rgba_color result; + result.is_valid = true; + result.p = color; + result.r = ((color >> 16) & 0xff) / 255.0; + result.g = ((color >> 8) & 0xff) / 255.0; + result.b = ((color >> 0) & 0xff) / 255.0; + result.a = ((color >> 24) & 0xff) / 255.0; + return result; +} + +static inline bool is_root(void) +{ + return getuid() == 0 || geteuid() == 0; +} + +static inline bool string_equals(const char *a, const char *b) +{ + return a && b && strcmp(a, b) == 0; +} + +static inline char *string_escape_quote(char *s) +{ + if (!s) return NULL; + + char *cursor = s; + int num_quotes = 0; + + while (*cursor) { + if (*cursor == '"') ++num_quotes; + ++cursor; + } + + if (!num_quotes) return NULL; + + int size_in_bytes = (int)(cursor - s) + num_quotes; + char *result = malloc(sizeof(char) * (size_in_bytes+1)); + result[size_in_bytes] = '\0'; + + for (char *dst = result, *cursor = s; *cursor; ++cursor) { + if (*cursor == '"') *dst++ = '\\'; + *dst++ = *cursor; + } + + return result; +} + +static inline char *cfstring_copy(CFStringRef string) +{ + CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8); + char *result = malloc(num_bytes + 1); + if (!result) return NULL; + + if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) { + free(result); + result = NULL; + } + + return result; +} + +static inline char *string_copy(char *s) +{ + int length = strlen(s); + char *result = malloc(length + 1); + if (!result) return NULL; + + memcpy(result, s, length); + result[length] = '\0'; + return result; +} + +static inline bool file_exists(char *filename) +{ + struct stat buffer; + + if (stat(filename, &buffer) != 0) { + return false; + } + + if (buffer.st_mode & S_IFDIR) { + return false; + } + + return true; +} + +static inline bool ensure_executable_permission(char *filename) +{ + struct stat buffer; + + if (stat(filename, &buffer) != 0) { + return false; + } + + bool is_executable = buffer.st_mode & S_IXUSR; + if (!is_executable && chmod(filename, S_IXUSR | buffer.st_mode) != 0) { + return false; + } + + return true; +} + +static bool fork_exec(char *command, struct signal_args *args) +{ + int pid = fork(); + if (pid == -1) return false; + if (pid != 0) return true; + + if (args) { + if (*args->name[0]) setenv(args->name[0], args->value[0], 1); + if (*args->name[1]) setenv(args->name[1], args->value[1], 1); + } + + char *exec[] = { "/usr/bin/env", "sh", "-c", command, NULL}; + exit(execvp(exec[0], exec)); +} + +static bool ax_privilege(void) +{ + const void *keys[] = { kAXTrustedCheckOptionPrompt }; + const void *values[] = { kCFBooleanTrue }; + CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + bool result = AXIsProcessTrustedWithOptions(options); + CFRelease(options); + return result; +} + +static inline uint32_t ax_window_id(AXUIElementRef ref) +{ + uint32_t wid = 0; + _AXUIElementGetWindow(ref, &wid); + return wid; +} + +static inline pid_t ax_window_pid(AXUIElementRef ref) +{ + return *(pid_t *)((void *) ref + 0x10); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +static inline bool psn_equals(ProcessSerialNumber *a, ProcessSerialNumber *b) +{ + Boolean result; + SameProcess(a, b, &result); + return result == 1; +} +#pragma clang diagnostic pop + +static inline float clampf_range(float value, float min, float max) +{ + if (value < min) return min; + if (value > max) return max; + return value; +} + +#endif diff --git a/src/misc/log.h b/src/misc/log.h new file mode 100644 index 0000000..6546dd6 --- /dev/null +++ b/src/misc/log.h @@ -0,0 +1,36 @@ +#ifndef LOG_H +#define LOG_H + +extern bool g_verbose; + +static inline void +debug(const char *format, ...) +{ + if (!g_verbose) return; + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); +} + +static inline void +warn(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +static inline void +error(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(EXIT_FAILURE); +} + +#endif diff --git a/src/misc/macros.h b/src/misc/macros.h new file mode 100644 index 0000000..f81e90e --- /dev/null +++ b/src/misc/macros.h @@ -0,0 +1,33 @@ +#ifndef MACROS_H +#define MACROS_H + +#define array_count(a) (sizeof((a)) / sizeof(*(a))) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define add_and_clamp_to_zero(a, b) (((a) + (b) <= 0) ? 0 : (a) + (b)) + +#define MAXLEN 512 + +#define REGEX_MATCH_UD 0 +#define REGEX_MATCH_YES 1 +#define REGEX_MATCH_NO 2 + +#define DIR_NORTH 360 +#define DIR_EAST 90 +#define DIR_SOUTH 180 +#define DIR_WEST 270 + +#define TYPE_ABS 0x1 +#define TYPE_REL 0x2 + +#define HANDLE_TOP 0x01 +#define HANDLE_BOTTOM 0x02 +#define HANDLE_LEFT 0x04 +#define HANDLE_RIGHT 0x08 +#define HANDLE_ABS 0x10 + +#define LAYER_BELOW kCGBackstopMenuLevelKey +#define LAYER_NORMAL kCGNormalWindowLevelKey +#define LAYER_ABOVE kCGFloatingWindowLevelKey + +#endif diff --git a/src/misc/memory_pool.h b/src/misc/memory_pool.h new file mode 100644 index 0000000..8e59e0e --- /dev/null +++ b/src/misc/memory_pool.h @@ -0,0 +1,42 @@ +#ifndef MEMORY_POOL_H +#define MEMORY_POOL_H + +#define KILOBYTES(value) ((value) * 1024ULL) +#define MEGABYTES(value) (KILOBYTES(value) * 1024ULL) +#define GIGABYTES(value) (MEGABYTES(value) * 1024ULL) + +struct memory_pool +{ + void *memory; + uint64_t size; + volatile uint64_t used; +}; + +bool memory_pool_init(struct memory_pool *pool, uint64_t size) +{ + pool->used = 0; + pool->size = size; + pool->memory = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + return pool->memory != NULL; +} + +#define memory_pool_push(p, t) memory_pool_push_size(p, sizeof(t)) +void *memory_pool_push_size(struct memory_pool *pool, uint64_t size) +{ + for (;;) { + uint64_t used = pool->used; + uint64_t new_used = used + size; + + if (new_used < pool->size) { + if (__sync_bool_compare_and_swap(&pool->used, used, new_used)) { + return pool->memory + used; + } + } else { + if (__sync_bool_compare_and_swap(&pool->used, used, size)) { + return pool->memory; + } + } + } +} + +#endif diff --git a/src/misc/notify.h b/src/misc/notify.h new file mode 100644 index 0000000..67ba726 --- /dev/null +++ b/src/misc/notify.h @@ -0,0 +1,47 @@ +#import +#import + +static bool g_notify_init; +static NSImage *g_notify_img; + +@implementation NSBundle(swizzle) +- (NSString *)fake_bundleIdentifier +{ + if (self == [NSBundle mainBundle]) { + return @"com.somdoron.spacebar"; + } else { + return [self fake_bundleIdentifier]; + } +} +@end + +static bool notify_init(void) +{ + Class c = objc_getClass("NSBundle"); + if (!c) return false; + + method_exchangeImplementations(class_getInstanceMethod(c, @selector(bundleIdentifier)), class_getInstanceMethod(c, @selector(fake_bundleIdentifier))); + g_notify_img = [[[NSWorkspace sharedWorkspace] iconForFile:[[[NSBundle mainBundle] executablePath] stringByResolvingSymlinksInPath]] retain]; + g_notify_init = true; + + return true; +} + +static void notify(const char *subtitle, const char *format, ...) +{ + @autoreleasepool { + if (!g_notify_init) notify_init(); + + va_list args; + va_start(args, format); + NSUserNotification *notification = [[NSUserNotification alloc] init]; + notification.title = @"spacebar"; + notification.subtitle = [NSString stringWithUTF8String:subtitle]; + notification.informativeText = [[[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:args] autorelease]; + [notification setValue:g_notify_img forKey:@"_identityImage"]; + [notification setValue:@(false) forKey:@"_identityImageHasBorder"]; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + [notification release]; + va_end(args); + } +} diff --git a/src/misc/sbuffer.h b/src/misc/sbuffer.h new file mode 100644 index 0000000..3e06e95 --- /dev/null +++ b/src/misc/sbuffer.h @@ -0,0 +1,36 @@ +#ifndef SBUFFER_H +#define SBUFFER_H + +struct buf_hdr +{ + size_t len; + size_t cap; + char buf[0]; +}; + +#define OFFSETOF(t, f) (size_t)((char *)&(((t *)0)->f) - (char *)0) + +#define buf__hdr(b) ((struct buf_hdr *)((char *)(b) - OFFSETOF(struct buf_hdr, buf))) +#define buf__should_grow(b, n) (buf_len(b) + (n) >= buf_cap(b)) +#define buf__fit(b, n) (buf__should_grow(b, n) ? ((b) = buf__grow_f(b, buf_len(b) + (n), sizeof(*(b)))) : 0) + +#define buf_len(b) ((b) ? buf__hdr(b)->len : 0) +#define buf_cap(b) ((b) ? buf__hdr(b)->cap : 0) +#define buf_last(b) ((b)[buf_len(b)-1]) +#define buf_push(b, x) (buf__fit(b, 1), (b)[buf_len(b)] = (x), buf__hdr(b)->len++) +#define buf_del(b, x) ((b) ? (b)[x] = (b)[buf_len(b)-1], buf__hdr(b)->len-- : 0) +#define buf_free(b) ((b) ? free(buf__hdr(b)) : 0) + +static void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size) +{ + size_t new_cap = max(1 + 2*buf_cap(buf), new_len); + size_t new_size = OFFSETOF(struct buf_hdr, buf) + new_cap*elem_size; + struct buf_hdr *new_hdr = realloc(buf ? buf__hdr(buf) : 0, new_size); + new_hdr->cap = new_cap; + if (!buf) { + new_hdr->len = 0; + } + return new_hdr->buf; +} + +#endif diff --git a/src/misc/socket.c b/src/misc/socket.c new file mode 100644 index 0000000..3c9e521 --- /dev/null +++ b/src/misc/socket.c @@ -0,0 +1,175 @@ +#include "socket.h" + +char *socket_read(int sockfd, int *len) +{ + int cursor = 0; + int bytes_read = 0; + char *result = NULL; + char buffer[BUFSIZ]; + + while ((bytes_read = read(sockfd, buffer, sizeof(buffer)-1)) > 0) { + char *temp = realloc(result, cursor+bytes_read+1); + if (!temp) goto err; + + result = temp; + memcpy(result+cursor, buffer, bytes_read); + cursor += bytes_read; + } + + if (result && bytes_read != -1) { + result[cursor] = '\0'; + *len = cursor; + } else { +err: + if (result) free(result); + result = NULL; + *len = 0; + } + + return result; +} + +bool socket_write_bytes(int sockfd, char *message, int len) +{ + return send(sockfd, message, len, 0) != -1; +} + +bool socket_write(int sockfd, char *message) +{ + return send(sockfd, message, strlen(message), 0) != -1; +} + +bool socket_connect_in(int *sockfd, int port) +{ + struct sockaddr_in socket_address; + + *sockfd = socket(PF_INET, SOCK_STREAM, 0); + if (*sockfd == -1) return false; + + socket_address.sin_family = AF_INET; + socket_address.sin_port = htons(port); + socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memset(&socket_address.sin_zero, '\0', 8); + + return connect(*sockfd, (struct sockaddr*) &socket_address, sizeof(struct sockaddr)) != -1; +} + +bool socket_connect_un(int *sockfd, char *socket_path) +{ + struct sockaddr_un socket_address; + socket_address.sun_family = AF_UNIX; + + *sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (*sockfd == -1) return false; + + snprintf(socket_address.sun_path, sizeof(socket_address.sun_path), "%s", socket_path); + return connect(*sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) != -1; +} + +void socket_wait(int sockfd) +{ + struct pollfd fds[] = { + { sockfd, POLLIN, 0 } + }; + + char dummy[1]; + int bytes = 0; + + while (poll(fds, 1, -1) > 0) { + if (fds[0].revents & POLLIN) { + if ((bytes = recv(sockfd, dummy, 0, 0)) <= 0) { + break; + } + } + } +} + +void socket_close(int sockfd) +{ + shutdown(sockfd, SHUT_RDWR); + close(sockfd); +} + +static void *socket_connection_handler(void *context) +{ + struct daemon *daemon = context; + + while (daemon->is_running) { + int sockfd = accept(daemon->sockfd, NULL, 0); + if (sockfd == -1) continue; + + int length; + char *message = socket_read(sockfd, &length); + if (message) { + daemon->handler(message, length, sockfd); + } else { + socket_close(sockfd); + } + } + + return NULL; +} + +bool socket_daemon_begin_in(struct daemon *daemon, int port, socket_daemon_handler *handler) +{ + struct sockaddr_in socket_address; + socket_address.sin_family = AF_INET; + socket_address.sin_port = htons(port); + socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memset(&socket_address.sin_zero, '\0', 8); + + if ((daemon->sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + return false; + } + + if (bind(daemon->sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) == -1) { + return false; + } + + if (listen(daemon->sockfd, SOMAXCONN) == -1) { + return false; + } + + daemon->handler = handler; + daemon->is_running = true; + pthread_create(&daemon->thread, NULL, &socket_connection_handler, daemon); + + return true; +} + +bool socket_daemon_begin_un(struct daemon *daemon, char *socket_path, socket_daemon_handler *handler) +{ + struct sockaddr_un socket_address; + socket_address.sun_family = AF_UNIX; + snprintf(socket_address.sun_path, sizeof(socket_address.sun_path), "%s", socket_path); + unlink(socket_path); + + if ((daemon->sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + return false; + } + + if (bind(daemon->sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) == -1) { + return false; + } + + if (chmod(socket_path, 0600) != 0) { + return false; + } + + if (listen(daemon->sockfd, SOMAXCONN) == -1) { + return false; + } + + daemon->handler = handler; + daemon->is_running = true; + pthread_create(&daemon->thread, NULL, &socket_connection_handler, daemon); + + return true; +} + +void socket_daemon_end(struct daemon *daemon) +{ + daemon->is_running = false; + pthread_join(daemon->thread, NULL); + socket_close(daemon->sockfd); +} diff --git a/src/misc/socket.h b/src/misc/socket.h new file mode 100644 index 0000000..cf459ce --- /dev/null +++ b/src/misc/socket.h @@ -0,0 +1,39 @@ +#ifndef SOCKET_H +#define SOCKET_H + +#define SOCKET_DAEMON_HANDLER(name) void name(char *message, int length, int sockfd) +typedef SOCKET_DAEMON_HANDLER(socket_daemon_handler); + +#define FAILURE_MESSAGE "\x07" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct daemon +{ + int sockfd; + bool is_running; + pthread_t thread; + socket_daemon_handler *handler; +}; + +char *socket_read(int sockfd, int *len); +bool socket_write_bytes(int sockfd, char *message, int len); +bool socket_write(int sockfd, char *message); +bool socket_connect_in(int *sockfd, int port); +bool socket_connect_un(int *sockfd, char *socket_path); +void socket_wait(int sockfd); +void socket_close(int sockfd); +bool socket_daemon_begin_in(struct daemon *daemon, int port, socket_daemon_handler *handler); +bool socket_daemon_begin_un(struct daemon *daemon, char *socket_path, socket_daemon_handler *handler); +void socket_daemon_end(struct daemon *daemon); + +#endif diff --git a/src/misc/timing.h b/src/misc/timing.h new file mode 100644 index 0000000..4c2bf41 --- /dev/null +++ b/src/misc/timing.h @@ -0,0 +1,29 @@ +#include +#include + +static inline uint64_t time_clock(void) +{ + return mach_absolute_time(); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +static inline uint64_t time_elapsed_ns(uint64_t begin, uint64_t end) +{ + uint64_t elapsed = end - begin; + Nanoseconds nano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed); + return *(uint64_t *) &nano; +} +#pragma clang diagnostic pop + +static inline double time_elapsed_ms(uint64_t begin, uint64_t end) +{ + uint64_t ns = time_elapsed_ns(begin, end); + return (double)(ns / 1000000.0); +} + +static inline double time_elapsed_s(uint64_t begin, uint64_t end) +{ + uint64_t ns = time_elapsed_ns(begin, end); + return (double)(ns / 1000000000.0); +} diff --git a/src/process_manager.c b/src/process_manager.c new file mode 100644 index 0000000..b8fe11b --- /dev/null +++ b/src/process_manager.c @@ -0,0 +1,222 @@ +#include "process_manager.h" + +extern struct event_loop g_event_loop; + +static TABLE_HASH_FUNC(hash_psn) +{ + unsigned long result = ((ProcessSerialNumber*) key)->lowLongOfPSN; + result = (result + 0x7ed55d16) + (result << 12); + result = (result ^ 0xc761c23c) ^ (result >> 19); + result = (result + 0x165667b1) + (result << 5); + result = (result + 0xd3a2646c) ^ (result << 9); + result = (result + 0xfd7046c5) + (result << 3); + result = (result ^ 0xb55a4f09) ^ (result >> 16); + return result; +} + +static TABLE_COMPARE_FUNC(compare_psn) +{ + return psn_equals(key_a, key_b); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +struct process *process_create(ProcessSerialNumber psn) +{ + struct process *process = malloc(sizeof(struct process)); + memset(process, 0, sizeof(struct process)); + + CFStringRef process_name_ref; + if (CopyProcessName(&psn, &process_name_ref) == noErr) { + process->name = cfstring_copy(process_name_ref); + CFRelease(process_name_ref); + } else { + process->name = string_copy(""); + } + + ProcessInfoRec process_info = {}; + process_info.processInfoLength = sizeof(ProcessInfoRec); + GetProcessInformation(&psn, &process_info); + + process->psn = psn; + GetProcessPID(&process->psn, &process->pid); + process->background = (process_info.processMode & modeOnlyBackground) != 0; + process->xpc = process_info.processType == 'XPC!'; + + CFDictionaryRef process_dict = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask); + if (process_dict) { + CFBooleanRef process_lsuielement = CFDictionaryGetValue(process_dict, CFSTR("LSUIElement")); + if (process_lsuielement) process->lsuielement = CFBooleanGetValue(process_lsuielement); + CFBooleanRef process_lsbackground = CFDictionaryGetValue(process_dict, CFSTR("LSBackgroundOnly")); + if (process_lsbackground) process->lsbackground = CFBooleanGetValue(process_lsbackground); + CFRelease(process_dict); + } + + return process; +} + +void process_destroy(struct process *process) +{ + free(process->name); + free(process); +} + +static bool process_is_observable(struct process *process) +{ + if (process->lsbackground) { + debug("%s: %s was marked as background only! ignoring..\n", __FUNCTION__, process->name); + return false; + } + + if (process->lsuielement) { + debug("%s: %s was marked as agent! ignoring..\n", __FUNCTION__, process->name); + return false; + } + + if (process->background) { + debug("%s: %s was marked as daemon! ignoring..\n", __FUNCTION__, process->name); + return false; + } + + if (process->xpc) { + debug("%s: %s was marked as xpc service! ignoring..\n", __FUNCTION__, process->name); + return false; + } + + return true; +} + +static PROCESS_EVENT_HANDLER(process_handler) +{ + struct process_manager *pm = (struct process_manager *) user_data; + + ProcessSerialNumber psn; + if (GetEventParameter(event, kEventParamProcessID, typeProcessSerialNumber, NULL, sizeof(psn), NULL, &psn) != noErr) { + return -1; + } + + switch (GetEventKind(event)) { + case kEventAppLaunched: { + struct process *process = process_create(psn); + if (!process) return noErr; + + if (process_is_observable(process)) { + struct event *event = event_create(&g_event_loop, APPLICATION_LAUNCHED, process); + event_loop_post(&g_event_loop, event); + process_manager_add_process(pm, process); + } else { + process_destroy(process); + } + } break; + case kEventAppTerminated: { + struct process *process = process_manager_find_process(pm, &psn); + if (!process) return noErr; + + process->terminated = true; + process_manager_remove_process(pm, &psn); + + struct event *event = event_create(&g_event_loop, APPLICATION_TERMINATED, process); + event_loop_post(&g_event_loop, event); + } break; + case kEventAppFrontSwitched: { + struct process *process = process_manager_find_process(pm, &psn); + if (!process) return noErr; + + struct event *event = event_create(&g_event_loop, APPLICATION_FRONT_SWITCHED, process); + event_loop_post(&g_event_loop, event); + } break; + } + + return noErr; +} + +static void +process_manager_add_running_processes(struct process_manager *pm) +{ + ProcessSerialNumber psn = { kNoProcess, kNoProcess }; + while (GetNextProcess(&psn) == noErr) { + struct process *process = process_create(psn); + if (!process) continue; + + if (process_is_observable(process)) { + if (string_equals(process->name, "Finder")) { + debug("%s: %s was found! caching psn..\n", __FUNCTION__, process->name); + pm->finder_psn = psn; + } + + process_manager_add_process(pm, process); + } else { + process_destroy(process); + } + } +} +#pragma clang diagnostic pop + +struct process *process_manager_find_process(struct process_manager *pm, ProcessSerialNumber *psn) +{ + return table_find(&pm->process, psn); +} + +void process_manager_remove_process(struct process_manager *pm, ProcessSerialNumber *psn) +{ + table_remove(&pm->process, psn); +} + +void process_manager_add_process(struct process_manager *pm, struct process *process) +{ + table_add(&pm->process, &process->psn, process); +} + +#if 0 +bool process_manager_next_process(ProcessSerialNumber *next_psn) +{ + CFArrayRef applications =_LSCopyApplicationArrayInFrontToBackOrder(0xFFFFFFFE, 1); + if (!applications) return false; + + bool found_front_psn = false; + ProcessSerialNumber front_psn; + _SLPSGetFrontProcess(&front_psn); + + for (int i = 0; i < CFArrayGetCount(applications); ++i) { + CFTypeRef asn = CFArrayGetValueAtIndex(applications, i); + assert(CFGetTypeID(asn) == _LSASNGetTypeID()); + _LSASNExtractHighAndLowParts(asn, &next_psn->highLongOfPSN, &next_psn->lowLongOfPSN); + if (found_front_psn) break; + found_front_psn = psn_equals(&front_psn, next_psn); + } + + CFRelease(applications); + return true; +} +#endif + +void process_manager_init(struct process_manager *pm) +{ + pm->target = GetApplicationEventTarget(); + pm->handler = NewEventHandlerUPP(process_handler); + pm->type[0].eventClass = kEventClassApplication; + pm->type[0].eventKind = kEventAppLaunched; + pm->type[1].eventClass = kEventClassApplication; + pm->type[1].eventKind = kEventAppTerminated; + pm->type[2].eventClass = kEventClassApplication; + pm->type[2].eventKind = kEventAppFrontSwitched; + table_init(&pm->process, 125, hash_psn, compare_psn); + process_manager_add_running_processes(pm); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +bool process_manager_begin(struct process_manager *pm) +{ + ProcessSerialNumber front_psn; + _SLPSGetFrontProcess(&front_psn); + GetProcessPID(&front_psn, &g_process_manager.front_pid); + g_process_manager.last_front_pid = g_process_manager.front_pid; + return InstallEventHandler(pm->target, pm->handler, 3, pm->type, pm, &pm->ref) == noErr; +} +#pragma clang diagnostic pop + +bool process_manager_end(struct process_manager *pm) +{ + return RemoveEventHandler(pm->ref) == noErr; +} diff --git a/src/process_manager.h b/src/process_manager.h new file mode 100644 index 0000000..17e3e1b --- /dev/null +++ b/src/process_manager.h @@ -0,0 +1,51 @@ +#ifndef PROCESS_MANAGER_H +#define PROCESS_MANAGER_H + +extern OSStatus _SLPSGetFrontProcess(ProcessSerialNumber *psn); +extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect); +extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point); + +#if 0 +extern CFArrayRef _LSCopyApplicationArrayInFrontToBackOrder(int negative_one, int one); +extern void _LSASNExtractHighAndLowParts(const void *asn, uint32_t *high, uint32_t *low); +extern CFTypeID _LSASNGetTypeID(void); +#endif + +#define PROCESS_EVENT_HANDLER(name) OSStatus name(EventHandlerCallRef ref, EventRef event, void *user_data) +typedef PROCESS_EVENT_HANDLER(process_event_handler); + +struct process +{ + ProcessSerialNumber psn; + pid_t pid; + char *name; + bool background; + bool lsuielement; + bool lsbackground; + bool xpc; + bool volatile terminated; +}; + +struct process_manager +{ + struct table process; + EventTargetRef target; + EventHandlerUPP handler; + EventTypeSpec type[3]; + EventHandlerRef ref; + pid_t front_pid; + pid_t last_front_pid; + ProcessSerialNumber finder_psn; +}; + +void process_destroy(struct process *process); +struct process *process_create(ProcessSerialNumber psn); +struct process *process_manager_find_process(struct process_manager *pm, ProcessSerialNumber *psn); +void process_manager_remove_process(struct process_manager *pm, ProcessSerialNumber *psn); +void process_manager_add_process(struct process_manager *pm, struct process *process); +// bool process_manager_next_process(ProcessSerialNumber *next_psn); +void process_manager_init(struct process_manager *pm); +bool process_manager_begin(struct process_manager *pm); +bool process_manager_end(struct process_manager *pm); + +#endif diff --git a/src/spacebar.c b/src/spacebar.c new file mode 100644 index 0000000..d9dce4a --- /dev/null +++ b/src/spacebar.c @@ -0,0 +1,268 @@ +#define SOCKET_PATH_FMT "/tmp/spacebar_%s.socket" +#define LCFILE_PATH_FMT "/tmp/spacebar_%s.lock" + +#define CLIENT_OPT_LONG "--message" +#define CLIENT_OPT_SHRT "-m" + +#define DEBUG_VERBOSE_OPT_LONG "--verbose" +#define DEBUG_VERBOSE_OPT_SHRT "-V" +#define VERSION_OPT_LONG "--version" +#define VERSION_OPT_SHRT "-v" +#define CONFIG_OPT_LONG "--config" +#define CONFIG_OPT_SHRT "-c" + +#define MAJOR 2 +#define MINOR 4 +#define PATCH 1 + +extern int SLSMainConnectionID(void); + +#define CONNECTION_CALLBACK(name) void name(uint32_t type, void *data, size_t data_length, void *context, int cid) +typedef CONNECTION_CALLBACK(connection_callback); +extern CGError SLSRegisterConnectionNotifyProc(int cid, connection_callback *handler, uint32_t event, void *context); + +struct event_loop g_event_loop; +void *g_workspace_context; +struct process_manager g_process_manager; +struct display_manager g_display_manager; +struct application_manager g_application_manager; +struct daemon g_daemon; +struct bar_manager g_bar_manager; +int g_connection; + +char g_socket_file[MAXLEN]; +char g_config_file[4096]; +char g_lock_file[MAXLEN]; +bool g_verbose; + +static int client_send_message(int argc, char **argv) +{ + if (argc <= 1) { + error("spacebar-msg: no arguments given! abort..\n"); + } + + char *user = getenv("USER"); + if (!user) { + error("spacebar-msg: 'env USER' not set! abort..\n"); + } + + int sockfd; + char socket_file[MAXLEN]; + snprintf(socket_file, sizeof(socket_file), SOCKET_PATH_FMT, user); + + if (!socket_connect_un(&sockfd, socket_file)) { + error("spacebar-msg: failed to connect to socket..\n"); + } + + int message_length = argc - 1; + int argl[argc]; + + for (int i = 1; i < argc; ++i) { + argl[i] = strlen(argv[i]); + message_length += argl[i]; + } + + char message[message_length]; + char *temp = message; + + for (int i = 1; i < argc; ++i) { + memcpy(temp, argv[i], argl[i]); + temp += argl[i]; + *temp++ = '\0'; + } + + if (!socket_write_bytes(sockfd, message, message_length)) { + error("spacebar-msg: failed to send data..\n"); + } + + shutdown(sockfd, SHUT_WR); + + int result = EXIT_SUCCESS; + int byte_count = 0; + char rsp[BUFSIZ]; + + struct pollfd fds[] = { + { sockfd, POLLIN, 0 } + }; + + while (poll(fds, 1, -1) > 0) { + if (fds[0].revents & POLLIN) { + if ((byte_count = recv(sockfd, rsp, sizeof(rsp)-1, 0)) <= 0) { + break; + } + + rsp[byte_count] = '\0'; + + if (rsp[0] == FAILURE_MESSAGE[0]) { + result = EXIT_FAILURE; + fprintf(stderr, "%s", rsp + 1); + fflush(stderr); + } else { + fprintf(stdout, "%s", rsp); + fflush(stdout); + } + } + } + + socket_close(sockfd); + return result; +} + +static void acquire_lockfile(void) +{ + int handle = open(g_lock_file, O_CREAT | O_WRONLY, 0600); + if (handle == -1) { + error("spacebar: could not create lock-file! abort..\n"); + } + + struct flock lockfd = { + .l_start = 0, + .l_len = 0, + .l_pid = getpid(), + .l_type = F_WRLCK, + .l_whence = SEEK_SET + }; + + if (fcntl(handle, F_SETLK, &lockfd) == -1) { + error("spacebar: could not acquire lock-file! abort..\n"); + } +} + +static bool get_config_file(char *restrict filename, char *restrict buffer, int buffer_size) +{ + char *xdg_home = getenv("XDG_CONFIG_HOME"); + if (xdg_home && *xdg_home) { + snprintf(buffer, buffer_size, "%s/spacebar/%s", xdg_home, filename); + if (file_exists(buffer)) return true; + } + + char *home = getenv("HOME"); + if (!home) return false; + + snprintf(buffer, buffer_size, "%s/.config/spacebar/%s", home, filename); + if (file_exists(buffer)) return true; + + snprintf(buffer, buffer_size, "%s/.%s", home, filename); + return file_exists(buffer); +} + +static void exec_config_file(void) +{ + if (!*g_config_file && !get_config_file("spacebarrc", g_config_file, sizeof(g_config_file))) { + notify("configuration", "could not locate config file.."); + return; + } + + if (!file_exists(g_config_file)) { + notify("configuration", "file '%s' does not exist..", g_config_file); + return; + } + + if (!ensure_executable_permission(g_config_file)) { + notify("configuration", "could not set the executable permission bit for '%s'", g_config_file); + return; + } + + if (!fork_exec(g_config_file, NULL)) { + notify("configuration", "failed to execute file '%s'", g_config_file); + return; + } +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +static inline void init_misc_settings(void) +{ + char *user = getenv("USER"); + if (!user) { + error("spacebar: 'env USER' not set! abort..\n"); + } + + snprintf(g_socket_file, sizeof(g_socket_file), SOCKET_PATH_FMT, user); + snprintf(g_lock_file, sizeof(g_lock_file), LCFILE_PATH_FMT, user); + + NSApplicationLoad(); + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + CGSetLocalEventsSuppressionInterval(0.0f); + CGEnableEventStateCombining(false); + g_connection = SLSMainConnectionID(); +} +#pragma clang diagnostic pop + +static CONNECTION_CALLBACK(connection_handler) +{ +} + +static void parse_arguments(int argc, char **argv) +{ + if ((string_equals(argv[1], VERSION_OPT_LONG)) || + (string_equals(argv[1], VERSION_OPT_SHRT))) { + fprintf(stdout, "spacebar-v%d.%d.%d\n", MAJOR, MINOR, PATCH); + exit(EXIT_SUCCESS); + } + + if ((string_equals(argv[1], CLIENT_OPT_LONG)) || + (string_equals(argv[1], CLIENT_OPT_SHRT))) { + exit(client_send_message(argc-1, argv+1)); + } + + for (int i = 1; i < argc; ++i) { + char *opt = argv[i]; + + if ((string_equals(opt, DEBUG_VERBOSE_OPT_LONG)) || + (string_equals(opt, DEBUG_VERBOSE_OPT_SHRT))) { + g_verbose = true; + } else if ((string_equals(opt, CONFIG_OPT_LONG)) || + (string_equals(opt, CONFIG_OPT_SHRT))) { + char *val = i < argc - 1 ? argv[++i] : NULL; + if (!val) error("spacebar: option '%s|%s' requires an argument!\n", CONFIG_OPT_LONG, CONFIG_OPT_SHRT); + snprintf(g_config_file, sizeof(g_config_file), "%s", val); + } else { + error("spacebar: '%s' is not a valid option!\n", opt); + } + } +} + +int main(int argc, char **argv) +{ + if (argc > 1) { + parse_arguments(argc, argv); + } + + if (is_root()) { + error("spacebar: running as root is not allowed! abort..\n"); + } + + if (!ax_privilege()) { + error("spacebar: could not access accessibility features! abort..\n"); + } + + init_misc_settings(); + acquire_lockfile(); + + if (!event_loop_init(&g_event_loop)) { + error("spacebar: could not initialize event_loop! abort..\n"); + } + + process_manager_init(&g_process_manager); + workspace_event_handler_init(&g_workspace_context); + application_manager_init(&g_application_manager); + bar_manager_init(&g_bar_manager); + + event_loop_begin(&g_event_loop); + display_manager_begin(&g_display_manager); + process_manager_begin(&g_process_manager); + workspace_event_handler_begin(&g_workspace_context); + application_manager_begin(&g_application_manager); + bar_manager_begin(&g_bar_manager); + SLSRegisterConnectionNotifyProc(g_connection, connection_handler, 1204, NULL); + + if (!socket_daemon_begin_un(&g_daemon, g_socket_file, message_handler)) { + error("spacebar: could not initialize daemon! abort..\n"); + } + + exec_config_file(); + CFRunLoopRun(); + return 0; +} diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..1097c5b --- /dev/null +++ b/src/window.c @@ -0,0 +1,440 @@ +#include "window.h" + +extern int g_connection; +extern struct window_manager g_window_manager; + +int g_normal_window_level; +int g_floating_window_level; + +static void +window_observe_notification(struct window *window, int notification) +{ + AXError result = AXObserverAddNotification(window->application->observer_ref, window->ref, ax_window_notification[notification], window->id_ptr); + if (result == kAXErrorSuccess || result == kAXErrorNotificationAlreadyRegistered) window->notification |= 1 << notification; +} + +static void +window_unobserve_notification(struct window *window, int notification) +{ + AXObserverRemoveNotification(window->application->observer_ref, window->ref, ax_window_notification[notification]); + window->notification &= ~(1 << notification); +} + +bool window_observe(struct window *window) +{ + for (int i = 0; i < array_count(ax_window_notification); ++i) { + window_observe_notification(window, i); + } + + return (window->notification & AX_WINDOW_ALL) == AX_WINDOW_ALL; +} + +void window_unobserve(struct window *window) +{ + for (int i = 0; i < array_count(ax_window_notification); ++i) { + if (!(window->notification & (1 << i))) continue; + window_unobserve_notification(window, i); + } +} + +CFStringRef window_display_uuid(struct window *window) +{ + CFStringRef uuid = SLSCopyManagedDisplayForWindow(g_connection, window->id); + if (!uuid) { + CGRect frame = window_frame(window); + uuid = SLSCopyBestManagedDisplayForRect(g_connection, frame); + } + return uuid; +} + +int window_display_id(struct window *window) +{ + CFStringRef uuid_string = window_display_uuid(window); + if (!uuid_string) return 0; + + CFUUIDRef uuid = CFUUIDCreateFromString(NULL, uuid_string); + int id = CGDisplayGetDisplayIDFromUUID(uuid); + + CFRelease(uuid); + CFRelease(uuid_string); + + return id; +} + +uint64_t window_space(struct window *window) +{ + uint64_t sid = 0; + CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type); + CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref); + if (!space_list_ref) goto err; + + int count = CFArrayGetCount(space_list_ref); + if (count) { + CFNumberRef id_ref = CFArrayGetValueAtIndex(space_list_ref, 0); + CFNumberGetValue(id_ref, CFNumberGetType(id_ref), &sid); + } + + CFRelease(space_list_ref); +err: + CFRelease(window_list_ref); + return sid; +} + +uint64_t *window_space_list(struct window *window, int *count) +{ + uint64_t *space_list = NULL; + CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type); + CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref); + if (!space_list_ref) goto err; + + *count = CFArrayGetCount(space_list_ref); + if (!*count) goto out; + + space_list = malloc(*count * sizeof(uint64_t)); + for (int i = 0; i < *count; ++i) { + CFNumberRef id_ref = CFArrayGetValueAtIndex(space_list_ref, i); + CFNumberGetValue(id_ref, CFNumberGetType(id_ref), space_list + i); + } + +out: + CFRelease(space_list_ref); +err: + CFRelease(window_list_ref); + return space_list; +} + +void window_serialize(FILE *rsp, struct window *window) +{ + char *title = window_title(window); + char *escaped_title = string_escape_quote(title); + CGRect frame = window_frame(window); + char *role = NULL; + char *subrole = NULL; + bool sticky = window_is_sticky(window); + uint64_t sid = window_space(window); + int space = space_manager_mission_control_index(sid); + int display = display_arrangement(space_display_id(sid)); + bool visible = sticky || space_is_visible(sid); + bool is_topmost = window_is_topmost(window); + + CFStringRef cfrole = window_role(window); + if (cfrole) { + role = cfstring_copy(cfrole); + CFRelease(cfrole); + } + + CFStringRef cfsubrole = window_subrole(window); + if (cfsubrole) { + subrole = cfstring_copy(cfsubrole); + CFRelease(cfsubrole); + } + + struct view *view = window_manager_find_managed_window(&g_window_manager, window); + struct window_node *node = view ? view_find_window_node(view, window->id) : NULL; + + char split[MAXLEN]; + snprintf(split, sizeof(split), "%s", window_node_split_str[node && node->parent ? node->parent->split : 0]); + bool zoom_parent = node && node->zoom && node->zoom == node->parent; + bool zoom_fullscreen = node && node->zoom && node->zoom == view->root; + + fprintf(rsp, + "{\n" + "\t\"id\":%d,\n" + "\t\"pid\":%d,\n" + "\t\"app\":\"%s\",\n" + "\t\"title\":\"%s\",\n" + "\t\"frame\":{\n\t\t\"x\":%.4f,\n\t\t\"y\":%.4f,\n\t\t\"w\":%.4f,\n\t\t\"h\":%.4f\n\t},\n" + "\t\"level\":%d,\n" + "\t\"role\":\"%s\",\n" + "\t\"subrole\":\"%s\",\n" + "\t\"movable\":%d,\n" + "\t\"resizable\":%d,\n" + "\t\"display\":%d,\n" + "\t\"space\":%d,\n" + "\t\"visible\":%d,\n" + "\t\"focused\":%d,\n" + "\t\"split\":\"%s\",\n" + "\t\"floating\":%d,\n" + "\t\"sticky\":%d,\n" + "\t\"topmost\":%d,\n" + "\t\"border\":%d,\n" + "\t\"shadow\":%d,\n" + "\t\"zoom-parent\":%d,\n" + "\t\"zoom-fullscreen\":%d,\n" + "\t\"native-fullscreen\":%d\n" + "}", + window->id, + window->application->pid, + window->application->name, + escaped_title ? escaped_title : title ? title : "", + frame.origin.x, frame.origin.y, + frame.size.width, frame.size.height, + window_level(window), + role ? role : "", + subrole ? subrole : "", + window_can_move(window), + window_can_resize(window), + display, + space, + visible, + window->id == g_window_manager.focused_window_id, + split, + window->is_floating, + sticky, + is_topmost, + window->border.enabled, + window->has_shadow, + zoom_parent, + zoom_fullscreen, + window_is_fullscreen(window)); + + if (subrole) free(subrole); + if (role) free(role); + if (title) free(title); + if (escaped_title) free(escaped_title); +} + +char *window_title(struct window *window) +{ + char *title = NULL; + CFTypeRef value = NULL; + +#if 0 + SLSCopyWindowProperty(g_connection, window->id, CFSTR("kCGSWindowTitle"), &value); +#else + AXUIElementCopyAttributeValue(window->ref, kAXTitleAttribute, &value); +#endif + + if (value) { + title = cfstring_copy(value); + CFRelease(value); + } + + return title; +} + +CGRect window_ax_frame(struct window *window) +{ + CGRect frame = {}; + CFTypeRef position_ref = NULL; + CFTypeRef size_ref = NULL; + + AXUIElementCopyAttributeValue(window->ref, kAXPositionAttribute, &position_ref); + AXUIElementCopyAttributeValue(window->ref, kAXSizeAttribute, &size_ref); + + if (position_ref != NULL) { + AXValueGetValue(position_ref, kAXValueTypeCGPoint, &frame.origin); + CFRelease(position_ref); + } + + if (size_ref != NULL) { + AXValueGetValue(size_ref, kAXValueTypeCGSize, &frame.size); + CFRelease(size_ref); + } + + return frame; +} + +CGRect window_frame(struct window *window) +{ + CGRect frame = {}; + SLSGetWindowBounds(g_connection, window->id, &frame); + return frame; +} + +bool window_can_move(struct window *window) +{ + Boolean result; + if (AXUIElementIsAttributeSettable(window->ref, kAXPositionAttribute, &result) != kAXErrorSuccess) { + result = 0; + } + return result; +} + +bool window_can_resize(struct window *window) +{ + Boolean result; + if (AXUIElementIsAttributeSettable(window->ref, kAXSizeAttribute, &result) != kAXErrorSuccess) { + result = 0; + } + return result; +} + +bool window_is_undersized(struct window *window) +{ + CGRect frame = window_frame(window); + if (frame.size.width < 200.0f) return true; + if (frame.size.height < 200.0f) return true; + return false; +} + +bool window_is_minimized(struct window *window) +{ + Boolean result = 0; + CFTypeRef value; + + if (AXUIElementCopyAttributeValue(window->ref, kAXMinimizedAttribute, &value) == kAXErrorSuccess) { + result = CFBooleanGetValue(value); + CFRelease(value); + } + + return result || window->is_minimized; +} + +bool window_is_fullscreen(struct window *window) +{ + Boolean result = 0; + CFTypeRef value; + if (AXUIElementCopyAttributeValue(window->ref, kAXFullscreenAttribute, &value) == kAXErrorSuccess) { + result = CFBooleanGetValue(value); + CFRelease(value); + } + return result; +} + +bool window_is_sticky(struct window *window) +{ + bool result = false; + CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type); + CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref); + if (!space_list_ref) goto err; + + result = CFArrayGetCount(space_list_ref) > 1; + + CFRelease(space_list_ref); +err: + CFRelease(window_list_ref); + return result; +} + +bool window_is_topmost(struct window *window) +{ + bool is_topmost = window_level(window) == CGWindowLevelForKey(LAYER_ABOVE); + return is_topmost; +} + +int window_level(struct window *window) +{ + int level = 0; + SLSGetWindowLevel(g_connection, window->id, &level); + return level; +} + +CFStringRef window_role(struct window *window) +{ + const void *role = NULL; + AXUIElementCopyAttributeValue(window->ref, kAXRoleAttribute, &role); + return role; +} + +CFStringRef window_subrole(struct window *window) +{ + const void *srole = NULL; + AXUIElementCopyAttributeValue(window->ref, kAXSubroleAttribute, &srole); + return srole; +} + +bool window_level_is_standard(struct window *window) +{ + if (!g_normal_window_level) g_normal_window_level = CGWindowLevelForKey(4); + if (!g_floating_window_level) g_floating_window_level = CGWindowLevelForKey(5); + + int level = window_level(window); + return level == g_normal_window_level || level == g_floating_window_level; +} + +bool window_is_standard(struct window *window) +{ + bool standard_win = false; + CFStringRef role = NULL; + CFStringRef srole = NULL; + + if (!(role = window_role(window))) goto out; + if (!(srole = window_subrole(window))) goto role; + + standard_win = CFEqual(role, kAXWindowRole) && + CFEqual(srole, kAXStandardWindowSubrole); + + CFRelease(srole); +role: + CFRelease(role); +out: + return standard_win; +} + +bool window_is_dialog(struct window *window) +{ + bool standard_win = false; + CFStringRef role = NULL; + CFStringRef srole = NULL; + + if (!(role = window_role(window))) goto out; + if (!(srole = window_subrole(window))) goto role; + + standard_win = CFEqual(role, kAXWindowRole) && + CFEqual(srole, kAXDialogSubrole); + + CFRelease(srole); +role: + CFRelease(role); +out: + return standard_win; +} + +bool window_is_popover(struct window *window) +{ + CFStringRef role = window_role(window); + if (!role) return false; + + bool result = CFEqual(role, kAXPopoverRole); + CFRelease(role); + + return result; +} + +bool window_is_unknown(struct window *window) +{ + CFStringRef subrole = window_subrole(window); + if (!subrole) return false; + + bool result = CFEqual(subrole, kAXUnknownSubrole); + CFRelease(subrole); + + return result; +} + +struct window *window_create(struct application *application, AXUIElementRef window_ref, uint32_t window_id) +{ + struct window *window = malloc(sizeof(struct window)); + memset(window, 0, sizeof(struct window)); + + window->application = application; + window->ref = window_ref; + window->id = window_id; + SLSGetWindowOwner(g_connection, window->id, &window->connection); + window->is_minimized = window_is_minimized(window); + window->is_fullscreen = window_is_fullscreen(window) || space_is_fullscreen(window_space(window)); + window->id_ptr = malloc(sizeof(uint32_t *)); + *window->id_ptr = &window->id; + window->has_shadow = true; + + if ((window_is_standard(window)) || (window_is_dialog(window))) { + border_window_create(window); + + if ((!window->application->is_hidden) && + (!window->is_minimized) && + (!window->is_fullscreen)) { + border_window_refresh(window); + } + } + + return window; +} + +void window_destroy(struct window *window) +{ + border_window_destroy(window); + CFRelease(window->ref); + free(window->id_ptr); + free(window); +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..3f0954d --- /dev/null +++ b/src/window.h @@ -0,0 +1,78 @@ +#ifndef WINDOW_H +#define WINDOW_H + +extern int SLSMainConnectionID(void); +extern CGError SLSGetWindowBounds(int cid, uint32_t wid, CGRect *frame); +extern CGError SLSGetWindowLevel(int cid, uint32_t wid, int *level); +extern CGError SLSCopyWindowProperty(int cid, uint32_t wid, CFStringRef property, CFTypeRef *value); +extern CFStringRef SLSCopyManagedDisplayForWindow(int cid, uint32_t wid); +extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect); +extern CFArrayRef SLSCopySpacesForWindows(int cid, int selector, CFArrayRef window_list); + +const CFStringRef kAXFullscreenAttribute = CFSTR("AXFullScreen"); + +#define AX_WINDOW_MINIMIZED_INDEX 0 +#define AX_WINDOW_DEMINIMIZED_INDEX 1 +#define AX_WINDOW_DESTROYED_INDEX 2 + +#define AX_WINDOW_DESTROYED (1 << AX_WINDOW_DESTROYED_INDEX) +#define AX_WINDOW_MINIMIZED (1 << AX_WINDOW_MINIMIZED_INDEX) +#define AX_WINDOW_DEMINIMIZED (1 << AX_WINDOW_DEMINIMIZED_INDEX) +#define AX_WINDOW_ALL (AX_WINDOW_DESTROYED |\ + AX_WINDOW_MINIMIZED |\ + AX_WINDOW_DEMINIMIZED) + +static CFStringRef ax_window_notification[] = +{ + [AX_WINDOW_DESTROYED_INDEX] = kAXUIElementDestroyedNotification, + [AX_WINDOW_MINIMIZED_INDEX] = kAXWindowMiniaturizedNotification, + [AX_WINDOW_DEMINIMIZED_INDEX] = kAXWindowDeminiaturizedNotification +}; + +struct window +{ + struct application *application; + AXUIElementRef ref; + int connection; + uint32_t id; + uint32_t **volatile id_ptr; + uint8_t notification; + struct border border; + bool has_shadow; + bool is_fullscreen; + bool is_minimized; + bool is_floating; + float rule_alpha; + bool rule_manage; + bool rule_fullscreen; +}; + +CFStringRef window_display_uuid(struct window *window); +int window_display_id(struct window *window); +uint64_t window_space(struct window *window); +uint64_t *window_space_list(struct window *window, int *count); +void window_serialize(FILE *rsp, struct window *window); +char *window_title(struct window *window); +CGRect window_ax_frame(struct window *window); +CGRect window_frame(struct window *window); +int window_level(struct window *window); +CFStringRef window_role(struct window *window); +CFStringRef window_subrole(struct window *window); +bool window_can_move(struct window *window); +bool window_can_resize(struct window *window); +bool window_level_is_standard(struct window *window); +bool window_is_undersized(struct window *window); +bool window_is_minimized(struct window *window); +bool window_is_fullscreen(struct window *window); +bool window_is_sticky(struct window *window); +bool window_is_topmost(struct window *window); +bool window_is_standard(struct window *window); +bool window_is_dialog(struct window *window); +bool window_is_popover(struct window *window); +bool window_is_unknown(struct window *window); +bool window_observe(struct window *window); +void window_unobserve(struct window *window); +struct window *window_create(struct application *application, AXUIElementRef window_ref, uint32_t window_id); +void window_destroy(struct window *window); + +#endif diff --git a/src/workspace.h b/src/workspace.h new file mode 100644 index 0000000..a0a4d01 --- /dev/null +++ b/src/workspace.h @@ -0,0 +1,13 @@ +#ifndef WORKSPACE_H +#define WORKSPACE_H + +@interface workspace_context : NSObject { +} +- (id)init; +@end + +void workspace_event_handler_init(void **context); +void workspace_event_handler_begin(void **context); +void workspace_event_handler_end(void *context); + +#endif diff --git a/src/workspace.m b/src/workspace.m new file mode 100644 index 0000000..2894665 --- /dev/null +++ b/src/workspace.m @@ -0,0 +1,83 @@ +#include "workspace.h" + +extern struct event_loop g_event_loop; + +void workspace_event_handler_init(void **context) +{ + workspace_context *ws_context = [workspace_context alloc]; + *context = ws_context; +} + +void workspace_event_handler_begin(void **context) +{ + workspace_context *ws_context = *context; + [ws_context init]; +} + +void workspace_event_handler_end(void *context) +{ + workspace_context *ws_context = (workspace_context *) context; + [ws_context dealloc]; +} + +@implementation workspace_context +- (id)init +{ + if ((self = [super init])) { + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self + selector:@selector(activeDisplayDidChange:) + name:@"NSWorkspaceActiveDisplayDidChangeNotification" + object:nil]; + + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self + selector:@selector(activeSpaceDidChange:) + name:NSWorkspaceActiveSpaceDidChangeNotification + object:nil]; + + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self + selector:@selector(didWake:) + name:NSWorkspaceDidWakeNotification + object:nil]; + + [[NSDistributedNotificationCenter defaultCenter] addObserver:self + selector:@selector(didChangeMenuBarHiding:) + name:@"AppleInterfaceMenuBarHidingChangedNotification" + object:nil]; + } + + return self; +} + +- (void)dealloc +{ + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)didWake:(NSNotification *)notification +{ + struct event *event = event_create(&g_event_loop, SYSTEM_WOKE, NULL); + event_loop_post(&g_event_loop, event); +} + +- (void)didChangeMenuBarHiding:(NSNotification *)notification +{ + struct event *event = event_create(&g_event_loop, MENU_BAR_HIDDEN_CHANGED, NULL); + event_loop_post(&g_event_loop, event); +} + +- (void)activeDisplayDidChange:(NSNotification *)notification +{ + struct event *event = event_create(&g_event_loop, DISPLAY_CHANGED, NULL); + event_loop_post(&g_event_loop, event); +} + +- (void)activeSpaceDidChange:(NSNotification *)notification +{ + struct event *event = event_create(&g_event_loop, SPACE_CHANGED, NULL); + event_loop_post(&g_event_loop, event); +} + +@end