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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 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