first commit

This commit is contained in:
Doron Somech 2020-03-29 13:22:38 +03:00
commit 7724fb905a
46 changed files with 4576 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/bin
/archive
/.idea

4
.travis.yml Normal file
View file

@ -0,0 +1,4 @@
language: c
os: osx
compiler: clang
script: make install

9
CHANGELOG.md Normal file
View file

@ -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

21
LICENSE.txt Normal file
View file

@ -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.

67
README.md Normal file
View file

@ -0,0 +1,67 @@
<!-- Please be careful editing the below HTML, as GitHub is quite finicky with anything that looks like an HTML tag in GitHub Flavored Markdown. -->
<p align="center">
<b>Status Bar for the Mac.</b>
</p>
<p align="center">
<a href="https://travis-ci.org/somdoron/spacebar">
<img src="https://travis-ci.org/somdoron/spacebar.svg?branch=master" alt="CI Status Badge">
</a>
<a href="https://github.com/somdoron/spacebar/blob/master/LICENSE.txt">
<img src="https://img.shields.io/github/license/somdoron/spacebar.svg?color=green" alt="License Badge">
</a>
<a href="https://github.com/spacebar/blob/blob/master/CHANGELOG.md">
<img src="https://img.shields.io/badge/view-changelog-green.svg" alt="Changelog Badge">
</a>
<a href="https://github.com/somdoron/spacebar/releases">
<img src="https://img.shields.io/github/commits-since/somdoron/spacebar/latest.svg?color=green" alt="Version Badge">
</a>
</p>
## About
spacebar is a status bar for [&nearr;&nbsp;yabai][gh-yabai] tiling window management.
## Installation and Configuration
- TODO: release package
- Sample configuration files can be found in the [&nearr;&nbsp;examples][spacebar-examples] directory. Refer to the [&nearr;&nbsp;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&nbsp;System|macOS Catalina 10.15.0+ is supported.|
|Accessibility&nbsp;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&nbsp;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&nbsp;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 [&nearr;&nbsp;MIT&nbsp;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.
<!-- Project internal links -->
[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
<!-- Links to other GitHub projects/users -->
[gh-koekeishiya]: https://github.com/koekeishiya
[gh-yabai]: https://github.com/koekeishiya/yabai

123
doc/spacebar.1 Normal file
View file

@ -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<msg>\fP
.RS 4
Send message to a running instance of spacebar.
.RE
.sp
\fB\-c\fP, \fB\-\-config\fP \fI<config_file>\fP
.RS 4
Use the specified configuration file.
.RE
.SH "CONFIG"
.SS "General Syntax"
.sp
spacebar \-m config <setting>
.RS 4
Get or set the value of <setting>.
.RE
.SS "Settings"
.sp
\fBdebug_output\fP [\fI<BOOL_SEL>\fP]
.RS 4
Enable output of debug information to stdout.
.RE
.sp
\fBstatus_bar_text_font\fP [\fI<font_family>:<font_style>:<font_size>\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<font_family>:<font_style>:<font_size>\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<COLOR>\fP]
.RS 4
Color to use for drawing status bar background.
.RE
.sp
\fBstatus_bar_foreground_color\fP [\fI<COLOR>\fP]
.RS 4
Color to use for drawing status bar elements.
.RE
.sp
\fBstatus_bar_space_icon_strip\fP [\fI<sym_1> <sym_2> <sym_n>\fP]
.RS 4
Specify symbols separated by whitespace to be used for visualizing spaces.
.RE
.sp
\fBstatus_bar_power_icon_strip\fP [\fI<sym_battery> <sym_ac>\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<sym>\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<sym>\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 <somdoron at gmail.com>

98
doc/spacebar.asciidoc Normal file
View file

@ -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* '<msg>'::
Send message to a running instance of spacebar.
*-c*, *--config* '<config_file>'::
Use the specified configuration file.
Config
------
General Syntax
~~~~~~~~~~~~~~
spacebar -m config <setting>::
Get or set the value of <setting>.
Settings
~~~~~~~~
*debug_output* ['<BOOL_SEL>']::
Enable output of debug information to stdout.
*status_bar_text_font* ['<font_family>:<font_style>:<font_size>']::
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* ['<font_family>:<font_style>:<font_size>']::
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>']::
Color to use for drawing status bar background.
*status_bar_foreground_color* ['<COLOR>']::
Color to use for drawing status bar elements.
*status_bar_space_icon_strip* ['<sym_1> <sym_2> <sym_n>']::
Specify symbols separated by whitespace to be used for visualizing spaces.
*status_bar_power_icon_strip* ['<sym_battery> <sym_ac>']::
Specify two symbols separated by whitespace. +
The first symbol represents battery power and the second symbol indicates AC.
*status_bar_space_icon* ['<sym>']::
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* ['<sym>']::
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 <somdoron at gmail.com>

12
examples/spacebarrc Executable file
View file

@ -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.."

46
makefile Normal file
View file

@ -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 $@

47
scripts/codesign Executable file
View file

@ -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 <path/to/executable> [<certificate>]"
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}"

95
src/application.c Normal file
View file

@ -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);
}

38
src/application.h Normal file
View file

@ -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

81
src/application_manager.c Normal file
View file

@ -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;
}
}
}

35
src/application_manager.h Normal file
View file

@ -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

418
src/bar.c Normal file
View file

@ -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);
}

62
src/bar.h Normal file
View file

@ -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

229
src/bar_manager.c Normal file
View file

@ -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);
}
}

46
src/bar_manager.h Normal file
View file

@ -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

106
src/display.c Normal file
View file

@ -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;
}

24
src/display.h Normal file
View file

@ -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

187
src/display_manager.c Normal file
View file

@ -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;
}

49
src/display_manager.h Normal file
View file

@ -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

237
src/event.c Normal file
View file

@ -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;
}

110
src/event.h Normal file
View file

@ -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

154
src/event_loop.c Normal file
View file

@ -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;
}

43
src/event_loop.h Normal file
View file

@ -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

66
src/manifest.m Normal file
View file

@ -0,0 +1,66 @@
#include <ScriptingBridge/ScriptingBridge.h>
#include <Carbon/Carbon.h>
#include <Cocoa/Cocoa.h>
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/ps/IOPSKeys.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <dirent.h>
#include <stdbool.h>
#include <assert.h>
#include <fcntl.h>
#include <regex.h>
#include <execinfo.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <pthread.h>
#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"

229
src/message.c Normal file
View file

@ -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);
}

13
src/message.h Normal file
View file

@ -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

147
src/misc/hashtable.h Normal file
View file

@ -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

185
src/misc/helpers.h Normal file
View file

@ -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

36
src/misc/log.h Normal file
View file

@ -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

33
src/misc/macros.h Normal file
View file

@ -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

42
src/misc/memory_pool.h Normal file
View file

@ -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

47
src/misc/notify.h Normal file
View file

@ -0,0 +1,47 @@
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
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);
}
}

36
src/misc/sbuffer.h Normal file
View file

@ -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

175
src/misc/socket.c Normal file
View file

@ -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);
}

39
src/misc/socket.h Normal file
View file

@ -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 <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <pthread.h>
#include <unistd.h>
#include <netdb.h>
#include <poll.h>
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

29
src/misc/timing.h Normal file
View file

@ -0,0 +1,29 @@
#include <CoreServices/CoreServices.h>
#include <mach/mach_time.h>
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);
}

222
src/process_manager.c Normal file
View file

@ -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("<unknown>");
}
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;
}

51
src/process_manager.h Normal file
View file

@ -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

268
src/spacebar.c Normal file
View file

@ -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;
}

440
src/window.c Normal file
View file

@ -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);
}

78
src/window.h Normal file
View file

@ -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

13
src/workspace.h Normal file
View file

@ -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

83
src/workspace.m Normal file
View file

@ -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